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

realm / realm-core / 2294

02 May 2024 08:09PM UTC coverage: 90.761% (+0.01%) from 90.747%
2294

push

Evergreen

web-flow
Fix a deadlock when accessing current user from inside an App listener (#7671)

App::switch_user() emitted changes without first releasing the lock on
m_user_mutex, leading to a deadlock if anyone inside the listener tried to
acquire the mutex. The rest of the places where we emitted changes were
correct.

The newly added wrapper catches this error when building with clang.

101982 of 180246 branches covered (56.58%)

14 of 17 new or added lines in 2 files covered. (82.35%)

66 existing lines in 15 files now uncovered.

212573 of 234212 relevant lines covered (90.76%)

5645071.85 hits per line

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

93.18
/test/test_lang_bind_helper.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 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 <map>
20
#include <sstream>
21
#include <mutex>
22
#include <condition_variable>
23
#include <atomic>
24
#include <chrono>
25
#include <thread>
26
#include "testsettings.hpp"
27
#ifdef TEST_LANG_BIND_HELPER
28

29
#include <realm.hpp>
30
#include <realm/util/encrypted_file_mapping.hpp>
31
#include <realm/util/to_string.hpp>
32
#include <realm/replication.hpp>
33
#include <realm/util/backtrace.hpp>
34

35
#include "test.hpp"
36
#include "test_table_helper.hpp"
37
#include "util/misc.hpp"
38
#include "util/spawned_process.hpp"
39

40
using namespace realm;
41
using namespace realm::util;
42
using namespace realm::test_util;
43
using unit_test::TestContext;
44

45
// Test independence and thread-safety
46
// -----------------------------------
47
//
48
// All tests must be thread safe and independent of each other. This
49
// is required because it allows for both shuffling of the execution
50
// order and for parallelized testing.
51
//
52
// In particular, avoid using std::rand() since it is not guaranteed
53
// to be thread safe. Instead use the API offered in
54
// `test/util/random.hpp`.
55
//
56
// All files created in tests must use the TEST_PATH macro (or one of
57
// its friends) to obtain a suitable file system path. See
58
// `test/util/test_path.hpp`.
59
//
60
//
61
// Debugging and the ONLY() macro
62
// ------------------------------
63
//
64
// A simple way of disabling all tests except one called `Foo`, is to
65
// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the
66
// test suite. Note that you can also use filtering by setting the
67
// environment varible `UNITTEST_FILTER`. See `README.md` for more on
68
// this.
69
//
70
// Another way to debug a particular test, is to copy that test into
71
// `experiments/testcase.cpp` and then run `sh build.sh
72
// check-testcase` (or one of its friends) from the command line.
73

74
namespace {
75

76
void work_on_frozen(TestContext& test_context, TransactionRef frozen)
77
{
200✔
78
    CHECK(frozen->is_frozen());
200✔
79
    CHECK_THROW(frozen->promote_to_write(), LogicError);
200✔
80
    auto table = frozen->get_table("my_table");
200✔
81
    CHECK(table->is_frozen());
200✔
82
    auto col = table->get_column_key("my_col_1");
200✔
83
    int64_t sum = 0;
200✔
84
    for (auto i : *table) {
171,974✔
85
        sum += i.get<int64_t>(col);
171,974✔
86
    }
171,974✔
87
    CHECK_EQUAL(sum, 1000 / 2 * 999);
200✔
88
    TableView tv = table->where().not_equal(col, 42).find_all();
200✔
89
    CHECK(tv.is_frozen());
200✔
90
}
200✔
91

92
TEST(Transactions_Frozen)
93
{
2✔
94
    SHARED_GROUP_TEST_PATH(path);
2✔
95
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
96
    DBRef db = DB::create(*hist_w, path);
2✔
97
    TransactionRef frozen;
2✔
98
    {
2✔
99
        auto wt = db->start_write();
2✔
100
        auto table = wt->add_table("my_table");
2✔
101
        auto col = table->add_column(type_Int, "my_col_1");
2✔
102
        for (int j = 0; j < 1000; ++j) {
2,002✔
103
            table->create_object().set_all(j);
2,000✔
104
        }
2,000✔
105
        wt->commit_and_continue_as_read();
2✔
106
        frozen = wt->freeze();
2✔
107
        auto imported_table = frozen->import_copy_of(table);
2✔
108
        CHECK(imported_table->is_frozen());
2✔
109
        TableView tv = table->where().not_equal(col, 42).find_all();
2✔
110
        CHECK(!tv.is_frozen());
2✔
111
        auto imported = frozen->import_copy_of(tv, PayloadPolicy::Move);
2✔
112
        CHECK(frozen->is_frozen());
2✔
113
        CHECK(imported->is_frozen());
2✔
114
        auto imported2 = frozen->import_copy_of(tv, PayloadPolicy::Stay);
2✔
115
        CHECK(!imported2->is_frozen());
2✔
116
        imported2->sync_if_needed();
2✔
117
        CHECK(imported2->is_frozen());
2✔
118
    }
2✔
119
    // create multiple threads, all doing read-only work on Frozen
120
    const int num_threads = 100;
2✔
121
    std::thread frozen_workers[num_threads];
2✔
122
    for (int j = 0; j < num_threads; ++j)
202✔
123
        frozen_workers[j] = std::thread([&] {
200✔
124
            work_on_frozen(test_context, frozen);
200✔
125
        });
200✔
126
    for (int j = 0; j < num_threads; ++j)
202✔
127
        frozen_workers[j].join();
200✔
128
}
2✔
129

130
TEST(Transactions_ConcurrentFrozenTableGetByName)
131
{
2✔
132
#if REALM_VALGRIND
133
    // This test is slow under valgrind. Additionally, there is
134
    // a --max-threads config of 5000 for all (concurrent) tests
135
    constexpr int num_threads = 3;
136
#else
137
    constexpr int num_threads = 1000;
2✔
138
#endif
2✔
139
    constexpr int num_tables = 3 * num_threads;
2✔
140
    SHARED_GROUP_TEST_PATH(path);
2✔
141
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
142
    DBRef db = DB::create(*hist_w, path);
2✔
143
    TransactionRef frozen;
2✔
144
    std::string table_names[num_tables];
2✔
145
    {
2✔
146
        auto wt = db->start_write();
2✔
147
        for (int j = 0; j < num_tables; ++j) {
6,002✔
148
            std::string name = "Table" + to_string(j);
6,000✔
149
            table_names[j] = name;
6,000✔
150
            wt->add_table(name);
6,000✔
151
        }
6,000✔
152
        wt->commit_and_continue_as_read();
2✔
153
        frozen = wt->freeze();
2✔
154
    }
2✔
155
    auto runner = [&](int first, int last) {
1,987✔
156
        millisleep(1);
1,987✔
157
        for (int j = first; j < last; ++j) {
1,986,465✔
158
            frozen->get_table(table_names[j]);
1,984,478✔
159
        }
1,984,478✔
160
    };
1,987✔
161
    std::thread threads[num_threads];
2✔
162
    for (int j = 0; j < num_threads; ++j) {
2,002✔
163
        threads[j] = std::thread(runner, j * 2, j * 2 + num_threads);
2,000✔
164
    }
2,000✔
165
    for (int j = 0; j < num_threads; ++j)
2,002✔
166
        threads[j].join();
2,000✔
167
}
2✔
168

169
TEST(Transactions_ReclaimFrozen)
170
{
2✔
171
    struct Entry {
2✔
172
        TransactionRef frozen;
2✔
173
        Obj o;
2✔
174
        int64_t value;
2✔
175
    };
2✔
176
    int num_pending_transactions = 100;
2✔
177
    int num_transactions_created = 1000;
2✔
178
    int num_objects = 200;
2✔
179
    int num_checks_pr_trans = 10;
2✔
180

181
    SHARED_GROUP_TEST_PATH(path);
2✔
182
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
183
    DBRef db = DB::create(*hist_w, path);
2✔
184
    std::vector<Entry> refs;
2✔
185
    refs.resize(num_pending_transactions);
2✔
186
    Random random(random_int<unsigned long>());
2✔
187

188
    auto wt = db->start_write();
2✔
189
    auto tbl = wt->add_table("TestTable");
2✔
190
    auto col = tbl->add_column(type_Int, "IntCol");
2✔
191
    Obj o;
2✔
192
    for (int j = 0; j < num_objects; ++j) {
402✔
193
        o = tbl->create_object(ObjKey(j));
400✔
194
        o.set<Int>(col, 10000000000 + j);
400✔
195
    }
400✔
196
    wt->commit_and_continue_as_read();
2✔
197
    for (int j = 0; j < num_transactions_created; ++j) {
2,002✔
198
        int trans_number = random.draw_int_mod(num_pending_transactions);
2,000✔
199
        auto frozen = wt->freeze();
2,000✔
200
        // auto frozen = wt->duplicate();
201
        refs[trans_number].frozen = frozen;
2,000✔
202
        refs[trans_number].o = frozen->import_copy_of(o);
2,000✔
203
        refs[trans_number].value = o.get<Int>(col);
2,000✔
204
        wt->promote_to_write();
2,000✔
205
        int key = random.draw_int_mod(num_objects);
2,000✔
206
        o = tbl->get_object(ObjKey(key));
2,000✔
207
        o.set<Int>(col, o.get<Int>(col) + 42);
2,000✔
208
        wt->commit_and_continue_as_read();
2,000✔
209
        for (int k = 0; k < num_checks_pr_trans; ++k) {
22,000✔
210
            int selected_trans = random.draw_int_mod(num_pending_transactions);
20,000✔
211
            if (refs[selected_trans].frozen) {
20,000✔
212
                CHECK(refs[selected_trans].value == refs[selected_trans].o.get<Int>(col));
18,096✔
213
            }
18,096✔
214
        }
20,000✔
215
    }
2,000✔
216
    for (auto& e : refs) {
200✔
217
        e.frozen.reset();
200✔
218
    }
200✔
219
    wt->promote_to_write();
2✔
220
    wt->commit();
2✔
221
    // frozen = wt->freeze();
222
}
2✔
223

224
TEST(Transactions_ConcurrentFrozenTableGetByKey)
225
{
2✔
226
#if REALM_VALGRIND
227
    // This test is slow under valgrind. Additionally, there is
228
    // a --max-threads config of 5000 for all (concurrent) tests
229
    constexpr int num_threads = 3;
230
#else
231
    constexpr int num_threads = 1000;
2✔
232
#endif
2✔
233
    constexpr int num_tables = 3 * num_threads;
2✔
234
    SHARED_GROUP_TEST_PATH(path);
2✔
235
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
236
    DBRef db = DB::create(*hist_w, path);
2✔
237
    TransactionRef frozen;
2✔
238
    TableKey table_keys[num_tables];
2✔
239
    {
2✔
240
        auto wt = db->start_write();
2✔
241
        for (int j = 0; j < num_tables; ++j) {
6,002✔
242
            std::string name = "Table" + to_string(j);
6,000✔
243
            auto table = wt->add_table(name);
6,000✔
244
            table_keys[j] = table->get_key();
6,000✔
245
        }
6,000✔
246
        wt->commit_and_continue_as_read();
2✔
247
        frozen = wt->freeze();
2✔
248
    }
2✔
249
    auto runner = [&](int first, int last) {
1,982✔
250
        millisleep(1);
1,982✔
251
        for (int j = first; j < last; ++j) {
1,337,186✔
252
            auto table = frozen->get_table(table_keys[j]);
1,335,204✔
253
            CHECK(table->get_key() == table_keys[j]);
1,335,204✔
254
        }
1,335,204✔
255
    };
1,982✔
256
    std::thread threads[num_threads];
2✔
257
    for (int j = 0; j < num_threads; ++j) {
2,002✔
258
        threads[j] = std::thread(runner, j * 2, j * 2 + num_threads);
2,000✔
259
    }
2,000✔
260
    for (int j = 0; j < num_threads; ++j)
2,002✔
261
        threads[j].join();
2,000✔
262
}
2✔
263

264

265
TEST(Transactions_ConcurrentFrozenQueryAndObj)
266
{
2✔
267
    SHARED_GROUP_TEST_PATH(path);
2✔
268
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
269
    DBRef db = DB::create(*hist_w, path);
2✔
270
    TransactionRef frozen;
2✔
271
    ObjKey obj_keys[1000];
2✔
272
    {
2✔
273
        auto wt = db->start_write();
2✔
274
        auto table = wt->add_table("MyTable");
2✔
275
        table->add_column(type_Int, "MyCol");
2✔
276
        for (int i = 0; i < 1000; ++i) {
2,002✔
277
            obj_keys[i] = table->create_object().set_all(i).get_key();
2,000✔
278
        }
2,000✔
279
        wt->commit_and_continue_as_read();
2✔
280
        frozen = wt->freeze();
2✔
281
    }
2✔
282
    auto runner = [&](int first, int last) {
992✔
283
        millisleep(1);
992✔
284
        auto table = frozen->get_table("MyTable");
992✔
285
        auto col = table->get_column_key("MyCol");
992✔
286
        for (int j = first; j < last; ++j) {
474,130✔
287
            // loads of concurrent queries created and executed:
288
            TableView tb = table->where().equal(col, j).find_all();
473,138✔
289
            CHECK(tb.size() == 1);
473,138✔
290
            CHECK(tb.get_key(0) == obj_keys[j]);
473,138✔
291
            // concurrent reads from results are just fine:
292
            auto obj = tb[0];
473,138✔
293
            CHECK(obj.get<Int>(col) == j);
473,138✔
294
        }
473,138✔
295
    };
992✔
296
    std::thread threads[500];
2✔
297
    for (int j = 0; j < 500; ++j) {
1,002✔
298
        threads[j] = std::thread(runner, j, j + 500);
1,000✔
299
    }
1,000✔
300
    for (int j = 0; j < 500; ++j)
1,002✔
301
        threads[j].join();
1,000✔
302
}
2✔
303

304
// this tests resilience against some violations of the Core API.
305
// It creates a lot of races between accessor use and transaction close.
306
// This is undefined behaviour
307
// but the goal is none the less to "harden" Core against just crashing
308
// **           THIS TEST MAY CRASH OCCASIONALLY          **
309
// ** if so, disable it and run it in a different setting **
310
#if 0 // it actually fails occationally
311
TEST_IF(Transactions_ConcurrentFrozenQueryAndObjAndTransactionClose, !REALM_TSAN)
312
{
313
    SHARED_GROUP_TEST_PATH(path);
314
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
315
    DBRef db = DB::create(*hist_w, path);
316
    TransactionRef frozen;
317
    ObjKey obj_keys[1000];
318
    {
319
        auto wt = db->start_write();
320
        auto table = wt->add_table("MyTable");
321
        table->add_column(type_Int, "MyCol");
322
        for (int i = 0; i < 1000; ++i) {
323
            obj_keys[i] = table->create_object().set_all(i).get_key();
324
        }
325
        wt->commit_and_continue_as_read();
326
        frozen = wt->freeze();
327
    }
328
    auto runner = [&](int first, int last) {
329
        millisleep(1);
330
        try {
331
            auto table = frozen->get_table("MyTable");
332
            auto col = table->get_column_key("MyCol");
333
            while (1) {
334
                for (int j = first; j < last; ++j) {
335
                    // loads of concurrent queries created and executed:
336
                    TableView tb = table->where().equal(col, j).find_all();
337
                    CHECK(tb.size() == 1);
338
                    CHECK(tb.get_key(0) == obj_keys[j]);
339
                    // concurrent reads from results are just fine:
340
                    auto obj = tb[0];
341
                    CHECK(obj.get<Int>(col) == j);
342
                }
343
            }
344
        }
345
        catch (NoSuchTable&) {
346
        }
347
        catch (LogicError&) {
348
        }
349
    };
350
    std::thread threads[100];
351
    for (int j = 0; j < 100; ++j) {
352
        threads[j] = std::thread(runner, j, j + 100);
353
    }
354
    millisleep(10);
355
    frozen->close(); // this should cause all threads to throw
356
    for (int j = 0; j < 100; ++j)
357
        threads[j].join();
358
}
359
#endif
360

361
class MyHistory : public _impl::History {
362
public:
363
    MyHistory(const MyHistory&) = delete;
364
    explicit MyHistory(MyHistory* write_history = nullptr)
365
        : m_write_history(write_history)
52✔
366
    {
104✔
367
    }
104✔
368
    std::vector<char> m_incoming_changeset;
369
    version_type m_incoming_version;
370
    struct ChangeSet {
371
        std::vector<char> changes;
372
        bool finalized = false;
373
    };
374
    std::map<uint_fast64_t, ChangeSet> m_changesets;
375
    MyHistory* m_write_history = nullptr;
376

377
    void update_from_ref_and_version(ref_type, version_type version) override
378
    {
110✔
379
        update_from_parent(version);
110✔
380
    }
110✔
381
    void update_from_parent(version_type) override
382
    {
110✔
383
        if (m_write_history)
110✔
384
            m_changesets = m_write_history->m_changesets;
108✔
385
    }
110✔
386
    version_type add_changeset(const char* data, size_t size, version_type orig_version)
387
    {
5,207✔
388
        m_incoming_changeset.assign(data, data + size); // Throws
5,207✔
389
        version_type new_version = orig_version + 1;
5,207✔
390
        m_incoming_version = new_version;
5,207✔
391
        // Allocate space for the new changeset in m_changesets such that we can
392
        // be sure no exception will be thrown whan adding the changeset in
393
        // finalize_changeset().
394
        m_changesets[new_version]; // Throws
5,207✔
395
        return new_version;
5,207✔
396
    }
5,207✔
397
    void finalize()
398
    {
5,207✔
399
        // The following operation will not throw due to the space reservation
400
        // carried out in prepare_new_changeset().
401
        m_changesets[m_incoming_version].changes = std::move(m_incoming_changeset);
5,207✔
402
        m_changesets[m_incoming_version].finalized = true;
5,207✔
403
    }
5,207✔
404
    void get_changesets(version_type begin_version, version_type end_version,
405
                        BinaryIterator* buffer) const noexcept override
406
    {
60✔
407
        size_t n = size_t(end_version - begin_version);
60✔
408
        for (size_t i = 0; i < n; ++i) {
120✔
409
            uint_fast64_t version = begin_version + i + 1;
60✔
410
            auto j = m_changesets.find(version);
60✔
411
            REALM_ASSERT(j != m_changesets.end());
60✔
412
            const ChangeSet& changeset = j->second;
60✔
413
            REALM_ASSERT(changeset.finalized); // Must have been finalized
60✔
414
            buffer[i] = BinaryData(changeset.changes.data(), changeset.changes.size());
60✔
415
        }
60✔
416
    }
60✔
417
    void set_oldest_bound_version(version_type) override
418
    {
5,207✔
419
        // No-op
420
    }
5,207✔
421

422
    void verify() const override
423
    {
50✔
424
        // No-op
425
    }
50✔
426
};
427

428
class ShortCircuitHistory : public Replication {
429
public:
430
    using version_type = _impl::History::version_type;
431

432
    version_type prepare_changeset(const char* data, size_t size, version_type orig_version) override
433
    {
5,207✔
434
        return m_history.add_changeset(data, size, orig_version); // Throws
5,207✔
435
    }
5,207✔
436

437
    void finalize_changeset() noexcept override
438
    {
5,207✔
439
        m_history.finalize();
5,207✔
440
    }
5,207✔
441

442
    HistoryType get_history_type() const noexcept override
443
    {
20,286✔
444
        return hist_InRealm;
20,286✔
445
    }
20,286✔
446

447
    _impl::History* _get_history_write() override
448
    {
10,414✔
449
        return &m_history;
10,414✔
450
    }
10,414✔
451

452
    std::unique_ptr<_impl::History> _create_history_read() override
453
    {
74✔
454
        return std::make_unique<MyHistory>(&m_history);
74✔
455
    }
74✔
456

457
    int get_history_schema_version() const noexcept override
458
    {
38✔
459
        return 0;
38✔
460
    }
38✔
461

462
    bool is_upgradable_history_schema(int) const noexcept override
463
    {
×
464
        REALM_ASSERT(false);
×
465
        return false;
×
466
    }
×
467

468
    void upgrade_history_schema(int) override
469
    {
×
470
        REALM_ASSERT(false);
×
471
    }
×
472

473

474
private:
475
    MyHistory m_history;
476
};
477

478
} // anonymous namespace
479

480

481
TEST(LangBindHelper_AdvanceReadTransact_Basics)
482
{
2✔
483
    SHARED_GROUP_TEST_PATH(path);
2✔
484
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
485
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
486

487
    // Start a read transaction (to be repeatedly advanced)
488
    TransactionRef rt = sg->start_read();
2✔
489
    CHECK_EQUAL(0, rt->size());
2✔
490

491
    // Try to advance without anything having happened
492
    rt->advance_read();
2✔
493
    rt->verify();
2✔
494
    CHECK_EQUAL(0, rt->size());
2✔
495

496
    // Try to advance after an empty write transaction
497
    {
2✔
498
        WriteTransaction wt(sg);
2✔
499
        wt.commit();
2✔
500
    }
2✔
501
    rt->advance_read();
2✔
502
    rt->verify();
2✔
503
    CHECK_EQUAL(0, rt->size());
2✔
504

505
    // Try to advance after a superfluous rollback
506
    {
2✔
507
        WriteTransaction wt(sg);
2✔
508
        // Implicit rollback
509
    }
2✔
510
    rt->advance_read();
2✔
511
    rt->verify();
2✔
512
    CHECK_EQUAL(0, rt->size());
2✔
513

514
    // Try to advance after a propper rollback
515
    {
2✔
516
        WriteTransaction wt(sg);
2✔
517
        wt.add_table("bad");
2✔
518
        // Implicit rollback
519
    }
2✔
520
    rt->advance_read();
2✔
521
    rt->verify();
2✔
522
    CHECK_EQUAL(0, rt->size());
2✔
523

524
    // Create a table via the other SharedGroup
525
    ObjKey k0;
2✔
526
    {
2✔
527
        WriteTransaction wt(sg);
2✔
528
        TableRef foo_w = wt.add_table("foo");
2✔
529
        foo_w->add_column(type_Int, "i");
2✔
530
        k0 = foo_w->create_object().get_key();
2✔
531
        wt.commit();
2✔
532
    }
2✔
533

534
    rt->advance_read();
2✔
535
    rt->verify();
2✔
536
    CHECK_EQUAL(1, rt->size());
2✔
537
    ConstTableRef foo = rt->get_table("foo");
2✔
538
    CHECK_EQUAL(1, foo->get_column_count());
2✔
539
    auto cols = foo->get_column_keys();
2✔
540
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
541
    CHECK_EQUAL(1, foo->size());
2✔
542
    CHECK_EQUAL(0, foo->get_object(k0).get<int64_t>(cols[0]));
2✔
543
    uint_fast64_t version = foo->get_content_version();
2✔
544

545
    // Modify the table via the other SharedGroup
546
    ObjKey k1;
2✔
547
    {
2✔
548
        WriteTransaction wt(sg);
2✔
549
        TableRef foo_w = wt.get_table("foo");
2✔
550
        foo_w->add_column(type_String, "s");
2✔
551
        foo_w->add_column(type_Bool, "b");
2✔
552
        foo_w->add_column(type_Float, "f");
2✔
553
        foo_w->add_column(type_Double, "d");
2✔
554
        foo_w->add_column(type_Binary, "bin");
2✔
555
        foo_w->add_column(type_Timestamp, "t");
2✔
556
        foo_w->add_column(type_Decimal, "dec");
2✔
557
        foo_w->add_column(type_ObjectId, "oid");
2✔
558
        foo_w->add_column(*foo_w, "link");
2✔
559
        cols = foo_w->get_column_keys();
2✔
560
        auto obj1 = foo_w->create_object();
2✔
561
        auto obj0 = foo_w->get_object(k0);
2✔
562
        k1 = obj1.get_key();
2✔
563
        obj1.set_all(2, StringData("b"), true, 1.1f, 1.2, BinaryData("hopla"), Timestamp(100, 300), Decimal("100"),
2✔
564
                     ObjectId("abcdefabcdefabcdefabcdef"), k1);
2✔
565
        obj0.set<int>(cols[0], 1);
2✔
566
        obj0.set<StringData>(cols[1], "a");
2✔
567
        wt.commit();
2✔
568
    }
2✔
569
    rt->advance_read();
2✔
570
    CHECK(version != foo->get_content_version());
2✔
571
    rt->verify();
2✔
572
    cols = foo->get_column_keys();
2✔
573
    CHECK_EQUAL(10, foo->get_column_count());
2✔
574
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
575
    CHECK_EQUAL(type_String, foo->get_column_type(cols[1]));
2✔
576
    CHECK_EQUAL(2, foo->size());
2✔
577
    auto obj0 = foo->get_object(k0);
2✔
578
    auto obj1 = foo->get_object(k1);
2✔
579
    CHECK_EQUAL(1, obj0.get<int64_t>(cols[0]));
2✔
580
    CHECK_EQUAL(2, obj1.get<int64_t>(cols[0]));
2✔
581
    CHECK_EQUAL("a", obj0.get<StringData>(cols[1]));
2✔
582
    CHECK_EQUAL("b", obj1.get<StringData>(cols[1]));
2✔
583
    CHECK_EQUAL(obj1.get<Bool>(cols[2]), true);
2✔
584
    CHECK_EQUAL(obj1.get<float>(cols[3]), 1.1f);
2✔
585
    CHECK_EQUAL(obj1.get<double>(cols[4]), 1.2);
2✔
586
    CHECK_EQUAL(obj1.get<BinaryData>(cols[5]), BinaryData("hopla"));
2✔
587
    CHECK_EQUAL(obj1.get<Timestamp>(cols[6]), Timestamp(100, 300));
2✔
588
    CHECK_EQUAL(obj1.get<Decimal>(cols[7]), Decimal("100"));
2✔
589
    CHECK_EQUAL(obj1.get<ObjectId>(cols[8]), ObjectId("abcdefabcdefabcdefabcdef"));
2✔
590
    CHECK_EQUAL(obj1.get<ObjKey>(cols[9]), obj1.get_key());
2✔
591
    CHECK_EQUAL(foo, rt->get_table("foo"));
2✔
592

593
    // Again, with no change
594
    rt->advance_read();
2✔
595
    rt->verify();
2✔
596
    CHECK_EQUAL(10, foo->get_column_count());
2✔
597
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
598
    CHECK_EQUAL(type_String, foo->get_column_type(cols[1]));
2✔
599
    CHECK_EQUAL(2, foo->size());
2✔
600
    CHECK_EQUAL(1, obj0.get<int64_t>(cols[0]));
2✔
601
    CHECK_EQUAL(2, obj1.get<int64_t>(cols[0]));
2✔
602
    CHECK_EQUAL("a", obj0.get<StringData>(cols[1]));
2✔
603
    CHECK_EQUAL("b", obj1.get<StringData>(cols[1]));
2✔
604
    CHECK_EQUAL(foo, rt->get_table("foo"));
2✔
605

606
    // Perform several write transactions before advancing the read transaction
607
    {
2✔
608
        WriteTransaction wt(sg);
2✔
609
        TableRef bar_w = wt.add_table("bar");
2✔
610
        bar_w->add_column(type_Int, "a");
2✔
611
        wt.commit();
2✔
612
    }
2✔
613
    {
2✔
614
        WriteTransaction wt(sg);
2✔
615
        wt.commit();
2✔
616
    }
2✔
617
    {
2✔
618
        WriteTransaction wt(sg);
2✔
619
        TableRef bar_w = wt.get_table("bar");
2✔
620
        bar_w->add_column(type_Float, "b");
2✔
621
        wt.commit();
2✔
622
    }
2✔
623
    {
2✔
624
        WriteTransaction wt(sg);
2✔
625
        // Implicit rollback
626
    }
2✔
627
    {
2✔
628
        WriteTransaction wt(sg);
2✔
629
        TableRef bar_w = wt.get_table("bar");
2✔
630
        bar_w->add_column(type_Double, "c");
2✔
631
        wt.commit();
2✔
632
    }
2✔
633

634
    rt->advance_read();
2✔
635
    rt->verify();
2✔
636
    CHECK_EQUAL(2, rt->size());
2✔
637
    CHECK_EQUAL(10, foo->get_column_count());
2✔
638
    cols = foo->get_column_keys();
2✔
639
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
640
    CHECK_EQUAL(type_String, foo->get_column_type(cols[1]));
2✔
641
    CHECK_EQUAL(2, foo->size());
2✔
642
    CHECK_EQUAL(1, obj0.get<int64_t>(cols[0]));
2✔
643
    CHECK_EQUAL(2, obj1.get<int64_t>(cols[0]));
2✔
644
    CHECK_EQUAL("a", obj0.get<StringData>(cols[1]));
2✔
645
    CHECK_EQUAL("b", obj1.get<StringData>(cols[1]));
2✔
646
    CHECK_EQUAL(foo, rt->get_table("foo"));
2✔
647
    ConstTableRef bar = rt->get_table("bar");
2✔
648
    cols = bar->get_column_keys();
2✔
649
    CHECK_EQUAL(3, bar->get_column_count());
2✔
650
    CHECK_EQUAL(type_Int, bar->get_column_type(cols[0]));
2✔
651
    CHECK_EQUAL(type_Float, bar->get_column_type(cols[1]));
2✔
652
    CHECK_EQUAL(type_Double, bar->get_column_type(cols[2]));
2✔
653

654
    // Clear tables - not supported before backlinks work again
655
    {
2✔
656
        WriteTransaction wt(sg);
2✔
657
        TableRef foo_w = wt.get_table("foo");
2✔
658
        foo_w->clear();
2✔
659
        TableRef bar_w = wt.get_table("bar");
2✔
660
        bar_w->clear();
2✔
661
        wt.commit();
2✔
662
    }
2✔
663
    rt->advance_read();
2✔
664
    rt->verify();
2✔
665

666
    size_t free_space, used_space;
2✔
667
    sg->get_stats(free_space, used_space);
2✔
668

669
    CHECK_EQUAL(2, rt->size());
2✔
670
    CHECK(foo);
2✔
671
    cols = foo->get_column_keys();
2✔
672
    CHECK_EQUAL(10, foo->get_column_count());
2✔
673
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
674
    CHECK_EQUAL(type_String, foo->get_column_type(cols[1]));
2✔
675
    CHECK_EQUAL(0, foo->size());
2✔
676
    CHECK(bar);
2✔
677
    cols = bar->get_column_keys();
2✔
678
    CHECK_EQUAL(3, bar->get_column_count());
2✔
679
    CHECK_EQUAL(type_Int, bar->get_column_type(cols[0]));
2✔
680
    CHECK_EQUAL(type_Float, bar->get_column_type(cols[1]));
2✔
681
    CHECK_EQUAL(type_Double, bar->get_column_type(cols[2]));
2✔
682
    CHECK_EQUAL(0, bar->size());
2✔
683
    CHECK_EQUAL(foo, rt->get_table("foo"));
2✔
684
    CHECK_EQUAL(bar, rt->get_table("bar"));
2✔
685
}
2✔
686

687
TEST(LangBindHelper_AdvanceReadTransact_AddTableWithFreshSharedGroup)
688
{
2✔
689
    SHARED_GROUP_TEST_PATH(path);
2✔
690

691
    // Testing that a foreign transaction, that adds a table, can be applied to
692
    // a freshly created SharedGroup, even when another table existed in the
693
    // group prior to the one being added in the mentioned transaction. This
694
    // test is relevant because of the way table accesors are created and
695
    // managed inside a SharedGroup, in particular because table accessors are
696
    // created lazily, and will therefore not be present in a freshly created
697
    // SharedGroup instance.
698

699
    // Add the first table
700
    {
2✔
701
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
702
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
703
        WriteTransaction wt(sg_w);
2✔
704
        wt.add_table("table_1");
2✔
705
        wt.commit();
2✔
706
    }
2✔
707

708
    // Create a SharedGroup to which we can apply a foreign transaction
709
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
710
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
711
    TransactionRef rt = sg->start_read();
2✔
712

713
    // Add the second table in a "foreign" transaction
714
    {
2✔
715
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
716
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
717
        WriteTransaction wt(sg_w);
2✔
718
        wt.add_table("table_2");
2✔
719
        wt.commit();
2✔
720
    }
2✔
721

722
    rt->advance_read();
2✔
723
}
2✔
724

725

726
TEST(LangBindHelper_AdvanceReadTransact_RemoveTableWithFreshSharedGroup)
727
{
2✔
728
    SHARED_GROUP_TEST_PATH(path);
2✔
729

730
    // Testing that a foreign transaction, that removes a table, can be applied
731
    // to a freshly created Sharedrt-> This test is relevant because of the
732
    // way table accesors are created and managed inside a SharedGroup, in
733
    // particular because table accessors are created lazily, and will therefore
734
    // not be present in a freshly created SharedGroup instance.
735

736
    // Add the table
737
    {
2✔
738
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
739
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
740
        WriteTransaction wt(sg_w);
2✔
741
        wt.add_table("table");
2✔
742
        wt.commit();
2✔
743
    }
2✔
744

745
    // Create a SharedGroup to which we can apply a foreign transaction
746
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
747
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
748
    TransactionRef rt = sg->start_read();
2✔
749

750
    // remove the table in a "foreign" transaction
751
    {
2✔
752
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
753
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
754
        WriteTransaction wt(sg_w);
2✔
755
        wt.get_group().remove_table("table");
2✔
756
        wt.commit();
2✔
757
    }
2✔
758

759
    rt->advance_read();
2✔
760
}
2✔
761

762

763
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_CreateManyTables, testing_supports_spawn_process)
764
{
2✔
765
    SHARED_GROUP_TEST_PATH(path);
2✔
766
    SHARED_GROUP_TEST_PATH(path2);
2✔
767

768
    if (SpawnedProcess::is_parent()) {
2✔
769
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
770
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
771
        WriteTransaction wt(sg_w);
2✔
772
        wt.add_table("table");
2✔
773
        wt.commit();
2✔
774
    }
2✔
775

776
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
777
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
778
    TransactionRef rt = sg->start_read();
2✔
779

780
    auto process = test_util::spawn_process(test_context.test_details.test_name, "make_many_tables");
2✔
781
    if (process->is_child()) {
2✔
782
        size_t free_space, used_space;
×
783
        {
×
784
            std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
×
785
            DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
×
786

787
            WriteTransaction wt(sg_w);
×
788
            for (int i = 0; i < 16; ++i) {
×
789
                wt.add_table(util::format("table_%1", i));
×
790
            }
×
791
            wt.commit();
×
792
            sg_w->get_stats(free_space, used_space);
×
793
        }
×
794
        {
×
795
            std::unique_ptr<Replication> hist_w2(realm::make_in_realm_history());
×
796
            DBRef sg_w2 = DB::create(*hist_w2, path2, DBOptions(crypt_key()));
×
797
            WriteTransaction wt(sg_w2);
×
798
            auto table = wt.add_table("stats");
×
799
            ColKey col = table->add_column(type_Int, "used_space");
×
800
            table->create_object().set<int64_t>(col, used_space);
×
801
            wt.commit();
×
802
        }
×
803

804
        exit(0);
×
805
    }
×
806
    else {
2✔
807
        process->wait_for_child_to_finish();
2✔
808
    }
2✔
809
    size_t reported_used_space = 0;
2✔
810
    {
2✔
811
        std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
812
        DBRef sg = DB::create(*hist, path2, DBOptions(crypt_key()));
2✔
813
        WriteTransaction wt(sg);
2✔
814
        auto table = wt.get_table("stats");
2✔
815
        CHECK(table);
2✔
816
        CHECK_EQUAL(table->size(), 1);
2✔
817
        reported_used_space = size_t(table->begin()->get<int64_t>("used_space"));
2✔
818
    }
2✔
819

820
    rt->advance_read();
2✔
821
    auto used_space1 = rt->get_used_space();
2✔
822
    CHECK_EQUAL(reported_used_space, used_space1);
2✔
823
}
2✔
824

