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

realm / realm-core / 1868

27 Nov 2023 10:27AM UTC coverage: 91.681% (-0.003%) from 91.684%
1868

push

Evergreen

web-flow
Support wildcard notation for key path arrays (#7163)

* Use Realm::create_key_path_array in 'object' test
* Add bindgen entry

92398 of 169340 branches covered (0.0%)

265 of 284 new or added lines in 4 files covered. (93.31%)

63 existing lines in 14 files now uncovered.

231841 of 252877 relevant lines covered (91.68%)

6599244.58 hits per line

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

93.04
/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
    {
398,228✔
68
        ++m_count;
398,228✔
69
    }
398,228✔
70
    ~CountGuard()
71
    {
398,227✔
72
        --m_count;
398,227✔
73
    }
398,227✔
74

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

80
Realm::Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator,
81
             MakeSharedTag)
82
    : m_config(std::move(config))
83
    , m_frozen_version(version)
84
    , m_scheduler(m_config.scheduler)
85
{
59,758✔
86
    if (version) {
59,758✔
87
        m_auto_refresh = false;
986✔
88
        REALM_ASSERT(*version != VersionID());
986✔
89
    }
986✔
90
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
58,772✔
91
        m_transaction = coordinator->begin_read();
43,424✔
92
        read_schema_from_group_if_needed();
43,424✔
93
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
43,424✔
94
        m_transaction = nullptr;
43,424✔
95
    }
43,424✔
96
    m_coordinator = std::move(coordinator);
59,758✔
97
}
59,758✔
98

99
Realm::~Realm()
100
{
59,758✔
101
    if (m_transaction) {
59,758✔
102
        // Wait for potential syncing to finish
26,414✔
103
        m_transaction->prepare_for_close();
53,618✔
104
        call_completion_callbacks();
53,618✔
105
    }
53,618✔
106

29,464✔
107
    if (m_coordinator) {
59,758✔
108
        m_coordinator->unregister_realm(this);
55,479✔
109
    }
55,479✔
110
}
59,758✔
111

112
Group& Realm::read_group()
113
{
731,190✔
114
    return transaction();
731,190✔
115
}
731,190✔
116

117
Transaction& Realm::transaction()
118
{
1,923,706✔
119
    verify_open();
1,923,706✔
120
    if (!m_transaction)
1,923,706✔
121
        begin_read(m_frozen_version.value_or(VersionID{}));
114,938✔
122
    return *m_transaction;
1,923,706✔
123
}
1,923,706✔
124

125
Transaction& Realm::transaction() const
126
{
620,024✔
127
    // one day we should change the way we use constness
304,014✔
128
    Realm* nc_realm = const_cast<Realm*>(this);
620,024✔
129
    return nc_realm->transaction();
620,024✔
130
}
620,024✔
131

132
std::shared_ptr<Transaction> Realm::transaction_ref()
133
{
119,151✔
134
    return m_transaction;
119,151✔
135
}
119,151✔
136

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

143
std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm)
144
{
7,004✔
145
    return realm.m_coordinator->m_db;
7,004✔
146
}
7,004✔
147

148
void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
149
{
46✔
150
    realm.begin_read(version_id);
46✔
151
}
46✔
152

153
void Realm::begin_read(VersionID version_id)
154
{
114,984✔
155
    REALM_ASSERT(!m_transaction);
114,984✔
156
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
114,984✔
157
    add_schema_change_handler();
114,984✔
158
    read_schema_from_group_if_needed();
114,984✔
159
}
114,984✔
160

161
SharedRealm Realm::get_shared_realm(Config config)
162
{
57,990✔
163
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
57,990✔
164
    return coordinator->get_realm(std::move(config), util::none);
57,990✔
165
}
57,990✔
166

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

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

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

195
std::shared_ptr<SyncSession> Realm::sync_session() const
196
{
5,446,462✔
197
    return m_coordinator ? m_coordinator->sync_session() : nullptr;
5,446,461✔
198
}
5,446,462✔
199

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

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

223
void Realm::set_schema(Schema const& reference, Schema schema)
224
{
35,117✔
225
    m_dynamic_schema = false;
35,117✔
226
    schema.copy_keys_from(reference, m_config.schema_subset_mode);
35,117✔
227
    m_schema = std::move(schema);
35,117✔
228
    notify_schema_changed();
35,117✔
229
}
35,117✔
230

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

78,048✔
243
    Group& group = read_group();
158,282✔
244
    auto current_version = transaction().get_version_of_current_transaction().version;
158,282✔
245
    if (m_schema_transaction_version == current_version)
158,282✔
246
        return;
113,838✔
247

21,943✔
248
    m_schema_transaction_version = current_version;
44,444✔
249
    m_schema_version = ObjectStore::get_schema_version(group);
44,444✔
250
    auto schema = ObjectStore::schema_from_group(group);
44,444✔
251

21,943✔
252
    if (m_coordinator)
44,444✔
253
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,082✔
254

21,943✔
255
    if (m_dynamic_schema) {
44,444✔
256
        if (m_schema == schema) {
43,958✔
257
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
11,080✔
258
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
22,395✔
259
        }
22,395✔
260
        else {
21,563✔
261
            // The structure of the schema has changed, so replace our copy of the schema.
10,625✔
262
            m_schema = std::move(schema);
21,563✔
263
        }
21,563✔
264
    }
43,958✔
265
    else {
486✔
266
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
486✔
267
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
486✔
268
    }
486✔
269
    notify_schema_changed();
44,444✔
270
}
44,444✔
271

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

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

288
bool Realm::schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes,
289
                                                  uint64_t version)
290
{
57,462✔
291
    if (version == m_schema_version && changes.empty())
57,462✔
292
        return false;
34,811✔
293

11,207✔
294
    switch (m_config.schema_mode) {
22,651✔
295
        case SchemaMode::Automatic:
14,042✔
296
            if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
14,042✔
297
                throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
298
            return true;
14,040✔
299

6,982✔
300
        case SchemaMode::Immutable:
6,988✔
301
            if (version != m_schema_version)
12✔
302
                throw InvalidSchemaVersionException(m_schema_version, version, true);
2✔
303
            REALM_FALLTHROUGH;
10✔
304
        case SchemaMode::ReadOnly:
36✔
305
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
36✔
306
            return m_schema_version == ObjectStore::NotVersioned;
36✔
307

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

3✔
318
        case SchemaMode::AdditiveDiscovered:
4,176✔
319
        case SchemaMode::AdditiveExplicit: {
8,397✔
320
            bool will_apply_index_changes = version > m_schema_version;
8,397✔
321
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
8,397✔
322
                return true;
8,269✔
323
            return version != m_schema_version;
128✔
324
        }
128✔
325

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

338
Schema Realm::get_full_schema()
339
{
57,693✔
340
    if (!m_config.immutable())
57,693✔
341
        do_refresh();
57,627✔
342

28,438✔
343
    // If the user hasn't specified a schema previously then m_schema is always
28,438✔
344
    // the full schema if it's been read
28,438✔
345
    if (m_dynamic_schema && !m_schema.empty())
57,693✔
346
        return m_schema;
34,893✔
347

11,287✔
348
    // Otherwise we may have a subset of the file's schema, so we need to get
11,287✔
349
    // the complete thing to calculate what changes to make
11,287✔
350
    Schema actual_schema;
22,800✔
351
    uint64_t actual_version;
22,800✔
352
    uint64_t version = -1;
22,800✔
353
    bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version);
22,800✔
354
    if (!got_cached || version != transaction().get_version_of_current_transaction().version)
22,800✔
355
        return ObjectStore::schema_from_group(read_group());
22,146✔
356
    return actual_schema;
654✔
357
}
654✔
358

359
bool Realm::is_empty()
360
{
4✔
361
    return ObjectStore::is_empty(read_group());
4✔
362
}
4✔
363

364
Class Realm::get_class(StringData object_type)
365
{
20✔
366
    auto it = m_schema.find(object_type);
20✔
367
    if (it == m_schema.end()) {
20✔
368
        throw LogicError(ErrorCodes::NoSuchTable, util::format("No type '%1'", object_type));
×
369
    }
×
370
    return {shared_from_this(), &*it};
20✔
371
}
20✔
372

373
std::vector<Class> Realm::get_classes()
374
{
×
375
    std::vector<Class> ret;
×
376
    ret.reserve(m_schema.size());
×
377
    auto r = shared_from_this();
×
378
    for (auto& os : m_schema) {
×
379
        ret.emplace_back(r, &os);
×
380
    }
×
381
    return ret;
×
382
}
×
383

384
void Realm::set_schema_subset(Schema schema)
385
{
12✔
386
    verify_thread();
12✔
387
    verify_open();
12✔
388
    REALM_ASSERT(m_dynamic_schema);
12✔
389
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
12✔
390

6✔
391
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
12✔
392
    switch (m_config.schema_mode) {
12✔
393
        case SchemaMode::Automatic:
2✔
394
        case SchemaMode::SoftResetFile:
2✔
395
        case SchemaMode::HardResetFile:
2✔
396
            ObjectStore::verify_no_migration_required(changes);
2✔
397
            break;
2✔
398

1✔
399
        case SchemaMode::Immutable:
1✔
400
        case SchemaMode::ReadOnly:
✔
401
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
×
402
            break;
×
403

404
        case SchemaMode::AdditiveDiscovered:
4✔
405
        case SchemaMode::AdditiveExplicit:
8✔
406
            ObjectStore::verify_valid_additive_changes(changes);
8✔
407
            break;
8✔
408

4✔
409
        case SchemaMode::Manual:
4✔
410
            ObjectStore::verify_no_changes_required(changes);
×
411
            break;
×
412
    }
10✔
413

5✔
414
    set_schema(m_schema, std::move(schema));
10✔
415
}
10✔
416

417
void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
418
                          DataInitializationFunction initialization_function, bool in_transaction)