825

826
TEST(LangBindHelper_AdvanceReadTransact_PinnedSize)
827
{
2✔
828
    SHARED_GROUP_TEST_PATH(path);
2✔
829
    constexpr int num_rows = 1000;
2✔
830
    constexpr int iterations = 10;
2✔
831
    constexpr int rows_per_iteration = num_rows / iterations;
2✔
832

833
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
834
    auto sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
835
    ObjKeys keys;
2✔
836

837
    // Create some data
838
    {
2✔
839
        {
2✔
840
            WriteTransaction wt(sg);
2✔
841
            auto table = wt.add_table("table");
2✔
842
            table->add_column(type_Int, "int");
2✔
843
            wt.commit();
2✔
844
        }
2✔
845
        for (size_t i = 0; i < iterations; i++) {
22✔
846
            WriteTransaction wt(sg);
20✔
847
            auto table = wt.get_table("table");
20✔
848
            auto col = table->get_column_key("int");
20✔
849
            for (int j = 0; j < rows_per_iteration; j++) {
2,020✔
850
                auto k = table->create_object().set(col, j).get_key();
2,000✔
851
                keys.push_back(k);
2,000✔
852
            }
2,000✔
853
            wt.commit();
20✔
854
        }
20✔
855
    }
2✔
856

857
    // Pin this version
858
    auto rt = sg->start_read();
2✔
859
    size_t free_space, used_space, locked_space;
2✔
860

861
    // Make some more versions
862
    {
2✔
863
        for (int i = 0; i < iterations; i++) {
22✔
864
            WriteTransaction wt(sg);
20✔
865
            auto table = wt.get_table("table");
20✔
866
            auto col = table->get_column_key("int");
20✔
867
            for (int j = 0; j < rows_per_iteration; j++) {
2,020✔
868
                int ndx = rows_per_iteration * i + j;
2,000✔
869
                table->get_object(keys[ndx]).set(col, 2 * ndx);
2,000✔
870
            }
2,000✔
871
            wt.commit();
20✔
872
        }
20✔
873
        sg->get_stats(free_space, used_space, &locked_space);
2✔
874
    }
2✔
875

876
    CHECK_GREATER(locked_space, 0);
2✔
877
    CHECK_LESS(locked_space, free_space);
2✔
878

879
    // Cancel read transaction
880
    rt = nullptr;
2✔
881
    size_t new_locked_space;
2✔
882
    {
2✔
883
        WriteTransaction wt(sg);
2✔
884
        wt.commit();
2✔
885
        // Large history entries are freed here
886
    }
2✔
887
    {
2✔
888
        WriteTransaction wt(sg);
2✔
889
        wt.commit();
2✔
890
        // History entries still held by previous commit
891
    }
2✔
892
    {
2✔
893
        WriteTransaction wt(sg);
2✔
894
        wt.commit();
2✔
895
        // History entries now finally free
896
    }
2✔
897
    sg->get_stats(free_space, used_space, &new_locked_space);
2✔
898

899
    // Some space must have been released
900
    CHECK_LESS(new_locked_space, locked_space);
2✔
901
}
2✔
902

903

904
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_InsertTable, testing_supports_spawn_process)
905
{
2✔
906
    SHARED_GROUP_TEST_PATH(path);
2✔
907

908
    if (test_util::SpawnedProcess::is_parent()) {
2✔
909
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
910
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
911
        WriteTransaction wt(sg_w);
2✔
912

913
        TableRef table = wt.add_table("table1");
2✔
914
        table->add_column(type_Int, "col");
2✔
915

916
        table = wt.add_table("table2");
2✔
917
        table->add_column(type_Float, "col1");
2✔
918
        table->add_column(type_Float, "col2");
2✔
919

920
        wt.commit();
2✔
921
    }
2✔
922

923
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
924
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
925
    TransactionRef rt = sg->start_read();
2✔
926

927
    ConstTableRef table1 = rt->get_table("table1");
2✔
928
    ConstTableRef table2 = rt->get_table("table2");
2✔
929

930
    auto process = test_util::spawn_process(test_context.test_details.test_name, "add_table");
2✔
931
    if (process->is_child()) {
2✔
932
        {
×
933
            std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
×
934
            DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
×
935
            WriteTransaction wt(sg_w);
×
936
            wt.get_group().add_table("new table");
×
937
            wt.get_table("table1")->create_object();
×
938
            wt.get_table("table2")->create_object();
×
939
            wt.get_table("table2")->create_object();
×
940
            wt.commit();
×
941
        } // clean up sg before exit
×
942
        exit(0);
×
943
    }
×
944
    else {
2✔
945
        process->wait_for_child_to_finish();
2✔
946
    }
2✔
947

948
    rt->advance_read();
2✔
949

950
    CHECK_EQUAL(table1->size(), 1);
2✔
951
    CHECK_EQUAL(table2->size(), 2);
2✔
952
    CHECK_EQUAL(rt->get_table("new table")->size(), 0);
2✔
953
}
2✔
954

955
TEST(LangBindHelper_AdvanceReadTransact_LinkColumnInNewTable)
956
{
2✔
957
    // Verify that the table accessor of a link-opposite table is refreshed even
958
    // when the origin table is created in the same transaction as the link
959
    // column is added to it. This case is slightly involved, as there is a rule
960
    // that requires the two opposite table accessors of a link column (origin
961
    // and target sides) to either both exist or both not exist. On the other
962
    // hand, tables accessors are normally not created during
963
    // Group::advance_transact() for newly created tables.
964

965
    SHARED_GROUP_TEST_PATH(path);
2✔
966
    ShortCircuitHistory hist;
2✔
967
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
968
    DBRef sg_w = DB::create(hist, path, DBOptions(crypt_key()));
2✔
969
    {
2✔
970
        WriteTransaction wt(sg_w);
2✔
971
        wt.get_or_add_table("a");
2✔
972
        wt.commit();
2✔
973
    }
2✔
974

975
    TransactionRef rt = sg->start_read();
2✔
976
    ConstTableRef a_r = rt->get_table("a");
2✔
977

978
    {
2✔
979
        WriteTransaction wt(sg_w);
2✔
980
        TableRef a_w = wt.get_table("a");
2✔
981
        TableRef b_w = wt.get_or_add_table("b");
2✔
982
        b_w->add_column(*a_w, "foo");
2✔
983
        wt.commit();
2✔
984
    }
2✔
985

986
    rt->advance_read();
2✔
987
    CHECK(a_r);
2✔
988
    rt->verify();
2✔
989
}
2✔
990

991

992
TEST(LangBindHelper_AdvanceReadTransact_EnumeratedStrings)
993
{
2✔
994
    SHARED_GROUP_TEST_PATH(path);
2✔
995
    ShortCircuitHistory hist;
2✔
996
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
997
    ColKey c0, c1, c2;
2✔
998

999
    // Start a read transaction (to be repeatedly advanced)
1000
    auto rt = sg->start_read();
2✔
1001
    CHECK_EQUAL(0, rt->size());
2✔
1002

1003
    // Create 3 string columns, one primed for conversion to "unique string
1004
    // enumeration" representation
1005
    {
2✔
1006
        WriteTransaction wt(sg);
2✔
1007
        TableRef table_w = wt.add_table("t");
2✔
1008
        c0 = table_w->add_column(type_String, "a");
2✔
1009
        c1 = table_w->add_column(type_String, "b");
2✔
1010
        c2 = table_w->add_column(type_String, "c");
2✔
1011
        for (int i = 0; i < 1000; ++i) {
2,002✔
1012
            std::ostringstream out;
2,000✔
1013
            out << i;
2,000✔
1014
            std::string str = out.str();
2,000✔
1015
            table_w->create_object(ObjKey{}, {{c0, str}, {c1, "foo"}, {c2, str}});
2,000✔
1016
        }
2,000✔
1017
        wt.commit();
2✔
1018
    }
2✔
1019
    rt->advance_read();
2✔
1020
    rt->verify();
2✔
1021
    ConstTableRef table = rt->get_table("t");
2✔
1022
    CHECK_EQUAL(0, table->get_num_unique_values(c0));
2✔
1023
    CHECK_EQUAL(0, table->get_num_unique_values(c1)); // Not yet "optimized"
2✔
1024
    CHECK_EQUAL(0, table->get_num_unique_values(c2));
2✔
1025

1026
    // Optimize
1027
    {
2✔
1028
        WriteTransaction wt(sg);
2✔
1029
        TableRef table_w = wt.get_table("t");
2✔
1030
        table_w->enumerate_string_column(c1);
2✔
1031
        wt.commit();
2✔
1032
    }
2✔
1033
    rt->advance_read();
2✔
1034
    rt->verify();
2✔
1035
    CHECK_EQUAL(0, table->get_num_unique_values(c0));
2✔
1036
    CHECK_NOT_EQUAL(0, table->get_num_unique_values(c1)); // Must be "optimized" now
2✔
1037
    CHECK_EQUAL(0, table->get_num_unique_values(c2));
2✔
1038
}
2✔
1039

1040
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_SearchIndex, testing_supports_spawn_process)
1041
{
2✔
1042
    SHARED_GROUP_TEST_PATH(path);
2✔
1043
    if (test_util::SpawnedProcess::is_parent()) {
2✔
1044
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1045
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1046

1047
        // Start a read transaction (to be repeatedly advanced)
1048
        TransactionRef rt = sg->start_read();
2✔
1049
        CHECK_EQUAL(0, rt->size());
2✔
1050
    }
2✔
1051
    // Create 5 columns, and make 3 of them indexed
1052
    auto process = test_util::spawn_process(test_context.test_details.test_name, "init");
2✔
1053
    if (process->is_child()) {
2✔
1054
        {
×
1055
            std::vector<ObjKey> keys;
×
1056
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1057
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1058
            WriteTransaction wt(sg_w);
×
1059
            TableRef table_w = wt.add_table("t");
×
1060
            ColKey col_int = table_w->add_column(type_Int, "i0");
×
1061
            table_w->add_column(type_String, "s1");
×
1062
            ColKey col_str2 = table_w->add_column(type_String, "s2");
×
1063
            table_w->add_column(type_Int, "i3");
×
1064
            ColKey col_int4 = table_w->add_column(type_Int, "i4");
×
1065
            table_w->add_search_index(col_int);
×
1066
            table_w->add_search_index(col_str2);
×
1067
            table_w->add_search_index(col_int4);
×
1068
            table_w->create_objects(8, keys);
×
1069
            wt.commit();
×
1070
        } // clean up sg before exit
×
1071
        exit(0);
×
1072
    }
×
1073

1074
    if (process->is_parent()) {
2✔
1075
        process->wait_for_child_to_finish();
2✔
1076

1077
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1078
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1079

1080
        // Start a read transaction (to be repeatedly advanced)
1081
        TransactionRef rt = sg->start_read();
2✔
1082
        rt->advance_read();
2✔
1083
        rt->verify();
2✔
1084
        ConstTableRef table = rt->get_table("t");
2✔
1085
        CHECK(table->has_search_index(table->get_column_key("i0")));
2✔
1086
        CHECK_NOT(table->has_search_index(table->get_column_key("s1")));
2✔
1087
        CHECK(table->has_search_index(table->get_column_key("s2")));
2✔
1088
        CHECK_NOT(table->has_search_index(table->get_column_key("i3")));
2✔
1089
        CHECK(table->has_search_index(table->get_column_key("i4")));
2✔
1090
    }
2✔
1091

1092
    // Remove the previous search indexes and add 2 new ones
1093
    process = test_util::spawn_process(test_context.test_details.test_name, "change_indexes");
2✔
1094
    if (process->is_child()) {
2✔
1095
        {
×
1096
            std::vector<ObjKey> keys;
×
1097
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1098
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1099
            WriteTransaction wt(sg_w);
×
1100
            TableRef table_w = wt.get_table("t");
×
1101
            table_w->create_objects(8, keys);
×
1102
            table_w->remove_search_index(table_w->get_column_key("s2"));
×
1103
            table_w->add_search_index(table_w->get_column_key("i3"));
×
1104
            table_w->remove_search_index(table_w->get_column_key("i0"));
×
1105
            table_w->add_search_index(table_w->get_column_key("s1"));
×
1106
            table_w->remove_search_index(table_w->get_column_key("i4"));
×
1107
            wt.commit();
×
1108
        }
×
1109
        exit(0);
×
1110
    }
×
1111

1112
    if (process->is_parent()) {
2✔
1113
        process->wait_for_child_to_finish();
2✔
1114

1115
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1116
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1117

1118
        // Start a read transaction (to be repeatedly advanced)
1119
        TransactionRef rt = sg->start_read();
2✔
1120
        ConstTableRef table = rt->get_table("t");
2✔
1121
        rt->advance_read();
2✔
1122
        rt->verify();
2✔
1123
        CHECK_NOT(table->has_search_index(table->get_column_key("i0")));
2✔
1124
        CHECK(table->has_search_index(table->get_column_key("s1")));
2✔
1125
        CHECK_NOT(table->has_search_index(table->get_column_key("s2")));
2✔
1126
        CHECK(table->has_search_index(table->get_column_key("i3")));
2✔
1127
        CHECK_NOT(table->has_search_index(table->get_column_key("i4")));
2✔
1128
    }
2✔
1129

1130
    // Add some searchable contents
1131
    process = test_util::spawn_process(test_context.test_details.test_name, "add_content");
2✔
1132
    if (process->is_child()) {
2✔
1133
        {
×
1134
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1135
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1136
            WriteTransaction wt(sg_w);
×
1137
            TableRef table_w = wt.get_table("t");
×
1138
            int_fast64_t v = 7;
×
1139
            for (auto obj : *table_w) {
×
1140
                std::string out(util::to_string(v));
×
1141
                obj.set(table_w->get_column_key("s1"), StringData(out));
×
1142
                obj.set(table_w->get_column_key("i3"), v);
×
1143
                v = (v + 1581757577LL) % 1000;
×
1144
            }
×
1145
            wt.commit();
×
1146
        }
×
1147
        exit(0);
×
1148
    }
×
1149
    if (process->is_parent()) {
2✔
1150
        process->wait_for_child_to_finish();
2✔
1151

1152
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1153
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1154

1155
        // Start a read transaction (to be repeatedly advanced)
1156
        TransactionRef rt = sg->start_read();
2✔
1157
        ConstTableRef table = rt->get_table("t");
2✔
1158
        rt->advance_read();
2✔
1159
        rt->verify();
2✔
1160

1161
        CHECK_NOT(table->has_search_index(table->get_column_key("i0")));
2✔
1162
        CHECK(table->has_search_index(table->get_column_key("s1")));
2✔
1163
        CHECK_NOT(table->has_search_index(table->get_column_key("s2")));
2✔
1164
        CHECK(table->has_search_index(table->get_column_key("i3")));
2✔
1165
        CHECK_NOT(table->has_search_index(table->get_column_key("i4")));
2✔
1166
        CHECK_EQUAL(ObjKey(12), table->find_first_string(table->get_column_key("s1"), "931"));
2✔
1167
        CHECK_EQUAL(ObjKey(4), table->find_first_int(table->get_column_key("i3"), 315));
2✔
1168
        CHECK_EQUAL(ObjKey(13), table->find_first_int(table->get_column_key("i3"), 508));
2✔
1169
    }
2✔
1170
    // Move the indexed columns by removal
1171
    process = test_util::spawn_process(test_context.test_details.test_name, "move_and_remove");
2✔
1172
    if (process->is_child()) {
2✔
1173
        {
×
1174
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1175
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1176
            WriteTransaction wt(sg_w);
×
1177
            TableRef table_w = wt.get_table("t");
×
1178
            table_w->remove_column(table_w->get_column_key("i0"));
×
1179
            table_w->remove_column(table_w->get_column_key("s2"));
×
1180
            wt.commit();
×
1181
        }
×
1182
        exit(0);
×
1183
    }
×
1184
    if (process->is_parent()) {
2✔
1185
        process->wait_for_child_to_finish();
2✔
1186

1187
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1188
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1189

1190
        // Start a read transaction (to be repeatedly advanced)
1191
        TransactionRef rt = sg->start_read();
2✔
1192
        ConstTableRef table = rt->get_table("t");
2✔
1193
        rt->advance_read();
2✔
1194
        rt->verify();
2✔
1195
        CHECK(table->has_search_index(table->get_column_key("s1")));
2✔
1196
        CHECK(table->has_search_index(table->get_column_key("i3")));
2✔
1197
        CHECK_NOT(table->has_search_index(table->get_column_key("i4")));
2✔
1198
        CHECK_EQUAL(ObjKey(3), table->find_first_string(table->get_column_key("s1"), "738"));
2✔
1199
        CHECK_EQUAL(ObjKey(13), table->find_first_int(table->get_column_key("i3"), 508));
2✔
1200
    }
2✔
1201
}
2✔
1202

1203
TEST(LangBindHelper_AdvanceReadTransact_LinkView)
1204
{
2✔
1205
    SHARED_GROUP_TEST_PATH(path);
2✔
1206
    ShortCircuitHistory hist;
2✔
1207
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1208
    DBRef sg_w = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1209
    DBRef sg_q = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1210

1211
    // Start a continuous read transaction
1212
    TransactionRef rt = sg->start_read();
2✔
1213

1214
    // Add some tables and rows.
1215
    {
2✔
1216
        WriteTransaction wt(sg_w);
2✔
1217
        TableRef origin = wt.add_table("origin");
2✔
1218
        TableRef target = wt.add_table("target");
2✔
1219
        target->add_column(type_Int, "value");
2✔
1220
        auto col = origin->add_column_list(*target, "list");
2✔
1221

1222
        std::vector<ObjKey> keys;
2✔
1223
        target->create_objects(10, keys);
2✔
1224

1225
        Obj o0 = origin->create_object(ObjKey(0));
2✔
1226
        Obj o1 = origin->create_object(ObjKey(1));
2✔
1227

1228
        o0.get_linklist(col).add(keys[1]);
2✔
1229
        o1.get_linklist(col).add(keys[2]);
2✔
1230
        // state:
1231
        // origin[0].ll[0] -> target[1]
1232
        // origin[1].ll[0] -> target[2]
1233
        wt.commit();
2✔
1234
    }
2✔
1235
    rt->advance_read();
2✔
1236
    rt->verify();
2✔
1237

1238
    // Grab references to the LinkViews
1239
    auto origin = rt->get_table("origin");
2✔
1240
    auto col_link = origin->get_column_key("list");
2✔
1241
    const Obj obj0 = origin->get_object(ObjKey(0));
2✔
1242
    const Obj obj1 = origin->get_object(ObjKey(1));
2✔
1243

1244
    auto ll1 = obj0.get_linklist(col_link); // lv1[0] -> target[1]
2✔
1245
    auto ll2 = obj1.get_linklist(col_link); // lv2[0] -> target[2]
2✔
1246
    CHECK_EQUAL(ll1.size(), 1);
2✔
1247
    CHECK_EQUAL(ll2.size(), 1);
2✔
1248

1249
    ObjKey ll1_target = ll1.get_object(0).get_key();
2✔
1250
    CHECK_EQUAL(ll1.find_first(ll1_target), 0);
2✔
1251

1252
    {
2✔
1253
        WriteTransaction wt(sg_w);
2✔
1254
        wt.get_table("origin")->get_object(ObjKey(0)).get_linklist(col_link).clear();
2✔
1255
        wt.commit();
2✔
1256
    }
2✔
1257
    rt->advance_read();
2✔
1258
    rt->verify();
2✔
1259

1260
    CHECK_EQUAL(ll1.find_first(ll1_target), not_found);
2✔
1261
}
2✔
1262

1263
namespace {
1264

1265
template <typename T>
1266
class ConcurrentQueue {
1267
public:
1268
    ConcurrentQueue(size_t size)
1269
        : sz(size)
1✔
1270
    {
2✔
1271
        data.reset(new T[sz]);
2✔
1272
    }
2✔
1273
    inline bool is_full()
1274
    {
198,234✔
1275
        return writer - reader == sz;
198,234✔
1276
    }
198,234✔
1277
    inline bool is_empty()
1278
    {
206,823✔
1279
        return writer - reader == 0;
206,823✔
1280
    }
206,823✔
1281
    void put(T& e)
1282
    {
100,000✔
1283
        std::unique_lock<std::mutex> lock(mutex);
100,000✔
1284
        while (is_full())
100,000✔
UNCOV
1285
            not_full.wait(lock);
×
1286
        if (is_empty())
100,000✔
1287
            not_empty_or_closed.notify_all();
12,010✔
1288
        data[writer++ % sz] = std::move(e);
100,000✔
1289
    }
100,000✔
1290

1291
    bool get(T& e)
1292
    {
98,236✔
1293
        std::unique_lock<std::mutex> lock(mutex);
98,236✔
1294
        while (is_empty() && !closed)
106,823✔
1295
            not_empty_or_closed.wait(lock);
8,587✔
1296
        if (closed)
98,236✔
1297
            return false;
2✔
1298
        if (is_full())
98,234✔
UNCOV
1299
            not_full.notify_all();
×
1300
        e = std::move(data[reader++ % sz]);
98,234✔
1301
        return true;
98,234✔
1302
    }
98,236✔
1303

1304
    void reopen()
1305
    {
1306
        // no concurrent access allowed here
1307
        closed = false;
1308
    }
1309

1310
    void close()
1311
    {
2✔
1312
        std::unique_lock<std::mutex> lock(mutex);
2✔
1313
        closed = true;
2✔
1314
        not_empty_or_closed.notify_all();
2✔
1315
    }
2✔
1316

1317
private:
1318
    std::mutex mutex;
1319
    std::condition_variable not_full;
1320
    std::condition_variable not_empty_or_closed;
1321
    size_t reader = 0;
1322
    size_t writer = 0;
1323
    bool closed = false;
1324
    size_t sz;
1325
    std::unique_ptr<T[]> data;
1326
};
1327

1328
// Background thread for test below.
1329
void deleter_thread(ConcurrentQueue<LnkLstPtr>& queue)
1330
{
2✔
1331
    Random random(random_int<unsigned long>());
2✔
1332
    bool closed = false;
2✔
1333
    while (!closed) {
98,238✔
1334
        LnkLstPtr r;
98,236✔
1335
        // prevent the compiler from eliminating a loop:
1336
        volatile int delay = random.draw_int_mod(10000);
98,236✔
1337
        closed = !queue.get(r);
98,236✔
1338
        // random delay goes *after* get(), so that it comes
1339
        // after the potentially synchronizing locking
1340
        // operation inside queue.get()
1341
        while (delay > 0)
490,967,191✔
1342
            delay = delay - 1;
490,868,955✔
1343
        // just let 'r' die
1344
    }
98,236✔
1345
}
2✔
1346
} // namespace
1347

1348
TEST(LangBindHelper_ConcurrentLinkViewDeletes)
1349
{
2✔
1350
    // This tests checks concurrent deletion of LinkViews.
1351
    // It is structured as a mutator which creates and uses
1352
    // LinkView accessors, and a background deleter which
1353
    // consumes LinkViewRefs and makes them go out of scope
1354
    // concurrently with the new references being created.
1355

1356
    // Number of table entries (and hence, max number of accessors)
1357
    const int table_size = 1000;
2✔
1358

1359
    // Number of references produced (some will refer to the same
1360
    // accessor)
1361
    const int max_refs = 50000;
2✔
1362

1363
    // Frequency of references that are used to change the
1364
    // database during the test.
1365
    const int change_frequency_per_mill = 50000; // 5pct changes
2✔
1366

1367
    // Number of references that may be buffered for communication
1368
    // between main thread and deleter thread. Should be large enough
1369
    // to allow considerable overlap.
1370
    const int buffer_size = 2000;
2✔
1371

1372
    Random random(random_int<unsigned long>());
2✔
1373

1374
    // setup two tables with empty linklists inside
1375
    SHARED_GROUP_TEST_PATH(path);
2✔
1376
    ShortCircuitHistory hist;
2✔
1377
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1378

1379
    // Start a read transaction (to be repeatedly advanced)
1380
    std::vector<ObjKey> o_keys;
2✔
1381
    std::vector<ObjKey> t_keys;
2✔
1382
    ColKey ck;
2✔
1383
    auto rt = sg->start_read();
2✔
1384
    {
2✔
1385
        // setup tables with empty linklists
1386
        WriteTransaction wt(sg);
2✔
1387
        TableRef origin = wt.add_table("origin");
2✔
1388
        TableRef target = wt.add_table("target");
2✔
1389
        ck = origin->add_column_list(*target, "ll");
2✔
1390
        origin->create_objects(table_size, o_keys);
2✔
1391
        target->create_objects(table_size, t_keys);
2✔
1392
        wt.commit();
2✔
1393
    }
2✔
1394
    rt->advance_read();
2✔
1395

1396
    // Create accessors for random entries in the table.
1397
    // occasionally modify the database through the accessor.
1398
    // feed the accessor refs to the background thread for
1399
    // later deletion.
1400
    util::Thread deleter;
2✔
1401
    ConcurrentQueue<LnkLstPtr> queue(buffer_size);
2✔
1402
    deleter.start([&] {
2✔
1403
        deleter_thread(queue);
2✔
1404
    });
2✔
1405
    for (int i = 0; i < max_refs; ++i) {
100,002✔
1406
        TableRef origin = rt->get_table("origin");
100,000✔
1407
        int ndx = random.draw_int_mod(table_size);
100,000✔
1408
        Obj o = origin->get_object(o_keys[ndx]);
100,000✔
1409
        LnkLstPtr lw = o.get_linklist_ptr(ck);
100,000✔
1410
        bool will_add = change_frequency_per_mill > random.draw_int_mod(1000000);
100,000✔
1411
        if (will_add) {
100,000✔
1412
            rt->promote_to_write();
5,123✔
1413
            lw->add(t_keys[ndx]);
5,123✔
1414
            rt->commit_and_continue_as_read();
5,123✔
1415
        }
5,123✔
1416
        queue.put(lw);
100,000✔
1417
    }
100,000✔
1418
    queue.close();
2✔
1419
    deleter.join();
2✔
1420
}
2✔
1421

1422
TEST(LangBindHelper_AdvanceReadTransact_InsertLink)
1423
{
2✔
1424
    // This test checks that Table::insert_link() works across transaction
1425
    // boundaries (advance transaction).
1426

1427
    SHARED_GROUP_TEST_PATH(path);
2✔
1428
    ShortCircuitHistory hist;
2✔
1429
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1430

1431
    // Start a read transaction (to be repeatedly advanced)
1432
    TransactionRef rt = sg->start_read();
2✔
1433
    CHECK_EQUAL(0, rt->size());
2✔
1434
    ColKey col;
2✔
1435
    ObjKey target_key;
2✔
1436
    {
2✔
1437
        WriteTransaction wt(sg);
2✔
1438
        TableRef origin_w = wt.add_table("origin");
2✔
1439
        TableRef target_w = wt.add_table("target");
2✔
1440
        col = origin_w->add_column(*target_w, "");
2✔
1441
        target_w->add_column(type_Int, "");
2✔
1442
        target_key = target_w->create_object().get_key();
2✔
1443
        wt.commit();
2✔
1444
    }
2✔
1445
    rt->advance_read();
2✔
1446
    rt->verify();
2✔
1447
    ConstTableRef origin = rt->get_table("origin");
2✔
1448
    ConstTableRef target = rt->get_table("target");
2✔
1449
    {
2✔
1450
        WriteTransaction wt(sg);
2✔
1451
        TableRef origin_w = wt.get_table("origin");
2✔
1452
        auto obj = origin_w->create_object();
2✔
1453
        obj.set(col, target_key);
2✔
1454
        wt.commit();
2✔
1455
    }
2✔
1456
    rt->advance_read();
2✔
1457
    CHECK(origin);
2✔
1458
    CHECK(target);
2✔
1459
    rt->verify();
2✔
1460
}
2✔
1461

1462

1463
TEST(LangBindHelper_AdvanceReadTransact_LinkToNeighbour)
1464
{
2✔
1465
    // This test checks that you can insert a link to an object that resides
1466
    // in the same cluster as the origin object.
1467

1468
    SHARED_GROUP_TEST_PATH(path);
2✔
1469
    ShortCircuitHistory hist;
2✔
1470
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1471

1472
    // Start a read transaction (to be repeatedly advanced)
1473
    TransactionRef rt = sg->start_read();
2✔
1474
    CHECK_EQUAL(0, rt->size());
2✔
1475
    ColKey col;
2✔
1476
    std::vector<ObjKey> keys;
2✔
1477
    {
2✔
1478
        WriteTransaction wt(sg);
2✔
1479
        TableRef table = wt.add_table("table");
2✔
1480
        table->add_column(type_Int, "integers");
2✔
1481
        col = table->add_column(*table, "links");
2✔
1482
        table->create_objects(10, keys);
2✔
1483
        wt.commit();
2✔
1484
    }
2✔
1485
    rt->advance_read();
2✔
1486
    rt->verify();
2✔
1487
    {
2✔
1488
        WriteTransaction wt(sg);
2✔
1489
        TableRef table = wt.get_table("table");
2✔
1490
        table->get_object(keys[0]).set(col, keys[1]);
2✔
1491
        table->get_object(keys[1]).set(col, keys[2]);
2✔
1492
        wt.commit();
2✔
1493
    }
2✔
1494
    rt->advance_read();
2✔
1495
    rt->verify();
2✔
1496
}
2✔
1497

1498
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_RemoveTableWithColumns, testing_supports_spawn_process)
1499
{
2✔
1500
    SHARED_GROUP_TEST_PATH(path);
2✔
1501
    if (test_util::SpawnedProcess::is_parent()) {
2✔
1502
        std::unique_ptr<Replication> hist_parent(make_in_realm_history());
2✔
1503
        DBRef sg = DB::create(*hist_parent, path, DBOptions(crypt_key()));
2✔
1504

1505
        // Start a read transaction (to be repeatedly advanced)
1506
        TransactionRef rt = sg->start_read();
2✔
1507
        CHECK_EQUAL(0, rt->size());
2✔
1508
    }
2✔
1509
    auto process = test_util::spawn_process(test_context.test_details.test_name, "initial_write");
2✔
1510
    if (process->is_child()) {
2✔
1511
        {
×
1512
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1513
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1514
            WriteTransaction wt(sg_w);
×
1515
            TableRef alpha_w = wt.add_table("alpha");
×
1516
            TableRef beta_w = wt.add_table("beta");
×
1517
            TableRef gamma_w = wt.add_table("gamma");
×
1518
            TableRef delta_w = wt.add_table("delta");
×
1519
            TableRef epsilon_w = wt.add_table("epsilon");
×
1520
            alpha_w->add_column(type_Int, "alpha-1");
×
1521
            beta_w->add_column(*delta_w, "beta-1");
×
1522
            gamma_w->add_column(*gamma_w, "gamma-1");
×
1523
            delta_w->add_column(type_Int, "delta-1");
×
1524
            epsilon_w->add_column(*delta_w, "epsilon-1");
×
1525
            wt.commit();
×
1526
        } // clean up sg before exit
×
1527
        exit(0);
×
1528
    }
×
1529
    else if (process->is_parent()) {
2✔
1530
        process->wait_for_child_to_finish();
2✔
1531

1532
        std::unique_ptr<Replication> hist_parent(make_in_realm_history());
2✔
1533
        DBRef sg = DB::create(*hist_parent, path, DBOptions(crypt_key()));
2✔
1534

1535
        // Start a read transaction (to be repeatedly advanced)
1536
        TransactionRef rt = sg->start_read();
2✔
1537
        rt->advance_read();
2✔
1538
        rt->verify();
2✔
1539

1540
        CHECK_EQUAL(5, rt->size());
2✔
1541
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1542
        ConstTableRef beta = rt->get_table("beta");
2✔
1543
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1544
        ConstTableRef delta = rt->get_table("delta");
2✔
1545
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1546
        CHECK(alpha);
2✔
1547
        CHECK(beta);
2✔
1548
        CHECK(gamma);
2✔
1549
        CHECK(delta);
2✔
1550
        CHECK(epsilon);
2✔
1551
    }
2✔
1552
    // Remove table with columns, but no link columns, and table is not a link
1553
    // target.
1554
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_alpha");
2✔
1555
    if (process->is_child()) {
2✔
1556
        {
×
1557
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1558
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1559
            WriteTransaction wt(sg_w);
×
1560
            wt.get_group().remove_table("alpha");
×
1561
            wt.commit();
×
1562
        }
×
1563
        exit(0);
×
1564
    }
×
1565
    else if (process->is_parent()) {
2✔
1566
        process->wait_for_child_to_finish();
2✔
1567

1568
        std::unique_ptr<Replication> hist_parent(make_in_realm_history());
2✔
1569
        DBRef sg = DB::create(*hist_parent, path, DBOptions(crypt_key()));
2✔
1570

1571
        // Start a read transaction (to be repeatedly advanced)
1572
        TransactionRef rt = sg->start_read();
2✔
1573
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1574
        ConstTableRef beta = rt->get_table("beta");
2✔
1575
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1576
        ConstTableRef delta = rt->get_table("delta");
2✔
1577
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1578
        rt->advance_read();
2✔
1579
        rt->verify();
2✔
1580

1581
        CHECK_EQUAL(4, rt->size());
2✔
1582
        CHECK_NOT(alpha);
2✔
1583
        CHECK(beta);
2✔
1584
        CHECK(gamma);
2✔
1585
        CHECK(delta);
2✔
1586
        CHECK(epsilon);
2✔
1587
    }
2✔
1588
    // Remove table with link column, and table is not a link target.
1589
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_beta");
2✔
1590
    if (process->is_child()) {
2✔
1591
        {
×
1592
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1593
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1594
            WriteTransaction wt(sg_w);
×
1595
            wt.get_group().remove_table("beta");
×
1596
            wt.commit();
×
1597
        }
×
1598
        exit(0);
×
1599
    }
×
1600
    else if (process->is_parent()) {
2✔
1601
        process->wait_for_child_to_finish();
2✔
1602

1603
        std::unique_ptr<Replication> hist_parent(make_in_realm_history());
2✔
1604
        DBRef sg = DB::create(*hist_parent, path, DBOptions(crypt_key()));
2✔
1605

1606
        // Start a read transaction (to be repeatedly advanced)
1607
        TransactionRef rt = sg->start_read();
2✔
1608
        rt->advance_read();
2✔
1609
        rt->verify();
2✔
1610

1611
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1612
        ConstTableRef beta = rt->get_table("beta");
2✔
1613
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1614
        ConstTableRef delta = rt->get_table("delta");
2✔
1615
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1616
        CHECK_EQUAL(3, rt->size());
2✔
1617
        CHECK_NOT(alpha);
2✔
1618
        CHECK_NOT(beta);
2✔
1619
        CHECK(gamma);
2✔
1620
        CHECK(delta);
2✔
1621
        CHECK(epsilon);
2✔
1622
    }
2✔
1623
    // Remove table with self-link column, and table is not a target of link
1624
    // columns of other tables.
1625
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_gamma");
2✔
1626
    if (process->is_child()) {
2✔
1627
        {
×
1628
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1629
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1630
            WriteTransaction wt(sg_w);
×
1631
            wt.get_group().remove_table("gamma");
×
1632
            wt.commit();
×
1633
        }
×
1634
        exit(0);
×
1635
    }
×
1636
    else if (process->is_parent()) {
2✔
1637
        process->wait_for_child_to_finish();
2✔
1638

1639
        std::unique_ptr<Replication> hist_parent(make_in_realm_history());
2✔
1640
        DBRef sg = DB::create(*hist_parent, path, DBOptions(crypt_key()));
2✔
1641

1642
        // Start a read transaction (to be repeatedly advanced)
1643
        TransactionRef rt = sg->start_read();
2✔
1644
        rt->advance_read();
2✔
1645
        rt->verify();
2✔
1646

1647
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1648
        ConstTableRef beta = rt->get_table("beta");
2✔
1649
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1650
        ConstTableRef delta = rt->get_table("delta");
2✔
1651
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1652
        CHECK_EQUAL(2, rt->size());
2✔
1653
        CHECK_NOT(alpha);
2✔
1654
        CHECK_NOT(beta);
2✔
1655
        CHECK_NOT(gamma);
2✔
1656
        CHECK(delta);
2✔
1657
        CHECK(epsilon);
2✔
1658
    }
2✔
1659
    // Try, but fail to remove table which is a target of link columns of other
1660
    // tables.
1661
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_delta");
2✔
1662
    if (process->is_child()) {
2✔
1663
        {
×
1664
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1665
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1666
            WriteTransaction wt(sg_w);
×
1667
            CHECK_THROW(wt.get_group().remove_table("delta"), CrossTableLinkTarget);
×
1668
            wt.commit();
×
1669
        }
×
1670
        exit(0);
×
1671
    }
×
1672
    else if (process->is_parent()) {
2✔
1673
        process->wait_for_child_to_finish();
2✔
1674

1675
        std::unique_ptr<Replication> hist_parent(make_in_realm_history());
2✔
1676
        DBRef sg = DB::create(*hist_parent, path, DBOptions(crypt_key()));
2✔
1677

1678
        // Start a read transaction (to be repeatedly advanced)
1679
        TransactionRef rt = sg->start_read();
2✔
1680
        rt->advance_read();
2✔
1681
        rt->verify();
2✔
1682
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1683
        ConstTableRef beta = rt->get_table("beta");
2✔
1684
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1685
        ConstTableRef delta = rt->get_table("delta");
2✔
1686
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1687

1688
        CHECK_EQUAL(2, rt->size());
2✔
1689
        CHECK_NOT(alpha);
2✔
1690
        CHECK_NOT(beta);
2✔
1691
        CHECK_NOT(gamma);
2✔
1692
        CHECK(delta);
2✔
1693
        CHECK(epsilon);
2✔
1694
    }
2✔
1695
}
2✔
1696

1697
TEST(LangBindHelper_AdvanceReadTransact_CascadeRemove_ColumnLink)
1698
{
2✔
1699
    SHARED_GROUP_TEST_PATH(path);
2✔
1700
    ShortCircuitHistory hist;
2✔
1701
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1702

1703
    ColKey col;
2✔
1704
    {
2✔
1705
        WriteTransaction wt(sg);
2✔
1706
        auto origin = wt.add_table("origin");
2✔
1707
        auto target = wt.add_table("target", Table::Type::Embedded);
2✔
1708
        col = origin->add_column(*target, "o_1");
2✔
1709
        target->add_column(type_Int, "t_1");
2✔
1710
        wt.commit();
2✔
1711
    }
2✔
1712

1713
    // Start a read transaction (to be repeatedly advanced)
1714
    auto rt = sg->start_read();
2✔
1715
    auto target = rt->get_table("target");
2✔
1716

1717
    ObjKey target_key0, target_key1;
2✔
1718
    Obj target_obj0, target_obj1;
2✔
1719

1720
    auto perform_change = [&](util::FunctionRef<void(Table&)> func) {
6✔
1721
        // Ensure there are two rows in each table, with each row in `origin`
1722
        // pointing to the corresponding row in `target`
1723
        {
6✔
1724
            WriteTransaction wt(sg);
6✔
1725
            auto origin_w = wt.get_table("origin");
6✔
1726
            auto target_w = wt.get_table("target");
6✔
1727

1728
            origin_w->clear();
6✔
1729
            target_w->clear();
6✔
1730
            auto o0 = origin_w->create_object();
6✔
1731
            auto o1 = origin_w->create_object();
6✔
1732
            target_key0 = o0.create_and_set_linked_object(col).get_key();
6✔
1733
            target_key1 = o1.create_and_set_linked_object(col).get_key();
6✔
1734
            wt.commit();
6✔
1735
        }
6✔
1736

1737
        // Grab the row accessors before applying the modification being tested
1738
        rt->advance_read();
6✔
1739
        rt->verify();
6✔
1740
        target_obj0 = target->get_object(target_key0);
6✔
1741
        target_obj1 = target->get_object(target_key1);
6✔
1742

1743
        // Perform the modification
1744
        {
6✔
1745
            WriteTransaction wt(sg);
6✔
1746
            func(*wt.get_table("origin"));
6✔
1747
            wt.commit();
6✔
1748
        }
6✔
1749

1750
        rt->advance_read();
6✔
1751
        rt->verify();
6✔
1752
        // Leave `group` and the target accessors in a state which can be tested
1753
        // with the changes applied
1754
    };
6✔
1755

1756
    // Break link by clearing table
1757
    perform_change([](Table& origin) {
2✔
1758
        origin.clear();
2✔
1759
    });
2✔
1760
    CHECK(!target_obj0.is_valid());
2✔
1761
    CHECK(!target_obj1.is_valid());
2✔
1762
    CHECK_EQUAL(target->size(), 0);
2✔
1763

1764
    // Break link by nullifying
1765
    perform_change([&](Table& origin) {
2✔
1766
        origin.get_object(1).set_null(col);
2✔
1767
    });
2✔
1768
    CHECK(target_obj0.is_valid());
2✔
1769
    CHECK(!target_obj1.is_valid());
2✔
1770
    CHECK_EQUAL(target->size(), 1);
2✔
1771

1772
    // Break link by reassign
1773
    perform_change([&](Table& origin) {
2✔
1774
        origin.get_object(1).create_and_set_linked_object(col);
2✔
1775
    });
2✔
1776
    CHECK(target_obj0.is_valid());
2✔
1777
    CHECK(!target_obj1.is_valid());
2✔
1778
    CHECK_EQUAL(target->size(), 2);
2✔
1779
}
2✔
1780

1781

1782
TEST(LangBindHelper_AdvanceReadTransact_CascadeRemove_ColumnLinkList)
1783
{
2✔
1784
    SHARED_GROUP_TEST_PATH(path);
2✔
1785
    ShortCircuitHistory hist;
2✔
1786
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1787

1788
    ColKey col;
2✔
1789
    {
2✔
1790
        WriteTransaction wt(sg);
2✔
1791
        auto origin = wt.add_table("origin");
2✔
1792
        auto target = wt.add_table("target", Table::Type::Embedded);
2✔
1793
        col = origin->add_column_list(*target, "o_1");
2✔
1794
        target->add_column(type_Int, "t_1");
2✔
1795
        wt.commit();
2✔
1796
    }
2✔
1797

1798
    // Start a read transaction (to be repeatedly advanced)
1799
    auto rt = sg->start_read();
2✔
1800
    auto target = rt->get_table("target");
2✔
1801

1802
    ObjKey target_key0, target_key1;
2✔
1803
    Obj target_obj0, target_obj1;
2✔
1804

1805
    auto perform_change = [&](util::FunctionRef<void(Table&)> func) {
8✔
1806
        // Ensure there are two rows in each table, with each row in `origin`
1807
        // pointing to the corresponding row in `target`
1808
        {
8✔
1809
            WriteTransaction wt(sg);
8✔
1810
            auto origin_w = wt.get_table("origin");
8✔
1811
            auto target_w = wt.get_table("target");
8✔
1812

1813
            origin_w->clear();
8✔
1814
            target_w->clear();
8✔
1815
            auto o0 = origin_w->create_object();
8✔
1816
            auto o1 = origin_w->create_object();
8✔
1817
            target_key0 = o0.get_linklist(col).create_and_insert_linked_object(0).get_key();
8✔
1818
            target_key1 = o1.get_linklist(col).create_and_insert_linked_object(0).get_key();
8✔
1819
            wt.commit();
8✔
1820
        }
8✔
1821

1822
        // Grab the row accessors before applying the modification being tested
1823
        rt->advance_read();
8✔
1824
        rt->verify();
8✔
1825
        target_obj0 = target->get_object(target_key0);
8✔
1826
        target_obj1 = target->get_object(target_key1);
8✔
1827

1828
        // Perform the modification
1829
        {
8✔
1830
            WriteTransaction wt(sg);
8✔
1831
            func(*wt.get_table("origin"));
8✔
1832
            wt.commit();
8✔
1833
        }
8✔
1834

1835
        rt->advance_read();
8✔
1836
        rt->verify();
8✔
1837
        // Leave `group` and the target accessors in a state which can be tested
1838
        // with the changes applied
1839
    };
8✔
1840

1841

1842
    // Break link by clearing list
1843
    perform_change([&](Table& origin) {
2✔
1844
        origin.get_object(1).get_linklist(col).clear();
2✔
1845
    });
2✔
1846
    CHECK(target_obj0.is_valid() && !target_obj1.is_valid());
2✔
1847
    CHECK_EQUAL(target->size(), 1);
2✔
1848

1849
    // Break link by removal from list
1850
    perform_change([&](Table& origin) {
2✔
1851
        origin.get_object(1).get_linklist(col).remove(0);
2✔
1852
    });
2✔
1853
    CHECK(target_obj0.is_valid() && !target_obj1.is_valid());
2✔
1854
    CHECK_EQUAL(target->size(), 1);
2✔
1855

1856
    // Break link by reassign
1857
    perform_change([&](Table& origin) {
2✔
1858
        origin.get_object(1).get_linklist(col).create_and_set_linked_object(0);
2✔
1859
    });
2✔
1860
    CHECK(target_obj0.is_valid() && !target_obj1.is_valid());
2✔
1861
    CHECK_EQUAL(target->size(), 2);
2✔
1862

1863
    // Break link by clearing table
1864
    perform_change([](Table& origin) {
2✔
1865
        origin.clear();
2✔
1866
    });
2✔
1867
    CHECK(!target_obj0.is_valid() && !target_obj1.is_valid());
2✔
1868
    CHECK_EQUAL(target->size(), 0);
2✔
1869
}
2✔
1870

1871

1872
TEST(LangBindHelper_AdvanceReadTransact_IntIndex)
1873
{
2✔
1874
    SHARED_GROUP_TEST_PATH(path);
2✔
1875

1876
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
1877
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
1878
    auto g = sg->start_read();
2✔
1879
    g->promote_to_write();
2✔
1880

1881
    TableRef target = g->add_table("target");
2✔
1882
    auto col = target->add_column(type_Int, "pk");
2✔
1883
    target->add_search_index(col);
2✔
1884

1885
    std::vector<ObjKey> obj_keys;
2✔
1886
    target->create_objects(REALM_MAX_BPNODE_SIZE + 1, obj_keys);
2✔
1887

1888
    g->commit_and_continue_as_read();
2✔
1889

1890
    // open a second copy that'll be advanced over the write
1891
    auto g_r = sg->start_read();
2✔
1892
    TableRef t_r = g_r->get_table("target");
2✔
1893

1894
    g->promote_to_write();
2✔
1895

1896
    // Ensure that the index has a different bptree layout so that failing to
1897
    // refresh it will do bad things
1898
    int i = 0;
2✔
1899
    for (auto it = target->begin(); it != target->end(); ++it)
2,004✔
1900
        it->set(col, i++);
2,002✔
1901

1902
    g->commit_and_continue_as_read();
2✔
1903

1904
    g_r->promote_to_write();
2✔
1905
    // Crashes if index has an invalid parent ref
1906
    t_r->clear();
2✔
1907
}
2✔
1908

1909
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_TableClear, testing_supports_spawn_process)
1910
{
2✔
1911
    SHARED_GROUP_TEST_PATH(path);
2✔
1912

1913
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
1914
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
1915
    ColKey col;
2✔
1916
    if (SpawnedProcess::is_parent()) {
2✔
1917
        WriteTransaction wt(sg);
2✔
1918
        TableRef table = wt.add_table("table");
2✔
1919
        col = table->add_column(type_Int, "col");
2✔
1920
        table->create_object();
2✔
1921
        wt.commit();
2✔
1922
    }
2✔
1923

1924
    auto reader = sg->start_read();
2✔
1925
    auto table = reader->get_table("table");
2✔
1926
    TableView tv = table->where().find_all();
2✔
1927
    auto obj = *table->begin();
2✔
1928
    CHECK(obj.is_valid());
2✔
1929

1930
    auto process = test_util::spawn_process(test_context.test_details.test_name, "external_clear");
2✔
1931
    if (process->is_child()) {
2✔
1932
        {
×
1933
            std::unique_ptr<Replication> hist_w(make_in_realm_history());
×
1934
            DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
×
1935
            WriteTransaction wt(sg_w);
×
1936
            wt.get_table("table")->clear();
×
1937
            wt.commit();
×
1938
        }
×
1939
        exit(0);
×
1940
    }
×
1941
    else if (process->is_parent()) {
2✔
1942
        process->wait_for_child_to_finish();
2✔
1943

1944
        reader->advance_read();
2✔
1945

1946
        CHECK(!obj.is_valid());
2✔
1947

1948
        CHECK_EQUAL(tv.size(), 1);
2✔
1949
        CHECK(!tv.is_in_sync());
2✔
1950
        // key is still there...
1951
        CHECK(tv.get_key(0));
2✔
1952
        // but no obj for that key...
1953
        CHECK_NOT(tv.get_object(0).is_valid());
2✔
1954

1955
        tv.sync_if_needed();
2✔
1956
        CHECK_EQUAL(tv.size(), 0);
2✔
1957
    }
2✔
1958
}
2✔
1959

1960
TEST(LangBindHelper_AdvanceReadTransact_UnorderedTableViewClear)
1961
{
2✔
1962
    SHARED_GROUP_TEST_PATH(path);
2✔
1963

1964
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
1965
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
1966
    ObjKey first_obj, last_obj;
2✔
1967
    ColKey col;
2✔
1968
    {
2✔
1969
        WriteTransaction wt(sg);
2✔
1970
        TableRef table = wt.add_table("table");
2✔
1971
        col = table->add_column(type_Int, "col");
2✔
1972
        first_obj = table->create_object().set_all(0).get_key();
2✔
1973
        table->create_object().set_all(1);
2✔
1974
        last_obj = table->create_object().set_all(2).get_key();
2✔
1975
        wt.commit();
2✔
1976
    }
2✔
1977

1978
    auto reader = sg->start_read();
2✔
1979
    auto table = reader->get_table("table");
2✔
1980
    auto obj = table->get_object(last_obj);
2✔
1981
    CHECK_EQUAL(obj.get<int64_t>(col), 2);
2✔
1982

1983
    {
2✔
1984
        // Remove the first row via unordered removal, resulting in the '2' row
1985
        // moving to index 0 (with ordered removal it would instead move to index 1)
1986
        WriteTransaction wt(sg);
2✔
1987
        wt.get_table("table")->where().equal(col, 0).find_all().clear();
2✔
1988
        wt.commit();
2✔
1989
    }
2✔
1990

1991
    reader->advance_read();
2✔
1992

1993
    CHECK(obj.is_valid());
2✔
1994
    CHECK_EQUAL(obj.get<int64_t>(col), 2);
2✔
1995
}
2✔
1996

1997
namespace {
1998
struct AdvanceReadTransact {
1999
    template <typename Func>
2000
    static void call(TransactionRef tr, Func* func)
2001
    {
10✔
2002
        tr->advance_read(func);
10✔
2003
    }
10✔
2004
};
2005

2006
struct PromoteThenRollback {
2007
    template <typename Func>
2008
    static void call(TransactionRef tr, Func* func)
2009
    {
10✔
2010
        tr->promote_to_write(func);
10✔
2011
        tr->rollback_and_continue_as_read();
10✔
2012
    }
10✔
2013
};
2014

2015
} // unnamed namespace
2016

2017
TEST_TYPES(LangBindHelper_AdvanceReadTransact_TransactLog, AdvanceReadTransact, PromoteThenRollback)
2018
{
4✔
2019
    SHARED_GROUP_TEST_PATH(path);
4✔
2020
    std::unique_ptr<Replication> hist(make_in_realm_history());
4✔
2021
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4✔
2022
    ColKey c0, c1;
4✔
2023
    {
4✔
2024
        WriteTransaction wt(sg);
4✔
2025
        c0 = wt.add_table("table 1")->add_column(type_Int, "int");
4✔
2026
        c1 = wt.add_table("table 2")->add_column(type_Int, "int");
4✔
2027
        wt.commit();
4✔
2028
    }
4✔
2029

2030
    auto tr = sg->start_read();
4✔
2031

2032
    {
4✔
2033
        // With no changes, the handler should not be called at all
2034
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2035
            TestContext& test_context;
4✔
2036
            Parser(TestContext& context)
4✔
2037
                : test_context(context)
4✔
2038
            {
4✔
2039
            }
4✔
2040
            void parse_complete()
4✔
2041
            {
4✔
2042
                CHECK(false);
×
2043
            }
×
2044
        } parser(test_context);
4✔
2045
        TEST_TYPE::call(tr, &parser);
4✔
2046
    }
4✔
2047

2048
    {
4✔
2049
        // With an empty change, parse_complete() and nothing else should be called
2050
        auto wt = sg->start_write();
4✔
2051
        wt->commit();
4✔
2052

2053
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2054
            TestContext& test_context;
4✔
2055
            bool called = false;
4✔
2056

2057
            Parser(TestContext& context)
4✔
2058
                : test_context(context)
4✔
2059
            {
4✔
2060
            }
4✔
2061

2062
            void parse_complete()
4✔
2063
            {
4✔
2064
                called = true;
4✔
2065
            }
4✔
2066
        } parser(test_context);
4✔
2067
        TEST_TYPE::call(tr, &parser);
4✔
2068
        CHECK(parser.called);
4✔
2069
    }
4✔
2070
    ObjKey o0, o1;
4✔
2071
    {
4✔
2072
        // Make a simple modification and verify that the appropriate handler is called
2073
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2074
            TestContext& test_context;
4✔
2075
            size_t expected_table = 0;
4✔
2076
            TableKey t1;
4✔
2077
            TableKey t2;
4✔
2078

2079
            Parser(TestContext& context)
4✔
2080
                : test_context(context)
4✔
2081
            {
4✔
2082
            }
4✔
2083

2084
            bool create_object(ObjKey)
4✔
2085
            {
8✔
2086
                CHECK_EQUAL(expected_table ? t2 : t1, get_current_table());
8✔
2087
                ++expected_table;
8✔
2088

2089
                return true;
8✔
2090
            }
8✔
2091
        } parser(test_context);
4✔
2092

2093
        WriteTransaction wt(sg);
4✔
2094
        parser.t1 = wt.get_table("table 1")->get_key();
4✔
2095
        parser.t2 = wt.get_table("table 2")->get_key();
4✔
2096
        o0 = wt.get_table("table 1")->create_object().get_key();
4✔
2097
        o1 = wt.get_table("table 2")->create_object().get_key();
4✔
2098
        wt.commit();
4✔
2099

2100
        TEST_TYPE::call(tr, &parser);
4✔
2101
        CHECK_EQUAL(2, parser.expected_table);
4✔
2102
    }
4✔
2103
    ColKey c2, c3;
4✔
2104
    ObjKey okey;
4✔
2105
    {
4✔
2106
        // Add a table with some links
2107
        WriteTransaction wt(sg);
4✔
2108
        TableRef table = wt.add_table("link origin");
4✔
2109
        c2 = table->add_column(*wt.get_table("table 1"), "link");
4✔
2110
        c3 = table->add_column_list(*wt.get_table("table 2"), "linklist");
4✔
2111
        Obj o = table->create_object();
4✔
2112
        o.set(c2, o.get_key());
4✔
2113
        o.get_linklist(c3).add(o.get_key());
4✔
2114
        okey = o.get_key();
4✔
2115
        wt.commit();
4✔
2116

2117
        tr->advance_read();
4✔
2118
    }
4✔
2119
    {
4✔
2120
        // Verify that deleting the targets of the links logs link nullifications
2121
        WriteTransaction wt(sg);
4✔
2122
        wt.get_table("table 1")->remove_object(o0);
4✔
2123
        wt.get_table("table 2")->remove_object(o1);
4✔
2124
        wt.commit();
4✔
2125

2126
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2127
            TestContext& test_context;
4✔
2128
            Parser(TestContext& context)
4✔
2129
                : test_context(context)
4✔
2130
            {
4✔
2131
            }
4✔
2132

2133
            bool remove_object(ObjKey o)
4✔
2134
            {
8✔
2135
                CHECK(o == o1 || o == o0);
8✔
2136
                return true;
8✔
2137
            }
8✔
2138
            bool select_collection(ColKey col, ObjKey o, const StablePath&)
4✔
2139
            {
4✔
2140
                CHECK(col == link_list_col);
4✔
2141
                CHECK(o == okey);
4✔
2142
                return true;
4✔
2143
            }
4✔
2144
            bool collection_erase(size_t ndx)
4✔
2145
            {
4✔
2146
                CHECK(ndx == 0);
4✔
2147
                return true;
4✔
2148
            }
4✔
2149

2150
            bool modify_object(ColKey col, ObjKey obj)
4✔
2151
            {
4✔
2152
                CHECK(col == link_col && obj == okey);
4✔
2153
                return true;
4✔
2154
            }
4✔
2155
            ObjKey o0, o1, okey;
4✔
2156
            ColKey link_col, link_list_col;
4✔
2157
        } parser(test_context);
4✔
2158
        parser.o1 = o1;
4✔
2159
        parser.o0 = o0;
4✔
2160
        parser.okey = okey;
4✔
2161
        parser.link_col = c2;
4✔
2162
        parser.link_list_col = c3;
4✔
2163
        TEST_TYPE::call(tr, &parser);
4✔
2164
    }
4✔
2165
    {
4✔
2166
        // Verify that clear() logs the correct rows
2167
        WriteTransaction wt(sg);
4✔
2168
        std::vector<ObjKey> keys;
4✔
2169
        wt.get_table("table 2")->create_objects(10, keys);
4✔
2170

2171
        auto lv = wt.get_table("link origin")->begin()->get_linklist(c3);
4✔
2172
        lv.add(keys[1]);
4✔
2173
        lv.add(keys[3]);
4✔
2174
        lv.add(keys[5]);
4✔
2175

2176
        wt.commit();
4✔
2177
        tr->advance_read();
4✔
2178
    }
4✔
2179
    {
4✔
2180
        WriteTransaction wt(sg);
4✔
2181
        wt.get_table("link origin")->begin()->get_linklist(c3).clear();
4✔
2182
        wt.commit();
4✔
2183
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2184
            TestContext& test_context;
4✔
2185
            Parser(TestContext& context)
4✔
2186
                : test_context(context)
4✔
2187
            {
4✔
2188
            }
4✔
2189

2190
            bool collection_clear(size_t old_size) const
4✔
2191
            {
4✔
2192
                CHECK_EQUAL(3, old_size);
4✔
2193
                return true;
4✔
2194
            }
4✔
2195
        } parser(test_context);
4✔
2196
        TEST_TYPE::call(tr, &parser);
4✔
2197
    }
4✔
2198
}
4✔
2199

2200

2201
TEST(LangBindHelper_AdvanceReadTransact_ErrorInObserver)
2202
{
2✔
2203
    SHARED_GROUP_TEST_PATH(path);
2✔
2204
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2205
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2206
    ColKey col;
2✔
2207
    Obj obj;
2✔
2208
    // Add some initial data and then begin a read transaction at that version
2209
    auto wt1 = sg->start_write();
2✔
2210
    TableRef table = wt1->add_table("Table");
2✔
2211
    col = table->add_column(type_Int, "int");
2✔
2212
    auto obj2 = table->create_object().set_all(10);
2✔
2213
    wt1->commit_and_continue_as_read();
2✔
2214

2215
    auto g = sg->start_read();     // must follow commit, to see table just created
2✔
2216
    obj = g->import_copy_of(obj2); // cannot be imported if table does not exist
2✔
2217
    wt1->end_read();               // wt1 must live long enough to support import_copy_of of obj2
2✔
2218
    // Modify the data with a different SG so that we can determine which version
2219
    // the read transaction is using
2220
    {
2✔
2221
        auto wt = sg->start_write();
2✔
2222
        Obj o2 = wt->import_copy_of(obj);
2✔
2223
        o2.set<int64_t>(col, 20);
2✔
2224
        wt->commit();
2✔
2225
    }
2✔
2226

2227
    struct ObserverError {};
2✔
2228
    try {
2✔
2229
        struct Parser : _impl::NoOpTransactionLogParser {
2✔
2230
            TestContext& test_context;
2✔
2231
            Parser(TestContext& context)
2✔
2232
                : test_context(context)
2✔
2233
            {
2✔
2234
            }
2✔
2235

2236
            bool modify_object(ColKey, ObjKey) const
2✔
2237
            {
2✔
2238
                throw ObserverError();
2✔
2239
            }
2✔
2240
        } parser(test_context);
2✔
2241
        g->advance_read(&parser);
2✔
2242
        CHECK(false); // Should not be reached
2✔
2243
    }
2✔
2244
    catch (ObserverError) {
2✔
2245
    }
2✔
2246

2247
    // Should still see data from old version
2248
    auto o = g->import_copy_of(obj);
2✔
2249
    CHECK_EQUAL(10, o.get<int64_t>(col));
2✔
2250

2251
    // Should be able to advance to the new version still
2252
    g->advance_read();
2✔
2253

2254
    // And see that version's data
2255
    CHECK_EQUAL(20, o.get<int64_t>(col));
2✔
2256
}
2✔
2257

2258

2259
TEST(LangBindHelper_ImplicitTransactions)
2260
{
2✔
2261
    SHARED_GROUP_TEST_PATH(path);
2✔
2262
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2263
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2264
    ObjKey o;
2✔
2265
    ColKey col;
2✔
2266
    {
2✔
2267
        WriteTransaction wt(sg);
2✔
2268
        auto table = wt.add_table("table");
2✔
2269
        col = table->add_column(type_Int, "first");
2✔
2270
        table->add_column(type_Int, "second");
2✔
2271
        table->add_column(type_Bool, "third");
2✔
2272
        table->add_column(type_String, "fourth");
2✔
2273
        o = table->create_object().get_key();
2✔
2274
        wt.commit();
2✔
2275
    }
2✔
2276
    auto g = sg->start_read();
2✔
2277
    auto table = g->get_table("table");
2✔
2278
    for (int i = 0; i < 100; i++) {
202✔
2279
        {
200✔
2280
            // change table in other context
2281
            WriteTransaction wt(sg);
200✔
2282
            wt.get_table("table")->get_object(o).add_int(col, 100);
200✔
2283
            wt.commit();
200✔
2284
        }
200✔
2285
        // verify we can't see the update
2286
        CHECK_EQUAL(i, table->get_object(o).get<int64_t>(col));
200✔
2287
        g->advance_read();
200✔
2288
        // now we CAN see it, and through the same accessor
2289
        CHECK(table);
200✔
2290
        CHECK_EQUAL(i + 100, table->get_object(o).get<int64_t>(col));
200✔
2291
        {
200✔
2292
            // change table in other context
2293
            WriteTransaction wt(sg);
200✔
2294
            wt.get_table("table")->get_object(o).add_int(col, 10000);
200✔
2295
            wt.commit();
200✔
2296
        }
200✔
2297
        // can't see it:
2298
        CHECK_EQUAL(i + 100, table->get_object(o).get<int64_t>(col));
200✔
2299
        g->promote_to_write();
200✔
2300
        // CAN see it:
2301
        CHECK(table);
200✔
2302
        CHECK_EQUAL(i + 10100, table->get_object(o).get<int64_t>(col));
200✔
2303
        table->get_object(o).add_int(col, -10100);
200✔
2304
        table->get_object(o).add_int(col, 1);
200✔
2305
        g->commit_and_continue_as_read();
200✔
2306
        CHECK(table);
200✔
2307
        CHECK_EQUAL(i + 1, table->get_object(o).get<int64_t>(col));
200✔
2308
    }
200✔
2309
    g->end_read();
2✔
2310
}
2✔
2311

2312

2313
TEST(LangBindHelper_RollbackAndContinueAsRead)
2314
{
2✔
2315
    SHARED_GROUP_TEST_PATH(path);
2✔
2316
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2317
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2318
    {
2✔
2319
        ObjKey key;
2✔
2320
        ColKey col;
2✔
2321
        auto group = sg->start_read();
2✔
2322
        {
2✔
2323
            group->promote_to_write();
2✔
2324
            TableRef origin = group->get_or_add_table("origin");
2✔
2325
            col = origin->add_column(type_Int, "");
2✔
2326
            key = origin->create_object().set_all(42).get_key();
2✔
2327
            group->commit_and_continue_as_read();
2✔
2328
        }
2✔
2329
        group->verify();
2✔
2330
        {
2✔
2331
            // rollback of group level table insertion
2332
            group->promote_to_write();
2✔
2333
            group->get_or_add_table("nullermand");
2✔
2334
            TableRef o2 = group->get_table("nullermand");
2✔
2335
            REALM_ASSERT(o2);
2✔
2336
            group->rollback_and_continue_as_read();
2✔
2337
            TableRef o3 = group->get_table("nullermand");
2✔
2338
            REALM_ASSERT(!o3);
2✔
2339
            REALM_ASSERT(!o2);
2✔
2340
        }
2✔
2341

2342
        TableRef origin = group->get_table("origin");
2✔
2343
        Obj row = origin->get_object(key);
2✔
2344
        CHECK_EQUAL(42, row.get<int64_t>(col));
2✔
2345

2346
        {
2✔
2347
            group->promote_to_write();
2✔
2348
            auto row2 = origin->create_object().set_all(5746);
2✔
2349
            CHECK_EQUAL(42, row.get<int64_t>(col));
2✔
2350
            CHECK_EQUAL(5746, row2.get<int64_t>(col));
2✔
2351
            CHECK_EQUAL(2, origin->size());
2✔
2352
            group->verify();
2✔
2353
            group->rollback_and_continue_as_read();
2✔
2354
        }
2✔
2355
        CHECK_EQUAL(1, origin->size());
2✔
2356
        group->verify();
2✔
2357
        CHECK_EQUAL(42, row.get<int64_t>(col));
2✔
2358
        Obj row2;
2✔
2359
        {
2✔
2360
            group->promote_to_write();
2✔
2361
            row2 = origin->create_object().set_all(42);
2✔
2362
            group->commit_and_continue_as_read();
2✔
2363
        }
2✔
2364
        CHECK_EQUAL(2, origin->size());
2✔
2365
        group->verify();
2✔
2366
        CHECK_EQUAL(42, row2.get<int64_t>(col));
2✔
2367
        group->end_read();
2✔
2368
    }
2✔
2369
}
2✔
2370