419
{
57,701✔
420
    uint64_t validation_mode = SchemaValidationMode::Basic;
57,701✔
421
#if REALM_ENABLE_SYNC
57,701✔
422
    if (auto sync_config = m_config.sync_config) {
57,701✔
423
        validation_mode |=
1,482✔
424
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,243✔
425
    }
1,482✔
426
#endif
57,701✔
427
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
57,701✔
428
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
9,042✔
429
    }
9,042✔
430

28,442✔
431
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
57,701✔
432

28,442✔
433
    bool was_in_read_transaction = is_in_read_transaction();
57,701✔
434
    Schema actual_schema = get_full_schema();
57,701✔
435

28,442✔
436
    // Frozen Realms never modify the schema on disk and we just need to verify
28,442✔
437
    // that the requested schema is compatible with what actually exists on disk
28,442✔
438
    // at that frozen version. Tables are allowed to be missing as those can be
28,442✔
439
    // represented by empty Results, but tables which exist must have all of the
28,442✔
440
    // requested properties with the correct type.
28,442✔
441
    if (m_frozen_version) {
57,701✔
442
        ObjectStore::verify_compatible_for_immutable_and_readonly(
242✔
443
            actual_schema.compare(schema, m_config.schema_mode, true));
242✔
444
        set_schema(actual_schema, std::move(schema));
242✔
445
        return;
242✔
446
    }
242✔
447

28,321✔
448
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
57,459✔
449
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
57,459✔
450
        if (!was_in_read_transaction)
34,883✔
451
            m_transaction = nullptr;
34,853✔
452
        set_schema(actual_schema, std::move(schema));
34,883✔
453
        return;
34,883✔
454
    }
34,883✔
455
    // Either the schema version has changed or we need to do non-migration changes
11,175✔
456

11,175✔
457
    // Cancel the write transaction if we exit this function before committing it
11,175✔
458
    auto cleanup = util::make_scope_exit([&]() noexcept {
22,576✔
459
        // When in_transaction is true, caller is responsible to cancel the transaction.
11,129✔
460
        if (!in_transaction && is_in_transaction())
22,484✔
461
            cancel_transaction();
110✔
462
        if (!was_in_read_transaction)
22,484✔
463
            m_transaction = nullptr;
22,130✔
464
    });
22,484✔
465

11,175✔
466
    if (!in_transaction) {
22,576✔
467
        transaction().promote_to_write();
22,458✔
468

11,116✔
469
        // Beginning the write transaction may have advanced the version and left
11,116✔
470
        // us with nothing to do if someone else initialized the schema on disk
11,116✔
471
        if (m_new_schema) {
22,458✔
472
            actual_schema = *m_new_schema;
13✔
473
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
13✔
474
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
13✔
475
                cancel_transaction();
2✔
476
                cache_new_schema();
2✔
477
                set_schema(actual_schema, std::move(schema));
2✔
478
                return;
2✔
479
            }
2✔
480
        }
22,456✔
481
        cache_new_schema();
22,456✔
482
    }
22,456✔
483

11,175✔
484
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
22,575✔
485

11,174✔
486
    uint64_t old_schema_version = m_schema_version;
22,574✔
487
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
22,574✔
488
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
22,487✔
489
                    m_config.schema_mode == SchemaMode::ReadOnly;
18,305✔
490
    if (migration_function && !additive) {
22,574✔
491
        auto wrapper = [&] {
2,375✔
492
            auto config = m_config;
210✔
493
            config.schema_mode = SchemaMode::ReadOnly;
210✔
494
            config.schema = util::none;
210✔
495
            // Don't go through the normal codepath for opening a Realm because
105✔
496
            // we're using a mismatched config
105✔
497
            auto old_realm = std::make_shared<Realm>(std::move(config), none, m_coordinator, MakeSharedTag{});
210✔
498
            // block autorefresh for the old realm
105✔
499
            old_realm->m_auto_refresh = false;
210✔
500
            migration_function(old_realm, shared_from_this(), m_schema);
210✔
501
        };
210✔
502

2,270✔
503
        // migration function needs to see the target schema on the "new" Realm
2,270✔
504
        std::swap(m_schema, schema);
4,611✔
505
        std::swap(m_schema_version, version);
4,611✔
506
        m_in_migration = true;
4,611✔
507
        auto restore = util::make_scope_exit([&]() noexcept {
4,611✔
508
            std::swap(m_schema, schema);
4,611✔
509
            std::swap(m_schema_version, version);
4,611✔
510
            m_in_migration = false;
4,611✔
511
        });
4,611✔
512

2,270✔
513
        ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, m_config.schema_mode,
4,611✔
514
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
4,611✔
515
                                          wrapper);
4,611✔
516
    }
4,611✔
517
    else {
17,963✔
518
        ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, m_config.schema_mode,
17,963✔
519
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations);
17,963✔
520
        REALM_ASSERT_DEBUG(additive ||
17,963✔
521
                           (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
17,963✔
522
    }
17,963✔
523

11,174✔
524
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
22,574✔
525
        // Initialization function needs to see the latest schema
15✔
526
        uint64_t temp_version = ObjectStore::get_schema_version(read_group());
30✔
527
        std::swap(m_schema, schema);
30✔
528
        std::swap(m_schema_version, temp_version);
30✔
529
        auto restore = util::make_scope_exit([&]() noexcept {
30✔
530
            std::swap(m_schema, schema);
30✔
531
            std::swap(m_schema_version, temp_version);
30✔
532
        });
30✔
533
        initialization_function(shared_from_this());
30✔
534
    }
30✔
535

11,174✔
536
    m_schema = std::move(schema);
22,574✔
537
    m_new_schema = ObjectStore::schema_from_group(read_group());