2371

2372
TEST(LangBindHelper_RollbackAndContinueAsReadGroupLevelTableRemoval)
2373
{
2✔
2374
    SHARED_GROUP_TEST_PATH(path);
2✔
2375
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2376
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2377
    auto reader = sg->start_read();
2✔
2378
    {
2✔
2379
        reader->promote_to_write();
2✔
2380
        reader->get_or_add_table("a_table");
2✔
2381
        reader->commit_and_continue_as_read();
2✔
2382
    }
2✔
2383
    reader->verify();
2✔
2384
    {
2✔
2385
        // rollback of group level table delete
2386
        reader->promote_to_write();
2✔
2387
        TableRef o2 = reader->get_table("a_table");
2✔
2388
        REALM_ASSERT(o2);
2✔
2389
        reader->remove_table("a_table");
2✔
2390
        TableRef o3 = reader->get_table("a_table");
2✔
2391
        REALM_ASSERT(!o3);
2✔
2392
        reader->rollback_and_continue_as_read();
2✔
2393
        TableRef o4 = reader->get_table("a_table");
2✔
2394
        REALM_ASSERT(o4);
2✔
2395
    }
2✔
2396
    reader->verify();
2✔
2397
}
2✔
2398

2399
TEST(LangBindHelper_RollbackCircularReferenceRemoval)
2400
{
2✔
2401
    SHARED_GROUP_TEST_PATH(path);
2✔
2402
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2403
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2404
    ColKey ca, cb;
2✔
2405
    auto group = sg->start_read();
2✔
2406
    {
2✔
2407
        group->promote_to_write();
2✔
2408
        TableRef alpha = group->get_or_add_table("alpha");
2✔
2409
        TableRef beta = group->get_or_add_table("beta");
2✔
2410
        ca = alpha->add_column(*beta, "beta-1");
2✔
2411
        cb = beta->add_column(*alpha, "alpha-1");
2✔
2412
        group->commit_and_continue_as_read();
2✔
2413
    }
2✔
2414
    group->verify();
2✔
2415
    {
2✔
2416
        group->promote_to_write();
2✔
2417
        CHECK_EQUAL(2, group->size());
2✔
2418
        TableRef alpha = group->get_table("alpha");
2✔
2419
        TableRef beta = group->get_table("beta");
2✔
2420

2421
        CHECK_THROW(group->remove_table("alpha"), CrossTableLinkTarget);
2✔
2422
        beta->remove_column(cb);
2✔
2423
        alpha->remove_column(ca);
2✔
2424
        group->remove_table("beta");
2✔
2425
        CHECK_NOT(group->has_table("beta"));
2✔
2426

2427
        // Version 1: This crashes
2428
        group->rollback_and_continue_as_read();
2✔
2429
        CHECK_EQUAL(2, group->size());
2✔
2430

2431
        //        // Version 2: This works
2432
        //        LangBindHelper::commit_and_continue_as_read(sg);
2433
        //        CHECK_EQUAL(1, group->size());
2434
    }
2✔
2435
    group->verify();
2✔
2436
}
2✔
2437

2438

2439
TEST(LangBindHelper_RollbackAndContinueAsReadColumnAdd)
2440
{
2✔
2441
    SHARED_GROUP_TEST_PATH(path);
2✔
2442
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2443
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2444
    auto group = sg->start_read();
2✔
2445
    TableRef t;
2✔
2446
    {
2✔
2447
        group->promote_to_write();
2✔
2448
        t = group->get_or_add_table("a_table");
2✔
2449
        t->add_column(type_Int, "lorelei");
2✔
2450
        t->create_object().set_all(43);
2✔
2451
        CHECK_EQUAL(1, t->get_column_count());
2✔
2452
        group->commit_and_continue_as_read();
2✔
2453
    }
2✔
2454
    group->verify();
2✔
2455
    {
2✔
2456
        // add a column and regret it again
2457
        group->promote_to_write();
2✔
2458
        auto col = t->add_column(type_Int, "riget");
2✔
2459
        t->begin()->set(col, 44);
2✔
2460
        CHECK_EQUAL(2, t->get_column_count());
2✔
2461
        group->verify();
2✔
2462
        group->rollback_and_continue_as_read();
2✔
2463
        group->verify();
2✔
2464
        CHECK_EQUAL(1, t->get_column_count());
2✔
2465
    }
2✔
2466
    group->verify();
2✔
2467
}
2✔
2468

2469

2470
// This issue was uncovered while looking into the RollbackCircularReferenceRemoval issue
2471
TEST(LangBindHelper_TableLinkingRemovalIssue)
2472
{
2✔
2473
    SHARED_GROUP_TEST_PATH(path);
2✔
2474
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2475
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2476
    auto group = sg->start_read();
2✔
2477
    {
2✔
2478
        group->promote_to_write();
2✔
2479
        TableRef t1 = group->get_or_add_table("t1");
2✔
2480
        TableRef t2 = group->get_or_add_table("t2");
2✔
2481
        TableRef t3 = group->get_or_add_table("t3");
2✔
2482
        TableRef t4 = group->get_or_add_table("t4");
2✔
2483
        t1->add_column(*t2, "l12");
2✔
2484
        t2->add_column(*t3, "l23");
2✔
2485
        t3->add_column(*t4, "l34");
2✔
2486
        group->commit_and_continue_as_read();
2✔
2487
    }
2✔
2488
    group->verify();
2✔
2489
    {
2✔
2490
        group->promote_to_write();
2✔
2491
        CHECK_EQUAL(4, group->size());
2✔
2492

2493
        group->remove_table("t1");
2✔
2494
        group->remove_table("t2");
2✔
2495
        group->remove_table("t3"); // CRASHES HERE
2✔
2496
        group->remove_table("t4");
2✔
2497

2498
        group->rollback_and_continue_as_read();
2✔
2499
        CHECK_EQUAL(4, group->size());
2✔
2500
    }
2✔
2501
    group->verify();
2✔
2502
}
2✔
2503

2504

2505
// This issue was uncovered while looking into the RollbackCircularReferenceRemoval issue
2506
TEST(LangBindHelper_RollbackTableRemove)
2507
{
2✔
2508
    SHARED_GROUP_TEST_PATH(path);
2✔
2509
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2510
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2511
    auto group = sg->start_read();
2✔
2512
    {
2✔
2513
        group->promote_to_write();
2✔
2514
        TableRef alpha = group->get_or_add_table("alpha");
2✔
2515
        TableRef beta = group->get_or_add_table("beta");
2✔
2516
        beta->add_column(*alpha, "alpha-1");
2✔
2517
        group->commit_and_continue_as_read();
2✔
2518
    }
2✔
2519
    group->verify();
2✔
2520
    {
2✔
2521
        group->promote_to_write();
2✔
2522
        CHECK_EQUAL(2, group->size());
2✔
2523
        TableRef alpha = group->get_table("alpha");
2✔
2524
        TableRef beta = group->get_table("beta");
2✔
2525
        CHECK(alpha);
2✔
2526
        CHECK(beta);
2✔
2527
        group->remove_table("beta");
2✔
2528
        CHECK_NOT(group->has_table("beta"));
2✔
2529
        group->rollback_and_continue_as_read();
2✔
2530
        CHECK_EQUAL(2, group->size());
2✔
2531
    }
2✔
2532
    group->verify();
2✔
2533
}
2✔
2534

2535
TEST(LangBindHelper_RollbackTableRemove2)
2536
{
2✔
2537
    SHARED_GROUP_TEST_PATH(path);
2✔
2538
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2539
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2540
    auto group = sg->start_read();
2✔
2541
    {
2✔
2542
        group->promote_to_write();
2✔
2543
        TableRef a = group->get_or_add_table("a");
2✔
2544
        TableRef b = group->get_or_add_table("b");
2✔
2545
        TableRef c = group->get_or_add_table("c");
2✔
2546
        TableRef d = group->get_or_add_table("d");
2✔
2547
        c->add_column(*a, "a");
2✔
2548
        d->add_column(*b, "b");
2✔
2549
        group->commit_and_continue_as_read();
2✔
2550
    }
2✔
2551
    group->verify();
2✔
2552
    {
2✔
2553
        group->promote_to_write();
2✔
2554
        CHECK_EQUAL(4, group->size());
2✔
2555
        group->remove_table("c");
2✔
2556
        CHECK_NOT(group->has_table("c"));
2✔
2557
        group->verify();
2✔
2558
        group->rollback_and_continue_as_read();
2✔
2559
        CHECK_EQUAL(4, group->size());
2✔
2560
    }
2✔
2561
    group->verify();
2✔
2562
}
2✔
2563

2564
TEST(LangBindHelper_ContinuousTransactions_RollbackTableRemoval)
2565
{
2✔
2566
    // Test that it is possible to modify a table, then remove it from the
2567
    // group, and then rollback the transaction.
2568

2569
    // This triggered a bug in the instruction reverser which would incorrectly
2570
    // associate the table removal instruction with the table selection
2571
    // instruction induced by the modification, causing the latter to occur in
2572
    // the reverse log at a point where the selected table does not yet
2573
    // exist. The filler table is there to avoid an early-out in
2574
    // Group::TransactAdvancer::select_table() due to a misinterpretation of the
2575
    // reason for the missing table accessor entry.
2576

2577
    SHARED_GROUP_TEST_PATH(path);
2✔
2578
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2579
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2580
    auto group = sg->start_read();
2✔
2581
    group->promote_to_write();
2✔
2582
    group->get_or_add_table("filler");
2✔
2583
    TableRef table = group->get_or_add_table("table");
2✔
2584
    auto col = table->add_column(type_Int, "i");
2✔
2585
    Obj o = table->create_object();
2✔
2586
    group->commit_and_continue_as_read();
2✔
2587
    group->promote_to_write();
2✔
2588
    o.set<int>(col, 0);
2✔
2589
    group->remove_table("table");
2✔
2590
    group->rollback_and_continue_as_read();
2✔
2591
}
2✔
2592

2593
TEST(LangBindHelper_RollbackAndContinueAsReadLinkColumnRemove)
2594
{
2✔
2595
    SHARED_GROUP_TEST_PATH(path);
2✔
2596
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2597
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2598
    auto group = sg->start_read();
2✔
2599
    TableRef t, t2;
2✔
2600
    ColKey col;
2✔
2601
    {
2✔
2602
        // add a column
2603
        group->promote_to_write();
2✔
2604
        t = group->get_or_add_table("a_table");
2✔
2605
        t2 = group->get_or_add_table("b_table");
2✔
2606
        col = t->add_column(*t2, "bruno");
2✔
2607
        CHECK_EQUAL(1, t->get_column_count());
2✔
2608
        group->commit_and_continue_as_read();
2✔
2609
    }
2✔
2610
    group->verify();
2✔
2611
    {
2✔
2612
        // ... but then regret it
2613
        group->promote_to_write();
2✔
2614
        t->remove_column(col);
2✔
2615
        CHECK_EQUAL(0, t->get_column_count());
2✔
2616
        group->rollback_and_continue_as_read();
2✔
2617
    }
2✔
2618
}
2✔
2619

2620

2621
TEST(LangBindHelper_RollbackAndContinueAsReadColumnRemove)
2622
{
2✔
2623
    SHARED_GROUP_TEST_PATH(path);
2✔
2624
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2625
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2626
    auto group = sg->start_read();
2✔
2627
    TableRef t;
2✔
2628
    ColKey col;
2✔
2629
    {
2✔
2630
        group->promote_to_write();
2✔
2631
        t = group->get_or_add_table("a_table");
2✔
2632
        col = t->add_column(type_Int, "lorelei");
2✔
2633
        t->add_column(type_Int, "riget");
2✔
2634
        t->create_object().set_all(43, 44);
2✔
2635
        CHECK_EQUAL(2, t->get_column_count());
2✔
2636
        group->commit_and_continue_as_read();
2✔
2637
    }
2✔
2638
    group->verify();
2✔
2639
    {
2✔
2640
        // remove a column but regret it
2641
        group->promote_to_write();
2✔
2642
        CHECK_EQUAL(2, t->get_column_count());
2✔
2643
        t->remove_column(col);
2✔
2644
        group->verify();
2✔
2645
        group->rollback_and_continue_as_read();
2✔
2646
        group->verify();
2✔
2647
        CHECK_EQUAL(2, t->get_column_count());
2✔
2648
    }
2✔
2649
    group->verify();
2✔
2650
}
2✔
2651

2652

2653
TEST(LangBindHelper_RollbackAndContinueAsReadLinkList)
2654
{
2✔
2655
    SHARED_GROUP_TEST_PATH(path);
2✔
2656
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2657
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2658
    auto group = sg->start_read();
2✔
2659
    group->promote_to_write();
2✔
2660
    TableRef origin = group->add_table("origin");
2✔
2661
    TableRef target = group->add_table("target");
2✔
2662
    auto col0 = origin->add_column_list(*target, "");
2✔
2663
    target->add_column(type_Int, "");
2✔
2664
    auto o0 = origin->create_object();
2✔
2665
    auto t0 = target->create_object();
2✔
2666
    auto t1 = target->create_object();
2✔
2667
    auto t2 = target->create_object();
2✔
2668

2669
    auto link_list = o0.get_linklist(col0);
2✔
2670
    link_list.add(t0.get_key());
2✔
2671
    group->commit_and_continue_as_read();
2✔
2672
    CHECK_EQUAL(1, link_list.size());
2✔
2673
    group->verify();
2✔
2674
    // now change a link in link list and roll back the change
2675
    group->promote_to_write();
2✔
2676
    link_list.add(t1.get_key());
2✔
2677
    link_list.add(t2.get_key());
2✔
2678
    CHECK_EQUAL(3, link_list.size());
2✔
2679
    group->rollback_and_continue_as_read();
2✔
2680
    CHECK_EQUAL(1, link_list.size());
2✔
2681
    group->promote_to_write();
2✔
2682
    link_list.remove(0);
2✔
2683
    CHECK_EQUAL(0, link_list.size());
2✔
2684
    group->rollback_and_continue_as_read();
2✔
2685
    CHECK_EQUAL(1, link_list.size());
2✔
2686
}
2✔
2687

2688

2689
TEST(LangBindHelper_RollbackAndContinueAsRead_Links)
2690
{
2✔
2691
    SHARED_GROUP_TEST_PATH(path);
2✔
2692
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2693
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2694
    auto group = sg->start_read();
2✔
2695
    group->promote_to_write();
2✔
2696
    TableRef origin = group->add_table("origin");
2✔
2697
    TableRef target = group->add_table("target");
2✔
2698
    auto col0 = origin->add_column(*target, "");
2✔
2699
    target->add_column(type_Int, "");
2✔
2700
    auto o0 = origin->create_object();
2✔
2701
    target->create_object();
2✔
2702
    auto t1 = target->create_object();
2✔
2703
    auto t2 = target->create_object();
2✔
2704

2705
    o0.set(col0, t2.get_key());
2✔
2706
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2707
    group->commit_and_continue_as_read();
2✔
2708

2709
    // verify that we can revert a link change:
2710
    group->promote_to_write();
2✔
2711
    o0.set(col0, t1.get_key());
2✔
2712
    CHECK_EQUAL(t1.get_key(), o0.get<ObjKey>(col0));
2✔
2713
    group->rollback_and_continue_as_read();
2✔
2714
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2715
    // verify that we can revert addition of a row in target table
2716
    group->promote_to_write();
2✔
2717
    target->create_object();
2✔
2718
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2719
    group->rollback_and_continue_as_read();
2✔
2720
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2721
}
2✔
2722

2723

2724
TEST(LangBindHelper_RollbackAndContinueAsRead_LinkLists)
2725
{
2✔
2726
    SHARED_GROUP_TEST_PATH(path);
2✔
2727
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2728
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2729
    auto group = sg->start_read();
2✔
2730
    group->promote_to_write();
2✔
2731
    TableRef origin = group->add_table("origin");
2✔
2732
    TableRef target = group->add_table("target");
2✔
2733
    auto col0 = origin->add_column_list(*target, "");
2✔
2734
    target->add_column(type_Int, "");
2✔
2735
    auto o0 = origin->create_object();
2✔
2736
    auto t0 = target->create_object();
2✔
2737
    auto t1 = target->create_object();
2✔
2738
    auto t2 = target->create_object();
2✔
2739

2740
    auto link_list = o0.get_linklist(col0);
2✔
2741
    link_list.add(t0.get_key());
2✔
2742
    link_list.add(t1.get_key());
2✔
2743
    link_list.add(t2.get_key());
2✔
2744
    link_list.add(t0.get_key());
2✔
2745
    link_list.add(t2.get_key());
2✔
2746
    group->commit_and_continue_as_read();
2✔
2747
    // verify that we can reverse a LinkView::move()
2748
    CHECK_EQUAL(5, link_list.size());
2✔
2749
    CHECK_EQUAL(t0.get_key(), link_list.get(0));
2✔
2750
    CHECK_EQUAL(t1.get_key(), link_list.get(1));
2✔
2751
    CHECK_EQUAL(t2.get_key(), link_list.get(2));
2✔
2752
    CHECK_EQUAL(t0.get_key(), link_list.get(3));
2✔
2753
    CHECK_EQUAL(t2.get_key(), link_list.get(4));
2✔
2754
    group->promote_to_write();
2✔
2755
    link_list.move(1, 3);
2✔
2756
    CHECK_EQUAL(5, link_list.size());
2✔
2757
    CHECK_EQUAL(t0.get_key(), link_list.get(0));
2✔
2758
    CHECK_EQUAL(t2.get_key(), link_list.get(1));
2✔
2759
    CHECK_EQUAL(t0.get_key(), link_list.get(2));
2✔
2760
    CHECK_EQUAL(t1.get_key(), link_list.get(3));
2✔
2761
    CHECK_EQUAL(t2.get_key(), link_list.get(4));
2✔
2762
    group->rollback_and_continue_as_read();
2✔
2763
    CHECK_EQUAL(5, link_list.size());
2✔
2764
    CHECK_EQUAL(t0.get_key(), link_list.get(0));
2✔
2765
    CHECK_EQUAL(t1.get_key(), link_list.get(1));
2✔
2766
    CHECK_EQUAL(t2.get_key(), link_list.get(2));
2✔
2767
    CHECK_EQUAL(t0.get_key(), link_list.get(3));
2✔
2768
    CHECK_EQUAL(t2.get_key(), link_list.get(4));
2✔
2769
}
2✔
2770

2771

2772
TEST(LangBindHelper_RollbackAndContinueAsRead_TableClear)
2773
{
2✔
2774
    SHARED_GROUP_TEST_PATH(path);
2✔
2775
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2776
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2777
    auto group = sg->start_read();
2✔
2778

2779
    group->promote_to_write();
2✔
2780
    TableRef origin = group->add_table("origin");
2✔
2781
    TableRef target = group->add_table("target");
2✔
2782

2783
    auto c1 = origin->add_column_list(*target, "linklist");
2✔
2784
    target->add_column(type_Int, "int");
2✔
2785
    auto c2 = origin->add_column(*target, "link");
2✔
2786

2787
    Obj t = target->create_object();
2✔
2788
    Obj o = origin->create_object();
2✔
2789
    o.set(c2, t.get_key());
2✔
2790
    LnkLst l = o.get_linklist(c1);
2✔
2791
    l.add(t.get_key());
2✔
2792
    group->commit_and_continue_as_read();
2✔
2793

2794
    group->promote_to_write();
2✔
2795
    CHECK_EQUAL(1, l.size());
2✔
2796
    target->clear();
2✔
2797
    CHECK_EQUAL(0, l.size());
2✔
2798

2799
    group->rollback_and_continue_as_read();
2✔
2800
    CHECK_EQUAL(1, l.size());
2✔
2801
}
2✔
2802

2803
TEST(LangBindHelper_RollbackAndContinueAsRead_IntIndex)
2804
{
2✔
2805
    SHARED_GROUP_TEST_PATH(path);
2✔
2806
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2807
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2808
    auto g = sg->start_read();
2✔
2809
    g->promote_to_write();
2✔
2810

2811
    TableRef target = g->add_table("target");
2✔
2812
    ColKey col = target->add_column(type_Int, "pk");
2✔
2813
    target->add_search_index(col);
2✔
2814

2815
    std::vector<ObjKey> keys;
2✔
2816
    target->create_objects(REALM_MAX_BPNODE_SIZE + 1, keys);
2✔
2817
    g->commit_and_continue_as_read();
2✔
2818
    g->promote_to_write();
2✔
2819

2820
    // Ensure that the index has a different bptree layout so that failing to
2821
    // refresh it will do bad things
2822
    auto it = target->begin();
2✔
2823
    for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i) {
2,004✔
2824
        it->set<int64_t>(col, i);
2,002✔
2825
        ++it;
2,002✔
2826
    }
2,002✔
2827

2828
    g->rollback_and_continue_as_read();
2✔
2829
    g->promote_to_write();
2✔
2830

2831
    // Crashes if index has an invalid parent ref
2832
    target->clear();
2✔
2833
}
2✔
2834

2835

2836
TEST(LangBindHelper_ImplicitTransactions_OverSharedGroupDestruction)
2837
{
2✔
2838
    SHARED_GROUP_TEST_PATH(path);
2✔
2839
    // we hold on to write log collector and registry across a complete
2840
    // shutdown/initialization of shared rt->
2841
    std::unique_ptr<Replication> hist1(make_in_realm_history());
2✔
2842
    {
2✔
2843
        DBRef sg = DB::create(*hist1, path, DBOptions(crypt_key()));
2✔
2844
        {
2✔
2845
            WriteTransaction wt(sg);
2✔
2846
            TableRef tr = wt.add_table("table");
2✔
2847
            tr->add_column(type_Int, "first");
2✔
2848
            for (int i = 0; i < 20; i++)
42✔
2849
                tr->create_object();
40✔
2850
            wt.commit();
2✔
2851
        }
2✔
2852
        // no valid shared group anymore
2853
    }
2✔
2854
    {
2✔
2855
        std::unique_ptr<Replication> hist2(make_in_realm_history());
2✔
2856
        DBRef sg = DB::create(*hist2, path, DBOptions(crypt_key()));
2✔
2857
        {
2✔
2858
            WriteTransaction wt(sg);
2✔
2859
            TableRef tr = wt.get_table("table");
2✔
2860
            for (int i = 0; i < 20; i++)
42✔
2861
                tr->create_object();
40✔
2862
            wt.commit();
2✔
2863
        }
2✔
2864
    }
2✔
2865
}
2✔
2866

2867
TEST(LangBindHelper_ImplicitTransactions_LinkList)
2868
{
2✔
2869
    SHARED_GROUP_TEST_PATH(path);
2✔
2870
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2871
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2872
    auto group = sg->start_write();
2✔
2873
    TableRef origin = group->add_table("origin");
2✔
2874
    TableRef target = group->add_table("target");
2✔
2875
    auto col = origin->add_column_list(*target, "");
2✔
2876
    target->add_column(type_Int, "");
2✔
2877
    auto O0 = origin->create_object();
2✔
2878
    auto T0 = target->create_object();
2✔
2879
    auto link_list = O0.get_linklist(col);
2✔
2880
    link_list.add(T0.get_key());
2✔
2881
    group->commit_and_continue_as_read();
2✔
2882
    group->verify();
2✔
2883
}
2✔
2884

2885

2886
TEST(LangBindHelper_ImplicitTransactions_StringIndex)
2887
{
2✔
2888
    SHARED_GROUP_TEST_PATH(path);
2✔
2889
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2890
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2891
    auto group = sg->start_write();
2✔
2892
    TableRef table = group->add_table("a");
2✔
2893
    auto col = table->add_column(type_String, "b");
2✔
2894
    table->add_search_index(col);
2✔
2895
    group->verify();
2✔
2896
    group->commit_and_continue_as_read();
2✔
2897
    group->verify();
2✔
2898
}
2✔
2899

2900

2901
namespace {
2902

2903
// Test that multiple trackers (of changes) always see a consistent picture.
2904
// This is done by having multiple writers update a table A, query it, and store
2905
// the count of the query in another table, B. Multiple readers then track the
2906
// changes and verify that the count on their query is consistent with the count
2907
// they read from table B. Terminate verification by signalling through a third
2908
// table, C, after a chosen number of yields, which should allow readers to
2909
// run their verification.
2910
void multiple_trackers_writer_thread(DBRef db)
2911
{
14✔
2912
    // insert new random values
2913
    Random random(random_int<unsigned long>());
14✔
2914
    for (int i = 0; i < 10; ++i) {
154✔
2915
        WriteTransaction wt(db);
140✔
2916
        auto ta = wt.get_table("A");
140✔
2917
        auto col = ta->get_column_keys()[0];
140✔
2918
        for (auto it = ta->begin(); it != ta->end(); ++it) {
28,140✔
2919
            auto e = *it;
28,000✔
2920
            e.set(col, random.draw_int_mod(200));
28,000✔
2921
        }
28,000✔
2922
        auto tb = wt.get_table("B");
140✔
2923
        auto count = ta->where().greater(col, 100).count();
140✔
2924
        tb->begin()->set<int64_t>(tb->get_column_keys()[0], count);
140✔
2925
        wt.commit();
140✔
2926
    }
140✔
2927
}
14✔
2928

2929
void multiple_trackers_reader_thread(TestContext& test_context, DBRef db)
2930
{
6✔
2931
    // verify that consistency is maintained as we advance_read through a
2932
    // stream of transactions
2933
    auto g = db->start_read();
6✔
2934
    auto ta = g->get_table("A");
6✔
2935
    auto tb = g->get_table("B");
6✔
2936
    auto tc = g->get_table("C");
6✔
2937
    auto col = ta->get_column_keys()[0];
6✔
2938
    auto b_col = tb->get_column_keys()[0];
6✔
2939
    TableView tv = ta->where().greater(col, 100).find_all();
6✔
2940
    const auto wait_start = std::chrono::steady_clock::now();
6✔
2941
    std::chrono::seconds max_wait_seconds = std::chrono::seconds(1050);
6✔
2942
    while (tc->size() == 0) {
39,241✔
2943
        auto count = tb->begin()->get<int64_t>(b_col);
39,235✔
2944
        tv.sync_if_needed();
39,235✔
2945
        CHECK_EQUAL(tv.size(), count);
39,235✔
2946
        std::this_thread::yield();
39,235✔
2947
        g->advance_read();
39,235✔
2948
        if (std::chrono::steady_clock::now() - wait_start > max_wait_seconds) {
39,235✔
2949
            // if there is a fatal problem with a writer process we don't want the
2950
            // readers to wait forever as a spawned background processs
2951
            constexpr bool reader_process_timed_out = false;
×
2952
            REALM_ASSERT(reader_process_timed_out);
×
2953
        }
×
2954
    }
39,235✔
2955
}
6✔
2956

2957
} // anonymous namespace
2958

2959
TEST(LangBindHelper_ImplicitTransactions_MultipleTrackers)
2960
{
2✔
2961
    const int write_thread_count = 7;
2✔
2962
    const int read_thread_count = 3;
2✔
2963

2964
    SHARED_GROUP_TEST_PATH(path);
2✔
2965

2966
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2967
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2968
    {
2✔
2969
        // initialize table with 200 entries holding 0..200
2970
        WriteTransaction wt(sg);
2✔
2971
        TableRef tr = wt.add_table("A");
2✔
2972
        auto col = tr->add_column(type_Int, "first");
2✔
2973
        for (int j = 0; j < 200; j++) {
402✔
2974
            tr->create_object().set(col, j);
400✔
2975
        }
400✔
2976
        auto table_b = wt.add_table("B");
2✔
2977
        table_b->add_column(type_Int, "bussemand");
2✔
2978
        table_b->create_object().set_all(99);
2✔
2979
        wt.add_table("C");
2✔
2980
        wt.commit();
2✔
2981
    }
2✔
2982
    // FIXME: Use separate arrays for reader and writer threads for safety and readability.
2983
    Thread threads[write_thread_count + read_thread_count];
2✔
2984
    for (int i = 0; i < write_thread_count; ++i)
16✔
2985
        threads[i].start([&] {
14✔
2986
            multiple_trackers_writer_thread(sg);
14✔
2987
        });
14✔
2988
    std::this_thread::yield();
2✔
2989
    for (int i = 0; i < read_thread_count; ++i) {
8✔
2990
        threads[write_thread_count + i].start([&] {
6✔
2991
            multiple_trackers_reader_thread(test_context, sg);
6✔
2992
        });
6✔
2993
    }
6✔
2994

2995
    // Wait for all writer threads to complete
2996
    for (int i = 0; i < write_thread_count; ++i)
16✔
2997
        threads[i].join();
14✔
2998

2999
    // Allow readers time to catch up
3000
    for (int k = 0; k < 100; ++k)
202✔
3001
        std::this_thread::yield();
200✔
3002

3003
    // signal to all readers to complete
3004
    {
2✔
3005
        WriteTransaction wt(sg);
2✔
3006
        TableRef tr = wt.get_table("C");
2✔
3007
        tr->create_object();
2✔
3008
        wt.commit();
2✔
3009
    }
2✔
3010
    // Wait for all reader threads to complete
3011
    for (int i = 0; i < read_thread_count; ++i)
8✔
3012
        threads[write_thread_count + i].join();
6✔
3013
}
2✔
3014

3015
// Interprocess communication does not work with encryption enabled on Apple.
3016
// This is because fork() does not play well with Apple primitives such as
3017
// dispatch_queue_t in ReclaimerThreadStopper. This could possibly be fixed if
3018
// we need more tests like this.
3019

3020
#if !REALM_ANDROID && !REALM_IOS
3021

3022
// fork should not be used on android or ios.
3023
// This test must be non-concurrant due to fork. If a child process
3024
// is created while a static mutex is locked (eg. util::GlobalRandom::m_mutex)
3025
// then any attempt to use the mutex would hang infinitely and the child would
3026
// crash upon exit(0) when attempting to destroy a locked mutex.
3027
// This is not run with ASAN because children intentionally call exit(0) which does not
3028
// invoke destructors.
3029
NONCONCURRENT_TEST_IF(LangBindHelper_ImplicitTransactions_InterProcess, testing_supports_spawn_process)
3030
{
2✔
3031
    const int write_process_count = 7;
2✔
3032
    const int read_process_count = 3;
2✔
3033

3034
    std::vector<std::unique_ptr<SpawnedProcess>> readers;
2✔
3035
    std::vector<std::unique_ptr<SpawnedProcess>> writers;
2✔
3036
    SHARED_GROUP_TEST_PATH(path);
2✔
3037
    auto key = crypt_key();
2✔
3038
    auto process = test_util::spawn_process(test_context.test_details.test_name, "populate");
2✔
3039
    if (process->is_child()) {
2✔
3040
        try {
×
3041
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
3042
            DBRef sg = DB::create(*hist, path, DBOptions(key));
×
3043
            // initialize table with 200 entries holding 0..200
3044
            WriteTransaction wt(sg);
×
3045
            TableRef tr = wt.add_table("A");
×
3046
            auto col = tr->add_column(type_Int, "first");
×
3047
            for (int j = 0; j < 200; j++) {
×
3048
                tr->create_object().set(col, j);
×
3049
            }
×
3050
            auto table_b = wt.add_table("B");
×
3051
            table_b->add_column(type_Int, "bussemand");
×
3052
            table_b->create_object().set_all(99);
×
3053
            wt.add_table("C");
×
3054
            wt.commit();
×
3055
        }
×
3056
        catch (const std::exception& e) {
×
3057
            REALM_ASSERT_EX(false, e.what());
×
3058
            static_cast<void>(e); // e is unused without assertions on
×
3059
        }
×
3060
        exit(0);
×
3061
    }
×
3062

3063
    if (process->is_parent()) {
2✔
3064
        process->wait_for_child_to_finish();
2✔
3065
    }
2✔
3066

3067
    // intialization complete. Start writers:
3068
    for (int i = 0; i < write_process_count; ++i) {
16✔
3069
        writers.push_back(
14✔
3070
            test_util::spawn_process(test_context.test_details.test_name, util::format("writer[%1]", i)));
14✔
3071
        if (writers.back()->is_child()) {
14✔
3072
            {
×
3073
                // util::format(std::cout, "Writer[%1](%2) starting.\n", test_util::get_pid(), i);
3074
                std::unique_ptr<Replication> hist(make_in_realm_history());
×
3075
                DBRef sg = DB::create(*hist, path, DBOptions(key));
×
3076
                multiple_trackers_writer_thread(sg);
×
3077
                // util::format(std::cout, "Writer[%1](%2) done.\n", test_util::get_pid(), i);
3078
            } // clean up sg before exit
×
3079
            exit(0);
×
3080
        }
×
3081
    }
14✔
3082
    std::this_thread::yield();
2✔
3083
    // then start readers:
3084
    for (int i = 0; i < read_process_count; ++i) {
8✔
3085
        readers.push_back(
6✔
3086
            test_util::spawn_process(test_context.test_details.test_name, util::format("reader[%1]", i)));
6✔
3087
        if (readers[i]->is_child()) {
6✔
3088
            {
×
3089
                // util::format(std::cout, "Reader[%1](%2) starting.\n", test_util::get_pid(), i);
3090
                std::unique_ptr<Replication> hist(make_in_realm_history());
×
3091
                DBRef sg = DB::create(*hist, path, DBOptions(key));
×
3092
                multiple_trackers_reader_thread(test_context, sg);
×
3093
                // util::format(std::cout, "Reader[%1] done.\n", i);
3094
            } // clean up sg before exit
×
3095
            exit(0);
×
3096
        }
×
3097
    }
6✔
3098

3099
    if (process->is_parent()) {
2✔
3100
        // Wait for all writer threads to complete
3101
        for (int i = 0; i < write_process_count; ++i) {
16✔
3102
            writers[i]->wait_for_child_to_finish();
14✔
3103
        }
14✔
3104

3105
        // Allow readers time to catch up
3106
        for (int k = 0; k < 100; ++k)
202✔
3107
            std::this_thread::yield();
200✔
3108

3109
        // signal to all readers to complete
3110
        {
2✔
3111
            std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3112
            DBRef sg = DB::create(*hist, path, DBOptions(key));
2✔
3113
            WriteTransaction wt(sg);
2✔
3114
            TableRef tr = wt.get_table("C");
2✔
3115
            tr->create_object();
2✔
3116
            wt.commit();
2✔
3117
        }
2✔
3118

3119
        // Wait for all reader threads to complete
3120
        for (int i = 0; i < read_process_count; ++i) {
8✔
3121
            readers[i]->wait_for_child_to_finish();
6✔
3122
        }
6✔
3123
    }
2✔
3124
}
2✔
3125

3126
#endif // !REALM_ANDROID && !REALM_IOS
3127

3128
TEST(LangBindHelper_ImplicitTransactions_NoExtremeFileSpaceLeaks)
3129
{
2✔
3130
    SHARED_GROUP_TEST_PATH(path);
2✔
3131

3132
    for (int i = 0; i < 100; ++i) {
202✔
3133
        std::unique_ptr<Replication> hist(make_in_realm_history());
200✔
3134
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
200✔
3135
        auto trans = sg->start_read();
200✔
3136
        trans->promote_to_write();
200✔
3137
        trans->commit_and_continue_as_read();
200✔
3138
    }
200✔
3139

3140
// the miminum filesize (after a commit) is one or two pages, depending on the
3141
// page size.
3142
#if REALM_ENABLE_ENCRYPTION
2✔
3143
    if (crypt_key())
2✔
3144
        // Encrypted files are always at least a 4096 byte header plus payload
3145
        CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size() + 4096);
×
3146
    else
2✔
3147
        CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size());
2✔
3148
#else
3149
    CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size());
3150
#endif // REALM_ENABLE_ENCRYPTION
3151
}
2✔
3152

3153

3154
TEST(LangBindHelper_ImplicitTransactions_ContinuedUseOfTable)
3155
{
2✔
3156
    SHARED_GROUP_TEST_PATH(path);
2✔
3157

3158
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3159
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3160
    auto group = sg->start_read();
2✔
3161
    auto group_w = sg->start_write();
2✔
3162

3163
    TableRef table_w = group_w->add_table("table");
2✔
3164
    auto col = table_w->add_column(type_Int, "");
2✔
3165
    auto obj = table_w->create_object();
2✔
3166
    group_w->commit_and_continue_as_read();
2✔
3167
    group_w->verify();
2✔
3168

3169
    group->advance_read();
2✔
3170
    ConstTableRef table = group->get_table("table");
2✔
3171
    CHECK_EQUAL(1, table->size());
2✔
3172
    group->verify();
2✔
3173

3174
    group_w->promote_to_write();
2✔
3175
    obj.set<int64_t>(col, 1);
2✔
3176
    group_w->commit_and_continue_as_read();
2✔
3177
    group_w->verify();
2✔
3178

3179
    group->advance_read();
2✔
3180
    auto obj2 = group->import_copy_of(obj);
2✔
3181
    CHECK_EQUAL(1, obj2.get<int64_t>(col));
2✔
3182
    group->verify();
2✔
3183
}
2✔
3184

3185

3186
TEST(LangBindHelper_ImplicitTransactions_ContinuedUseOfLinkList)
3187
{
2✔
3188
    SHARED_GROUP_TEST_PATH(path);
2✔
3189

3190
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3191
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3192
    auto group = sg->start_read();
2✔
3193
    auto group_w = sg->start_write();
2✔
3194

3195
    TableRef table_w = group_w->add_table("table");
2✔
3196
    auto col = table_w->add_column_list(*table_w, "flubber");
2✔
3197
    auto obj = table_w->create_object();
2✔
3198
    auto link_list_w = obj.get_linklist(col);
2✔
3199
    link_list_w.add(obj.get_key());
2✔
3200
    // CHECK_EQUAL(1, link_list_w.size()); // avoid this, it hides missing updates
3201
    group_w->commit_and_continue_as_read();
2✔
3202
    group_w->verify();
2✔
3203

3204
    group->advance_read();
2✔
3205
    auto link_list = obj.get_linklist(col);
2✔
3206
    CHECK_EQUAL(1, link_list.size());
2✔
3207
    group->verify();
2✔
3208

3209
    group_w->promote_to_write();
2✔
3210
    // CHECK_EQUAL(1, link_list_w.size()); // avoid this, it hides missing updates
3211
    link_list_w.add(obj.get_key());
2✔
3212
    CHECK_EQUAL(2, link_list_w.size());
2✔
3213
    group_w->commit_and_continue_as_read();
2✔
3214
    group_w->verify();
2✔
3215

3216
    group->advance_read();
2✔
3217
    CHECK_EQUAL(2, link_list.size());
2✔
3218
    group->verify();
2✔
3219
}
2✔
3220

3221

3222
TEST(LangBindHelper_MemOnly)
3223
{
2✔
3224
    SHARED_GROUP_TEST_PATH(path);
2✔
3225
    ShortCircuitHistory hist;
2✔
3226
    DBRef sg = DB::create(hist, path, DBOptions(DBOptions::Durability::MemOnly));
2✔
3227

3228
    // Verify that the db is empty after populating and then re-opening a file
3229
    {
2✔
3230
        WriteTransaction wt(sg);
2✔
3231
        wt.add_table("table");
2✔
3232
        wt.commit();
2✔
3233
    }
2✔
3234
    {
2✔
3235
        TransactionRef rt = sg->start_read();
2✔
3236
        CHECK(!rt->is_empty());
2✔
3237
    }
2✔
3238
    sg->close();
2✔
3239
    sg = DB::create(hist, path, DBOptions(DBOptions::Durability::MemOnly));
2✔
3240

3241
    // Verify that basic replication functionality works
3242
    auto rt = sg->start_read();
2✔
3243
    {
2✔
3244
        WriteTransaction wt(sg);
2✔
3245
        wt.add_table("table");
2✔
3246
        wt.commit();
2✔
3247
    }
2✔
3248

3249
    CHECK(rt->is_empty());
2✔
3250
    rt->advance_read();
2✔
3251
    CHECK(!rt->is_empty());
2✔
3252
}
2✔
3253

3254
TEST(LangBindHelper_ImplicitTransactions_SearchIndex)
3255
{
2✔
3256
    SHARED_GROUP_TEST_PATH(path);
2✔
3257

3258
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3259
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3260
    auto rt = sg->start_read();
2✔
3261
    auto group_w = sg->start_read();
2✔
3262

3263
    // Add initial data
3264
    group_w->promote_to_write();
2✔
3265
    TableRef table_w = group_w->add_table("table");
2✔
3266
    auto c0 = table_w->add_column(type_Int, "int1");
2✔
3267
    auto c1 = table_w->add_column(type_String, "str");
2✔
3268
    auto c2 = table_w->add_column(type_Int, "int2");
2✔
3269
    auto ok = table_w->create_object(ObjKey{}, {{c1, "2"}, {c0, 1}, {c2, 3}}).get_key();
2✔
3270
    group_w->commit_and_continue_as_read();
2✔
3271
    group_w->verify();
2✔
3272

3273
    rt->advance_read();
2✔
3274
    ConstTableRef table = rt->get_table("table");
2✔
3275
    auto obj = table->get_object(ok);
2✔
3276
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
2✔
3277
    CHECK_EQUAL("2", obj.get<StringData>(c1));
2✔
3278
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
2✔
3279
    rt->verify();
2✔
3280

3281
    // Add search index and re-verify
3282
    group_w->promote_to_write();
2✔
3283
    table_w->add_search_index(c1);
2✔
3284
    group_w->commit_and_continue_as_read();
2✔
3285
    group_w->verify();
2✔
3286

3287
    rt->advance_read();
2✔
3288
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
2✔
3289
    CHECK_EQUAL("2", obj.get<StringData>(c1));
2✔
3290
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
2✔
3291
    CHECK(table->has_search_index(c1));
2✔
3292
    rt->verify();
2✔
3293

3294
    // Remove search index and re-verify
3295
    group_w->promote_to_write();
2✔
3296
    table_w->remove_search_index(c1);
2✔
3297
    group_w->commit_and_continue_as_read();
2✔
3298
    group_w->verify();
2✔
3299

3300
    rt->advance_read();
2✔
3301
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
2✔
3302
    CHECK_EQUAL("2", obj.get<StringData>(c1));
2✔
3303
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
2✔
3304
    CHECK(!table->has_search_index(c1));
2✔
3305
    rt->verify();
2✔
3306
}
2✔
3307

3308
TEST(LangBindHelper_HandoverQuery)
3309
{
2✔
3310
    SHARED_GROUP_TEST_PATH(path);
2✔
3311
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3312
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3313
    TransactionRef rt = sg->start_read();
2✔
3314
    {
2✔
3315
        WriteTransaction wt(sg);
2✔
3316
        Group& group_w = wt.get_group();
2✔
3317
        TableRef t = group_w.add_table("table2");
2✔
3318
        t->add_column(type_String, "first");
2✔
3319
        auto int_col = t->add_column(type_Int, "second");
2✔
3320
        for (int i = 0; i < 100; ++i) {
202✔
3321
            t->create_object().set(int_col, i);
200✔
3322
        }
200✔
3323
        wt.commit();
2✔
3324
    }
2✔
3325
    rt->advance_read();
2✔
3326
    auto table = rt->get_table("table2");
2✔
3327
    auto int_col = table->get_column_key("second");
2✔
3328
    Query query = table->column<Int>(int_col) < 50;
2✔
3329
    size_t count = query.count();
2✔
3330
    // CHECK(query.is_in_sync());
3331
    auto vtrans = rt->duplicate();
2✔
3332
    std::unique_ptr<Query> q2 = vtrans->import_copy_of(query, PayloadPolicy::Move);
2✔
3333
    CHECK_EQUAL(count, 50);
2✔
3334
    {
2✔
3335
        // Delete first column. This alters the index of 'second' column
3336
        WriteTransaction wt(sg);
2✔
3337
        Group& group_w = wt.get_group();
2✔
3338
        TableRef t = group_w.get_table("table2");
2✔
3339
        auto str_col = table->get_column_key("first");
2✔
3340
        t->remove_column(str_col);
2✔
3341
        wt.commit();
2✔
3342
    }
2✔
3343
    rt->advance_read();
2✔
3344
    count = query.count();
2✔
3345
    CHECK_EQUAL(count, 50);
2✔
3346
    count = q2->count();
2✔
3347
    CHECK_EQUAL(count, 50);
2✔
3348
}
2✔
3349

3350
TEST(LangBindHelper_SubqueryHandoverQueryCreatedFromDeletedLinkView)
3351
{
2✔
3352
    SHARED_GROUP_TEST_PATH(path);
2✔
3353
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3354
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3355
    TransactionRef reader;
2✔
3356
    auto writer = sg->start_write();
2✔
3357
    {
2✔
3358
        TableView tv1;
2✔
3359
        auto table = writer->add_table("table");
2✔
3360
        auto table2 = writer->add_table("table2");
2✔
3361
        table2->add_column(type_Int, "int");
2✔
3362
        auto key = table2->create_object().set_all(42).get_key();
2✔
3363

3364
        auto col = table->add_column_list(*table2, "first");
2✔
3365
        auto obj = table->create_object();
2✔
3366
        auto link_view = obj.get_linklist(col);
2✔
3367

3368
        link_view.add(key);
2✔
3369
        writer->commit_and_continue_as_read();
2✔
3370

3371
        Query qq = table2->where(link_view);
2✔
3372
        CHECK_EQUAL(qq.count(), 1);
2✔
3373
        writer->promote_to_write();
2✔
3374
        table->clear();
2✔
3375
        writer->commit_and_continue_as_read();
2✔
3376
        CHECK_EQUAL(link_view.size(), 0);
2✔
3377
        CHECK_EQUAL(qq.count(), 0);
2✔
3378

3379
        reader = writer->duplicate();
2✔
3380
#ifdef OLD_CORE_BEHAVIOR
3381
        // FIXME: Old core would allow the code below, but new core will throw.
3382
        //
3383
        // Why should a query still be valid after a change, when it would not be possible
3384
        // to reconstruct the query from new after said change?
3385
        //
3386
        // In this specific case, the query is constructed from a linkview on an object
3387
        // which is destroyed. After the object is destroyed, the linkview obviously
3388
        // cannot be constructed, and hence the query can also not be constructed.
3389
        auto lv2 = reader->import_copy_of(link_view);
3390
        auto rq = reader->import_copy_of(qq, PayloadPolicy::Copy);
3391
        writer->close();
3392
        auto tv = rq->find_all();
3393

3394
        CHECK(tv.is_in_sync());
3395
        CHECK(tv.is_attached());
3396
        CHECK_EQUAL(0, tv.size());
3397
#endif
3398
    }
2✔
3399
}
2✔
3400

3401

3402
TEST(LangBindHelper_SubqueryHandoverDependentViews)
3403
{
2✔
3404
    SHARED_GROUP_TEST_PATH(path);
2✔
3405
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3406
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3407
    std::unique_ptr<Query> qq2;
2✔
3408
    TransactionRef reader;
2✔
3409
    ColKey col1;
2✔
3410
    {
2✔
3411
        {
2✔
3412
            TableView tv1;
2✔
3413
            auto writer = sg->start_write();
2✔
3414
            TableRef table = writer->add_table("table2");
2✔
3415
            auto col0 = table->add_column(type_Int, "first");
2✔
3416
            col1 = table->add_column(type_Bool, "even");
2✔
3417
            for (int i = 0; i < 100; ++i) {
202✔
3418
                auto obj = table->create_object();
200✔
3419
                obj.set<int>(col0, i);
200✔
3420
                bool isEven = ((i % 2) == 0);
200✔
3421
                obj.set<bool>(col1, isEven);
200✔
3422
            }
200✔
3423
            writer->commit_and_continue_as_read();
2✔
3424
            tv1 = table->where().less_equal(col0, 50).find_all();
2✔
3425
            Query qq = tv1.get_parent()->where(&tv1);
2✔
3426
            reader = writer->duplicate();
2✔
3427
            qq2 = reader->import_copy_of(qq, PayloadPolicy::Copy);
2✔
3428
            CHECK(tv1.is_attached());
2✔
3429
            CHECK_EQUAL(51, tv1.size());
2✔
3430
        }
2✔
3431
        {
2✔
3432
            realm::TableView tv = qq2->equal(col1, true).find_all();
2✔
3433

3434
            CHECK(tv.is_in_sync());
2✔
3435
            CHECK(tv.is_attached());
2✔
3436
            CHECK_EQUAL(26, tv.size()); // BOOM! fail with 50
2✔
3437
        }
2✔
3438
    }
2✔
3439
}
2✔
3440

3441
TEST(LangBindHelper_HandoverPartialQuery)
3442
{
2✔
3443
    SHARED_GROUP_TEST_PATH(path);
2✔
3444
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3445
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3446
    std::unique_ptr<Query> qq2;
2✔
3447
    TransactionRef reader;
2✔
3448
    ColKey col0;
2✔
3449
    {
2✔
3450
        {
2✔
3451
            TableView tv1;
2✔
3452
            auto writer = sg->start_write();
2✔
3453
            TableRef table = writer->add_table("table2");
2✔
3454
            col0 = table->add_column(type_Int, "first");
2✔
3455
            auto col1 = table->add_column(type_Bool, "even");
2✔
3456
            for (int i = 0; i < 100; ++i) {
202✔
3457
                auto obj = table->create_object();
200✔
3458
                obj.set<int>(col0, i);
200✔
3459
                bool isEven = ((i % 2) == 0);
200✔
3460
                obj.set<bool>(col1, isEven);
200✔
3461
            }
200✔
3462
            writer->commit_and_continue_as_read();
2✔
3463
            tv1 = table->where().less_equal(col0, 50).find_all();
2✔
3464
            Query qq = tv1.get_parent()->where(&tv1);
2✔
3465
            reader = writer->duplicate();
2✔
3466
            qq2 = reader->import_copy_of(qq, PayloadPolicy::Copy);
2✔
3467
            CHECK(tv1.is_attached());
2✔
3468
            CHECK_EQUAL(51, tv1.size());
2✔
3469
        }
2✔
3470
        {
2✔
3471
            TableView tv = qq2->greater(col0, 48).find_all();
2✔
3472
            CHECK(tv.is_attached());
2✔
3473
            CHECK_EQUAL(2, tv.size());
2✔
3474
            auto obj = tv.get_object(0);
2✔
3475
            CHECK_EQUAL(49, obj.get<int64_t>(col0));
2✔
3476
            obj = tv.get_object(1);
2✔
3477
            CHECK_EQUAL(50, obj.get<int64_t>(col0));
2✔
3478
        }
2✔
3479
    }
2✔
3480
}
2✔
3481

3482

3483
// Verify that an in-sync TableView backed by a Query that is restricted to a TableView
3484
// remains in sync when handed-over using a mutable payload.
3485
TEST(LangBindHelper_HandoverNestedTableViews)
3486
{
2✔
3487
    SHARED_GROUP_TEST_PATH(path);
2✔
3488
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3489
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3490
    {
2✔
3491
        TransactionRef reader;
2✔
3492
        std::unique_ptr<TableView> tv;
2✔
3493
        {
2✔
3494
            auto writer = sg->start_write();
2✔
3495
            TableRef table = writer->add_table("table2");
2✔
3496
            auto col = table->add_column(type_Int, "first");
2✔
3497
            for (int i = 0; i < 100; ++i) {
202✔
3498
                table->create_object().set_all(i);
200✔
3499
            }
200✔
3500
            writer->commit_and_continue_as_read();
2✔
3501
            // Create a TableView tv2 that is backed by a Query that is restricted to rows from TableView tv1.
3502
            TableView tv1 = table->where().less_equal(col, 50).find_all();
2✔
3503
            TableView tv2 = tv1.get_parent()->where(&tv1).greater(col, 25).find_all();
2✔
3504
            CHECK(tv2.is_in_sync());
2✔
3505
            reader = writer->duplicate();
2✔
3506
            tv = reader->import_copy_of(tv2, PayloadPolicy::Move);
2✔
3507
        }
2✔
3508
        CHECK(tv->is_in_sync());
2✔
3509
        CHECK(tv->is_attached());
2✔
3510
        CHECK_EQUAL(25, tv->size());
2✔
3511
    }
2✔
3512
}
2✔
3513

3514

3515
TEST(LangBindHelper_HandoverAccessors)
3516
{
2✔
3517
    SHARED_GROUP_TEST_PATH(path);
2✔
3518
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3519
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3520
    TransactionRef reader;
2✔
3521
    ColKey col;
2✔
3522
    std::unique_ptr<TableView> tv2;
2✔
3523
    std::unique_ptr<TableView> tv3;
2✔
3524
    std::unique_ptr<TableView> tv4;
2✔
3525
    std::unique_ptr<TableView> tv5;
2✔
3526
    std::unique_ptr<TableView> tv6;
2✔
3527
    std::unique_ptr<TableView> tv7;
2✔
3528
    {
2✔
3529
        TableView tv;
2✔
3530
        auto writer = sg->start_write();
2✔
3531
        TableRef table = writer->add_table("table2");
2✔
3532
        col = table->add_column(type_Int, "first");
2✔
3533
        for (int i = 0; i < 100; ++i) {
202✔
3534
            table->create_object().set_all(i);
200✔
3535
        }
200✔
3536
        writer->commit_and_continue_as_read();
2✔
3537

3538
        tv = table->where().find_all();
2✔
3539
        CHECK(tv.is_attached());
2✔
3540
        CHECK_EQUAL(100, tv.size());
2✔
3541
        for (int i = 0; i < 100; ++i)
202✔
3542
            CHECK_EQUAL(i, tv.get_object(i).get<Int>(col));
200✔
3543

3544
        reader = writer->duplicate();
2✔
3545
        tv2 = reader->import_copy_of(tv, PayloadPolicy::Copy);
2✔
3546
        CHECK(tv.is_attached());
2✔
3547
        CHECK(tv.is_in_sync());
2✔
3548

3549
        tv3 = reader->import_copy_of(tv, PayloadPolicy::Stay);
2✔
3550
        CHECK(tv.is_attached());
2✔
3551
        CHECK(tv.is_in_sync());
2✔
3552

3553
        tv4 = reader->import_copy_of(tv, PayloadPolicy::Move);
2✔
3554
        CHECK(tv.is_attached());
2✔
3555
        CHECK(!tv.is_in_sync());
2✔
3556

3557
        // and again, but this time with the source out of sync:
3558
        tv5 = reader->import_copy_of(tv, PayloadPolicy::Copy);
2✔
3559
        CHECK(tv.is_attached());
2✔
3560
        CHECK(!tv.is_in_sync());
2✔
3561

3562
        tv6 = reader->import_copy_of(tv, PayloadPolicy::Stay);
2✔
3563
        CHECK(tv.is_attached());
2✔
3564
        CHECK(!tv.is_in_sync());
2✔
3565

3566
        tv7 = reader->import_copy_of(tv, PayloadPolicy::Move);
2✔
3567
        CHECK(tv.is_attached());
2✔
3568
        CHECK(!tv.is_in_sync());
2✔
3569

3570
        // and verify, that even though it was out of sync, we can bring it in sync again
3571
        tv.sync_if_needed();
2✔
3572
        CHECK(tv.is_in_sync());
2✔
3573

3574
        // Obj handover tested elsewhere
3575
    }
2✔
3576
    {
2✔
3577
        // now examining stuff handed over to other transaction
3578
        // with payload:
3579
        CHECK(tv2->is_attached());
2✔
3580
        CHECK(tv2->is_in_sync());
2✔
3581
        CHECK_EQUAL(100, tv2->size());
2✔
3582
        for (int i = 0; i < 100; ++i)
202✔
3583
            CHECK_EQUAL(i, tv2->get_object(i).get<Int>(col));
200✔
3584
        // importing one without payload:
3585
        CHECK(tv3->is_attached());
2✔
3586
        CHECK(!tv3->is_in_sync());
2✔
3587
        tv3->sync_if_needed();
2✔
3588
        CHECK_EQUAL(100, tv3->size());
2✔
3589
        for (int i = 0; i < 100; ++i)
202✔
3590
            CHECK_EQUAL(i, tv3->get_object(i).get<Int>(col));
200✔
3591

3592
        // one with payload:
3593
        CHECK(tv4->is_attached());
2✔
3594
        CHECK(tv4->is_in_sync());
2✔
3595
        CHECK_EQUAL(100, tv4->size());
2✔
3596
        for (int i = 0; i < 100; ++i)
202✔
3597
            CHECK_EQUAL(i, tv4->get_object(i).get<Int>(col));
200✔
3598

3599
        // verify that subsequent imports are all without payload:
3600
        CHECK(tv5->is_attached());
2✔
3601
        CHECK(!tv5->is_in_sync());
2✔
3602

3603
        CHECK(tv6->is_attached());
2✔
3604
        CHECK(!tv6->is_in_sync());
2✔
3605

3606
        CHECK(tv7->is_attached());
2✔
3607
        CHECK(!tv7->is_in_sync());
2✔
3608
    }
2✔
3609
}
2✔
3610

3611
TEST(LangBindHelper_TableViewAndTransactionBoundaries)
3612
{
2✔
3613
    SHARED_GROUP_TEST_PATH(path);
2✔
3614
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3615
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3616
    ColKey col;
2✔
3617
    {
2✔
3618
        WriteTransaction wt(sg);
2✔
3619
        auto table = wt.add_table("myTable");
2✔
3620
        col = table->add_column(type_Int, "myColumn");
2✔
3621
        table->create_object().set_all(42);
2✔
3622
        wt.commit();
2✔
3623
    }
2✔
3624
    auto rt = sg->start_read();
2✔
3625
    auto tv = rt->get_table("myTable")->where().greater(col, 40).find_all();
2✔
3626
    CHECK(tv.is_in_sync());
2✔
3627
    {
2✔
3628
        WriteTransaction wt(sg);
2✔
3629
        wt.commit();
2✔
3630
    }
2✔
3631
    rt->advance_read();
2✔
3632
    CHECK(tv.is_in_sync());
2✔
3633
    {
2✔
3634
        WriteTransaction wt(sg);
2✔
3635
        wt.commit();
2✔
3636
    }
2✔
3637
    rt->promote_to_write();
2✔
3638
    CHECK(tv.is_in_sync());
2✔
3639
    rt->commit_and_continue_as_read();
2✔
3640
    CHECK(tv.is_in_sync());
2✔
3641
    {
2✔
3642
        WriteTransaction wt(sg);
2✔
3643
        auto table = wt.get_table("myTable");
2✔
3644
        table->begin()->set_all(41);
2✔
3645
        wt.commit();
2✔
3646
    }
2✔
3647
    rt->advance_read();
2✔
3648
    CHECK(!tv.is_in_sync());
2✔
3649
    tv.sync_if_needed();
2✔
3650
    CHECK(tv.is_in_sync());
2✔
3651
    rt->advance_read();
2✔
3652
    CHECK(tv.is_in_sync());
2✔
3653
}
2✔
3654

3655
namespace {
3656
// support threads for handover test. The setup is as follows:
3657
// thread A writes a stream of updates to the database,
3658
// thread B listens and continously does advance_read to see the updates.
3659
// thread B also has a table view, which it continuosly keeps in sync in response
3660
// to the updates. It then hands over the result to thread C.
3661
// thread C continuously recieves copies of the results obtained in thead B and
3662
// verifies them (by comparing with its own local, but identical query)
3663

3664
template <typename T>
3665
struct HandoverControl {
3666
    Mutex m_lock;
3667
    CondVar m_changed;
3668
    std::unique_ptr<T> m_handover;
3669
    bool m_has_feedback = false;
3670
    void put(std::unique_ptr<T> h)
3671
    {
3,077✔
3672
        LockGuard lg(m_lock);
3,077✔
3673
        // std::cout << "put " << h << std::endl;
3674
        while (m_handover != nullptr)
3,077✔
3675
            m_changed.wait(lg);
×
3676
        // std::cout << " -- put " << h << std::endl;
3677
        m_handover = std::move(h);
3,077✔
3678
        m_changed.notify_all();
3,077✔
3679
    }
3,077✔
3680
    void get(std::unique_ptr<T>& h)
3681
    {
3,077✔
3682
        LockGuard lg(m_lock);
3,077✔
3683
        // std::cout << "get " << std::endl;
3684
        while (m_handover == nullptr)
3,810✔
3685
            m_changed.wait(lg);
733✔
3686
        // std::cout << " -- get " << m_handover << std::endl;
3687
        h = std::move(m_handover);
3,077✔
3688
        m_handover = nullptr;
3,077✔
3689
        m_changed.notify_all();
3,077✔
3690
    }
3,077✔
3691
    bool try_get(std::unique_ptr<T>& h)
3692
    {
3693
        LockGuard lg(m_lock);
3694
        if (m_handover == nullptr)
3695
            return false;
3696
        h = std::move(m_handover);
3697
        m_handover = nullptr;
3698
        m_changed.notify_all();
3699
        return true;
3700
    }
3701
    void signal_feedback()
3702
    {
3,077✔
3703
        LockGuard lg(m_lock);
3,077✔
3704
        m_has_feedback = true;
3,077✔
3705
        m_changed.notify_all();
3,077✔
3706
    }
3,077✔
3707
    void wait_feedback()
3708
    {
3,077✔
3709
        LockGuard lg(m_lock);
3,077✔
3710
        while (!m_has_feedback)
6,275✔
3711
            m_changed.wait(lg);
3,198✔
3712
        m_has_feedback = false;
3,077✔
3713
    }
3,077✔
3714
    HandoverControl(const HandoverControl&) = delete;
3715
    HandoverControl() {}
2✔
3716
};
3717

3718
void handover_writer(DBRef db)
3719
{
2✔
3720
    //    std::unique_ptr<Replication> hist(make_in_realm_history());
3721
    //    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
3722
    auto g = db->start_read();
2✔
3723
    auto table = g->get_table("table");
2✔
3724
    Random random(random_int<unsigned long>());
2✔
3725
    for (int i = 1; i < 5000; ++i) {
10,000✔
3726
        g->promote_to_write();
9,998✔
3727
        // table holds random numbers >= 1, until the writing process
3728
        // finishes, after n new entry with value 0 is added to signal termination
3729
        table->create_object().set_all(1 + random.draw_int_mod(100));
9,998✔
3730
        g->commit_and_continue_as_read();
9,998✔
3731
        // improve chance of consumers running concurrently with
3732
        // new writes:
3733
        for (int n = 0; n < 10; ++n)
109,978✔
3734
            std::this_thread::yield();
99,980✔
3735
    }
9,998✔
3736
    g->promote_to_write();
2✔
3737
    table->create_object().set_all(0); // <---- signals other threads to stop
2✔
3738
    g->commit();
2✔
3739
}
2✔
3740

3741
struct Work {
3742
    TransactionRef tr;
3743
    std::unique_ptr<TableView> tv;
3744
};
3745

3746
void handover_querier(HandoverControl<Work>* control, TestContext& test_context, DBRef db)
3747
{
2✔
3748
    // We need to ensure that the initial version observed is *before* the final
3749
    // one written by the writer thread. We do this (simplisticly) by locking on
3750
    // to the initial version before even starting the writer.
3751
    auto g = db->start_read();
2✔
3752
    Thread writer;
2✔
3753
    writer.start([&] {
2✔
3754
        handover_writer(db);
2✔
3755
    });
2✔
3756
    TableRef table = g->get_table("table");
2✔
3757
    ColKeys cols = table->get_column_keys();
2✔
3758
    TableView tv = table->where().greater(cols[0], 50).find_all();
2✔
3759
    for (;;) {
1,862,058✔
3760
        // wait here for writer to change the database. Kind of wasteful, but wait_for_change()
3761
        // is not available on osx.
3762
        if (!db->has_changed(g)) {
1,862,058✔
3763
            std::this_thread::yield();
1,858,981✔
3764
            continue;
1,858,981✔
3765
        }
1,858,981✔
3766

3767
        g->advance_read();
3,077✔
3768
        CHECK(!tv.is_in_sync());
3,077✔
3769
        tv.sync_if_needed();
3,077✔
3770
        CHECK(tv.is_in_sync());
3,077✔
3771
        auto ref = g->duplicate();
3,077✔
3772
        std::unique_ptr<Work> h = std::make_unique<Work>();
3,077✔
3773
        h->tr = ref;
3,077✔
3774
        h->tv = ref->import_copy_of(tv, PayloadPolicy::Move);
3,077✔
3775
        control->put(std::move(h));
3,077✔
3776

3777
        // here we need to allow the reciever to get hold on the proper version before
3778
        // we go through the loop again and advance_read().
3779
        control->wait_feedback();
3,077✔
3780
        std::this_thread::yield();
3,077✔
3781

3782
        if (table->where().equal(cols[0], 0).count() >= 1)
3,077✔
3783
            break;
2✔
3784
    }
3,077✔
3785
    g->end_read();
2✔
3786
    writer.join();
2✔
3787
}
2✔
3788

3789
void handover_verifier(HandoverControl<Work>* control, TestContext& test_context)
3790
{
2✔
3791
    bool not_done = true;
2✔
3792
    while (not_done) {
3,079✔
3793
        std::unique_ptr<Work> work;
3,077✔
3794
        control->get(work);
3,077✔
3795

3796
        auto g = work->tr;
3,077✔
3797
        control->signal_feedback();
3,077✔
3798
        TableRef table = g->get_table("table");
3,077✔
3799
        ColKeys cols = table->get_column_keys();
3,077✔
3800
        TableView tv = table->where().greater(cols[0], 50).find_all();
3,077✔
3801
        CHECK(tv.is_in_sync());
3,077✔
3802
        std::unique_ptr<TableView> tv2 = std::move(work->tv);
3,077✔
3803
        CHECK(tv.is_in_sync());
3,077✔
3804
        CHECK(tv2->is_in_sync());
3,077✔
3805
        CHECK_EQUAL(tv.size(), tv2->size());
3,077✔
3806
        for (size_t k = 0; k < tv.size(); ++k) {
2,647,271✔
3807
            auto o = tv.get_object(k);
2,644,194✔
3808
            auto o2 = tv2->get_object(k);
2,644,194✔
3809
            CHECK_EQUAL(o.get<int64_t>(cols[0]), o2.get<int64_t>(cols[0]));
2,644,194✔
3810
        }
2,644,194✔
3811
        if (table->where().equal(cols[0], 0).count() >= 1)
3,077✔
3812
            not_done = false;
2✔
3813
        g->close();
3,077✔
3814
    }
3,077✔
3815
}
2✔
3816

3817
} // anonymous namespace
3818