22,574✔
538
    m_schema_version = ObjectStore::get_schema_version(read_group());
22,574✔
539
    m_dynamic_schema = false;
22,574✔
540
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
22,574✔
541

11,174✔
542
    if (!in_transaction) {
22,574✔
543
        m_coordinator->commit_write(*this);
22,346✔
544
        cache_new_schema();
22,346✔
545
    }
22,346✔
546

11,174✔
547
    notify_schema_changed();
22,574✔
548
}
22,574✔
549

550
void Realm::rename_property(Schema schema, StringData object_type, StringData old_name, StringData new_name)
551
{
4✔
552
    ObjectStore::rename_property(read_group(), schema, object_type, old_name, new_name);
4✔
553
}
4✔
554

555
void Realm::add_schema_change_handler()
556
{
114,976✔
557
    if (m_config.immutable())
114,976✔
558
        return;
56✔
559
    m_transaction->set_schema_change_notification_handler([&] {
114,920✔
560
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,735✔
561
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,735✔
562
        if (m_dynamic_schema) {
3,735✔
563
            m_schema = *m_new_schema;
33✔
564
        }
33✔
565
        else {
3,702✔
566
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,702✔
567
        }
3,702✔
568
        notify_schema_changed();
3,735✔
569
    });
3,735✔
570
}
114,920✔
571

572
void Realm::cache_new_schema()
573
{
211,250✔
574
    if (is_closed()) {
211,250✔
575
        return;
×
576
    }
×
577

103,475✔
578
    auto new_version = transaction().get_version_of_current_transaction().version;
211,250✔
579
    if (m_new_schema)
211,250✔
580
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
26,107✔
581
    else
185,143✔
582
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
185,143✔
583
    m_schema_transaction_version = new_version;
211,250✔
584
    m_new_schema = util::none;
211,250✔
585
}
211,250✔
586

587
void Realm::translate_schema_error()
588
{
4✔
589
    // Read the new (incompatible) schema without changing our read transaction
2✔
590
    auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read());
4✔
591

2✔
592
    // Should always throw
2✔
593
    ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, m_config.schema_mode, true));
4✔
594

2✔
595
    // Something strange happened so just rethrow the old exception
2✔
596
    throw;
4✔
597
}
4✔
598

599
void Realm::notify_schema_changed()
600
{
105,662✔
601
    if (m_binding_context) {
105,662✔
602
        m_binding_context->schema_did_change(m_schema);
28✔
603
    }
28✔
604
}
105,662✔
605

606
static void check_can_create_write_transaction(const Realm* realm)
607
{
130,098✔
608
    realm->verify_thread();
130,098✔
609
    realm->verify_open();
130,098✔
610
    if (realm->config().immutable() || realm->config().read_only()) {
130,098✔
611
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
612
    }
6✔
613
    if (realm->is_frozen()) {
130,092✔
614
        throw WrongTransactionState("Can't perform transactions on a frozen Realm");
2✔
615
    }
2✔
616
    if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) {
130,090✔
617
        throw WrongTransactionState(
×
618
            util::format("Number of active versions (%1) in the Realm exceeded the limit of %2",
×
619
                         realm->get_number_of_versions(), realm->config().max_number_of_active_versions));
×
620
    }
×
621
}
130,090✔
622

623
void Realm::verify_thread() const
624
{
609,088✔
625
    if (m_scheduler && !m_scheduler->is_on_thread())
609,088✔
626
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
8✔
627
}
609,088✔
628

629
void Realm::verify_in_write() const
630
{
14,946✔
631
    if (!is_in_transaction()) {
14,946✔
632
        throw WrongTransactionState("Cannot modify managed objects outside of a write transaction.");
4✔
633
    }
4✔
634
}
14,946✔
635

636
void Realm::verify_open() const
637
{
2,189,090✔
638
    if (is_closed()) {
2,189,090✔
639
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
44✔
640
    }
44✔
641
}
2,189,090✔
642

643
bool Realm::verify_notifications_available(bool throw_on_error) const
644
{
44,355✔
645
    if (is_frozen()) {
44,355✔
646
        if (throw_on_error)
56✔
647
            throw WrongTransactionState(
12✔
648
                "Notifications are not available on frozen collections since they do not change.");
12✔
649
        return false;
44✔
650
    }
44✔
651
    if (config().immutable()) {
44,299✔
652
        if (throw_on_error)
2✔
653
            throw WrongTransactionState("Cannot create asynchronous query for immutable Realms");
×
654
        return false;
2✔
655
    }
2✔
656
    if (throw_on_error) {
44,297✔
657
        if (m_transaction && m_transaction->get_commit_size() > 0)
10,390✔
658
            throw WrongTransactionState(
2✔
659
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
660
    }
33,907✔
661
    else {
33,907✔
662
        // Don't create implicit notifiers inside write transactions even if
16,863✔
663
        // we could as it wouldn't actually be used
16,863✔
664
        if (is_in_transaction())
33,907✔
665
            return false;
20,894✔
666
    }
23,401✔
667

11,650✔
668
    return true;
23,401✔
669
}
23,401✔
670

671
VersionID Realm::read_transaction_version() const
672
{
4,873✔
673
    verify_thread();
4,873✔
674
    verify_open();
4,873✔
675
    REALM_ASSERT(m_transaction);
4,873✔
676
    return m_transaction->get_version_of_current_transaction();
4,873✔
677
}
4,873✔
678

679
uint_fast64_t Realm::get_number_of_versions() const
680
{
130,084✔
681
    verify_open();
130,084✔
682
    return m_coordinator->get_number_of_versions();
130,084✔
683
}
130,084✔
684

685
bool Realm::is_in_transaction() const noexcept
686
{
719,615✔
687
    return !m_config.immutable() && !is_closed() && m_transaction &&
719,615✔
688
           transaction().get_transact_stage() == DB::transact_Writing;
669,048✔
689
}
719,615✔
690

691
bool Realm::is_in_async_transaction() const noexcept
692
{
623✔
693
    return !m_config.immutable() && !is_closed() && m_transaction && m_transaction->is_async();
623✔
694
}
623✔
695

696
util::Optional<VersionID> Realm::current_transaction_version() const
697
{
109,165✔
698
    util::Optional<VersionID> ret;
109,165✔
699
    if (m_transaction) {
109,165✔
700
        ret = m_transaction->get_version_of_current_transaction();
108,891✔
701
    }
108,891✔
702
    else if (m_frozen_version) {
274✔
703
        ret = m_frozen_version;
×
704
    }
×
705
    return ret;
109,165✔
706
}
109,165✔
707

708
// Get the version of the latest snapshot
709
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
710
{
66✔
711
    util::Optional<DB::version_type> ret;
66✔
712
    if (m_transaction) {
66✔
713
        ret = m_transaction->get_version_of_latest_snapshot();
64✔
714
    }
64✔
715
    return ret;
66✔
716
}
66✔
717

718
void Realm::enable_wait_for_change()
719
{
2✔
720
    verify_open();
2✔
721
    m_coordinator->enable_wait_for_change();
2✔
722
}
2✔
723

724
bool Realm::wait_for_change()
725
{
6✔
726
    verify_open();
6✔
727
    if (m_frozen_version || m_config.schema_mode == SchemaMode::Immutable) {
6✔
728
        return false;
4✔
729
    }
4✔
730
    return m_transaction && m_coordinator->wait_for_change(m_transaction);
2!
731
}
2✔
732

733
void Realm::wait_for_change_release()
734
{
2✔
735
    verify_open();
2✔
736
    m_coordinator->wait_for_change_release();
2✔
737
}
2✔
738

739
bool Realm::has_pending_async_work() const
740
{
927✔
741
    verify_thread();
927✔
742
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
927✔
743
}
927✔
744

745
void Realm::run_writes_on_proper_thread()
746
{
7✔
747
    m_scheduler->invoke([self = shared_from_this()] {
7✔
748
        self->run_writes();
7✔
749
    });
7✔
750
}
7✔
751

752
void Realm::call_completion_callbacks()
753
{
181,052✔
754
    if (m_is_running_async_commit_completions) {
181,052✔
755
        return;
4✔
756
    }
4✔
757

88,823✔
758
    CountGuard sending_completions(m_is_running_async_commit_completions);
181,048✔
759
    auto error = m_transaction->get_commit_exception();
181,048✔
760
    auto completions = std::move(m_async_commit_q);
181,048✔
761
    m_async_commit_q.clear();
181,048✔
762
    for (auto& cb : completions) {
90,202✔
763
        if (!cb.when_completed)
2,510✔
764
            continue;
16✔
765
        if (m_async_exception_handler) {
2,494✔
766
            try {
2✔
767
                cb.when_completed(error);
2✔
768
            }
2✔
769
            catch (...) {
2✔
770
                m_async_exception_handler(cb.handle, std::current_exception());
2✔
771
            }
2✔
772
        }
2✔
773
        else {
2,492✔
774
            cb.when_completed(error);
2,492✔
775
        }
2,492✔
776
    }
2,494✔
777
}
181,048✔
778

779
void Realm::run_async_completions()
780
{
330✔
781
    call_completion_callbacks();
330✔
782
    check_pending_write_requests();
330✔
783
}
330✔
784

785
void Realm::check_pending_write_requests()
786
{
61,409✔
787
    if (!m_async_write_q.empty()) {
61,409✔
788
        if (m_transaction->is_async()) {
420✔
789
            run_writes_on_proper_thread();
7✔
790
        }
7✔
791
        else {
413✔
792
            m_coordinator->async_request_write_mutex(*this);
413✔
793
        }
413✔
794
    }
420✔
795
}
61,409✔
796

797
void Realm::end_current_write(bool check_pending)
798
{
3,296✔
799
    if (!m_transaction) {
3,296✔
800
        return;
2✔
801
    }
2✔
802
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,294✔
803
        m_scheduler->invoke([self = std::move(self), this]() mutable {
330✔
804
            run_async_completions();
330✔
805
            self.reset();
330✔
806
        });
330✔
807
    });
330✔
808
    if (check_pending && m_async_commit_q.empty()) {
3,294✔
809
        check_pending_write_requests();
2,838✔
810
    }
2,838✔
811
}
3,294✔
812

813
void Realm::run_writes()
814
{
501✔
815
    if (!m_transaction) {
501✔
816
        // Realm might have been closed
817
        return;
×
818
    }
×
819
    if (m_transaction->is_synchronizing()) {
501✔
820
        // Wait for the synchronization complete callback before we run more
821
        // writes as we can't add commits while in that state
822
        return;
×
823
    }
×
824
    if (is_in_transaction()) {
501✔
825
        // This is scheduled asynchronously after acquiring the write lock, so
1✔
826
        // in that time a synchronous transaction may have been started. If so,
1✔
827
        // we'll be re-invoked when that transaction ends.
1✔
828
        return;
2✔
829
    }
2✔
830

125✔
831
    CountGuard running_writes(m_is_running_async_writes);
499✔
832
    int run_limit = 20; // max number of commits without full sync to disk
499✔
833
    // this is tricky
125✔
834
    //  - each pending call may itself add other async writes
125✔
835
    //  - the 'run' will terminate as soon as a commit without grouping is requested
125✔
836
    while (!m_async_write_q.empty() && m_transaction) {
2,709✔
837
        // We might have made a sync commit and thereby given up the write lock
1,150✔
838
        if (!m_transaction->holds_write_mutex()) {
2,660✔
839
            return;
114✔
840
        }
114✔
841

1,149✔
842
        do_begin_transaction();
2,546✔
843

1,149✔
844
        auto write_desc = std::move(m_async_write_q.front());
2,546✔
845
        m_async_write_q.pop_front();
2,546✔
846

1,149✔
847
        // prevent any calls to commit/cancel during a simple notification
1,149✔
848
        m_notify_only = write_desc.notify_only;
2,546✔
849
        m_async_commit_barrier_requested = false;
2,546✔
850
        auto prev_version = m_transaction->get_version();
2,546✔
851
        try {
2,546✔
852
            write_desc.writer();
2,546✔
853
        }
2,546✔
854
        catch (const std::exception&) {
1,152✔
855
            if (m_transaction) {
6✔
856
                transaction::cancel(*m_transaction, m_binding_context.get());
4✔
857
            }
4✔
858
            m_notify_only = false;
6✔
859

3✔
860
            if (m_async_exception_handler) {
6✔
861
                m_async_exception_handler(write_desc.handle, std::current_exception());
2✔
862
                continue;
2✔
863
            }
2✔
864
            end_current_write();
4✔
865
            throw;
4✔
866
        }
4✔
867

1,146✔
868
        // if we've merely delivered a notification, the full transaction will follow later
1,146✔
869
        // and terminate with a call to async commit or async cancel
1,146✔
870
        if (m_notify_only) {
2,540✔
871
            m_notify_only = false;
12✔
872
            return;
12✔
873
        }
12✔
874

1,140✔
875
        // Realm may have been closed in the write function
1,140✔
876
        if (!m_transaction) {
2,528✔
877
            return;
20✔
878
        }
20✔
879

1,130✔
880
        auto new_version = m_transaction->get_version();
2,508✔
881
        // if we've run the full transaction, there is follow up work to do:
1,130✔
882
        if (new_version > prev_version) {
2,508✔
883
            // A commit was done during callback
1,125✔
884
            --run_limit;
2,498✔
885
            if (run_limit <= 0)
2,498✔
886
                break;
102✔
887
        }
10✔
888
        else {
10✔
889
            if (m_transaction->get_transact_stage() == DB::transact_Writing) {
10✔
890
                // Still in writing stage - we make a rollback
4✔
891
                transaction::cancel(transaction(), m_binding_context.get());
8✔
892
            }
8✔
893
        }
10✔
894
        if (m_async_commit_barrier_requested)
2,457✔
895
            break;
198✔
896
    }
2,406✔
897

125✔
898
    end_current_write();
368✔
899
}
349✔
900

901
auto Realm::async_begin_transaction(util::UniqueFunction<void()>&& the_write_block, bool notify_only) -> AsyncHandle
902
{
2,560✔
903
    check_can_create_write_transaction(this);
2,560✔
904
    if (m_is_running_async_commit_completions) {
2,560✔
905
        throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback.");
×
906
    }
×
907
    if (!m_scheduler->can_invoke()) {
2,560✔
908
        throw WrongTransactionState(
×
909
            "Cannot schedule async transaction. Make sure you are running from inside a run loop.");
×
910
    }
×
911
    REALM_ASSERT(the_write_block);
2,560✔
912

1,156✔
913
    // make sure we have a (at least a) read transaction
1,156✔
914
    transaction();
2,560✔
915
    auto handle = m_async_commit_handle++;
2,560✔
916
    m_async_write_q.push_back({std::move(the_write_block), notify_only, handle});
2,560✔
917

1,156✔
918
    if (!m_is_running_async_writes && !m_transaction->is_async() &&
2,560✔
919
        m_transaction->get_transact_stage() != DB::transact_Writing) {
1,236✔
920
        m_coordinator->async_request_write_mutex(*this);
125✔
921
    }
125✔
922
    return handle;
2,560✔
923
}
2,560✔
924

925
auto Realm::async_commit_transaction(util::UniqueFunction<void(std::exception_ptr)>&& completion, bool allow_grouping)
926
    -> AsyncHandle