3819
namespace {
3820

3821
void attacher(std::string path, ColKey col)
3822
{
20✔
3823
    // Creating a new DB in each attacher is on purpose, since we're
3824
    // testing races in the attachment process, and that only takes place
3825
    // during creation of the DB object.
3826
    std::unique_ptr<Replication> hist(make_in_realm_history());
20✔
3827
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
20✔
3828
    for (int i = 0; i < 100; ++i) {
2,020✔
3829
        auto g = sg->start_read();
2,000✔
3830
        g->verify();
2,000✔
3831
        auto table = g->get_table("table");
2,000✔
3832
        g->promote_to_write();
2,000✔
3833
        auto o = table->get_object(ObjKey(i));
2,000✔
3834
        auto o2 = table->get_object(ObjKey(i * 10));
2,000✔
3835
        o.set<int64_t>(col, 1 + o2.get<int64_t>(col));
2,000✔
3836
        g->commit_and_continue_as_read();
2,000✔
3837
        g->verify();
2,000✔
3838
        g->end_read();
2,000✔
3839
    }
2,000✔
3840
}
20✔
3841
} // anonymous namespace
3842

3843

3844
// Disable with TSAN because it needs to synchronize between multiple DBs, and TSAN isn't able to track
3845
// acquire/release across multiple mappings of the same underlying memory.
3846
TEST_IF(LangBindHelper_RacingAttachers, !running_with_tsan)
3847
{
2✔
3848
    const int num_attachers = 10;
2✔
3849
    SHARED_GROUP_TEST_PATH(path);
2✔
3850
    ColKey col;
2✔
3851
    {
2✔
3852
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3853
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3854
        auto g = sg->start_write();
2✔
3855
        auto table = g->add_table("table");
2✔
3856
        col = table->add_column(type_Int, "first");
2✔
3857
        for (int i = 0; i < 1000; ++i)
2,002✔
3858
            table->create_object(ObjKey(i));
2,000✔
3859
        g->commit();
2✔
3860
    }
2✔
3861
    Thread attachers[num_attachers];
2✔
3862
    for (int i = 0; i < num_attachers; ++i) {
22✔
3863
        attachers[i].start([&] {
20✔
3864
            attacher(path, col);
20✔
3865
        });
20✔
3866
    }
20✔
3867
    for (int i = 0; i < num_attachers; ++i) {
22✔
3868
        attachers[i].join();
20✔
3869
    }
20✔
3870
}
2✔
3871

3872
// This test takes a very long time when running with valgrind
3873
TEST_IF(LangBindHelper_HandoverBetweenThreads, !running_with_valgrind)
3874
{
2✔
3875
    SHARED_GROUP_TEST_PATH(path);
2✔
3876
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3877
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3878
    auto g = sg->start_write();
2✔
3879
    auto table = g->add_table("table");
2✔
3880
    table->add_column(type_Int, "first");
2✔
3881
    g->commit();
2✔
3882
    g = sg->start_read();
2✔
3883
    table = g->get_table("table");
2✔
3884
    CHECK(bool(table));
2✔
3885
    g->end_read();
2✔
3886

3887
    HandoverControl<Work> control;
2✔
3888
    Thread querier, verifier;
2✔
3889
    querier.start([&] {
2✔
3890
        handover_querier(&control, test_context, sg);
2✔
3891
    });
2✔
3892
    verifier.start([&] {
2✔
3893
        handover_verifier(&control, test_context);
2✔
3894
    });
2✔
3895
    querier.join();
2✔
3896
    verifier.join();
2✔
3897
}
2✔
3898

3899

3900
TEST(LangBindHelper_HandoverDependentViews)
3901
{
2✔
3902
    SHARED_GROUP_TEST_PATH(path);
2✔
3903
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3904
    DBRef db = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3905
    TransactionRef tr;
2✔
3906
    std::unique_ptr<TableView> tv_ov;
2✔
3907
    ColKey col;
2✔
3908
    {
2✔
3909
        // Untyped interface
3910
        {
2✔
3911
            TableView tv1;
2✔
3912
            TableView tv2;
2✔
3913
            auto group_w = db->start_write();
2✔
3914
            TableRef table = group_w->add_table("table2");
2✔
3915
            col = table->add_column(type_Int, "first");
2✔
3916
            for (int i = 0; i < 100; ++i) {
202✔
3917
                table->create_object().set_all(i);
200✔
3918
            }
200✔
3919
            group_w->commit_and_continue_as_read();
2✔
3920
            tv1 = table->where().find_all();
2✔
3921
            tv2 = table->where(&tv1).find_all();
2✔
3922
            CHECK(tv1.is_attached());
2✔
3923
            CHECK(tv2.is_attached());
2✔
3924
            CHECK_EQUAL(100, tv1.size());
2✔
3925
            for (int i = 0; i < 100; ++i) {
202✔
3926
                auto o = tv1.get_object(i);
200✔
3927
                CHECK_EQUAL(i, o.get<int64_t>(col));
200✔
3928
            }
200✔
3929
            CHECK_EQUAL(100, tv2.size());
2✔
3930
            for (int i = 0; i < 100; ++i) {
202✔
3931
                auto o = tv2.get_object(i);
200✔
3932
                CHECK_EQUAL(i, o.get<int64_t>(col));
200✔
3933
            }
200✔
3934
            tr = group_w->duplicate();
2✔
3935
            tv_ov = tr->import_copy_of(tv2, PayloadPolicy::Copy);
2✔
3936
            CHECK(tv1.is_attached());
2✔
3937
            CHECK(tv2.is_attached());
2✔
3938
        }
2✔
3939
        {
2✔
3940
            CHECK(tv_ov->is_in_sync());
2✔
3941
            // CHECK(tv1.is_attached());
3942
            CHECK(tv_ov->is_attached());
2✔
3943
            CHECK_EQUAL(100, tv_ov->size());
2✔
3944
            for (int i = 0; i < 100; ++i) {
202✔
3945
                auto o = tv_ov->get_object(i);
200✔
3946
                CHECK_EQUAL(i, o.get<int64_t>(col));
200✔
3947
            }
200✔
3948
        }
2✔
3949
    }
2✔
3950
}
2✔
3951

3952

3953
TEST(LangBindHelper_HandoverTableViewWithLnkLst)
3954
{
2✔
3955
    // First iteration hands-over a normal valid attached LnkLst. Second
3956
    // iteration hands-over a detached LnkLst.
3957
    for (int detached = 0; detached < 2; detached++) {
6✔
3958
        SHARED_GROUP_TEST_PATH(path);
4✔
3959
        std::unique_ptr<Replication> hist(make_in_realm_history());
4✔
3960
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4✔
3961
        ColKey col_link2, col0;
4✔
3962
        ObjKey ok0, ok1, ok2;
4✔
3963
        TransactionRef tr;
4✔
3964
        std::unique_ptr<TableView> tv2;
4✔
3965
        std::unique_ptr<Query> q2;
4✔
3966
        {
4✔
3967
            TableView tv;
4✔
3968
            auto group_w = sg->start_write();
4✔
3969

3970
            TableRef table1 = group_w->add_table("table1");
4✔
3971
            TableRef table2 = group_w->add_table("table2");
4✔
3972

3973
            // add some more columns to table1 and table2
3974
            col0 = table1->add_column(type_Int, "col1");
4✔
3975
            table1->add_column(type_String, "str1");
4✔
3976

3977
            // add some rows
3978
            ok0 = table1->create_object().set_all(300, "delta").get_key();
4✔
3979
            ok1 = table1->create_object().set_all(100, "alfa").get_key();
4✔
3980
            ok2 = table1->create_object().set_all(200, "beta").get_key();
4✔
3981

3982
            col_link2 = table2->add_column_list(*table1, "linklist");
4✔
3983

3984
            auto o = table2->create_object();
4✔
3985
            auto lvr = o.get_linklist(col_link2);
4✔
3986
            lvr.clear();
4✔
3987
            lvr.add(ok0);
4✔
3988
            lvr.add(ok1);
4✔
3989
            lvr.add(ok2);
4✔
3990

3991
            // Return all rows of table1 (the linked-to-table) that match the criteria and is in the LinkList
3992

3993
            // q.m_table = table1
3994
            // q.m_view = lvr
3995
            Query q = table1->where(lvr).and_query(table1->column<Int>(col0) > 100);
4✔
3996

3997
            // Remove the LinkList that the query depends on, to see if a detached
3998
            // LinkList can be handed over correctly
3999
            if (detached == 1)
4✔
4000
                table2->remove_object(o.get_key());
2✔
4001

4002
            tv = q.find_all(); // tv = { 0, 2 } (only first iteration)
4✔
4003
            CHECK(tv.is_in_sync());
4✔
4004
            group_w->commit_and_continue_as_read();
4✔
4005
            tr = group_w->duplicate();
4✔
4006
            CHECK(tv.is_in_sync());
4✔
4007
            tv2 = tr->import_copy_of(tv, PayloadPolicy::Copy);
4✔
4008
            q2 = tr->import_copy_of(q, PayloadPolicy::Copy);
4✔
4009
            auto tv3a = q.find_all();
4✔
4010
            auto tv3b = q2->find_all();
4✔
4011
        }
4✔
4012
        {
4✔
4013
            auto tv3 = q2->find_all();
4✔
4014
            CHECK(tv2->is_in_sync());
4✔
4015
            if (detached == 0) {
4✔
4016
                CHECK_EQUAL(2, tv2->size());
2✔
4017
                CHECK_EQUAL(ok0, tv2->get_key(0));
2✔
4018
                CHECK_EQUAL(ok2, tv2->get_key(1));
2✔
4019
                CHECK_EQUAL(2, tv3.size());
2✔
4020
                CHECK_EQUAL(ok0, tv3.get_key(0));
2✔
4021
                CHECK_EQUAL(ok2, tv3.get_key(1));
2✔
4022
            }
2✔
4023
            else {
2✔
4024
                CHECK_EQUAL(0, tv2->size());
2✔
4025
                CHECK_EQUAL(0, tv3.size());
2✔
4026
            }
2✔
4027
            tr->close();
4✔
4028
        }
4✔
4029
    }
4✔
4030
}
2✔
4031

4032

4033
TEST(LangBindHelper_HandoverTableViewWithQueryOnLink)
4034
{
2✔
4035
    for (int detached = 0; detached < 2; detached++) {
6✔
4036
        SHARED_GROUP_TEST_PATH(path);
4✔
4037
        std::unique_ptr<Replication> hist(make_in_realm_history());
4✔
4038
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4✔
4039
        TransactionRef tr;
4✔
4040
        ObjKey target;
4✔
4041
        std::unique_ptr<TableView> tv2;
4✔
4042
        std::unique_ptr<Query> q2;
4✔
4043
        {
4✔
4044
            auto group_w = sg->start_write();
4✔
4045

4046
            TableRef table1 = group_w->add_table("table1");
4✔
4047
            TableRef table2 = group_w->add_table("table2");
4✔
4048
            table1->add_column(type_Int, "col1");
4✔
4049
            auto col_link = table2->add_column(*table1, "link");
4✔
4050

4051
            target = table1->create_object().set_all(300).get_key();
4✔
4052
            auto o = table2->create_object().set_all(target);
4✔
4053
            Query q = table2->where().and_query(table2->column<Link>(col_link) == table1->get_object(target));
4✔
4054

4055
            // Remove the object that the query depends on, to see if a detached
4056
            // object can be handed over correctly
4057
            if (detached == 1)
4✔
4058
                table2->remove_object(o.get_key());
2✔
4059

4060
            auto tv = q.find_all();
4✔
4061
            CHECK(tv.is_in_sync());
4✔
4062
            group_w->commit_and_continue_as_read();
4✔
4063
            tr = group_w->duplicate();
4✔
4064
            CHECK(tv.is_in_sync());
4✔
4065
            tv2 = tr->import_copy_of(tv, PayloadPolicy::Copy);
4✔
4066
            q2 = tr->import_copy_of(q, PayloadPolicy::Copy);
4✔
4067
        }
4✔
4068
        {
4✔
4069
            auto tv3 = q2->find_all();
4✔
4070
            CHECK(tv2->is_in_sync());
4✔
4071
            if (detached == 0) {
4✔
4072
                CHECK_EQUAL(1, tv2->size());
2✔
4073
                CHECK_EQUAL(target, tv2->get_key(0));
2✔
4074
                CHECK_EQUAL(1, tv3.size());
2✔
4075
                CHECK_EQUAL(target, tv3.get_key(0));
2✔
4076
            }
2✔
4077
            else {
2✔
4078
                CHECK_EQUAL(0, tv2->size());
2✔
4079
                CHECK_EQUAL(0, tv3.size());
2✔
4080
            }
2✔
4081
            tr->close();
4✔
4082
        }
4✔
4083
    }
4✔
4084
}
2✔
4085

4086

4087
#ifdef LEGACY_TESTS // (not useful as std unittest)
4088
namespace {
4089

4090
void do_write_work(std::string path, size_t id, size_t num_rows)
4091
{
4092
    const size_t num_iterations = 5000000; // this makes it run for a loooong time
4093
    const size_t payload_length_small = 10;
4094
    const size_t payload_length_large = 5000;   // > 4096 == page_size
4095
    Random random(random_int<unsigned long>()); // Seed from slow global generator
4096
    const char* key = crypt_key(true);
4097
    for (size_t rep = 0; rep < num_iterations; ++rep) {
4098
        std::unique_ptr<Replication> hist(make_in_realm_history());
4099
        DBRef sg = DB::create(*hist, path, DBOptions(key));
4100

4101
        TransactionRef rt = sg->start_read() LangBindHelper::promote_to_write(sg);
4102
        Group& group = const_cast<Group&>(rt.get_group());
4103
        TableRef t = rt->get_table(0);
4104

4105
        for (size_t i = 0; i < num_rows; ++i) {
4106
            const size_t payload_length = i % 10 == 0 ? payload_length_large : payload_length_small;
4107
            const char payload_char = 'a' + static_cast<char>((id + rep + i) % 26);
4108
            std::string std_payload(payload_length, payload_char);
4109
            StringData payload(std_payload);
4110

4111
            t->set_int(0, i, payload.size());
4112
            t->set_string(1, i, StringData(std_payload.c_str(), 1));
4113
            t->set_string(2, i, payload);
4114
        }
4115
        LangBindHelper::commit_and_continue_as_read(sg);
4116
    }
4117
}
4118

4119
void do_read_verify(std::string path)
4120
{
4121
    Random random(random_int<unsigned long>()); // Seed from slow global generator
4122
    const char* key = crypt_key(true);
4123
    while (true) {
4124
        std::unique_ptr<Replication> hist(make_in_realm_history());
4125
        DBRef sg = DB::create(*hist, path, DBOptions(key));
4126
        TransactionRef rt =
4127
            sg->start_read() if (rt.get_version() <= 2) continue; // let the writers make some initial data
4128
        Group& group = const_cast<Group&>(rt.get_group());
4129
        ConstTableRef t = rt->get_table(0);
4130
        size_t num_rows = t->size();
4131
        for (size_t r = 0; r < num_rows; ++r) {
4132
            int64_t num_chars = t->get_int(0, r);
4133
            StringData c = t->get_string(1, r);
4134
            if (c == "stop reading") {
4135
                return;
4136
            }
4137
            else {
4138
                REALM_ASSERT_EX(c.size() == 1, c.size());
4139
            }
4140
            REALM_ASSERT_EX(t->get_name() == StringData("class_Table_Emulation_Name"), t->get_name().data());
4141
            REALM_ASSERT_EX(t->get_column_name(0) == StringData("count"), t->get_column_name(0).data());
4142
            REALM_ASSERT_EX(t->get_column_name(1) == StringData("char"), t->get_column_name(1).data());
4143
            REALM_ASSERT_EX(t->get_column_name(2) == StringData("payload"), t->get_column_name(2).data());
4144
            std::string std_validator(static_cast<unsigned int>(num_chars), c[0]);
4145
            StringData validator(std_validator);
4146
            StringData s = t->get_string(2, r);
4147
            REALM_ASSERT_EX(s.size() == validator.size(), r, s.size(), validator.size());
4148
            for (size_t i = 0; i < s.size(); ++i) {
4149
                REALM_ASSERT_EX(s[i] == validator[i], r, i, s[i], validator[i]);
4150
            }
4151
            REALM_ASSERT_EX(s == validator, r, s.size(), validator.size());
4152
        }
4153
    }
4154
}
4155

4156
} // end anonymous namespace
4157

4158

4159
// The following test is long running to try to catch race conditions
4160
// in with many reader writer threads on an encrypted realm and it is
4161
// not suited to automated testing.
4162
TEST_IF(Thread_AsynchronousIODataConsistency, false)
4163
{
4164
    SHARED_GROUP_TEST_PATH(path);
4165
    const int num_writer_threads = 2;
4166
    const int num_reader_threads = 2;
4167
    const int num_rows = 200; // 2 + REALM_MAX_BPNODE_SIZE;
4168
    const char* key = crypt_key(true);
4169
    std::unique_ptr<Replication> hist(make_in_realm_history());
4170
    DBRef sg = DB::create(*hist, path, DBOptions(key));
4171
    {
4172
        WriteTransaction wt(sg);
4173
        Group& group = wt.get_group();
4174
        TableRef t = rt->add_table("class_Table_Emulation_Name");
4175
        // add a column for each thread to write to
4176
        t->add_column(type_Int, "count", true);
4177
        t->add_column(type_String, "char", true);
4178
        t->add_column(type_String, "payload", true);
4179
        t->add_empty_row(num_rows);
4180
        wt.commit();
4181
    }
4182

4183
    Thread writer_threads[num_writer_threads];
4184
    for (int i = 0; i < num_writer_threads; ++i) {
4185
        writer_threads[i].start(std::bind(do_write_work, std::string(path), i, num_rows));
4186
    }
4187
    Thread reader_threads[num_reader_threads];
4188
    for (int i = 0; i < num_reader_threads; ++i) {
4189
        reader_threads[i].start(std::bind(do_read_verify, std::string(path)));
4190
    }
4191
    for (int i = 0; i < num_writer_threads; ++i) {
4192
        writer_threads[i].join();
4193
    }
4194

4195
    {
4196
        WriteTransaction wt(sg);
4197
        Group& group = wt.get_group();
4198
        TableRef t = rt->get_table("class_Table_Emulation_Name");
4199
        t->set_string(1, 0, "stop reading");
4200
        wt.commit();
4201
    }
4202

4203
    for (int i = 0; i < num_reader_threads; ++i) {
4204
        reader_threads[i].join();
4205
    }
4206
}
4207
#endif
4208

4209

4210
TEST(LangBindHelper_HandoverTableRef)
4211
{
2✔
4212
    SHARED_GROUP_TEST_PATH(path);
2✔
4213
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4214
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4215
    TransactionRef reader;
2✔
4216
    TableRef table;
2✔
4217
    {
2✔
4218
        auto writer = sg->start_write();
2✔
4219
        TableRef table1 = writer->add_table("table1");
2✔
4220
        writer->commit_and_continue_as_read();
2✔
4221
        auto vid = writer->get_version_of_current_transaction();
2✔
4222
        reader = sg->start_read(vid);
2✔
4223
        table = reader->import_copy_of(table1);
2✔
4224
    }
2✔
4225
    CHECK(bool(table));
2✔
4226
    CHECK(table->size() == 0);
2✔
4227
}
2✔
4228

4229
TEST(LangBindHelper_HandoverLinkView)
4230
{
2✔
4231
    SHARED_GROUP_TEST_PATH(path);
2✔
4232
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4233
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4234
    TransactionRef reader;
2✔
4235
    ColKey col1;
2✔
4236

4237
    auto writer = sg->start_write();
2✔
4238

4239
    TableRef table1 = writer->add_table("table1");
2✔
4240
    TableRef table2 = writer->add_table("table2");
2✔
4241

4242
    // add some more columns to table1 and table2
4243
    col1 = table1->add_column(type_Int, "col1");
2✔
4244
    table1->add_column(type_String, "str1");
2✔
4245

4246
    // add some rows
4247
    auto to1 = table1->create_object().set_all(300, "delta");
2✔
4248
    auto to2 = table1->create_object().set_all(100, "alfa");
2✔
4249
    auto to3 = table1->create_object().set_all(200, "beta");
2✔
4250

4251
    ColKey col_link2 = table2->add_column_list(*table1, "linklist");
2✔
4252

4253
    auto o1 = table2->create_object();
2✔
4254
    table2->create_object();
2✔
4255
    LnkLstPtr lvr = o1.get_linklist_ptr(col_link2);
2✔
4256
    lvr->clear();
2✔
4257
    lvr->add(to1.get_key());
2✔
4258
    lvr->add(to2.get_key());
2✔
4259
    lvr->add(to3.get_key());
2✔
4260
    writer->commit_and_continue_as_read();
2✔
4261
    reader = writer->duplicate();
2✔
4262
    auto ll = reader->import_copy_of(lvr);
2✔
4263
    {
2✔
4264
        // validate inside reader transaction
4265
        // Return all rows of table1 (the linked-to-table) that match the criteria and is in the LinkList
4266

4267
        // q.m_table = table1
4268
        // q.m_view = lvr
4269
        TableRef table1b = reader->get_table("table1");
2✔
4270
        Query q = table1b->where(*ll).and_query(table1b->column<Int>(col1) > 100);
2✔
4271

4272
        // tv.m_table == table1
4273
        TableView tv = q.find_all(); // tv = { 0, 2 }
2✔
4274

4275

4276
        CHECK_EQUAL(2, tv.size());
2✔
4277
        CHECK_EQUAL(to1.get_key(), tv.get_key(0));
2✔
4278
        CHECK_EQUAL(to3.get_key(), tv.get_key(1));
2✔
4279
    }
2✔
4280
    {
2✔
4281
        // Change table1 and verify that the change does not propagate through the handed-over linkview
4282
        writer->promote_to_write();
2✔
4283
        to1.set<int64_t>(col1, 50);
2✔
4284
        writer->commit_and_continue_as_read();
2✔
4285
    }
2✔
4286
    {
2✔
4287
        TableRef table1b = reader->get_table("table1");
2✔
4288
        Query q = table1b->where(*ll).and_query(table1b->column<Int>(col1) > 100);
2✔
4289

4290
        // tv.m_table == table1
4291
        TableView tv = q.find_all(); // tv = { 0, 2 }
2✔
4292

4293

4294
        CHECK_EQUAL(2, tv.size());
2✔
4295
        CHECK_EQUAL(to1.get_key(), tv.get_key(0));
2✔
4296
        CHECK_EQUAL(to3.get_key(), tv.get_key(1));
2✔
4297
    }
2✔
4298
}
2✔
4299

4300
TEST(LangBindHelper_HandoverDistinctView)
4301
{
2✔
4302
    SHARED_GROUP_TEST_PATH(path);
2✔
4303
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4304
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4305
    TransactionRef reader;
2✔
4306
    std::unique_ptr<TableView> tv2;
2✔
4307
    Obj obj2b;
2✔
4308
    {
2✔
4309
        {
2✔
4310
            TableView tv1;
2✔
4311
            auto writer = sg->start_write();
2✔
4312
            TableRef table = writer->add_table("table2");
2✔
4313
            auto col = table->add_column(type_Int, "first");
2✔
4314
            auto obj1 = table->create_object().set_all(100);
2✔
4315
            table->create_object().set_all(100);
2✔
4316

4317
            writer->commit_and_continue_as_read();
2✔
4318
            tv1 = table->where().find_all();
2✔
4319
            tv1.distinct(col);
2✔
4320
            CHECK(tv1.size() == 1);
2✔
4321
            CHECK(tv1.get_key(0) == obj1.get_key());
2✔
4322
            CHECK(tv1.is_attached());
2✔
4323

4324
            reader = writer->duplicate();
2✔
4325
            tv2 = reader->import_copy_of(tv1, PayloadPolicy::Copy);
2✔
4326
            obj2b = reader->import_copy_of(obj1);
2✔
4327
            CHECK(tv1.is_attached());
2✔
4328
        }
2✔
4329
        {
2✔
4330
            // importing side: working in the context of "reader"
4331
            CHECK(tv2->is_in_sync());
2✔
4332
            CHECK(tv2->is_attached());
2✔
4333

4334
            CHECK_EQUAL(tv2->size(), 1);
2✔
4335
            CHECK_EQUAL(tv2->get_key(0), obj2b.get_key());
2✔
4336

4337
            // distinct property must remain through handover such that second row is kept being omitted
4338
            // after sync_if_needed()
4339
            tv2->sync_if_needed();
2✔
4340
            CHECK_EQUAL(tv2->size(), 1);
2✔
4341
            CHECK_EQUAL(tv2->get_key(0), obj2b.get_key());
2✔
4342
        }
2✔
4343
    }
2✔
4344
}
2✔
4345

4346

4347
TEST(LangBindHelper_HandoverWithReverseDependency)
4348
{
2✔
4349
    SHARED_GROUP_TEST_PATH(path);
2✔
4350
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4351
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4352
    auto trans = sg->start_read();
2✔
4353
    {
2✔
4354
        // Untyped interface
4355
        TableView tv1;
2✔
4356
        TableView tv2;
2✔
4357
        ColKey ck;
2✔
4358
        {
2✔
4359
            trans->promote_to_write();
2✔
4360
            TableRef table = trans->add_table("table2");
2✔
4361
            ck = table->add_column(type_Int, "first");
2✔
4362
            for (int i = 0; i < 100; ++i) {
202✔
4363
                table->create_object().set_all(i);
200✔
4364
            }
200✔
4365
            trans->commit_and_continue_as_read();
2✔
4366
            tv1 = table->where().find_all();
2✔
4367
            tv2 = table->where(&tv1).find_all();
2✔
4368
            CHECK(tv1.is_attached());
2✔
4369
            CHECK(tv2.is_attached());
2✔
4370
            CHECK_EQUAL(100, tv1.size());
2✔
4371
            for (int i = 0; i < 100; ++i)
202✔
4372
                CHECK_EQUAL(i, tv1.get_object(i).get<int64_t>(ck));
200✔
4373
            CHECK_EQUAL(100, tv2.size());
2✔
4374
            for (int i = 0; i < 100; ++i)
202✔
4375
                CHECK_EQUAL(i, tv1.get_object(i).get<int64_t>(ck));
200✔
4376
            auto dummy_trans = trans->duplicate();
2✔
4377
            auto dummy_tv = dummy_trans->import_copy_of(tv1, PayloadPolicy::Copy);
2✔
4378
            CHECK(tv1.is_attached());
2✔
4379
            CHECK(tv2.is_attached());
2✔
4380
        }
2✔
4381
    }
2✔
4382
}
2✔
4383

4384
TEST(LangBindHelper_HandoverTableViewFromBacklink)
4385
{
2✔
4386
    SHARED_GROUP_TEST_PATH(path);
2✔
4387
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4388
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4389
    auto group_w = sg->start_write();
2✔
4390

4391
    TableRef source = group_w->add_table("source");
2✔
4392
    source->add_column(type_Int, "int");
2✔
4393

4394
    TableRef links = group_w->add_table("links");
2✔
4395
    ColKey col = links->add_column(*source, "link");
2✔
4396

4397
    std::vector<ObjKey> dummies;
2✔
4398
    source->create_objects(100, dummies);
2✔
4399
    links->create_objects(100, dummies);
2✔
4400
    auto source_it = source->begin();
2✔
4401
    auto links_it = links->begin();
2✔
4402
    for (int i = 0; i < 100; ++i) {
202✔
4403
        auto obj = source_it->set_all(i);
200✔
4404
        links_it->set(col, obj.get_key());
200✔
4405
        ++source_it;
200✔
4406
        ++links_it;
200✔
4407
    }
200✔
4408
    group_w->commit_and_continue_as_read();
2✔
4409

4410
    for (int i = 0; i < 100; ++i) {
202✔
4411
        TableView tv = source->get_object(i).get_backlink_view(links, col);
200✔
4412
        CHECK(tv.is_attached());
200✔
4413
        CHECK_EQUAL(1, tv.size());
200✔
4414
        ObjKey o_key = source->get_object(i).get_key();
200✔
4415
        CHECK_EQUAL(o_key, tv.get_key(0));
200✔
4416
        auto group = group_w->duplicate();
200✔
4417
        auto tv2 = group->import_copy_of(tv, PayloadPolicy::Copy);
200✔
4418
        CHECK(tv.is_attached());
200✔
4419
        CHECK(tv2->is_attached());
200✔
4420
        CHECK_EQUAL(1, tv2->size());
200✔
4421
        CHECK_EQUAL(o_key, tv2->get_key(0));
200✔
4422
    }
200✔
4423
}
2✔
4424

4425
// Verify that handing over an out-of-sync TableView that represents backlinks
4426
// to a deleted row results in a TableView that can be brought back into sync.
4427
TEST(LangBindHelper_HandoverOutOfSyncTableViewFromBacklinksToDeletedRow)
4428
{
2✔
4429
    SHARED_GROUP_TEST_PATH(path);
2✔
4430
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4431
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4432
    auto group_w = sg->start_write();
2✔
4433

4434
    TableRef target = group_w->add_table("target");
2✔
4435
    target->add_column(type_Int, "int");
2✔
4436

4437
    TableRef links = group_w->add_table("links");
2✔
4438
    auto col = links->add_column(*target, "link");
2✔
4439

4440
    auto obj_t = target->create_object().set_all(0);
2✔
4441

4442
    links->create_object().set_all(obj_t.get_key());
2✔
4443

4444
    TableView tv = obj_t.get_backlink_view(links, col);
2✔
4445
    CHECK_EQUAL(true, tv.is_attached());
2✔
4446
    CHECK_EQUAL(true, tv.is_in_sync());
2✔
4447
    CHECK_EQUAL(false, tv.depends_on_deleted_object());
2✔
4448
    CHECK_EQUAL(1, tv.size());
2✔
4449

4450
    // Bring the view out of sync, and have it depend on a deleted row.
4451
    target->remove_object(obj_t.get_key());
2✔
4452
    CHECK_EQUAL(true, tv.is_attached());
2✔
4453
    CHECK_EQUAL(false, tv.is_in_sync());
2✔
4454
    CHECK_EQUAL(true, tv.depends_on_deleted_object());
2✔
4455
    CHECK_EQUAL(1, tv.size());
2✔
4456
    tv.sync_if_needed();
2✔
4457
    CHECK_EQUAL(0, tv.size());
2✔
4458
    group_w->commit_and_continue_as_read();
2✔
4459
    auto group = group_w->duplicate();
2✔
4460
    auto tv2 = group->import_copy_of(tv, PayloadPolicy::Copy);
2✔
4461
    CHECK_EQUAL(true, tv2->depends_on_deleted_object());
2✔
4462
    CHECK_EQUAL(0, tv2->size());
2✔
4463
}
2✔
4464

4465
// Test that we can handover a query involving links, and that after the
4466
// handover export, the handover is completely decoupled from later changes
4467
// done on accessors belonging to the exporting shared group
4468
TEST(LangBindHelper_HandoverWithLinkQueries)
4469
{
2✔
4470
    SHARED_GROUP_TEST_PATH(path);
2✔
4471
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4472
    DBRef db = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4473
    auto group_w = db->start_write();
2✔
4474
    // First setup data so that we can do a query on links
4475
    TableRef table1 = group_w->add_table("table1");
2✔
4476
    TableRef table2 = group_w->add_table("table2");
2✔
4477
    // add some more columns to table1 and table2
4478
    table1->add_column(type_Int, "col1");
2✔
4479
    table1->add_column(type_String, "str1");
2✔
4480

4481
    table2->add_column(type_Int, "col1");
2✔
4482
    auto col_str = table2->add_column(type_String, "str2");
2✔
4483

4484
    // add some rows
4485
    auto o10 = table1->create_object().set_all(100, "foo");
2✔
4486
    auto o11 = table1->create_object().set_all(200, "!");
2✔
4487
    table1->create_object().set_all(300, "bar");
2✔
4488
    table2->create_object().set_all(400, "hello");
2✔
4489
    auto o21 = table2->create_object().set_all(500, "world");
2✔
4490
    auto o22 = table2->create_object().set_all(600, "!");
2✔
4491

4492
    ColKey col_link2 = table1->add_column_list(*table2, "link");
2✔
4493

4494
    // set some links
4495
    auto links1 = o10.get_linklist(col_link2);
2✔
4496
    CHECK(links1.is_attached());
2✔
4497
    links1.add(o21.get_key());
2✔
4498

4499
    auto links2 = o11.get_linklist(col_link2);
2✔
4500
    CHECK(links2.is_attached());
2✔
4501
    links2.add(o21.get_key());
2✔
4502
    links2.add(o22.get_key());
2✔
4503
    group_w->commit_and_continue_as_read();
2✔
4504

4505
    // Do a query (which will have zero results) and export it twice.
4506
    // To test separation, we'll later modify state at the exporting side,
4507
    // and verify that the two different imports still get identical results
4508
    realm::Query query = table1->link(col_link2).column<String>(col_str) == "nabil";
2✔
4509
    realm::TableView tv4 = query.find_all();
2✔
4510

4511
    auto rec1 = group_w->duplicate();
2✔
4512
    auto q1 = rec1->import_copy_of(query, PayloadPolicy::Copy);
2✔
4513
    auto rec2 = group_w->duplicate();
2✔
4514
    auto q2 = rec2->import_copy_of(query, PayloadPolicy::Copy);
2✔
4515

4516
    {
2✔
4517
        realm::TableView tv = q1->find_all();
2✔
4518
        CHECK_EQUAL(0, tv.size());
2✔
4519
    }
2✔
4520

4521
    // On the exporting side, change the data such that the query will now have
4522
    // non-zero results if evaluated in that context.
4523
    group_w->promote_to_write();
2✔
4524
    auto o23 = table2->create_object().set_all(700, "nabil");
2✔
4525
    links1.add(o23.get_key());
2✔
4526
    group_w->commit_and_continue_as_read();
2✔
4527
    CHECK_EQUAL(1, query.count());
2✔
4528
    {
2✔
4529
        // Import query and evaluate in the old context. This should *not* be
4530
        // affected by the change done above on the exporting side.
4531
        realm::TableView tv2 = q2->find_all();
2✔
4532
        CHECK_EQUAL(0, tv2.size());
2✔
4533
    }
2✔
4534
}
2✔
4535