927
{
2,514✔
928
    check_can_create_write_transaction(this);
2,514✔
929
    if (m_is_running_async_commit_completions) {
2,514✔
930
        throw WrongTransactionState("Can't commit a write transaction from inside a commit completion callback.");
×
931
    }
×
932
    if (!is_in_transaction()) {
2,514✔
933
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
934
    }
×
935

1,133✔
936
    m_transaction->promote_to_async();
2,514✔
937
    REALM_ASSERT(m_transaction->holds_write_mutex());
2,514✔
938
    REALM_ASSERT(!m_notify_only);
2,514✔
939
    // auditing is not supported
1,133✔
940
    REALM_ASSERT(!audit_context());
2,514✔
941
    // grab a version lock on current version, push it along with the done block
1,133✔
942
    // do in-buffer-cache commit_transaction();
1,133✔
943
    auto handle = m_async_commit_handle++;
2,514✔
944
    m_async_commit_q.push_back({std::move(completion), handle});
2,514✔
945
    try {
2,514✔
946
        m_coordinator->commit_write(*this, /* commit_to_disk: */ false);
2,514✔
947
    }
2,514✔
948
    catch (...) {
1,135✔
949
        // If the exception happened before the commit, we need to roll back the
2✔
950
        // transaction and remove the completion handler from the queue
2✔
951
        if (is_in_transaction()) {
4✔
952
            // Exception happened before the commit, so roll back the transaction
1✔
953
            // and remove the completion handler from the queue
1✔
954
            cancel_transaction();
2✔
955
            auto it = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), [=](auto& e) {
2✔
956
                return e.handle == handle;
2✔
957
            });
2✔
958
            if (it != m_async_commit_q.end()) {
2✔
959
                m_async_commit_q.erase(it);
2✔
960
            }
2✔
961
        }
2✔
962
        else if (m_transaction) {
2✔
963
            end_current_write(false);
2✔
964
        }
2✔
965
        throw;
4✔
966
    }
4✔
967

1,130✔
968
    if (m_is_running_async_writes) {
2,508✔
969
        // we're called from with the callback loop and it will take care of releasing lock
1,122✔
970
        // if applicable, and of triggering followup runs of callbacks
1,122✔
971
        if (!allow_grouping) {
2,368✔
972
            m_async_commit_barrier_requested = true;
206✔
973
        }
206✔
974
    }
2,368✔
975
    else {
140✔
976
        // we're called from outside the callback loop so we have to take care of
8✔
977
        // releasing any lock and of keeping callbacks coming.
8✔
978
        if (allow_grouping) {
140✔
979
            run_writes();
×
980
        }
×
981
        else {
140✔
982
            end_current_write(false);
140✔
983
        }
140✔
984
    }
140✔
985
    return handle;
2,508✔
986
}
2,508✔
987

988
bool Realm::async_cancel_transaction(AsyncHandle handle)
989
{
6✔
990
    verify_thread();
6✔
991
    verify_open();
6✔
992
    auto compare = [handle](auto& elem) {
5✔
993
        return elem.handle == handle;
4✔
994
    };
4✔
995

3✔
996
    auto it1 = std::find_if(m_async_write_q.begin(), m_async_write_q.end(), compare);
6✔
997
    if (it1 != m_async_write_q.end()) {
6✔
998
        m_async_write_q.erase(it1);
2✔
999
        return true;
2✔
1000
    }
2✔
1001
    auto it2 = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), compare);
4✔
1002
    if (it2 != m_async_commit_q.end()) {
4✔
1003
        // Just delete the callback. It is important that we know
1✔
1004
        // that there are still commits pending.
1✔
1005
        it2->when_completed = nullptr;
2✔
1006
        return true;
2✔
1007
    }
2✔
1008
    return false;
2✔
1009
}
2✔
1010

1011
void Realm::begin_transaction()
1012
{
63,970✔
1013
    check_can_create_write_transaction(this);
63,970✔
1014

31,437✔
1015
    if (is_in_transaction()) {
63,970✔
1016
        throw WrongTransactionState("The Realm is already in a write transaction");
×
1017
    }
×
1018

31,437✔
1019
    // Any of the callbacks to user code below could drop the last remaining
31,437✔
1020
    // strong reference to `this`
31,437✔
1021
    auto retain_self = shared_from_this();
63,970✔
1022

31,437✔
1023
    // make sure we have a read transaction
31,437✔
1024
    read_group();
63,970✔
1025

31,437✔
1026
    do_begin_transaction();
63,970✔
1027
}
63,970✔
1028

1029
void Realm::do_begin_transaction()
1030
{
66,508✔
1031
    CountGuard sending_notifications(m_is_sending_notifications);
66,508✔
1032
    try {
66,508✔
1033
        m_coordinator->promote_to_write(*this);
66,508✔
1034
    }
66,508✔
1035
    catch (_impl::UnsupportedSchemaChange const&) {
32,582✔
1036
        translate_schema_error();
×
1037
    }
×
1038
    cache_new_schema();
66,508✔
1039

32,582✔
1040
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
66,508✔
1041
        call_completion_callbacks();
64,458✔
1042
    }
64,458✔
1043
}
66,508✔
1044

1045
void Realm::commit_transaction()
1046
{
58,237✔
1047
    check_can_create_write_transaction(this);
58,237✔
1048

28,571✔
1049
    if (!is_in_transaction()) {
58,237✔
1050
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1051
    }
×
1052

28,571✔
1053
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
58,237✔
1054
    if (auto audit = audit_context()) {
58,237✔
1055
        audit->prepare_for_write(prev_version);
91✔
1056
    }
91✔
1057

28,571✔
1058
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
58,237✔
1059
    cache_new_schema();
58,237✔
1060

28,571✔
1061
    // Realm might have been closed
28,571✔
1062
    if (m_transaction) {
58,237✔
1063
        // Any previous async commits got flushed along with the sync commit
28,570✔
1064
        call_completion_callbacks();
58,235✔
1065
        // If we have pending async writes we need to rerequest the write mutex
28,570✔
1066
        check_pending_write_requests();
58,235✔
1067
    }
58,235✔
1068
    if (auto audit = audit_context()) {
58,237✔
1069
        audit->record_write(prev_version, transaction().get_version_of_current_transaction());
91✔
1070
    }
91✔
1071
}
58,237✔
1072

1073
void Realm::cancel_transaction()
1074
{
2,817✔
1075
    check_can_create_write_transaction(this);
2,817✔
1076

1,408✔
1077
    if (m_is_running_async_commit_completions) {
2,817✔
1078
        throw WrongTransactionState("Can't cancel a write transaction from inside a commit completion callback.");
×
1079
    }
×
1080
    if (!is_in_transaction()) {
2,817✔
1081
        throw WrongTransactionState("Can't cancel a non-existing write transaction");
×
1082
    }
×
1083

1,408✔
1084
    transaction::cancel(transaction(), m_binding_context.get());
2,817✔
1085

1,408✔
1086
    if (m_transaction && !m_is_running_async_writes) {
2,817✔
1087
        if (m_async_write_q.empty()) {
2,809✔
1088
            end_current_write();
2,801✔
1089
        }
2,801✔
1090
        else {
8✔
1091
            check_pending_write_requests();
8✔
1092
        }
8✔
1093
    }
2,809✔
1094
}
2,817✔
1095

1096
void Realm::invalidate()
1097
{
246✔
1098
    verify_thread();
246✔
1099
    verify_open();
246✔
1100

123✔
1101
    if (m_is_sending_notifications) {
246✔
1102
        // This was originally because closing the Realm during notification
3✔
1103
        // sending would break things, but we now support that. However, it's a
3✔
1104
        // breaking change so we keep the old behavior for now.
3✔
1105
        return;
6✔
1106
    }
6✔
1107

120✔
1108
    if (is_in_transaction()) {
240✔
1109
        cancel_transaction();
172✔
1110
    }
172✔
1111

120✔
1112
    do_invalidate();
240✔
1113
}
240✔
1114