4536

4537
TEST(LangBindHelper_HandoverQueryLinksTo)
4538
{
2✔
4539
    SHARED_GROUP_TEST_PATH(path);
2✔
4540
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4541
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4542

4543
    TransactionRef reader;
2✔
4544
    std::unique_ptr<Query> query;
2✔
4545
    std::unique_ptr<Query> queryOr;
2✔
4546
    std::unique_ptr<Query> queryAnd;
2✔
4547
    std::unique_ptr<Query> queryNot;
2✔
4548
    std::unique_ptr<Query> queryAndAndOr;
2✔
4549
    std::unique_ptr<Query> queryWithExpression;
2✔
4550
    std::unique_ptr<Query> queryLinksToDetached;
2✔
4551
    {
2✔
4552
        auto group_w = sg->start_write();
2✔
4553
        TableRef source = group_w->add_table("source");
2✔
4554
        TableRef target = group_w->add_table("target");
2✔
4555

4556
        ColKey col_link = source->add_column(*target, "link");
2✔
4557
        ColKey col_name = target->add_column(type_String, "name");
2✔
4558

4559
        std::vector<ObjKey> keys;
2✔
4560
        target->create_objects(4, keys);
2✔
4561
        target->get_object(0).set(col_name, "A");
2✔
4562
        target->get_object(1).set(col_name, "B");
2✔
4563
        target->get_object(2).set(col_name, "C");
2✔
4564
        target->get_object(3).set(col_name, "D");
2✔
4565

4566
        source->create_object().set_all(keys[0]);
2✔
4567
        source->create_object().set_all(keys[1]);
2✔
4568
        source->create_object().set_all(keys[2]);
2✔
4569

4570
        Obj detached_row = target->get_object(3);
2✔
4571
        target->remove_object(detached_row.get_key());
2✔
4572

4573
        group_w->commit_and_continue_as_read();
2✔
4574

4575
        Query _query = source->column<Link>(col_link) == target->get_object(0);
2✔
4576
        Query _queryOr = source->column<Link>(col_link) == target->get_object(0) ||
2✔
4577
                         source->column<Link>(col_link) == target->get_object(1);
2✔
4578
        Query _queryAnd = source->column<Link>(col_link) == target->get_object(0) &&
2✔
4579
                          source->column<Link>(col_link) == target->get_object(0);
2✔
4580
        Query _queryNot = !(source->column<Link>(col_link) == target->get_object(0)) &&
2✔
4581
                          source->column<Link>(col_link) == target->get_object(1);
2✔
4582
        Query _queryAndAndOr = source->where().group().and_query(_queryOr).end_group().and_query(_queryAnd);
2✔
4583
        Query _queryWithExpression = source->column<Link>(col_link).is_not_null() && _query;
2✔
4584
        Query _queryLinksToDetached = source->where().links_to(col_link, detached_row.get_key());
2✔
4585

4586
        // handover:
4587
        reader = group_w->duplicate();
2✔
4588
        query = reader->import_copy_of(_query, PayloadPolicy::Copy);
2✔
4589
        queryOr = reader->import_copy_of(_queryOr, PayloadPolicy::Copy);
2✔
4590
        queryAnd = reader->import_copy_of(_queryAnd, PayloadPolicy::Copy);
2✔
4591
        queryNot = reader->import_copy_of(_queryNot, PayloadPolicy::Copy);
2✔
4592
        queryAndAndOr = reader->import_copy_of(_queryAndAndOr, PayloadPolicy::Copy);
2✔
4593
        queryWithExpression = reader->import_copy_of(_queryWithExpression, PayloadPolicy::Copy);
2✔
4594
        queryLinksToDetached = reader->import_copy_of(_queryLinksToDetached, PayloadPolicy::Copy);
2✔
4595

4596
        CHECK_EQUAL(1, _query.count());
2✔
4597
        CHECK_EQUAL(2, _queryOr.count());
2✔
4598
        CHECK_EQUAL(1, _queryAnd.count());
2✔
4599
        CHECK_EQUAL(1, _queryNot.count());
2✔
4600
        CHECK_EQUAL(1, _queryAndAndOr.count());
2✔
4601
        CHECK_EQUAL(1, _queryWithExpression.count());
2✔
4602
        CHECK_EQUAL(0, _queryLinksToDetached.count());
2✔
4603
    }
2✔
4604
    {
2✔
4605
        CHECK_EQUAL(1, query->count());
2✔
4606
        CHECK_EQUAL(2, queryOr->count());
2✔
4607
        CHECK_EQUAL(1, queryAnd->count());
2✔
4608
        CHECK_EQUAL(1, queryNot->count());
2✔
4609
        CHECK_EQUAL(1, queryAndAndOr->count());
2✔
4610
        CHECK_EQUAL(1, queryWithExpression->count());
2✔
4611
        CHECK_EQUAL(0, queryLinksToDetached->count());
2✔
4612

4613

4614
        // Remove the linked-to row.
4615
        {
2✔
4616
            auto group_w = sg->start_write();
2✔
4617
            TableRef target = group_w->get_table("target");
2✔
4618
            target->remove_object(target->begin()->get_key());
2✔
4619
            group_w->commit();
2✔
4620
        }
2✔
4621

4622
        // Verify that the queries against the read-only shared group gives the same results.
4623
        CHECK_EQUAL(1, query->count());
2✔
4624
        CHECK_EQUAL(2, queryOr->count());
2✔
4625
        CHECK_EQUAL(1, queryAnd->count());
2✔
4626
        CHECK_EQUAL(1, queryNot->count());
2✔
4627
        CHECK_EQUAL(1, queryAndAndOr->count());
2✔
4628
        CHECK_EQUAL(1, queryWithExpression->count());
2✔
4629
        CHECK_EQUAL(0, queryLinksToDetached->count());
2✔
4630
    }
2✔
4631
}
2✔
4632

4633

4634
TEST(LangBindHelper_HandoverQuerySubQuery)
4635
{
2✔
4636
    SHARED_GROUP_TEST_PATH(path);
2✔
4637
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4638
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4639

4640
    TransactionRef reader;
2✔
4641
    std::unique_ptr<Query> query;
2✔
4642
    {
2✔
4643
        auto group_w = sg->start_write();
2✔
4644

4645
        TableRef source = group_w->add_table("source");
2✔
4646
        TableRef target = group_w->add_table("target");
2✔
4647

4648
        ColKey col_link = source->add_column(*target, "link");
2✔
4649
        ColKey col_name = target->add_column(type_String, "name");
2✔
4650

4651
        std::vector<ObjKey> keys;
2✔
4652
        target->create_objects(3, keys);
2✔
4653
        target->get_object(keys[0]).set(col_name, "A");
2✔
4654
        target->get_object(keys[1]).set(col_name, "B");
2✔
4655
        target->get_object(keys[2]).set(col_name, "C");
2✔
4656

4657
        source->create_object().set_all(keys[0]);
2✔
4658
        source->create_object().set_all(keys[1]);
2✔
4659
        source->create_object().set_all(keys[2]);
2✔
4660

4661
        group_w->commit_and_continue_as_read();
2✔
4662

4663
        realm::Query query_2 = source->column<Link>(col_link, target->column<String>(col_name) == "C").count() == 1;
2✔
4664
        reader = group_w->duplicate();
2✔
4665
        query = reader->import_copy_of(query_2, PayloadPolicy::Copy);
2✔
4666
    }
2✔
4667

4668
    CHECK_EQUAL(1, query->count());
2✔
4669

4670
    // Remove the linked-to row.
4671
    {
2✔
4672
        auto group_w = sg->start_write();
2✔
4673

4674
        TableRef target = group_w->get_table("target");
2✔
4675
        target->clear();
2✔
4676
        group_w->commit_and_continue_as_read();
2✔
4677
    }
2✔
4678

4679
    // Verify that the queries against the read-only shared group gives the same results.
4680
    CHECK_EQUAL(1, query->count());
2✔
4681
}
2✔
4682

4683
TEST(LangBindHelper_VersionControl)
4684
{
2✔
4685
    Random random(random_int<unsigned long>());
2✔
4686

4687
    const int num_versions = 10;
2✔
4688
    const int num_random_tests = 100;
2✔
4689
    DB::VersionID versions[num_versions];
2✔
4690
    std::vector<TransactionRef> trs;
2✔
4691
    SHARED_GROUP_TEST_PATH(path);
2✔
4692
    {
2✔
4693
        // Create a new shared db
4694
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4695
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4696
        // first create 'num_version' versions
4697
        ColKey col;
2✔
4698
        auto reader = sg->start_read();
2✔
4699
        {
2✔
4700
            WriteTransaction wt(sg);
2✔
4701
            col = wt.get_or_add_table("test")->add_column(type_Int, "a");
2✔
4702
            wt.commit();
2✔
4703
        }
2✔
4704
        for (int i = 0; i < num_versions; ++i) {
22✔
4705
            {
20✔
4706
                WriteTransaction wt(sg);
20✔
4707
                auto t = wt.get_table("test");
20✔
4708
                t->create_object().set_all(i);
20✔
4709
                wt.commit();
20✔
4710
            }
20✔
4711
            {
20✔
4712
                auto rt = sg->start_read();
20✔
4713
                trs.push_back(rt->duplicate());
20✔
4714
                versions[i] = rt->get_version_of_current_transaction();
20✔
4715
            }
20✔
4716
        }
20✔
4717

4718
        // do steps of increasing size from the first version to the last,
4719
        // including a "step on the spot" (from version 0 to 0)
4720
        {
2✔
4721
            for (int k = 0; k < num_versions; ++k) {
22✔
4722
                // std::cerr << "Advancing from initial version to version " << k << std::endl;
4723
                auto g = sg->start_read(versions[0]);
20✔
4724
                auto t = g->get_table("test");
20✔
4725
                CHECK(versions[k] >= versions[0]);
20✔
4726
                g->verify();
20✔
4727
                g->advance_read(versions[k]);
20✔
4728
                g->verify();
20✔
4729
                auto o = *(t->begin() + k);
20✔
4730
                CHECK_EQUAL(k, o.get<int64_t>(col));
20✔
4731
            }
20✔
4732
        }
2✔
4733

4734
        // step through the versions backward:
4735
        for (int i = num_versions - 1; i >= 0; --i) {
22✔
4736
            // std::cerr << "Jumping directly to version " << i << std::endl;
4737

4738
            auto g = sg->start_read(versions[i]);
20✔
4739
            g->verify();
20✔
4740
            auto t = g->get_table("test");
20✔
4741
            auto o = *(t->begin() + i);
20✔
4742
            CHECK_EQUAL(i, o.get<int64_t>(col));
20✔
4743
        }
20✔
4744

4745
        // then advance through the versions going forward
4746
        {
2✔
4747
            auto g = sg->start_read(versions[0]);
2✔
4748
            g->verify();
2✔
4749
            auto t = g->get_table("test");
2✔
4750
            for (int k = 0; k < num_versions; ++k) {
22✔
4751
                // std::cerr << "Advancing to version " << k << std::endl;
4752
                CHECK(k == 0 || versions[k] >= versions[k - 1]);
20✔
4753

4754
                g->advance_read(versions[k]);
20✔
4755
                g->verify();
20✔
4756
                auto o = *(t->begin() + k);
20✔
4757
                CHECK_EQUAL(k, o.get<int64_t>(col));
20✔
4758
            }
20✔
4759
        }
2✔
4760
        // sync to a randomly selected version - use advance_read when going
4761
        // forward in time, but begin_read when going back in time
4762
        int old_version = 0;
2✔
4763
        auto g = sg->start_read(versions[old_version]);
2✔
4764
        auto t = g->get_table("test");
2✔
4765
        for (int k = num_random_tests; k; --k) {
202✔
4766
            int new_version = random.draw_int_mod(num_versions);
200✔
4767
            // std::cerr << "Random jump: version " << old_version << " -> " << new_version << std::endl;
4768
            if (new_version < old_version) {
200✔
4769
                CHECK(versions[new_version] < versions[old_version]);
86✔
4770
                g->end_read();
86✔
4771
                g = sg->start_read(versions[new_version]);
86✔
4772
                g->verify();
86✔
4773
                t = g->get_table("test");
86✔
4774
                auto o = *(t->begin() + new_version);
86✔
4775
                CHECK_EQUAL(new_version, o.get<int64_t>(col));
86✔
4776
            }
86✔
4777
            else {
114✔
4778
                CHECK(versions[new_version] >= versions[old_version]);
114✔
4779
                g->verify();
114✔
4780
                g->advance_read(versions[new_version]);
114✔
4781
                g->verify();
114✔
4782
                auto o = *(t->begin() + new_version);
114✔
4783
                CHECK_EQUAL(new_version, o.get<int64_t>(col));
114✔
4784
            }
114✔
4785
            old_version = new_version;
200✔
4786
        }
200✔
4787
        trs.clear();
2✔
4788
        g->end_read();
2✔
4789
        // release the first readlock and commit something to force a cleanup
4790
        // we need to commit twice, because cleanup is done before the actual
4791
        // commit, so during the first commit, the last of the previous versions
4792
        // will still be kept. To get rid of it, we must commit once more.
4793
        reader->end_read();
2✔
4794
        g = sg->start_write();
2✔
4795
        g->commit();
2✔
4796
        g = sg->start_write();
2✔
4797
        g->commit();
2✔
4798

4799
        // Validate that all the versions are now unreachable
4800
        for (int i = 0; i < num_versions; ++i)
22✔
4801
            CHECK_THROW(sg->start_read(versions[i]), DB::BadVersion);
20✔
4802
    }
2✔
4803
}
2✔
4804

4805
TEST(LangBindHelper_RollbackToInitialState1)
4806
{
2✔
4807
    SHARED_GROUP_TEST_PATH(path);
2✔
4808
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
4809
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
4810
    auto trans = sg_w->start_read();
2✔
4811
    trans->promote_to_write();
2✔
4812
    trans->rollback_and_continue_as_read();
2✔
4813
}
2✔
4814

4815

4816
TEST(LangBindHelper_RollbackToInitialState2)
4817
{
2✔
4818
    SHARED_GROUP_TEST_PATH(path);
2✔
4819
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
4820
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
4821
    auto trans = sg_w->start_write();
2✔
4822
    trans->rollback();
2✔
4823
}
2✔
4824

4825
// non-concurrent because we test the filesystem which may
4826
// be used by other tests at the same time otherwise
4827
NONCONCURRENT_TEST(LangBindHelper_Compact)
4828
{
2✔
4829
    SHARED_GROUP_TEST_PATH(path);
2✔
4830
    size_t N = 100;
2✔
4831
    std::string dir_path = File::parent_dir(path);
2✔
4832
    dir_path = dir_path.empty() ? "." : dir_path;
2✔
4833
    auto dir_has_tmp_compaction = [&dir_path]() -> size_t {
6✔
4834
        DirScanner dir(dir_path);
6✔
4835
        std::string name;
6✔
4836
        while (dir.next(name)) {
204✔
4837
            if (name.find("tmp_compaction_space") != std::string::npos) {
198✔
4838
                return true;
×
4839
            }
×
4840
        }
198✔
4841
        return false;
6✔
4842
    };
6✔
4843

4844
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4845
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4846
    {
2✔
4847
        WriteTransaction w(sg);
2✔
4848
        TableRef table = w.get_or_add_table("test");
2✔
4849
        table->add_column(type_Int, "int");
2✔
4850
        for (size_t i = 0; i < N; ++i) {
202✔
4851
            table->create_object().set_all(static_cast<signed>(i));
200✔
4852
        }
200✔
4853
        w.commit();
2✔
4854
    }
2✔
4855
    {
2✔
4856
        ReadTransaction r(sg);
2✔
4857
        ConstTableRef table = r.get_table("test");
2✔
4858
        CHECK_EQUAL(N, table->size());
2✔
4859
        CHECK(File::exists(dir_path));
2✔
4860
        CHECK(File::is_dir(dir_path));
2✔
4861
        CHECK(!dir_has_tmp_compaction());
2✔
4862
    }
2✔
4863
    {
2✔
4864
        CHECK_EQUAL(true, sg->compact());
2✔
4865
        CHECK(!dir_has_tmp_compaction());
2✔
4866
    }
2✔
4867
    {
2✔
4868
        ReadTransaction r(sg);
2✔
4869
        ConstTableRef table = r.get_table("test");
2✔
4870
        CHECK_EQUAL(N, table->size());
2✔
4871
    }
2✔
4872
    {
2✔
4873
        WriteTransaction w(sg);
2✔
4874
        TableRef table = w.get_or_add_table("test");
2✔
4875
        table->create_object().set_all(0);
2✔
4876
        w.commit();
2✔
4877
    }
2✔
4878
    {
2✔
4879
        CHECK_EQUAL(true, sg->compact());
2✔
4880
        CHECK(!dir_has_tmp_compaction());
2✔
4881
    }
2✔
4882
}
2✔
4883

4884
TEST(LangBindHelper_CompactLargeEncryptedFile)
4885
{
2✔
4886
    SHARED_GROUP_TEST_PATH(path);
2✔
4887

4888
    std::vector<char> data(realm::util::page_size());
2✔
4889
    const size_t N = 32;
2✔
4890

4891
    {
2✔
4892
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4893
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key(true)));
2✔
4894
        WriteTransaction wt(sg);
2✔
4895
        TableRef table = wt.get_or_add_table("test");
2✔
4896
        table->add_column(type_String, "string");
2✔
4897
        for (size_t i = 0; i < N; ++i) {
66✔
4898
            table->create_object().set_all(StringData(data.data(), data.size()));
64✔
4899
        }
64✔
4900
        wt.commit();
2✔
4901

4902
        CHECK_EQUAL(true, sg->compact());
2✔
4903

4904
        sg->close();
2✔
4905
    }
2✔
4906

4907
    {
2✔
4908
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4909
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key(true)));
2✔
4910
        ReadTransaction r(sg);
2✔
4911
        ConstTableRef table = r.get_table("test");
2✔
4912
        CHECK_EQUAL(N, table->size());
2✔
4913
    }
2✔
4914
}
2✔
4915

4916
TEST(LangBindHelper_CloseDBvsTransactions)
4917
{
2✔
4918
    SHARED_GROUP_TEST_PATH(path);
2✔
4919
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4920
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key(true)));
2✔
4921
    auto tr0 = sg->start_read();
2✔
4922
    auto tr1 = sg->start_write();
2✔
4923
    CHECK(tr1->add_table("possible"));
2✔
4924
    // write transactions must be closed (one way or the other) before DB::close
4925
    CHECK_THROW(sg->close(), LogicError);
2✔
4926
    tr1->rollback();
2✔
4927
    // closing the DB explicitly while there are open read transactions will fail
4928
    CHECK_THROW(sg->close(), LogicError);
2✔
4929
    // unless we explicitly ask for it to succeed()
4930
    sg->close(true);
2✔
4931
    CHECK(!sg->is_attached());
2✔
4932
    CHECK(!tr0->is_attached());
2✔
4933
    CHECK(!tr1->is_attached());
2✔
4934
    CHECK_THROW(sg->start_read(), LogicError);
2✔
4935
}
2✔
4936

4937
TEST(LangBindHelper_TableViewAggregateAfterAdvanceRead)
4938
{
2✔
4939
    SHARED_GROUP_TEST_PATH(path);
2✔
4940

4941
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
4942
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
4943
    ColKey col;
2✔
4944
    {
2✔
4945
        WriteTransaction w(sg_w);
2✔
4946
        TableRef table = w.add_table("test");
2✔
4947
        col = table->add_column(type_Double, "double");
2✔
4948
        table->create_object().set_all(1234.0);
2✔
4949
        table->create_object().set_all(-5678.0);
2✔
4950
        table->create_object().set_all(1000.0);
2✔
4951
        w.commit();
2✔
4952
    }
2✔
4953

4954
    auto reader = sg_w->start_read();
2✔
4955
    auto table_r = reader->get_table("test");
2✔
4956

4957
    // Create a table view with all refs detached.
4958
    TableView view = table_r->where().find_all();
2✔
4959
    {
2✔
4960
        WriteTransaction w(sg_w);
2✔
4961
        w.get_table("test")->clear();
2✔
4962
        w.commit();
2✔
4963
    }
2✔
4964
    reader->advance_read();
2✔
4965

4966
    // Verify that an aggregate on the view with detached refs gives the expected result.
4967
    CHECK_EQUAL(false, view.is_in_sync());
2✔
4968
    ObjKey res;
2✔
4969
    CHECK(view.min(col, &res)->is_null());
2✔
4970
    CHECK_EQUAL(ObjKey(), res);
2✔
4971

4972
    // Sync the view to discard the detached refs.
4973
    view.sync_if_needed();
2✔
4974

4975
    // Verify that an aggregate on the view still gives the expected result.
4976
    res = ObjKey();
2✔
4977
    CHECK(view.min(col, &res)->is_null());
2✔
4978
    CHECK_EQUAL(ObjKey(), res);
2✔
4979
}
2✔
4980

4981
// Tests handover of a Query. Especially it tests if next-gen-syntax nodes are deep copied correctly by
4982
// executing an imported query multiple times in parallel
4983
TEST_IF(LangBindHelper_HandoverFuzzyTest, TEST_DURATION > 0)
4984
{
×
4985
    SHARED_GROUP_TEST_PATH(path);
×
4986

4987
    const size_t threads = 5;
×
4988

4989
    size_t numberOfOwner = 100;
×
4990
    size_t numberOfDogsPerOwner = 20;
×
4991

4992
    std::atomic<bool> end_signal(false);
×
4993
    std::unique_ptr<Replication> hist(make_in_realm_history());
×
4994
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
×
4995

4996
    std::vector<TransactionRef> vids;
×
4997
    std::vector<std::unique_ptr<Query>> qs;
×
4998
    std::mutex vector_mutex;
×
4999

5000
    ColKey c0, c1, c2, c3;
×
5001
    {
×
5002
        auto rt = sg->start_write();
×
5003

5004
        TableRef owner = rt->add_table("Owner");
×
5005
        TableRef dog = rt->add_table("Dog");
×
5006

5007
        c0 = owner->add_column(type_String, "name");
×
5008
        c1 = owner->add_column_list(*dog, "link");
×
5009

5010
        c2 = dog->add_column(type_String, "name");
×
5011
        c3 = dog->add_column(*owner, "link");
×
5012

5013
        for (size_t i = 0; i < numberOfOwner; i++) {
×
5014

5015
            auto o = owner->create_object();
×
5016
            std::string owner_str(std::string("owner") + to_string(i));
×
5017
            o.set<StringData>(c0, owner_str);
×
5018

5019
            for (size_t j = 0; j < numberOfDogsPerOwner; j++) {
×
5020
                auto o_d = dog->create_object();
×
5021
                std::string dog_str(std::string("dog") + to_string(i * numberOfOwner + j));
×
5022
                o_d.set<StringData>(c2, dog_str);
×
5023
                o_d.set(c3, o.get_key());
×
5024
                auto ll = o.get_linklist(c1);
×
5025
                ll.add(o_d.get_key());
×
5026
            }
×
5027
        }
×
5028
        rt->verify();
×
5029
        {
×
5030
            realm::Query query = dog->link(c3).column<String>(c0) == "owner" + to_string(rand() % numberOfOwner);
×
5031
            query.find_all(); // <-- fails
×
5032
        }
×
5033
        rt->commit();
×
5034
    }
×
5035

5036
    auto async = [&]() {
×
5037
        // Async thread
5038
        //************************************************************************************************
5039
        while (!end_signal) {
×
5040
            millisleep(10);
×
5041

5042
            vector_mutex.lock();
×
5043
            if (qs.size() > 0) {
×
5044

5045
                auto t = vids[0];
×
5046
                vids.erase(vids.begin());
×
5047
                auto q = std::move(qs[0]);
×
5048
                qs.erase(qs.begin());
×
5049
                vector_mutex.unlock();
×
5050

5051
                realm::TableView tv = q->find_all();
×
5052
            }
×
5053
            else {
×
5054
                vector_mutex.unlock();
×
5055
            }
×
5056
        }
×
5057
        //************************************************************************************************
5058
    };
×
5059

5060
    auto rt = sg->start_read();
×
5061
    // Create and export query
5062
    TableRef dog = rt->get_table("Dog");
×
5063

5064
    realm::Query query = dog->link(c3).column<String>(c0) == "owner" + to_string(rand() % numberOfOwner);
×
5065
    query.find_all(); // <-- fails
×
5066

5067
    Thread slaves[threads];
×
5068
    for (int i = 0; i != threads; ++i) {
×
5069
        slaves[i].start([=] {
×
5070
            async();
×
5071
        });
×
5072
    }
×
5073

5074
    // Main thread
5075
    //************************************************************************************************
5076
    for (size_t iter = 0; iter < 20 + TEST_DURATION * TEST_DURATION * 500; iter++) {
×
5077
        vector_mutex.lock();
×
5078
        rt->promote_to_write();
×
5079
        rt->commit_and_continue_as_read();
×
5080
        if (qs.size() < 100) {
×
5081
            for (size_t t = 0; t < 5; t++) {
×
5082
                auto t2 = rt->duplicate();
×
5083
                qs.push_back(t2->import_copy_of(query, PayloadPolicy::Move));
×
5084
                vids.push_back(t2);
×
5085
            }
×
5086
        }
×
5087
        vector_mutex.unlock();
×
5088

5089
        millisleep(100);
×
5090
    }
×
5091
    //************************************************************************************************
5092

5093
    end_signal = true;
×
5094
    for (int i = 0; i != threads; ++i)
×
5095
        slaves[i].join();
×
5096
}
×
5097

5098

5099
// TableView::clear() was originally reported to be slow when table was indexed and had links, but performance
5100
// has now doubled. This test is just a short sanity test that clear() still works.
5101
TEST(LangBindHelper_TableViewClear)
5102
{
2✔
5103
    SHARED_GROUP_TEST_PATH(path);
2✔
5104

5105
    int64_t number_of_history = 1000;
2✔
5106
    int64_t number_of_line = 18;
2✔
5107

5108
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5109
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
5110
    TransactionRef tr;
2✔
5111
    ColKey col0, col1, col2, colA, colB;
2✔
5112
    // set up tables:
5113
    // history : ["id" (int), "parent" (int), "lines" (list(line))]
5114
    // line    : ["id" (int), "parent" (int)]
5115
    {
2✔
5116
        tr = sg->start_write();
2✔
5117

5118
        TableRef history = tr->add_table("history");
2✔
5119
        TableRef line = tr->add_table("line");
2✔
5120

5121
        col0 = history->add_column(type_Int, "id");
2✔
5122
        col1 = history->add_column(type_Int, "parent");
2✔
5123
        col2 = history->add_column_list(*line, "lines");
2✔
5124
        history->add_search_index(col1);
2✔
5125

5126
        colA = line->add_column(type_Int, "id");
2✔
5127
        colB = line->add_column(type_Int, "parent");
2✔
5128
        line->add_search_index(colB);
2✔
5129
        tr->commit_and_continue_as_read();
2✔
5130
    }
2✔
5131

5132
    {
2✔
5133
        tr->promote_to_write();
2✔
5134

5135
        TableRef history = tr->get_table("history");
2✔
5136
        TableRef line = tr->get_table("line");
2✔
5137

5138
        auto obj = history->create_object();
2✔
5139
        obj.set(col0, 1);
2✔
5140
        auto ll = obj.get_linklist(col2);
2✔
5141
        for (int64_t j = 0; j < number_of_line; ++j) {
38✔
5142
            Obj o = line->create_object().set_all(j, 0);
36✔
5143
            ll.add(o.get_key());
36✔
5144
        }
36✔
5145

5146
        for (int64_t i = 1; i < number_of_history; ++i) {
2,000✔
5147
            history->create_object().set_all(i, i + 1);
1,998✔
5148
            int64_t rj = i * number_of_line;
1,998✔
5149
            for (int64_t j = 1; j <= number_of_line; ++j) {
37,962✔
5150
                line->create_object().set_all(rj, j);
35,964✔
5151
                ++rj;
35,964✔
5152
            }
35,964✔
5153
        }
1,998✔
5154
        tr->commit_and_continue_as_read();
2✔
5155
        CHECK_EQUAL(number_of_history, history->size());
2✔
5156
        CHECK_EQUAL(number_of_history * number_of_line, line->size());
2✔
5157
    }
2✔
5158

5159
    // query and delete
5160
    {
2✔
5161
        tr->promote_to_write();
2✔
5162

5163
        TableRef line = tr->get_table("line");
2✔
5164

5165
        //    number_of_line = 2;
5166
        for (int64_t i = 1; i <= number_of_line; ++i) {
38✔
5167
            TableView tv = (line->column<Int>(colB) == i).find_all();
36✔
5168
            tv.clear();
36✔
5169
        }
36✔
5170
        tr->commit_and_continue_as_read();
2✔
5171
    }
2✔
5172

5173
    {
2✔
5174
        TableRef history = tr->get_table("history");
2✔
5175
        TableRef line = tr->get_table("line");
2✔
5176

5177
        CHECK_EQUAL(number_of_history, history->size());
2✔
5178
        CHECK_EQUAL(number_of_line, line->size());
2✔
5179
    }
2✔
5180
}
2✔
5181

5182

5183
TEST(LangBindHelper_SessionHistoryConsistency)
5184
{
2✔
5185
    // Check that we can reliably detect inconsist history
5186
    // types across concurrent session participants.
5187

5188
    // Errors of this kind are considered as incorrect API usage, and will lead
5189
    // to throwing of LogicError exceptions.
5190

5191
    SHARED_GROUP_TEST_PATH(path);
2✔
5192

5193
    // When starting with an empty Realm, all history types are allowed, but all
5194
    // session participants must still agree
5195
    {
2✔
5196
        // No history
5197
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
5198

5199
        // Out-of-Realm history
5200
        std::unique_ptr<Replication> hist = realm::make_in_realm_history();
2✔
5201
        CHECK_RUNTIME_ERROR(DB::create(*hist, path, DBOptions(crypt_key())), ErrorCodes::IncompatibleSession);
2✔
5202
    }
2✔
5203
}
2✔
5204

5205

5206
TEST(LangBindHelper_InRealmHistory_Upgrade)
5207
{
2✔
5208
    SHARED_GROUP_TEST_PATH(path_1);
2✔
5209
    {
2✔
5210
        // Out-of-Realm history
5211
        std::unique_ptr<Replication> hist = make_in_realm_history();
2✔
5212
        DBRef sg = DB::create(*hist, path_1, DBOptions(crypt_key()));
2✔
5213
        WriteTransaction wt(sg);
2✔
5214
        wt.commit();
2✔
5215
    }
2✔
5216
    {
2✔
5217
        // In-Realm history
5218
        std::unique_ptr<Replication> hist = make_in_realm_history();
2✔
5219
        DBRef sg = DB::create(*hist, path_1, DBOptions(crypt_key()));
2✔
5220
        WriteTransaction wt(sg);
2✔
5221
        wt.commit();
2✔
5222
    }
2✔
5223
    SHARED_GROUP_TEST_PATH(path_2);
2✔
5224
    {
2✔
5225
        // No history
5226
        DBRef sg = DB::create(path_2, DBOptions(crypt_key()));
2✔
5227
        WriteTransaction wt(sg);
2✔
5228
        wt.commit();
2✔
5229
    }
2✔
5230
    {
2✔
5231
        // In-Realm history
5232
        std::unique_ptr<Replication> hist = make_in_realm_history();
2✔
5233
        DBRef sg = DB::create(*hist, path_2, DBOptions(crypt_key()));
2✔
5234
        WriteTransaction wt(sg);
2✔
5235
        wt.commit();
2✔
5236
    }
2✔
5237
}
2✔
5238

5239
TEST(LangBindHelper_InRealmHistory_Downgrade)
5240
{
2✔
5241
    SHARED_GROUP_TEST_PATH(path);
2✔
5242
    {
2✔
5243
        // In-Realm history
5244
        std::unique_ptr<Replication> hist = make_in_realm_history();
2✔
5245
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5246
        WriteTransaction wt(sg);
2✔
5247
        wt.commit();
2✔
5248
    }
2✔
5249
    {
2✔
5250
        // No history
5251
        CHECK_THROW(DB::create(path, DBOptions(crypt_key())), IncompatibleHistories);
2✔
5252
    }
2✔
5253
}
2✔
5254

5255
// Trigger erase_rows with num_rows == 0 by inserting zero rows
5256
// and then rolling back the transaction. There was a problem
5257
// where accessors were not updated correctly in this case because
5258
// of an early out when num_rows_to_erase is zero.
5259
TEST(LangBindHelper_RollbackInsertZeroRows)
5260
{
2✔
5261
    SHARED_GROUP_TEST_PATH(path)
2✔
5262
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5263
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
5264
    auto g = sg->start_write();
2✔
5265

5266
    auto t0 = g->add_table("t0");
2✔
5267
    auto t1 = g->add_table("t1");
2✔
5268

5269
    auto col = t0->add_column(*t1, "t0_link_to_t1");
2✔
5270
    t0->create_object();
2✔
5271
    auto o1 = t0->create_object();
2✔
5272
    t1->create_object();
2✔
5273
    auto v1 = t1->create_object();
2✔
5274
    o1.set(col, v1.get_key());
2✔
5275

5276
    CHECK_EQUAL(t0->size(), 2);
2✔
5277
    CHECK_EQUAL(t1->size(), 2);
2✔
5278
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5279

5280
    g->commit_and_continue_as_read();
2✔
5281
    g->promote_to_write();
2✔
5282

5283
    std::vector<ObjKey> keys;
2✔
5284
    t1->create_objects(0, keys); // Insert zero rows
2✔
5285

5286
    CHECK_EQUAL(t0->size(), 2);
2✔
5287
    CHECK_EQUAL(t1->size(), 2);
2✔
5288
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5289

5290
    g->rollback_and_continue_as_read();
2✔
5291
    g->verify();
2✔
5292

5293
    CHECK_EQUAL(t0->size(), 2);
2✔
5294
    CHECK_EQUAL(t1->size(), 2);
2✔
5295
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5296
}
2✔
5297