1115
void Realm::do_invalidate()
1116
{
4,517✔
1117
    if (!m_config.immutable() && m_transaction) {
4,517✔
1118
        m_transaction->prepare_for_close();
4,411✔
1119
        call_completion_callbacks();
4,411✔
1120
        transaction().close();
4,411✔
1121
    }
4,411✔
1122

2,243✔
1123
    m_transaction = nullptr;
4,517✔
1124
    m_async_write_q.clear();
4,517✔
1125
    m_async_commit_q.clear();
4,517✔
1126
}
4,517✔
1127

1128
bool Realm::compact()
1129
{
8✔
1130
    verify_thread();
8✔
1131
    verify_open();
8✔
1132

4✔
1133
    if (m_config.immutable() || m_config.read_only()) {
8✔
1134
        throw WrongTransactionState("Can't compact a read-only Realm");
4✔
1135
    }
4✔
1136
    if (is_in_transaction()) {
4✔
1137
        throw WrongTransactionState("Can't compact a Realm within a write transaction");
×
1138
    }
×
1139

2✔
1140
    verify_open();
4✔
1141
    m_transaction = nullptr;
4✔
1142
    return m_coordinator->compact();
4✔
1143
}
4✔
1144

1145
void Realm::convert(const Config& config, bool merge_into_existing)
1146
{
60✔
1147
    verify_thread();
60✔
1148
    verify_open();
60✔
1149

30✔
1150
#if REALM_ENABLE_SYNC
60✔
1151
    auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested;
60✔
1152
    auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested;
60✔
1153
    auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested;
60✔
1154

30✔
1155
    if (dst_is_flx_sync && !src_is_flx_sync) {
60✔
1156
        throw IllegalOperation(
4✔
1157
            "Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled");
4✔
1158
    }
4✔
1159
    if (dst_is_pbs_sync && src_is_flx_sync) {
56✔
1160
        throw IllegalOperation(
2✔
1161
            "Realm cannot be converted from a flexible sync realm to a partition based sync realm");
2✔
1162
    }
2✔
1163

27✔
1164
#endif
54✔
1165

27✔
1166
    if (merge_into_existing && util::File::exists(config.path)) {
54✔
1167
        auto destination_realm = Realm::get_shared_realm(config);
6✔
1168
        destination_realm->begin_transaction();
6✔
1169
        auto destination = destination_realm->transaction_ref();
6✔
1170
        m_transaction->copy_to(destination);
6✔
1171
        destination_realm->commit_transaction();
6✔
1172
        return;
6✔
1173
    }
6✔
1174

24✔
1175
    if (config.encryption_key.size() && config.encryption_key.size() != 64) {
48!
1176
        throw InvalidEncryptionKey();
×
1177
    }
×
1178

24✔
1179
    auto& tr = transaction();
48✔
1180
    auto repl = tr.get_replication();
48✔
1181
    bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient;
48✔
1182
    bool dst_is_sync = config.sync_config || config.force_sync_history;
48✔
1183

24✔
1184
    if (dst_is_sync) {
48✔
1185
        m_coordinator->write_copy(config.path, config.encryption_key.data());
20✔
1186
        if (!src_is_sync) {
20✔
1187
#if REALM_ENABLE_SYNC
8✔
1188
            DBOptions options;
8✔
1189
            if (config.encryption_key.size()) {
8✔
1190
                options.encryption_key = config.encryption_key.data();
×
1191
            }
×
1192
            auto db = DB::create(make_in_realm_history(), config.path, options);
8✔
1193
            db->create_new_history(sync::make_client_replication());
8✔
1194
#endif
8✔
1195
        }
8✔
1196
    }
20✔
1197
    else {
28✔
1198
        tr.write(config.path, config.encryption_key.data());
28✔
1199
    }
28✔
1200
}
48✔
1201

1202
OwnedBinaryData Realm::write_copy()
1203
{
6✔
1204
    verify_thread();
6✔
1205
    BinaryData buffer = read_group().write_to_mem();
6✔
1206

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

1212
void Realm::notify()
1213
{
24,956✔
1214
    if (is_closed() || is_in_transaction() || is_frozen()) {
24,956✔
1215
        return;
330✔
1216
    }
330✔
1217

10,811✔
1218
    verify_thread();
24,626✔
1219

10,811✔
1220
    // Any of the callbacks to user code below could drop the last remaining
10,811✔
1221
    // strong reference to `this`
10,811✔
1222
    auto retain_self = shared_from_this();
24,626✔
1223

10,811✔
1224
    if (m_binding_context) {
24,626✔
1225
        m_binding_context->before_notify();
586✔
1226
        if (is_closed() || is_in_transaction()) {
586✔
1227
            return;
×
1228
        }
×
1229
    }
24,626✔
1230

10,811✔
1231
    if (!m_coordinator->can_advance(*this)) {
24,626✔
1232
        CountGuard sending_notifications(m_is_sending_notifications);
17,387✔
1233
        m_coordinator->process_available_async(*this);
17,387✔
1234
        return;
17,387✔
1235
    }
17,387✔
1236

2,983✔
1237
    if (m_binding_context) {
7,239✔
1238
        m_binding_context->changes_available();
78✔
1239

39✔
1240
        // changes_available() may have advanced the read version, and if
39✔
1241
        // so we don't need to do anything further
39✔
1242
        if (!m_coordinator->can_advance(*this))
78✔
1243
            return;
4✔
1244
    }
7,235✔
1245

2,981✔
1246
    CountGuard sending_notifications(m_is_sending_notifications);
7,235✔
1247
    if (m_auto_refresh) {
7,238✔
1248
        if (m_transaction) {
7,235✔
1249
            try {
7,165✔
1250
                m_coordinator->advance_to_ready(*this);
7,165✔
1251
            }
7,165✔
1252
            catch (_impl::UnsupportedSchemaChange const&) {
2,943✔
1253
                translate_schema_error();
×
1254
            }
×
1255
            if (!is_closed())
7,165✔
1256
                cache_new_schema();
7,163✔
1257
        }
7,165✔
1258
        else {
70✔
1259
            if (m_binding_context) {
70✔
1260
                m_binding_context->did_change({}, {});
4✔
1261
            }
4✔
1262
            if (!is_closed()) {
70✔
1263
                m_coordinator->process_available_async(*this);
68✔
1264
            }
68✔
1265
        }
70✔
1266
    }
7,235✔
1267
}
7,235✔
1268

1269
bool Realm::refresh()
1270
{
68,209✔
1271
    verify_thread();
68,209✔
1272
    return do_refresh();
68,209✔
1273
}
68,209✔
1274

1275
bool Realm::do_refresh()
1276
{
125,836✔
1277
    // Frozen Realms never change.
62,186✔
1278
    if (is_frozen()) {
125,836✔
1279
        return false;
246✔
1280
    }
246✔
1281

62,063✔
1282
    if (m_config.immutable()) {
125,590✔
1283
        throw WrongTransactionState("Can't refresh an immutable Realm.");
4✔
1284
    }
4✔
1285

62,061✔
1286
    // can't be any new changes if we're in a write transaction
62,061✔
1287
    if (is_in_transaction()) {
125,586✔
1288
        return false;
30✔
1289
    }
30✔
1290
    // don't advance if we're already in the process of advancing as that just
62,046✔
1291
    // makes things needlessly complicated
62,046✔
1292
    if (m_is_sending_notifications) {
125,556✔
1293
        return false;
8✔
1294
    }
8✔
1295

62,042✔
1296
    // Any of the callbacks to user code below could drop the last remaining
62,042✔
1297
    // strong reference to `this`
62,042✔
1298
    auto retain_self = shared_from_this();
125,548✔
1299

62,042✔
1300
    CountGuard sending_notifications(m_is_sending_notifications);
125,548✔
1301
    if (m_binding_context) {
125,548✔
1302
        m_binding_context->before_notify();
138✔
1303
    }
138✔
1304
    if (m_transaction) {
125,548✔
1305
        try {
34,550✔
1306
            bool version_changed = m_coordinator->advance_to_latest(*this);
34,550✔
1307
            if (is_closed())
34,550✔
1308
                return false;
6✔
1309
            cache_new_schema();
34,544✔
1310
            return version_changed;
34,544✔
1311
        }
34,544✔
1312
        catch (_impl::UnsupportedSchemaChange const&) {
4✔
1313
            translate_schema_error();
4✔
1314
        }
4✔
1315
    }
34,550✔
1316

62,042✔
1317
    // No current read transaction, so just create a new one
62,042✔
1318
    read_group();
108,208✔
1319
    m_coordinator->process_available_async(*this);
90,998✔
1320
    return true;
90,998✔
1321
}
125,548✔
1322

1323
void Realm::set_auto_refresh(bool auto_refresh)
1324
{
16✔
1325
    if (is_frozen() && auto_refresh) {
16✔
1326
        throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms.");
2✔
1327
    }
2✔
1328
    m_auto_refresh = auto_refresh;
14✔
1329
}
14✔
1330

1331

1332
bool Realm::can_deliver_notifications() const noexcept
1333
{
13,013✔
1334
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
13,013✔
1335
        return false;
12,957✔
1336
    }
12,957✔
1337

28✔
1338
    if (!m_scheduler || !m_scheduler->can_invoke()) {
56✔
1339
        return false;
×
1340
    }
×
1341

28✔
1342
    return true;
56✔
1343
}
56✔
1344

1345
uint64_t Realm::get_schema_version(const Realm::Config& config)
1346
{
×
1347
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
×
1348
    auto version = coordinator->get_schema_version();
×
1349
    if (version == ObjectStore::NotVersioned)
×
1350
        version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group());
×
1351
    return version;
×
1352
}
×
1353

1354

1355
bool Realm::is_frozen() const
1356
{
391,963✔
1357
    bool result = bool(m_frozen_version);
391,963✔
1358
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
391,963✔
1359
    return result;
391,963✔
1360
}
391,963✔
1361

1362
SharedRealm Realm::freeze()
1363
{
622✔
1364
    read_group(); // Freezing requires a read transaction
622✔
1365
    return m_coordinator->freeze_realm(*this);
622✔
1366
}
622✔
1367

1368
void Realm::copy_schema_from(const Realm& source)
1369
{
618✔
1370
    REALM_ASSERT(is_frozen());
618✔
1371
    REALM_ASSERT(m_frozen_version == source.read_transaction_version());
618✔
1372
    m_schema = source.m_schema;
618✔
1373
    m_schema_version = source.m_schema_version;
618✔
1374
    m_schema_transaction_version = m_frozen_version->version;
618✔
1375
    m_dynamic_schema = false;
618✔
1376
}
618✔
1377

1378
void Realm::close()
1379
{
4,299✔
1380
    if (is_closed()) {
4,299✔
1381
        return;
20✔
1382
    }
20✔
1383
    if (m_coordinator) {
4,279✔
1384
        m_coordinator->unregister_realm(this);
4,279✔
1385
    }
4,279✔
1386

2,124✔
1387
    do_invalidate();
4,279✔
1388

2,124✔
1389
    m_binding_context = nullptr;
4,279✔
1390
    m_coordinator = nullptr;
4,279✔
1391
    m_scheduler = nullptr;
4,279✔
1392
    m_config = {};
4,279✔
1393
}
4,279✔
1394

1395
void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_realm)
1396
{
16✔
1397
    bool lock_successful = false;
16✔
1398
    try {
16✔
1399
        lock_successful = DB::call_with_lock(realm_file_path, [=](auto const& path) {
14✔
1400
            DB::delete_files(path, did_delete_realm);
12✔
1401
        });
12✔
1402
    }
16✔
1403
    catch (const FileAccessError& e) {
9✔
1404
        if (e.code() != ErrorCodes::FileNotFound) {
2✔
1405
            throw;
×
1406
        }
×
1407
        // Thrown only if the parent directory of the lock file does not exist,
1✔
1408
        // which obviously indicates that we didn't need to delete anything
1✔
1409
        if (did_delete_realm) {
2✔
1410
            *did_delete_realm = false;
2✔
1411
        }
2✔
1412
        return;
2✔
1413
    }
2✔
1414
    if (!lock_successful) {
14✔
1415
        throw FileAccessError(
2✔
1416
            ErrorCodes::DeleteOnOpenRealm,
2✔
1417
            util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path),
2✔
1418
            realm_file_path);
2✔
1419
    }
2✔
1420
}
14✔
1421

1422
AuditInterface* Realm::audit_context() const noexcept
1423
{
189,231✔
1424
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
189,231✔
1425
}
189,231✔
1426