5298

5299
TEST(LangBindHelper_RollbackRemoveZeroRows)
5300
{
2✔
5301
    SHARED_GROUP_TEST_PATH(path)
2✔
5302
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5303
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
5304
    auto g = sg->start_write();
2✔
5305

5306
    auto t0 = g->add_table("t0");
2✔
5307
    auto t1 = g->add_table("t1");
2✔
5308

5309
    auto col = t0->add_column(*t1, "t0_link_to_t1");
2✔
5310
    t0->create_object();
2✔
5311
    auto o1 = t0->create_object();
2✔
5312
    t1->create_object();
2✔
5313
    auto v1 = t1->create_object();
2✔
5314
    o1.set(col, v1.get_key());
2✔
5315

5316
    CHECK_EQUAL(t0->size(), 2);
2✔
5317
    CHECK_EQUAL(t1->size(), 2);
2✔
5318
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5319

5320
    g->commit_and_continue_as_read();
2✔
5321
    g->promote_to_write();
2✔
5322

5323
    t1->clear();
2✔
5324

5325
    CHECK_EQUAL(t0->size(), 2);
2✔
5326
    CHECK_EQUAL(t1->size(), 0);
2✔
5327
    CHECK_EQUAL(o1.get<ObjKey>(col), ObjKey());
2✔
5328

5329
    g->rollback_and_continue_as_read();
2✔
5330
    g->verify();
2✔
5331

5332
    CHECK_EQUAL(t0->size(), 2);
2✔
5333
    CHECK_EQUAL(t1->size(), 2);
2✔
5334
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5335
}
2✔
5336

5337
// Bug found by AFL during development of TimestampColumn
5338
TEST_TYPES(LangBindHelper_AddEmptyRowsAndRollBackTimestamp, std::true_type, std::false_type)
5339
{
4✔
5340
    constexpr bool nullable_toggle = TEST_TYPE::value;
4✔
5341
    SHARED_GROUP_TEST_PATH(path);
4✔
5342
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
4✔
5343
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
4✔
5344
    auto g = sg_w->start_write();
4✔
5345
    TableRef t = g->add_table("");
4✔
5346
    t->add_column(type_Int, "", nullable_toggle);
4✔
5347
    t->add_column(type_Timestamp, "gnyf", nullable_toggle);
4✔
5348
    g->commit_and_continue_as_read();
4✔
5349
    g->promote_to_write();
4✔
5350
    std::vector<ObjKey> keys;
4✔
5351
    t->create_objects(224, keys);
4✔
5352
    g->rollback_and_continue_as_read();
4✔
5353
    g->verify();
4✔
5354
}
4✔
5355

5356
// Another bug found by AFL during development of TimestampColumn
5357
TEST_TYPES(LangBindHelper_EmptyWrites, std::true_type, std::false_type)
5358
{
4✔
5359
    constexpr bool nullable_toggle = TEST_TYPE::value;
4✔
5360
    SHARED_GROUP_TEST_PATH(path);
4✔
5361
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
4✔
5362
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
4✔
5363
    auto g = sg_w->start_write();
4✔
5364
    TableRef t = g->add_table("");
4✔
5365
    t->add_column(type_Timestamp, "gnyf", nullable_toggle);
4✔
5366

5367
    for (int i = 0; i < 27; ++i) {
112✔
5368
        g->commit_and_continue_as_read();
108✔
5369
        g->promote_to_write();
108✔
5370
    }
108✔
5371

5372
    t->create_object();
4✔
5373
}
4✔
5374

5375

5376
// Found by AFL
5377
TEST_TYPES(LangBindHelper_SetTimestampRollback, std::true_type, std::false_type)
5378
{
4✔
5379
    constexpr bool nullable_toggle = TEST_TYPE::value;
4✔
5380
    SHARED_GROUP_TEST_PATH(path);
4✔
5381
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
4✔
5382
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
4✔
5383
    auto g = sg->start_write();
4✔
5384
    auto table = g->add_table("");
4✔
5385
    table->add_column(type_Timestamp, "gnyf", nullable_toggle);
4✔
5386
    table->create_object().set_all(Timestamp(-1, -1));
4✔
5387
    g->rollback_and_continue_as_read();
4✔
5388
    g->verify();
4✔
5389
}
4✔
5390

5391

5392
// Found by AFL, probably related to the rollback version above
5393
TEST_TYPES(LangBindHelper_SetTimestampAdvanceRead, std::true_type, std::false_type)
5394
{
4✔
5395
    constexpr bool nullable_toggle = TEST_TYPE::value;
4✔
5396
    SHARED_GROUP_TEST_PATH(path);
4✔
5397
    std::unique_ptr<Replication> hist(make_in_realm_history());
4✔
5398
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4✔
5399
    auto g_r = sg->start_read();
4✔
5400
    auto g_w = sg->start_write();
4✔
5401
    auto table = g_w->add_table("");
4✔
5402
    table->add_column(type_Timestamp, "gnyf", nullable_toggle);
4✔
5403
    table->create_object().set_all(Timestamp(-1, -1));
4✔
5404
    g_w->commit_and_continue_as_read();
4✔
5405
    g_w->verify();
4✔
5406
    g_r->advance_read();
4✔
5407
    g_r->verify();
4✔
5408
}
4✔
5409

5410

5411
// Found by AFL.
5412
TEST(LangbindHelper_BoolSearchIndexCommitPromote)
5413
{
2✔
5414
    SHARED_GROUP_TEST_PATH(path);
2✔
5415
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5416
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5417
    auto g = sg->start_write();
2✔
5418
    auto t = g->add_table("");
2✔
5419
    auto col = t->add_column(type_Bool, "gnyf", true);
2✔
5420
    std::vector<ObjKey> keys;
2✔
5421
    t->create_objects(5, keys);
2✔
5422
    t->get_object(keys[0]).set(col, false);
2✔
5423
    t->add_search_index(col);
2✔
5424
    g->commit_and_continue_as_read();
2✔
5425
    g->promote_to_write();
2✔
5426
    t->create_objects(5, keys);
2✔
5427
    t->remove_object(keys[8]);
2✔
5428
}
2✔
5429

5430

5431
// Found by AFL.
5432
TEST(LangbindHelper_GroupWriter_EdgeCaseAssert)
5433
{
2✔
5434
    SHARED_GROUP_TEST_PATH(path);
2✔
5435
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5436
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5437
    auto g_r = sg->start_read();
2✔
5438
    auto g_w = sg->start_write();
2✔
5439

5440
    auto t1 = g_w->add_table("dgrpnpgmjbchktdgagmqlihjckcdhpjccsjhnqlcjnbterse");
2✔
5441
    auto t2 = g_w->add_table("pknglaqnckqbffehqfgjnrepcfohoedkhiqsiedlotmaqitm");
2✔
5442
    t1->add_column(type_Double, "ggotpkoshbrcrmmqbagbfjetajlrrlbpjhhqrngfgdteilmj", true);
2✔
5443
    t2->add_column_list(*t1, "dtkiipajqdsfglbptieibknaoeeohqdlhftqmlriphobspjr");
2✔
5444
    std::vector<ObjKey> keys;
2✔
5445
    t1->create_objects(375, keys);
2✔
5446
    g_w->add_table("pnsidlijqeddnsgaesiijrrqedkdktmfekftogjccerhpeil");
2✔
5447
    g_r->close();
2✔
5448
    g_w->commit();
2✔
5449
    REALM_ASSERT_RELEASE(sg->compact());
2✔
5450
    g_w = sg->start_write();
2✔
5451
    g_r = sg->start_read();
2✔
5452
    g_r->verify();
2✔
5453
    g_w->add_table("citdgiaclkfbbksfaqegcfiqcserceaqmttkilnlbknoadtb");
2✔
5454
    g_w->add_table("tqtnnikpggeakeqcqhfqtshmimtjqkchgbnmbpttbetlahfi");
2✔
5455
    g_w->add_table("hkesaecjqbkemmmkffctacsnskekjbtqmpoetjnqkpactenf");
2✔
5456
    g_r->close();
2✔
5457
    g_w->commit();
2✔
5458
}
2✔
5459

5460
TEST(LangBindHelper_Bug2321)
5461
{
2✔
5462
    SHARED_GROUP_TEST_PATH(path);
2✔
5463
    ShortCircuitHistory hist;
2✔
5464
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
5465
    int i;
2✔
5466
    std::vector<ObjKey> target_keys;
2✔
5467
    std::vector<ObjKey> origin_keys;
2✔
5468
    ColKey col;
2✔
5469
    {
2✔
5470
        WriteTransaction wt(sg);
2✔
5471
        Group& group = wt.get_group();
2✔
5472
        TableRef target = group.add_table("target");
2✔
5473
        target->add_column(type_Int, "data");
2✔
5474
        target->create_objects(REALM_MAX_BPNODE_SIZE + 2, target_keys);
2✔
5475
        TableRef origin = group.add_table("origin");
2✔
5476
        col = origin->add_column_list(*target, "_link");
2✔
5477
        origin->create_objects(2, origin_keys);
2✔
5478
        wt.commit();
2✔
5479
    }
2✔
5480

5481
    {
2✔
5482
        WriteTransaction wt(sg);
2✔
5483
        Group& group = wt.get_group();
2✔
5484
        TableRef origin = group.get_table("origin");
2✔
5485
        auto lv0 = origin->begin()->get_linklist(col);
2✔
5486
        for (i = 0; i < (REALM_MAX_BPNODE_SIZE - 1); i++) {
2,000✔
5487
            lv0.add(target_keys[i]);
1,998✔
5488
        }
1,998✔
5489
        wt.commit();
2✔
5490
    }
2✔
5491

5492
    auto reader = sg->start_read();
2✔
5493
    auto lv1 = reader->get_table("origin")->begin()->get_linklist(col);
2✔
5494
    {
2✔
5495
        WriteTransaction wt(sg);
2✔
5496
        Group& group = wt.get_group();
2✔
5497
        TableRef origin = group.get_table("origin");
2✔
5498
        auto lv0 = origin->begin()->get_linklist(col);
2✔
5499
        lv0.add(target_keys[i++]);
2✔
5500
        lv0.add(target_keys[i++]);
2✔
5501
        wt.commit();
2✔
5502
    }
2✔
5503

5504
    // If MAX_BPNODE_SIZE is 4 and we run in debug mode, then the LinkView
5505
    // accessor was not refreshed correctly. It would still be a leaf class,
5506
    // but the header flags would tell it is a node.
5507
    reader->advance_read();
2✔
5508
    CHECK_EQUAL(lv1.size(), i);
2✔
5509
}
2✔
5510

5511
TEST(LangBindHelper_Bug2295)
5512
{
2✔
5513
    SHARED_GROUP_TEST_PATH(path);
2✔
5514
    ShortCircuitHistory hist;
2✔
5515
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
5516
    int i;
2✔
5517
    std::vector<ObjKey> target_keys;
2✔
5518
    std::vector<ObjKey> origin_keys;
2✔
5519
    ColKey col;
2✔
5520
    {
2✔
5521
        WriteTransaction wt(sg);
2✔
5522
        Group& group = wt.get_group();
2✔
5523
        TableRef target = group.add_table("target");
2✔
5524
        target->add_column(type_Int, "data");
2✔
5525
        target->create_objects(REALM_MAX_BPNODE_SIZE + 2, target_keys);
2✔
5526
        TableRef origin = group.add_table("origin");
2✔
5527
        col = origin->add_column_list(*target, "_link");
2✔
5528
        origin->create_objects(2, origin_keys);
2✔
5529
        wt.commit();
2✔
5530
    }
2✔
5531

5532
    {
2✔
5533
        WriteTransaction wt(sg);
2✔
5534
        Group& group = wt.get_group();
2✔
5535
        TableRef origin = group.get_table("origin");
2✔
5536
        auto lv0 = origin->begin()->get_linklist(col);
2✔
5537
        for (i = 0; i < (REALM_MAX_BPNODE_SIZE - 1); i++) {
2,000✔
5538
            lv0.add(target_keys[i]);
1,998✔
5539
        }
1,998✔
5540
        wt.commit();
2✔
5541
    }
2✔
5542

5543
    auto reader = sg->start_read();
2✔
5544
    auto lv1 = reader->get_table("origin")->begin()->get_linklist(col);
2✔
5545
    CHECK_EQUAL(lv1.size(), i);
2✔
5546
    {
2✔
5547
        WriteTransaction wt(sg);
2✔
5548
        Group& group = wt.get_group();
2✔
5549
        TableRef origin = group.get_table("origin");
2✔
5550
        // With the error present, this will cause some areas to be freed
5551
        // that has already been freed in the above transaction
5552
        auto lv0 = origin->begin()->get_linklist(col);
2✔
5553
        lv0.add(target_keys[i++]);
2✔
5554
        wt.commit();
2✔
5555
    }
2✔
5556
    reader->promote_to_write();
2✔
5557
    // Here we write the duplicates to the free list
5558
    reader->commit_and_continue_as_read();
2✔
5559
    reader->verify();
2✔
5560
    CHECK_EQUAL(lv1.size(), i);
2✔
5561
}
2✔
5562

5563
#ifdef LEGACY_TESTS // FIXME: Requires get_at() method to be available on Obj.
5564
ONLY(LangBindHelper_BigBinary)
5565
{
5566
    SHARED_GROUP_TEST_PATH(path);
5567
    ShortCircuitHistory hist;
5568
    DBRef sg = DB::create(hist, path);
5569
    std::string big_data(0x1000000, 'x');
5570
    auto rt = sg->start_read();
5571
    auto wt = sg->start_write();
5572

5573
    std::string data(16777362, 'y');
5574
    TableRef target = wt->add_table("big");
5575
    auto col = target->add_column(type_Binary, "data");
5576
    target->create_object().set(col, BinaryData(data.data(), data.size()));
5577
    wt->commit();
5578
    rt->advance_read();
5579
    {
5580
        WriteTransaction wt(sg);
5581
        TableRef t = wt.get_table("big");
5582
        t->begin()->set(col, BinaryData(big_data.data(), big_data.size()));
5583
        wt.get_group().verify();
5584
        wt.commit();
5585
    }
5586
    rt->advance_read();
5587
    auto t = rt->get_table("big");
5588
    size_t pos = 0;
5589
    BinaryData bin = t->begin()->get_at(col, pos); // <---- not there yet?
5590
    CHECK_EQUAL(memcmp(big_data.data(), bin.data(), bin.size()), 0);
5591
}
5592
#endif
5593

5594
TEST(LangBindHelper_CopyOnWriteOverflow)
5595
{
2✔
5596
    SHARED_GROUP_TEST_PATH(path);
2✔
5597
    ShortCircuitHistory hist;
2✔
5598
    DBRef sg = DB::create(hist, path);
2✔
5599
    auto g = sg->start_write();
2✔
5600
    auto table = g->add_table("big");
2✔
5601
    auto obj = table->create_object();
2✔
5602
    auto col = table->add_column(type_Binary, "data");
2✔
5603
    std::string data(0xfffff0, 'x');
2✔
5604
    obj.set(col, BinaryData(data.data(), data.size()));
2✔
5605
    g->commit();
2✔
5606
    g = sg->start_write();
2✔
5607
    g->get_table("big")->begin()->set(col, BinaryData{"Hello", 5});
2✔
5608
    g->verify();
2✔
5609
    g->commit();
2✔
5610
}
2✔
5611

5612

5613
TEST(LangBindHelper_RollbackOptimize)
5614
{
2✔
5615
    SHARED_GROUP_TEST_PATH(path);
2✔
5616
    const char* key = crypt_key();
2✔
5617
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5618
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(key));
2✔
5619
    auto g = sg_w->start_write();
2✔
5620

5621
    auto table = g->add_table("t0");
2✔
5622
    auto col = table->add_column(type_String, "str_col_0", true);
2✔
5623
    g->commit_and_continue_as_read();
2✔
5624
    g->verify();
2✔
5625
    g->promote_to_write();
2✔
5626
    g->verify();
2✔
5627
    std::vector<ObjKey> keys;
2✔
5628
    table->create_objects(198, keys);
2✔
5629
    table->enumerate_string_column(col);
2✔
5630
    g->rollback_and_continue_as_read();
2✔
5631
    g->verify();
2✔
5632
}
2✔
5633

5634

5635
TEST(LangBindHelper_BinaryReallocOverMax)
5636
{
2✔
5637
    SHARED_GROUP_TEST_PATH(path);
2✔
5638
    const char* key = crypt_key();
2✔
5639
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5640
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(key));
2✔
5641
    auto g = sg_w->start_write();
2✔
5642
    auto table = g->add_table("table");
2✔
5643
    auto col = table->add_column(type_Binary, "binary_col", false);
2✔
5644
    auto obj = table->create_object();
2✔
5645

5646
    // The sizes of these binaries were found with AFL. Essentially we must hit
5647
    // the case where doubling the allocated memory goes above max_array_payload
5648
    // and hits the condition to clamp to the maximum.
5649
    std::string blob1(8877637, static_cast<unsigned char>(133));
2✔
5650
    std::string blob2(15994373, static_cast<unsigned char>(133));
2✔
5651
    BinaryData dataAlloc(blob1);
2✔
5652
    BinaryData dataRealloc(blob2);
2✔
5653

5654
    obj.set(col, dataAlloc);
2✔
5655
    obj.set(col, dataRealloc);
2✔
5656
    g->verify();
2✔
5657
}
2✔
5658

5659

5660
// This test verifies that small unencrypted files are treated correctly if
5661
// opened as encrypted.
5662
#if REALM_ENABLE_ENCRYPTION
5663
TEST(LangBindHelper_OpenAsEncrypted)
5664
{
2✔
5665
    SHARED_GROUP_TEST_PATH(path);
2✔
5666
    {
2✔
5667
        ShortCircuitHistory hist;
2✔
5668
        DBRef sg_clear = DB::create(hist, path);
2✔
5669

5670
        {
2✔
5671
            WriteTransaction wt(sg_clear);
2✔
5672
            TableRef target = wt.add_table("table");
2✔
5673
            target->add_column(type_String, "mixed_col");
2✔
5674
            target->create_object();
2✔
5675
            wt.commit();
2✔
5676
        }
2✔
5677
    }
2✔
5678
    {
2✔
5679
        const char* key = crypt_key(true);
2✔
5680
        std::unique_ptr<Replication> hist_encrypt(make_in_realm_history());
2✔
5681
        CHECK_THROW(DB::create(*hist_encrypt, path, DBOptions(key)), InvalidDatabase);
2✔
5682
    }
2✔
5683
}
2✔
5684
#endif
5685

5686

5687
// Test case generated in [realm-core-4.0.4] on Mon Dec 18 13:33:24 2017.
5688
// Adding 0 rows to a StringEnumColumn would add the default value to the keys
5689
// but not the indexes creating an inconsistency.
5690
TEST(LangBindHelper_EnumColumnAddZeroRows)
5691
{
2✔
5692
    SHARED_GROUP_TEST_PATH(path);
2✔
5693
    const char* key = nullptr;
2✔
5694
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5695
    DBRef sg = DB::create(*hist, path, DBOptions(key));
2✔
5696
    auto g = sg->start_write();
2✔
5697
    auto g_r = sg->start_read();
2✔
5698
    auto table = g->add_table("");
2✔
5699

5700
    auto col = table->add_column(DataType(2), "table", false);
2✔
5701
    table->enumerate_string_column(col);
2✔
5702
    g->commit_and_continue_as_read();
2✔
5703
    g->verify();
2✔
5704
    g->promote_to_write();
2✔
5705
    g->verify();
2✔
5706
    table->create_object();
2✔
5707
    g->commit_and_continue_as_read();
2✔
5708
    g_r->advance_read();
2✔
5709
    g_r->verify();
2✔
5710
    g->verify();
2✔
5711
}
2✔
5712

5713

5714
TEST(LangBindHelper_RemoveObject)
5715
{
2✔
5716
    SHARED_GROUP_TEST_PATH(path);
2✔
5717
    ShortCircuitHistory hist;
2✔
5718
    DBRef sg = DB::create(hist, path);
2✔
5719
    ColKey col;
2✔
5720
    auto rt = sg->start_read();
2✔
5721
    {
2✔
5722
        auto wt = sg->start_write();
2✔
5723
        TableRef t = wt->add_table("Foo");
2✔
5724
        col = t->add_column(type_Int, "int");
2✔
5725
        t->create_object(ObjKey(123)).set(col, 1);
2✔
5726
        t->create_object(ObjKey(456)).set(col, 2);
2✔
5727
        wt->commit();
2✔
5728
    }
2✔
5729

5730
    rt->advance_read();
2✔
5731
    auto table = rt->get_table("Foo");
2✔
5732
    const Obj o1 = table->get_object(ObjKey(123));
2✔
5733
    const Obj o2 = table->get_object(ObjKey(456));
2✔
5734
    CHECK_EQUAL(o1.get<int64_t>(col), 1);
2✔
5735
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
2✔
5736

5737
    {
2✔
5738
        auto wt = sg->start_write();
2✔
5739
        TableRef t = wt->get_table("Foo");
2✔
5740
        t->remove_object(ObjKey(123));
2✔
5741
        wt->commit();
2✔
5742
    }
2✔
5743
    rt->advance_read();
2✔
5744
    CHECK_THROW(o1.get<int64_t>(col), KeyNotFound);
2✔
5745
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
2✔
5746
}
2✔
5747

5748
TEST(LangBindHelper_callWithLock)
5749
{
2✔
5750
    SHARED_GROUP_TEST_PATH(path);
2✔
5751
    auto callback = [&](const std::string& realm_path) {
4✔
5752
        CHECK(realm_path.compare(path) == 0);
4✔
5753
    };
4✔
5754

5755
    auto callback_not_called = [&](const std::string&) {
2✔
5756
        CHECK(false);
×
5757
    };
×
5758

5759
    // call_with_lock should run the callback if the lock file doesn't exist.
5760
    CHECK_NOT(File::exists(path.get_lock_path()));
2✔
5761
    CHECK(DB::call_with_lock(path, callback));
2✔
5762
    CHECK(File::exists(path.get_lock_path()));
2✔
5763

5764
    {
2✔
5765
        std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5766
        DBRef sg_w = DB::create(*hist_w, path);
2✔
5767
        WriteTransaction wt(sg_w);
2✔
5768
        CHECK_NOT(DB::call_with_lock(path, callback_not_called));
2✔
5769
        wt.commit();
2✔
5770
        CHECK_NOT(DB::call_with_lock(path, callback_not_called));
2✔
5771
    }
2✔
5772
    CHECK(DB::call_with_lock(path, callback));
2✔
5773
}
2✔
5774

5775
TEST(LangBindHelper_AdvanceReadCluster)
5776
{
2✔
5777
    SHARED_GROUP_TEST_PATH(path);
2✔
5778
    ShortCircuitHistory hist;
2✔
5779
    DBRef sg = DB::create(hist, path);
2✔
5780

5781
    auto rt = sg->start_read();
2✔
5782
    {
2✔
5783
        auto wt = sg->start_write();
2✔
5784
        TableRef t = wt->add_table("Foo");
2✔
5785
        auto int_col = t->add_column(type_Int, "int");
2✔
5786
        for (int64_t i = 0; i < 100; i++) {
202✔
5787
            t->create_object(ObjKey(i)).set(int_col, i);
200✔
5788
        }
200✔
5789
        wt->commit();
2✔
5790
    }
2✔
5791

5792
    rt->advance_read();
2✔
5793
    auto table = rt->get_table("Foo");
2✔
5794
    auto col = table->get_column_key("int");
2✔
5795
    for (int64_t i = 0; i < 100; i++) {
202✔
5796
        const Obj o = table->get_object(ObjKey(i));
200✔
5797
        CHECK_EQUAL(o.get<int64_t>(col), i);
200✔
5798
    }
200✔
5799
}
2✔
5800

5801
TEST(LangBindHelper_ImportDetachedLinkList)
5802
{
2✔
5803
    SHARED_GROUP_TEST_PATH(path);
2✔
5804
    auto hist = make_in_realm_history();
2✔
5805
    DBRef db = DB::create(*hist, path);
2✔
5806
    std::unique_ptr<TableView> tv_1;
2✔
5807

5808
    ColKey col_pet;
2✔
5809
    ColKey col_addr;
2✔
5810
    ColKey col_name;
2✔
5811
    ColKey col_age;
2✔
5812

5813
    {
2✔
5814
        WriteTransaction wt(db);
2✔
5815
        auto persons = wt.add_table("person");
2✔
5816
        auto dogs = wt.add_table("dog");
2✔
5817
        col_pet = persons->add_column_list(*dogs, "pet");
2✔
5818
        col_addr = persons->add_column_list(type_String, "address");
2✔
5819
        col_name = dogs->add_column(type_String, "name");
2✔
5820
        col_age = dogs->add_column(type_Int, "age");
2✔
5821

5822
        auto tago = dogs->create_object().set(col_name, "Tago").set(col_age, 9);
2✔
5823
        auto hector = dogs->create_object().set(col_name, "Hector").set(col_age, 7);
2✔
5824

5825
        auto me = persons->create_object();
2✔
5826
        me.set_list_values<String>(col_addr, {"Paradisæblevej 5", "2500 Andeby"});
2✔
5827
        auto pets = me.get_linklist(col_pet);
2✔
5828
        pets.add(tago.get_key());
2✔
5829
        pets.add(hector.get_key());
2✔
5830
        wt.commit();
2✔
5831
    }
2✔
5832

5833
    auto rt = db->start_read();
2✔
5834
    auto persons = rt->get_table("person");
2✔
5835
    auto dogs = rt->get_table("dog");
2✔
5836
    Obj me = *persons->begin();
2✔
5837
    auto my_pets = me.get_linklist(col_pet);
2✔
5838
    Query q = dogs->where(my_pets).equal(col_age, 7);
2✔
5839
    auto tv = q.find_all();
2✔
5840
    CHECK_EQUAL(tv.size(), 1);
2✔
5841
    auto my_address = me.get_listbase_ptr(col_addr);
2✔
5842
    CHECK_EQUAL(my_address->size(), 2);
2✔
5843

5844
    {
2✔
5845
        // Delete the person.
5846
        WriteTransaction wt(db);
2✔
5847
        wt.get_table("person")->begin()->remove();
2✔
5848
        wt.commit();
2✔
5849
    }
2✔
5850

5851
    {
2✔
5852
        auto read_transaction = db->start_read();
2✔
5853

5854
        // The link_list that is embedded in the query imported here should be detached
5855
        auto local_tv = read_transaction->import_copy_of(tv, PayloadPolicy::Stay);
2✔
5856
        local_tv->sync_if_needed();
2✔
5857
        CHECK_EQUAL(local_tv->size(), 0);
2✔
5858

5859
        // Check that we can import a detached link_list back
5860
        tv_1 = rt->import_copy_of(*local_tv, PayloadPolicy::Move);
2✔
5861

5862
        // The list imported here should be null
5863
        CHECK_NOT(read_transaction->import_copy_of(*my_address));
2✔
5864
    }
2✔
5865

5866
    CHECK_EQUAL(tv_1->size(), 0);
2✔
5867
}
2✔
5868

5869
TEST(LangBindHelper_SearchIndexAccessor)
5870
{
2✔
5871
    SHARED_GROUP_TEST_PATH(path);
2✔
5872
    auto hist = make_in_realm_history();
2✔
5873
    DBRef db = DB::create(*hist, path);
2✔
5874
    ColKey col_name;
2✔
5875

5876
    auto tr = db->start_write();
2✔
5877
    {
2✔
5878
        auto persons = tr->add_table("person");
2✔
5879
        col_name = persons->add_column(type_String, "name");
2✔
5880
        persons->add_search_index(col_name);
2✔
5881
        persons->create_object().set(col_name, "Per");
2✔
5882
    }
2✔
5883
    tr->commit_and_continue_as_read();
2✔
5884

5885
    tr->promote_to_write();
2✔
5886
    {
2✔
5887
        auto persons = tr->get_table("person");
2✔
5888
        persons->remove_column(col_name);
2✔
5889
        auto col_age = persons->add_column(type_Int, "age");
2✔
5890
        persons->add_search_index(col_age);
2✔
5891
        // Index referring to col_age is now at position 0
5892
        persons->create_object().set(col_age, 47);
2✔
5893
    }
2✔
5894
    // The index accessor must be refreshed with old ColKey (col_name)
5895
    tr->rollback_and_continue_as_read();
2✔
5896

5897
    tr->promote_to_write();
2✔
5898
    {
2✔
5899
        auto persons = tr->get_table("person");
2✔
5900
        // Index accssor uses its ColKey to find value in table
5901
        persons->create_object().set(col_name, "Poul");
2✔
5902
    }
2✔
5903
    tr->commit();
2✔
5904
}
2✔
5905

5906
TEST(LangBindHelper_ArrayXoverMapping)
5907
{
2✔
5908
    SHARED_GROUP_TEST_PATH(path);
2✔
5909
    auto hist = make_in_realm_history();
2✔
5910
    DBRef db = DB::create(*hist, path);
2✔
5911
    ColKey my_col;
2✔
5912
    {
2✔
5913
        auto tr = db->start_write();
2✔
5914
        auto tbl = tr->add_table("my_table");
2✔
5915
        my_col = tbl->add_column(type_String, "my_col");
2✔
5916
        std::string s(1'000'000, 'a');
2✔
5917
        for (auto i = 0; i < 100; ++i)
202✔
5918
            tbl->create_object().set_all(s);
200✔
5919
        tr->commit();
2✔
5920
    }
2✔
5921
    REALM_ASSERT(db->compact());
2✔
5922
    {
2✔
5923
        auto tr = db->start_read();
2✔
5924
        auto tbl = tr->get_table("my_table");
2✔
5925
        for (auto i = 0; i < 100; ++i) {
202✔
5926
            auto o = tbl->get_object(i);
200✔
5927
            StringData str = o.get<String>(my_col);
200✔
5928
            for (auto j = 0; j < 1'000'000; ++j)
200,000,200✔
5929
                REALM_ASSERT(str[j] == 'a');
200,000,000✔
5930
        }
200✔
5931
    }
2✔
5932
}
2✔
5933

5934
TEST(LangBindHelper_SchemaChangeNotification)
5935
{
2✔
5936
    SHARED_GROUP_TEST_PATH(path);
2✔
5937
    auto hist = make_in_realm_history();
2✔
5938
    DBRef db = DB::create(*hist, path);
2✔
5939

5940
    auto rt = db->start_read();
2✔
5941
    bool handler_called;
2✔
5942
    rt->set_schema_change_notification_handler([&handler_called]() {
4✔
5943
        handler_called = true;
4✔
5944
    });
4✔
5945
    CHECK(rt->has_schema_change_notification_handler());
2✔
5946

5947
    {
2✔
5948
        auto tr = db->start_write();
2✔
5949
        tr->add_table("my_table");
2✔
5950
        tr->commit();
2✔
5951
    }
2✔
5952
    handler_called = false;
2✔
5953
    rt->advance_read();
2✔
5954
    CHECK(handler_called);
2✔
5955

5956
    {
2✔
5957
        auto tr = db->start_write();
2✔
5958
        auto table = tr->get_table("my_table");
2✔
5959
        table->add_column(type_Int, "integer");
2✔
5960
        tr->commit();
2✔
5961
    }
2✔
5962
    handler_called = false;
2✔
5963
    rt->advance_read();
2✔
5964
    CHECK(handler_called);
2✔
5965
}
2✔
5966

5967
TEST(LangBindHelper_InMemoryDB)
5968
{
2✔
5969
    DBRef sg = DB::create(make_in_realm_history());
2✔
5970
    ColKey col;
2✔
5971
    auto rt = sg->start_read();
2✔
5972
    {
2✔
5973
        auto wt = sg->start_write();
2✔
5974
        TableRef t = wt->add_table("Foo");
2✔
5975
        col = t->add_column(type_Int, "int");
2✔
5976
        t->create_object(ObjKey(123)).set(col, 1);
2✔
5977
        t->create_object(ObjKey(456)).set(col, 2);
2✔
5978
        wt->commit();
2✔
5979
    }
2✔
5980

5981
    rt->advance_read();
2✔
5982
    auto table = rt->get_table("Foo");
2✔
5983
    const Obj o1 = table->get_object(ObjKey(123));
2✔
5984
    const Obj o2 = table->get_object(ObjKey(456));
2✔
5985
    CHECK_EQUAL(o1.get<int64_t>(col), 1);
2✔
5986
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
2✔
5987

5988
    {
2✔
5989
        auto wt = sg->start_write();
2✔
5990
        TableRef t = wt->get_table("Foo");
2✔
5991
        t->remove_object(ObjKey(123));
2✔
5992
        wt->commit();
2✔
5993
    }
2✔
5994
    rt->advance_read();
2✔
5995
    CHECK_THROW(o1.get<int64_t>(col), KeyNotFound);
2✔
5996
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
2✔
5997
}
2✔
5998

5999
#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

© 2025 Coveralls, Inc