1427
namespace {
1428

1429
/*********************************** PropId **********************************/
1430

1431
// The KeyPathResolver will build up a tree of these objects starting with the
1432
// first property. If a wildcard specifier is part of the path, one object can
1433
// have several children.
1434
struct PropId {
1435
    PropId(TableKey tk, ColKey ck, const Property* prop, const ObjectSchema* os, bool b)
1436
        : table_key(tk)
1437
        , col_key(ck)
1438
        , origin_prop(prop)
1439
        , target_schema(os)
1440
        , mandatory(b)
1441
    {
1,058✔
1442
    }
1,058✔
1443
    void expand(KeyPath& key_path, KeyPathArray& key_path_array) const;
1444

1445
    TableKey table_key;
1446
    ColKey col_key;
1447
    const Property* origin_prop;
1448
    const ObjectSchema* target_schema;
1449
    std::vector<PropId> children;
1450
    bool mandatory;
1451
};
1452

1453
// This function will create one KeyPath entry in key_path_array for every
1454
// branch in the tree,
1455
void PropId::expand(KeyPath& key_path, KeyPathArray& key_path_array) const
1456
{
1,020✔
1457
    key_path.emplace_back(table_key, col_key);
1,020✔
1458
    if (children.empty()) {
1,020✔
1459
        key_path_array.push_back(key_path);
676✔
1460
    }
676✔
1461
    else {
344✔
1462
        for (auto& child : children) {
354✔
1463
            child.expand(key_path, key_path_array);
354✔
1464
        }
354✔
1465
    }
344✔
1466
    key_path.pop_back();
1,020✔
1467
}
1,020✔
1468

1469
/****************************** KeyPathResolver ******************************/
1470

1471
class KeyPathResolver {
1472
public:
1473
    KeyPathResolver(Group& g, const Schema& schema)
1474
        : m_group(g)
1475
        , m_schema(schema)
1476
    {
666✔
1477
    }
666✔
1478

1479
    void resolve(const ObjectSchema* object_schema, const char* path)
1480
    {
666✔
1481
        m_full_path = path;
666✔
1482
        if (!_resolve(m_root_props, object_schema, path, true)) {
666✔
1483
            throw InvalidArgument(util::format("'%1' does not resolve in any valid key paths.", m_full_path));
2✔
1484
        }
2✔
1485
    }
666✔
1486

1487
    void expand(KeyPathArray& key_path_array) const
1488
    {
658✔
1489
        for (auto& elem : m_root_props) {
666✔
1490
            KeyPath key_path;
666✔
1491
            key_path.reserve(4);
666✔
1492
            elem.expand(key_path, key_path_array);
666✔
1493
        }
666✔
1494
    }
658✔
1495

1496
private:
1497
    std::pair<ColKey, const ObjectSchema*> get_col_key(const Property* prop);
1498
    bool _resolve(std::vector<PropId>& props, const ObjectSchema* object_schema, const char* path, bool mandatory);
1499
    bool _resolve(PropId& current, const char* path);
1500

1501
    Group& m_group;
1502
    const char* m_full_path = nullptr;
1503
    const Schema& m_schema;
1504
    std::vector<PropId> m_root_props;
1505
};
1506

1507
// Get the column key for a specific Property. In case the property is representing a backlink
1508
// we need to look up the backlink column based on the forward link properties.
1509
std::pair<ColKey, const ObjectSchema*> KeyPathResolver::get_col_key(const Property* prop)
1510
{
1,058✔
1511
    ColKey col_key = prop->column_key;
1,058✔
1512
    const ObjectSchema* target_schema = nullptr;
1,058✔
1513
    if (prop->type == PropertyType::Object || prop->type == PropertyType::LinkingObjects) {
1,058✔
1514
        auto found_schema = m_schema.find(prop->object_type);
568✔
1515
        if (found_schema != m_schema.end()) {
568✔
1516
            target_schema = &*found_schema;
568✔
1517
            if (prop->type == PropertyType::LinkingObjects) {
568✔
1518
                auto origin_prop = target_schema->property_for_name(prop->link_origin_property_name);
222✔
1519
                auto origin_table = ObjectStore::table_for_object_type(m_group, target_schema->name);
222✔
1520
                col_key = origin_table->get_opposite_column(origin_prop->column_key);
222✔
1521
            }
222✔
1522
        }
568✔
1523
    }
568✔
1524
    return {col_key, target_schema};
1,058✔
1525
}
1,058✔
1526

1527
// This function will add one or more PropId objects to the props array. This array can either be the root
1528
// array in the KeyPathResolver or it can be the 'children' array in one PropId.
1529
bool KeyPathResolver::_resolve(std::vector<PropId>& props, const ObjectSchema* object_schema, const char* path,
1530
                               bool mandatory)
1531
{
1,022✔
1532
    if (*path == '*') {
1,022✔
1533
        path++;
22✔
1534
        // Add all properties
11✔
1535
        props.reserve(object_schema->persisted_properties.size() + object_schema->computed_properties.size());
22✔
1536
        for (auto& p : object_schema->persisted_properties) {
60✔
1537
            auto [col_key, target_schema] = get_col_key(&p);
60✔
1538
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
60✔
1539
        }
60✔
1540
        for (const auto& p : object_schema->computed_properties) {
16✔
1541
            auto [col_key, target_schema] = get_col_key(&p);
10✔
1542
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
10✔
1543
        }
10✔
1544
    }
22✔
1545
    else {
1,000✔
1546
        auto p = find_chr(path, '.');
1,000✔
1547
        StringData property(path, p - path);
1,000✔
1548
        path = p;
1,000✔
1549
        auto prop = object_schema->property_for_public_name(property);
1,000✔
1550
        if (prop) {
1,000✔
1551
            auto [col_key, target_schema] = get_col_key(prop);
988✔
1552
            props.emplace_back(object_schema->table_key, col_key, prop, target_schema, true);
988✔
1553
        }
988✔
1554
        else {
12✔
1555
            if (mandatory) {
12✔
1556
                throw InvalidArgument(util::format("Property '%1' in KeyPath '%2' is not a valid property in %3.",
4✔
1557
                                                   property, m_full_path, object_schema->name));
4✔
1558
            }
4✔
1559
            else {
8✔
1560
                return false;
8✔
1561
            }
8✔
1562
        }
1,010✔
1563
    }
1,000✔
1564

505✔
1565
    if (*path++ == '.') {
1,010✔
1566
        auto it = props.begin();
346✔
1567
        while (it != props.end()) {
734✔
1568
            if (_resolve(*it, path)) {
388✔
1569
                ++it;
350✔
1570
            }
350✔
1571
            else {
38✔
1572
                it = props.erase(it);
38✔
1573
            }
38✔
1574
        }
388✔
1575
    }
346✔
1576
    return props.size();
1,010✔
1577
}
1,010✔
1578

1579
bool KeyPathResolver::_resolve(PropId& current, const char* path)
1580
{
388✔
1581
    auto object_schema = current.target_schema;
388✔
1582
    if (!object_schema) {
388✔
1583
        if (current.mandatory) {
32✔
1584
            throw InvalidArgument(
2✔
1585
                util::format("Property '%1' in KeyPath '%2' is not a collection of objects or an object "
2✔
1586
                             "reference, so it cannot be used as an intermediate keypath element.",
2✔
1587
                             current.origin_prop->public_name, m_full_path));
2✔
1588
        }
2✔
1589
        // Check if the rest of the path is stars. If not, we should exclude this property
15✔
1590
        do {
38✔
1591
            auto p = find_chr(path, '.');
38✔
1592
            StringData property(path, p - path);
38✔
1593
            path = p;
38✔
1594
            if (property != "*") {
38✔
1595
                return false;
24✔
1596
            }
24✔
1597
        } while (*path++ == '.');
14✔
1598
        return true;
18✔
1599
    }
356✔
1600
    // Target schema exists - proceed
178✔
1601
    return _resolve(current.children, object_schema, path, current.mandatory);
356✔
1602
}
356✔
1603

1604
} // namespace
1605

1606
KeyPathArray Realm::create_key_path_array(StringData table_name, const std::vector<std::string>& key_paths)
1607
{
652✔
1608
    std::vector<const char*> vec;
652✔
1609
    vec.reserve(key_paths.size());
652✔
1610
    for (auto& kp : key_paths) {
652✔
1611
        vec.push_back(kp.c_str());
652✔
1612
    }
652✔
1613
    return create_key_path_array(m_schema.find(table_name)->table_key, vec.size(), &vec.front());
652✔
1614
}
652✔
1615

1616
KeyPathArray Realm::create_key_path_array(TableKey table_key, size_t num_key_paths, const char** all_key_paths)
1617
{
666✔
1618
    auto object_schema = m_schema.find(table_key);
666✔
1619
    REALM_ASSERT(object_schema != m_schema.end());
666✔
1620
    KeyPathArray resolved_key_path_array;
666✔
1621
    for (size_t n = 0; n < num_key_paths; n++) {
1,332✔
1622
        KeyPathResolver resolver(read_group(), m_schema);
666✔
1623
        // Build property tree
333✔
1624
        resolver.resolve(&*object_schema, all_key_paths[n]);
666✔
1625
        // Expand tree into separate lines
333✔
1626
        resolver.expand(resolved_key_path_array);
666✔
1627
    }
666✔
1628
    return resolved_key_path_array;
666✔
1629
}
666✔
1630

1631
#ifdef REALM_DEBUG
1632
void Realm::print_key_path_array(const KeyPathArray& kpa)
NEW
1633
{
×
NEW
1634
    auto& g = read_group();
×
NEW
1635
    for (auto& kp : kpa) {
×
NEW
1636
        for (auto [tk, ck] : kp) {
×
NEW
1637
            auto table = g.get_table(tk);
×
NEW
1638
            std::cout << '{' << table->get_name() << ':';
×
NEW
1639
            if (ck.get_type() == col_type_BackLink) {
×
NEW
1640
                auto col_key = table->get_opposite_column(ck);
×
NEW
1641
                table = table->get_opposite_table(ck);
×
NEW
1642
                std::cout << '{' << table->get_name() << ':' << table->get_column_name(col_key) << "}->";
×
NEW
1643
            }
×
NEW
1644
            else {
×
NEW
1645
                std::cout << table->get_column_name(ck);
×
NEW
1646
            }
×
NEW
1647
            std::cout << '}';
×
NEW
1648
        }
×
NEW
1649
        std::cout << std::endl;
×
NEW
1650
    }
×
NEW
1651
}
×
1652
#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