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

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

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

93.2
/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
{
100✔
78
    CHECK(frozen->is_frozen());
100✔
79
    CHECK_THROW(frozen->promote_to_write(), LogicError);
100✔
80
    auto table = frozen->get_table("my_table");
100✔
81
    CHECK(table->is_frozen());
100✔
82
    auto col = table->get_column_key("my_col_1");
100✔
83
    int64_t sum = 0;
100✔
84
    for (auto i : *table) {
94,870✔
85
        sum += i.get<int64_t>(col);
94,870✔
86
    }
94,870✔
87
    CHECK_EQUAL(sum, 1000 / 2 * 999);
100✔
88
    TableView tv = table->where().not_equal(col, 42).find_all();
100✔
89
    CHECK(tv.is_frozen());
100✔
90
}
100✔
91

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

130
TEST(Transactions_ConcurrentFrozenTableGetByName)
131
{
1✔
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;
1✔
138
#endif
1✔
139
    constexpr int num_tables = 3 * num_threads;
1✔
140
    SHARED_GROUP_TEST_PATH(path);
1✔
141
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
142
    DBRef db = DB::create(*hist_w, path);
1✔
143
    TransactionRef frozen;
1✔
144
    std::string table_names[num_tables];
1✔
145
    {
1✔
146
        auto wt = db->start_write();
1✔
147
        for (int j = 0; j < num_tables; ++j) {
3,001✔
148
            std::string name = "Table" + to_string(j);
3,000✔
149
            table_names[j] = name;
3,000✔
150
            wt->add_table(name);
3,000✔
151
        }
3,000✔
152
        wt->commit_and_continue_as_read();
1✔
153
        frozen = wt->freeze();
1✔
154
    }
1✔
155
    auto runner = [&](int first, int last) {
990✔
156
        millisleep(1);
990✔
157
        for (int j = first; j < last; ++j) {
998,640✔
158
            frozen->get_table(table_names[j]);
997,650✔
159
        }
997,650✔
160
    };
990✔
161
    std::thread threads[num_threads];
1✔
162
    for (int j = 0; j < num_threads; ++j) {
1,001✔
163
        threads[j] = std::thread(runner, j * 2, j * 2 + num_threads);
1,000✔
164
    }
1,000✔
165
    for (int j = 0; j < num_threads; ++j)
1,001✔
166
        threads[j].join();
1,000✔
167
}
1✔
168

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

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

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

224
TEST(Transactions_ConcurrentFrozenTableGetByKey)
225
{
1✔
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;
1✔
232
#endif
1✔
233
    constexpr int num_tables = 3 * num_threads;
1✔
234
    SHARED_GROUP_TEST_PATH(path);
1✔
235
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
236
    DBRef db = DB::create(*hist_w, path);
1✔
237
    TransactionRef frozen;
1✔
238
    TableKey table_keys[num_tables];
1✔
239
    {
1✔
240
        auto wt = db->start_write();
1✔
241
        for (int j = 0; j < num_tables; ++j) {
3,001✔
242
            std::string name = "Table" + to_string(j);
3,000✔
243
            auto table = wt->add_table(name);
3,000✔
244
            table_keys[j] = table->get_key();
3,000✔
245
        }
3,000✔
246
        wt->commit_and_continue_as_read();
1✔
247
        frozen = wt->freeze();
1✔
248
    }
1✔
249
    auto runner = [&](int first, int last) {
996✔
250
        millisleep(1);
996✔
251
        for (int j = first; j < last; ++j) {
643,355✔
252
            auto table = frozen->get_table(table_keys[j]);
642,359✔
253
            CHECK(table->get_key() == table_keys[j]);
642,359✔
254
        }
642,359✔
255
    };
996✔
256
    std::thread threads[num_threads];
1✔
257
    for (int j = 0; j < num_threads; ++j) {
1,001✔
258
        threads[j] = std::thread(runner, j * 2, j * 2 + num_threads);
1,000✔
259
    }
1,000✔
260
    for (int j = 0; j < num_threads; ++j)
1,001✔
261
        threads[j].join();
1,000✔
262
}
1✔
263

264

265
TEST(Transactions_ConcurrentFrozenQueryAndObj)
266
{
1✔
267
    SHARED_GROUP_TEST_PATH(path);
1✔
268
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
269
    DBRef db = DB::create(*hist_w, path);
1✔
270
    TransactionRef frozen;
1✔
271
    ObjKey obj_keys[1000];
1✔
272
    {
1✔
273
        auto wt = db->start_write();
1✔
274
        auto table = wt->add_table("MyTable");
1✔
275
        table->add_column(type_Int, "MyCol");
1✔
276
        for (int i = 0; i < 1000; ++i) {
1,001✔
277
            obj_keys[i] = table->create_object().set_all(i).get_key();
1,000✔
278
        }
1,000✔
279
        wt->commit_and_continue_as_read();
1✔
280
        frozen = wt->freeze();
1✔
281
    }
1✔
282
    auto runner = [&](int first, int last) {
498✔
283
        millisleep(1);
498✔
284
        auto table = frozen->get_table("MyTable");
498✔
285
        auto col = table->get_column_key("MyCol");
498✔
286
        for (int j = first; j < last; ++j) {
241,462✔
287
            // loads of concurrent queries created and executed:
288
            TableView tb = table->where().equal(col, j).find_all();
240,964✔
289
            CHECK(tb.size() == 1);
240,964✔
290
            CHECK(tb.get_key(0) == obj_keys[j]);
240,964✔
291
            // concurrent reads from results are just fine:
292
            auto obj = tb[0];
240,964✔
293
            CHECK(obj.get<Int>(col) == j);
240,964✔
294
        }
240,964✔
295
    };
498✔
296
    std::thread threads[500];
1✔
297
    for (int j = 0; j < 500; ++j) {
501✔
298
        threads[j] = std::thread(runner, j, j + 500);
500✔
299
    }
500✔
300
    for (int j = 0; j < 500; ++j)
501✔
301
        threads[j].join();
500✔
302
}
1✔
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
    {
52✔
367
    }
52✔
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
    {
55✔
379
        update_from_parent(version);
55✔
380
    }
55✔
381
    void update_from_parent(version_type) override
382
    {
55✔
383
        if (m_write_history)
55✔
384
            m_changesets = m_write_history->m_changesets;
54✔
385
    }
55✔
386
    version_type add_changeset(const char* data, size_t size, version_type orig_version)
387
    {
2,478✔
388
        m_incoming_changeset.assign(data, data + size); // Throws
2,478✔
389
        version_type new_version = orig_version + 1;
2,478✔
390
        m_incoming_version = new_version;
2,478✔
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
2,478✔
395
        return new_version;
2,478✔
396
    }
2,478✔
397
    void finalize()
398
    {
2,478✔
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);
2,478✔
402
        m_changesets[m_incoming_version].finalized = true;
2,478✔
403
    }
2,478✔
404
    void get_changesets(version_type begin_version, version_type end_version,
405
                        BinaryIterator* buffer) const noexcept override
406
    {
30✔
407
        size_t n = size_t(end_version - begin_version);
30✔
408
        for (size_t i = 0; i < n; ++i) {
60✔
409
            uint_fast64_t version = begin_version + i + 1;
30✔
410
            auto j = m_changesets.find(version);
30✔
411
            REALM_ASSERT(j != m_changesets.end());
30✔
412
            const ChangeSet& changeset = j->second;
30✔
413
            REALM_ASSERT(changeset.finalized); // Must have been finalized
30✔
414
            buffer[i] = BinaryData(changeset.changes.data(), changeset.changes.size());
30✔
415
        }
30✔
416
    }
30✔
417
    void set_oldest_bound_version(version_type) override
418
    {
2,478✔
419
        // No-op
420
    }
2,478✔
421

422
    void verify() const override
423
    {
25✔
424
        // No-op
425
    }
25✔
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
    {
2,478✔
434
        return m_history.add_changeset(data, size, orig_version); // Throws
2,478✔
435
    }
2,478✔
436

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

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

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

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

457
    int get_history_schema_version() const noexcept override
458
    {
19✔
459
        return 0;
19✔
460
    }
19✔
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
{
1✔
483
    SHARED_GROUP_TEST_PATH(path);
1✔
484
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
485
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
486

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

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

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

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

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

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

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

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

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

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

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

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

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

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

687
TEST(LangBindHelper_AdvanceReadTransact_AddTableWithFreshSharedGroup)
688
{
1✔
689
    SHARED_GROUP_TEST_PATH(path);
1✔
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
    {
1✔
701
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
1✔
702
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
703
        WriteTransaction wt(sg_w);
1✔
704
        wt.add_table("table_1");
1✔
705
        wt.commit();
1✔
706
    }
1✔
707

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

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

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

725

726
TEST(LangBindHelper_AdvanceReadTransact_RemoveTableWithFreshSharedGroup)
727
{
1✔
728
    SHARED_GROUP_TEST_PATH(path);
1✔
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
    {
1✔
738
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
1✔
739
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
740
        WriteTransaction wt(sg_w);
1✔
741
        wt.add_table("table");
1✔
742
        wt.commit();
1✔
743
    }
1✔
744

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

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

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

762

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

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

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

780
    auto process = test_util::spawn_process(test_context.test_details.test_name, "make_many_tables");
1✔
781
    if (process->is_child()) {
1✔
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 {
1✔
807
        process->wait_for_child_to_finish();
1✔
808
    }
1✔
809
    size_t reported_used_space = 0;
1✔
810
    {
1✔
811
        std::unique_ptr<Replication> hist(realm::make_in_realm_history());
1✔
812
        DBRef sg = DB::create(*hist, path2, DBOptions(crypt_key()));
1✔
813
        WriteTransaction wt(sg);
1✔
814
        auto table = wt.get_table("stats");
1✔
815
        CHECK(table);
1✔
816
        CHECK_EQUAL(table->size(), 1);
1✔
817
        reported_used_space = size_t(table->begin()->get<int64_t>("used_space"));
1✔
818
    }
1✔
819

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

825

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

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

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

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

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

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

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

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

903

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

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

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

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

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

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

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

930
    auto process = test_util::spawn_process(test_context.test_details.test_name, "add_table");
1✔
931
    if (process->is_child()) {
1✔
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 {
1✔
945
        process->wait_for_child_to_finish();
1✔
946
    }
1✔
947

948
    rt->advance_read();
1✔
949

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

955
TEST(LangBindHelper_AdvanceReadTransact_LinkColumnInNewTable)
956
{
1✔
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);
1✔
966
    ShortCircuitHistory hist;
1✔
967
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
1✔
968
    DBRef sg_w = DB::create(hist, path, DBOptions(crypt_key()));
1✔
969
    {
1✔
970
        WriteTransaction wt(sg_w);
1✔
971
        wt.get_or_add_table("a");
1✔
972
        wt.commit();
1✔
973
    }
1✔
974

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

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

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

991

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

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

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

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

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

1047
        // Start a read transaction (to be repeatedly advanced)
1048
        TransactionRef rt = sg->start_read();
1✔
1049
        CHECK_EQUAL(0, rt->size());
1✔
1050
    }
1✔
1051
    // Create 5 columns, and make 3 of them indexed
1052
    auto process = test_util::spawn_process(test_context.test_details.test_name, "init");
1✔
1053
    if (process->is_child()) {
1✔
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()) {
1✔
1075
        process->wait_for_child_to_finish();
1✔
1076

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

1080
        // Start a read transaction (to be repeatedly advanced)
1081
        TransactionRef rt = sg->start_read();
1✔
1082
        rt->advance_read();
1✔
1083
        rt->verify();
1✔
1084
        ConstTableRef table = rt->get_table("t");
1✔
1085
        CHECK(table->has_search_index(table->get_column_key("i0")));
1✔
1086
        CHECK_NOT(table->has_search_index(table->get_column_key("s1")));
1✔
1087
        CHECK(table->has_search_index(table->get_column_key("s2")));
1✔
1088
        CHECK_NOT(table->has_search_index(table->get_column_key("i3")));
1✔
1089
        CHECK(table->has_search_index(table->get_column_key("i4")));
1✔
1090
    }
1✔
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");
1✔
1094
    if (process->is_child()) {
1✔
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()) {
1✔
1113
        process->wait_for_child_to_finish();
1✔
1114

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

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

1130
    // Add some searchable contents
1131
    process = test_util::spawn_process(test_context.test_details.test_name, "add_content");
1✔
1132
    if (process->is_child()) {
1✔
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()) {
1✔
1150
        process->wait_for_child_to_finish();
1✔
1151

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

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

1161
        CHECK_NOT(table->has_search_index(table->get_column_key("i0")));
1✔
1162
        CHECK(table->has_search_index(table->get_column_key("s1")));
1✔
1163
        CHECK_NOT(table->has_search_index(table->get_column_key("s2")));
1✔
1164
        CHECK(table->has_search_index(table->get_column_key("i3")));
1✔
1165
        CHECK_NOT(table->has_search_index(table->get_column_key("i4")));
1✔
1166
        CHECK_EQUAL(ObjKey(12), table->find_first_string(table->get_column_key("s1"), "931"));
1✔
1167
        CHECK_EQUAL(ObjKey(4), table->find_first_int(table->get_column_key("i3"), 315));
1✔
1168
        CHECK_EQUAL(ObjKey(13), table->find_first_int(table->get_column_key("i3"), 508));
1✔
1169
    }
1✔
1170
    // Move the indexed columns by removal
1171
    process = test_util::spawn_process(test_context.test_details.test_name, "move_and_remove");
1✔
1172
    if (process->is_child()) {
1✔
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()) {
1✔
1185
        process->wait_for_child_to_finish();
1✔
1186

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

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

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

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

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

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

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

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

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

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

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

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

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

1263
namespace {
1264

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

1291
    bool get(T& e)
1292
    {
50,001✔
1293
        std::unique_lock<std::mutex> lock(mutex);
50,001✔
1294
        while (is_empty() && !closed)
71,167✔
1295
            not_empty_or_closed.wait(lock);
21,166✔
1296
        if (closed)
50,001✔
1297
            return false;
1✔
1298
        if (is_full())
50,000✔
1299
            not_full.notify_all();
×
1300
        e = std::move(data[reader++ % sz]);
50,000✔
1301
        return true;
50,000✔
1302
    }
50,001✔
1303

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

1310
    void close()
1311
    {
1✔
1312
        std::unique_lock<std::mutex> lock(mutex);
1✔
1313
        closed = true;
1✔
1314
        not_empty_or_closed.notify_all();
1✔
1315
    }
1✔
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
{
1✔
1331
    Random random(random_int<unsigned long>());
1✔
1332
    bool closed = false;
1✔
1333
    while (!closed) {
50,002✔
1334
        LnkLstPtr r;
50,001✔
1335
        // prevent the compiler from eliminating a loop:
1336
        volatile int delay = random.draw_int_mod(10000);
50,001✔
1337
        closed = !queue.get(r);
50,001✔
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)
248,574,718✔
1342
            delay = delay - 1;
248,524,717✔
1343
        // just let 'r' die
1344
    }
50,001✔
1345
}
1✔
1346
} // namespace
1347

1348
TEST(LangBindHelper_ConcurrentLinkViewDeletes)
1349
{
1✔
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;
1✔
1358

1359
    // Number of references produced (some will refer to the same
1360
    // accessor)
1361
    const int max_refs = 50000;
1✔
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
1✔
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;
1✔
1371

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

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

1379
    // Start a read transaction (to be repeatedly advanced)
1380
    std::vector<ObjKey> o_keys;
1✔
1381
    std::vector<ObjKey> t_keys;
1✔
1382
    ColKey ck;
1✔
1383
    auto rt = sg->start_read();
1✔
1384
    {
1✔
1385
        // setup tables with empty linklists
1386
        WriteTransaction wt(sg);
1✔
1387
        TableRef origin = wt.add_table("origin");
1✔
1388
        TableRef target = wt.add_table("target");
1✔
1389
        ck = origin->add_column_list(*target, "ll");
1✔
1390
        origin->create_objects(table_size, o_keys);
1✔
1391
        target->create_objects(table_size, t_keys);
1✔
1392
        wt.commit();
1✔
1393
    }
1✔
1394
    rt->advance_read();
1✔
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
    ConcurrentQueue<LnkLstPtr> queue(buffer_size);
1✔
1401
    std::thread deleter([&] {
1✔
1402
        deleter_thread(queue);
1✔
1403
    });
1✔
1404
    for (int i = 0; i < max_refs; ++i) {
50,001✔
1405
        TableRef origin = rt->get_table("origin");
50,000✔
1406
        int ndx = random.draw_int_mod(table_size);
50,000✔
1407
        Obj o = origin->get_object(o_keys[ndx]);
50,000✔
1408
        LnkLstPtr lw = o.get_linklist_ptr(ck);
50,000✔
1409
        bool will_add = change_frequency_per_mill > random.draw_int_mod(1000000);
50,000✔
1410
        if (will_add) {
50,000✔
1411
            rt->promote_to_write();
2,436✔
1412
            lw->add(t_keys[ndx]);
2,436✔
1413
            rt->commit_and_continue_as_read();
2,436✔
1414
        }
2,436✔
1415
        queue.put(lw);
50,000✔
1416
    }
50,000✔
1417
    queue.close();
1✔
1418
    deleter.join();
1✔
1419
}
1✔
1420

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

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

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

1461

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1716
    ObjKey target_key0, target_key1;
1✔
1717
    Obj target_obj0, target_obj1;
1✔
1718

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

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

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

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

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

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

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

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

1780

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

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

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

1801
    ObjKey target_key0, target_key1;
1✔
1802
    Obj target_obj0, target_obj1;
1✔
1803

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

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

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

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

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

1840

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

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

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

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

1870

1871
TEST(LangBindHelper_AdvanceReadTransact_IntIndex)
1872
{
1✔
1873
    SHARED_GROUP_TEST_PATH(path);
1✔
1874

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

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

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

1887
    g->commit_and_continue_as_read();
1✔
1888

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

1893
    g->promote_to_write();
1✔
1894

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

1901
    g->commit_and_continue_as_read();
1✔
1902

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

1908
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_TableClear, testing_supports_spawn_process)
1909
{
1✔
1910
    SHARED_GROUP_TEST_PATH(path);
1✔
1911

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

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

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

1943
        reader->advance_read();
1✔
1944

1945
        CHECK(!obj.is_valid());
1✔
1946

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

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

1959
TEST(LangBindHelper_AdvanceReadTransact_UnorderedTableViewClear)
1960
{
1✔
1961
    SHARED_GROUP_TEST_PATH(path);
1✔
1962

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

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

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

1990
    reader->advance_read();
1✔
1991

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

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

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

2014
} // unnamed namespace
2015

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

2029
    auto tr = sg->start_read();
2✔
2030

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

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

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

2056
            Parser(TestContext& context)
2✔
2057
                : test_context(context)
2✔
2058
            {
2✔
2059
            }
2✔
2060

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

2078
            Parser(TestContext& context)
2✔
2079
                : test_context(context)
2✔
2080
            {
2✔
2081
            }
2✔
2082

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

2088
                return true;
4✔
2089
            }
4✔
2090
        } parser(test_context);
2✔
2091

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

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

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

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

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

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

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

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

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

2199

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

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

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

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

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

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

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

2257

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

2311

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

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

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

2370

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

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

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

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

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

2437

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

2468

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

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

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

2503

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

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

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

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

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

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

2619

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

2651

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

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

2687

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

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

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

2722

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

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

2770

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

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

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

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

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

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

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

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

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

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

2827
    g->rollback_and_continue_as_read();
1✔
2828
    g->promote_to_write();
1✔
2829

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

2834

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

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

2884

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

2899

2900
namespace {
2901

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

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

2956
} // anonymous namespace
2957

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

2963
    SHARED_GROUP_TEST_PATH(path);
1✔
2964

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

2990
    // Wait for all writer threads to complete
2991
    for (int i = 0; i < write_thread_count; ++i)
8✔
2992
        threads[i].join();
7✔
2993

2994
    // Allow readers time to catch up
2995
    for (int k = 0; k < 100; ++k)
101✔
2996
        std::this_thread::yield();
100✔
2997

2998
    // signal to all readers to complete
2999
    {
1✔
3000
        WriteTransaction wt(sg);
1✔
3001
        TableRef tr = wt.get_table("C");
1✔
3002
        tr->create_object();
1✔
3003
        wt.commit();
1✔
3004
    }
1✔
3005
    // Wait for all reader threads to complete
3006
    for (int i = 0; i < read_thread_count; ++i)
4✔
3007
        threads[write_thread_count + i].join();
3✔
3008
}
1✔
3009

3010
// Interprocess communication does not work with encryption enabled on Apple.
3011
// This is because fork() does not play well with Apple primitives such as
3012
// dispatch_queue_t in ReclaimerThreadStopper. This could possibly be fixed if
3013
// we need more tests like this.
3014

3015
#if !REALM_ANDROID && !REALM_IOS
3016

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

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

3058
    if (process->is_parent()) {
1✔
3059
        process->wait_for_child_to_finish();
1✔
3060
    }
1✔
3061

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

3094
    if (process->is_parent()) {
1✔
3095
        // Wait for all writer threads to complete
3096
        for (int i = 0; i < write_process_count; ++i) {
8✔
3097
            writers[i]->wait_for_child_to_finish();
7✔
3098
        }
7✔
3099

3100
        // Allow readers time to catch up
3101
        for (int k = 0; k < 100; ++k)
101✔
3102
            std::this_thread::yield();
100✔
3103

3104
        // signal to all readers to complete
3105
        {
1✔
3106
            std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3107
            DBRef sg = DB::create(*hist, path, DBOptions(key));
1✔
3108
            WriteTransaction wt(sg);
1✔
3109
            TableRef tr = wt.get_table("C");
1✔
3110
            tr->create_object();
1✔
3111
            wt.commit();
1✔
3112
        }
1✔
3113

3114
        // Wait for all reader threads to complete
3115
        for (int i = 0; i < read_process_count; ++i) {
4✔
3116
            readers[i]->wait_for_child_to_finish();
3✔
3117
        }
3✔
3118
    }
1✔
3119
}
1✔
3120

3121
#endif // !REALM_ANDROID && !REALM_IOS
3122

3123
TEST(LangBindHelper_ImplicitTransactions_NoExtremeFileSpaceLeaks)
3124
{
1✔
3125
    SHARED_GROUP_TEST_PATH(path);
1✔
3126

3127
    for (int i = 0; i < 100; ++i) {
101✔
3128
        std::unique_ptr<Replication> hist(make_in_realm_history());
100✔
3129
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
100✔
3130
        auto trans = sg->start_read();
100✔
3131
        trans->promote_to_write();
100✔
3132
        trans->commit_and_continue_as_read();
100✔
3133
    }
100✔
3134

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

3148

3149
TEST(LangBindHelper_ImplicitTransactions_ContinuedUseOfTable)
3150
{
1✔
3151
    SHARED_GROUP_TEST_PATH(path);
1✔
3152

3153
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3154
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
3155
    auto group = sg->start_read();
1✔
3156
    auto group_w = sg->start_write();
1✔
3157

3158
    TableRef table_w = group_w->add_table("table");
1✔
3159
    auto col = table_w->add_column(type_Int, "");
1✔
3160
    auto obj = table_w->create_object();
1✔
3161
    group_w->commit_and_continue_as_read();
1✔
3162
    group_w->verify();
1✔
3163

3164
    group->advance_read();
1✔
3165
    ConstTableRef table = group->get_table("table");
1✔
3166
    CHECK_EQUAL(1, table->size());
1✔
3167
    group->verify();
1✔
3168

3169
    group_w->promote_to_write();
1✔
3170
    obj.set<int64_t>(col, 1);
1✔
3171
    group_w->commit_and_continue_as_read();
1✔
3172
    group_w->verify();
1✔
3173

3174
    group->advance_read();
1✔
3175
    auto obj2 = group->import_copy_of(obj);
1✔
3176
    CHECK_EQUAL(1, obj2.get<int64_t>(col));
1✔
3177
    group->verify();
1✔
3178
}
1✔
3179

3180

3181
TEST(LangBindHelper_ImplicitTransactions_ContinuedUseOfLinkList)
3182
{
1✔
3183
    SHARED_GROUP_TEST_PATH(path);
1✔
3184

3185
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3186
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
3187
    auto group = sg->start_read();
1✔
3188
    auto group_w = sg->start_write();
1✔
3189

3190
    TableRef table_w = group_w->add_table("table");
1✔
3191
    auto col = table_w->add_column_list(*table_w, "flubber");
1✔
3192
    auto obj = table_w->create_object();
1✔
3193
    auto link_list_w = obj.get_linklist(col);
1✔
3194
    link_list_w.add(obj.get_key());
1✔
3195
    // CHECK_EQUAL(1, link_list_w.size()); // avoid this, it hides missing updates
3196
    group_w->commit_and_continue_as_read();
1✔
3197
    group_w->verify();
1✔
3198

3199
    group->advance_read();
1✔
3200
    auto link_list = obj.get_linklist(col);
1✔
3201
    CHECK_EQUAL(1, link_list.size());
1✔
3202
    group->verify();
1✔
3203

3204
    group_w->promote_to_write();
1✔
3205
    // CHECK_EQUAL(1, link_list_w.size()); // avoid this, it hides missing updates
3206
    link_list_w.add(obj.get_key());
1✔
3207
    CHECK_EQUAL(2, link_list_w.size());
1✔
3208
    group_w->commit_and_continue_as_read();
1✔
3209
    group_w->verify();
1✔
3210

3211
    group->advance_read();
1✔
3212
    CHECK_EQUAL(2, link_list.size());
1✔
3213
    group->verify();
1✔
3214
}
1✔
3215

3216

3217
TEST(LangBindHelper_MemOnly)
3218
{
1✔
3219
    SHARED_GROUP_TEST_PATH(path);
1✔
3220
    ShortCircuitHistory hist;
1✔
3221
    DBRef sg = DB::create(hist, path, DBOptions(DBOptions::Durability::MemOnly));
1✔
3222

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

3236
    // Verify that basic replication functionality works
3237
    auto rt = sg->start_read();
1✔
3238
    {
1✔
3239
        WriteTransaction wt(sg);
1✔
3240
        wt.add_table("table");
1✔
3241
        wt.commit();
1✔
3242
    }
1✔
3243

3244
    CHECK(rt->is_empty());
1✔
3245
    rt->advance_read();
1✔
3246
    CHECK(!rt->is_empty());
1✔
3247
}
1✔
3248

3249
TEST(LangBindHelper_ImplicitTransactions_SearchIndex)
3250
{
1✔
3251
    SHARED_GROUP_TEST_PATH(path);
1✔
3252

3253
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3254
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
3255
    auto rt = sg->start_read();
1✔
3256
    auto group_w = sg->start_read();
1✔
3257

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

3268
    rt->advance_read();
1✔
3269
    ConstTableRef table = rt->get_table("table");
1✔
3270
    auto obj = table->get_object(ok);
1✔
3271
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
1✔
3272
    CHECK_EQUAL("2", obj.get<StringData>(c1));
1✔
3273
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
1✔
3274
    rt->verify();
1✔
3275

3276
    // Add search index and re-verify
3277
    group_w->promote_to_write();
1✔
3278
    table_w->add_search_index(c1);
1✔
3279
    group_w->commit_and_continue_as_read();
1✔
3280
    group_w->verify();
1✔
3281

3282
    rt->advance_read();
1✔
3283
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
1✔
3284
    CHECK_EQUAL("2", obj.get<StringData>(c1));
1✔
3285
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
1✔
3286
    CHECK(table->has_search_index(c1));
1✔
3287
    rt->verify();
1✔
3288

3289
    // Remove search index and re-verify
3290
    group_w->promote_to_write();
1✔
3291
    table_w->remove_search_index(c1);
1✔
3292
    group_w->commit_and_continue_as_read();
1✔
3293
    group_w->verify();
1✔
3294

3295
    rt->advance_read();
1✔
3296
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
1✔
3297
    CHECK_EQUAL("2", obj.get<StringData>(c1));
1✔
3298
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
1✔
3299
    CHECK(!table->has_search_index(c1));
1✔
3300
    rt->verify();
1✔
3301
}
1✔
3302

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

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

3359
        auto col = table->add_column_list(*table2, "first");
1✔
3360
        auto obj = table->create_object();
1✔
3361
        auto link_view = obj.get_linklist(col);
1✔
3362

3363
        link_view.add(key);
1✔
3364
        writer->commit_and_continue_as_read();
1✔
3365

3366
        Query qq = table2->where(link_view);
1✔
3367
        CHECK_EQUAL(qq.count(), 1);
1✔
3368
        writer->promote_to_write();
1✔
3369
        table->clear();
1✔
3370
        writer->commit_and_continue_as_read();
1✔
3371
        CHECK_EQUAL(link_view.size(), 0);
1✔
3372
        CHECK_EQUAL(qq.count(), 0);
1✔
3373

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

3389
        CHECK(tv.is_in_sync());
3390
        CHECK(tv.is_attached());
3391
        CHECK_EQUAL(0, tv.size());
3392
#endif
3393
    }
1✔
3394
}
1✔
3395

3396

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

3429
            CHECK(tv.is_in_sync());
1✔
3430
            CHECK(tv.is_attached());
1✔
3431
            CHECK_EQUAL(26, tv.size()); // BOOM! fail with 50
1✔
3432
        }
1✔
3433
    }
1✔
3434
}
1✔
3435

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

3477

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

3509

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

3533
        tv = table->where().find_all();
1✔
3534
        CHECK(tv.is_attached());
1✔
3535
        CHECK_EQUAL(100, tv.size());
1✔
3536
        for (int i = 0; i < 100; ++i)
101✔
3537
            CHECK_EQUAL(i, tv.get_object(i).get<Int>(col));
100✔
3538

3539
        reader = writer->duplicate();
1✔
3540
        tv2 = reader->import_copy_of(tv, PayloadPolicy::Copy);
1✔
3541
        CHECK(tv.is_attached());
1✔
3542
        CHECK(tv.is_in_sync());
1✔
3543

3544
        tv3 = reader->import_copy_of(tv, PayloadPolicy::Stay);
1✔
3545
        CHECK(tv.is_attached());
1✔
3546
        CHECK(tv.is_in_sync());
1✔
3547

3548
        tv4 = reader->import_copy_of(tv, PayloadPolicy::Move);
1✔
3549
        CHECK(tv.is_attached());
1✔
3550
        CHECK(!tv.is_in_sync());
1✔
3551

3552
        // and again, but this time with the source out of sync:
3553
        tv5 = reader->import_copy_of(tv, PayloadPolicy::Copy);
1✔
3554
        CHECK(tv.is_attached());
1✔
3555
        CHECK(!tv.is_in_sync());
1✔
3556

3557
        tv6 = reader->import_copy_of(tv, PayloadPolicy::Stay);
1✔
3558
        CHECK(tv.is_attached());
1✔
3559
        CHECK(!tv.is_in_sync());
1✔
3560

3561
        tv7 = reader->import_copy_of(tv, PayloadPolicy::Move);
1✔
3562
        CHECK(tv.is_attached());
1✔
3563
        CHECK(!tv.is_in_sync());
1✔
3564

3565
        // and verify, that even though it was out of sync, we can bring it in sync again
3566
        tv.sync_if_needed();
1✔
3567
        CHECK(tv.is_in_sync());
1✔
3568

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

3587
        // one with payload:
3588
        CHECK(tv4->is_attached());
1✔
3589
        CHECK(tv4->is_in_sync());
1✔
3590
        CHECK_EQUAL(100, tv4->size());
1✔
3591
        for (int i = 0; i < 100; ++i)
101✔
3592
            CHECK_EQUAL(i, tv4->get_object(i).get<Int>(col));
100✔
3593

3594
        // verify that subsequent imports are all without payload:
3595
        CHECK(tv5->is_attached());
1✔
3596
        CHECK(!tv5->is_in_sync());
1✔
3597

3598
        CHECK(tv6->is_attached());
1✔
3599
        CHECK(!tv6->is_in_sync());
1✔
3600

3601
        CHECK(tv7->is_attached());
1✔
3602
        CHECK(!tv7->is_in_sync());
1✔
3603
    }
1✔
3604
}
1✔
3605

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

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

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

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

3736
struct Work {
3737
    TransactionRef tr;
3738
    std::unique_ptr<TableView> tv;
3739
};
3740

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

3759
        g->advance_read();
1,096✔
3760
        CHECK(!tv.is_in_sync());
1,096✔
3761
        tv.sync_if_needed();
1,096✔
3762
        CHECK(tv.is_in_sync());
1,096✔
3763
        auto ref = g->duplicate();
1,096✔
3764
        std::unique_ptr<Work> h = std::make_unique<Work>();
1,096✔
3765
        h->tr = ref;
1,096✔
3766
        h->tv = ref->import_copy_of(tv, PayloadPolicy::Move);
1,096✔
3767
        control->put(std::move(h));
1,096✔
3768

3769
        // here we need to allow the reciever to get hold on the proper version before
3770
        // we go through the loop again and advance_read().
3771
        control->wait_feedback();
1,096✔
3772
        std::this_thread::yield();
1,096✔
3773

3774
        if (table->where().equal(cols[0], 0).count() >= 1)
1,096✔
3775
            break;
1✔
3776
    }
1,096✔
3777
    g->end_read();
1✔
3778
    writer.join();
1✔
3779
}
1✔
3780

3781
void handover_verifier(HandoverControl<Work>* control, TestContext& test_context)
3782
{
1✔
3783
    bool not_done = true;
1✔
3784
    while (not_done) {
1,097✔
3785
        std::unique_ptr<Work> work;
1,096✔
3786
        control->get(work);
1,096✔
3787

3788
        auto g = work->tr;
1,096✔
3789
        control->signal_feedback();
1,096✔
3790
        TableRef table = g->get_table("table");
1,096✔
3791
        ColKeys cols = table->get_column_keys();
1,096✔
3792
        TableView tv = table->where().greater(cols[0], 50).find_all();
1,096✔
3793
        CHECK(tv.is_in_sync());
1,096✔
3794
        std::unique_ptr<TableView> tv2 = std::move(work->tv);
1,096✔
3795
        CHECK(tv.is_in_sync());
1,096✔
3796
        CHECK(tv2->is_in_sync());
1,096✔
3797
        CHECK_EQUAL(tv.size(), tv2->size());
1,096✔
3798
        for (size_t k = 0; k < tv.size(); ++k) {
792,860✔
3799
            auto o = tv.get_object(k);
791,764✔
3800
            auto o2 = tv2->get_object(k);
791,764✔
3801
            CHECK_EQUAL(o.get<int64_t>(cols[0]), o2.get<int64_t>(cols[0]));
791,764✔
3802
        }
791,764✔
3803
        if (table->where().equal(cols[0], 0).count() >= 1)
1,096✔
3804
            not_done = false;
1✔
3805
        g->close();
1,096✔
3806
    }
1,096✔
3807
}
1✔
3808

3809
} // anonymous namespace
3810

3811
namespace {
3812

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

3835

3836
// Disable with TSAN because it needs to synchronize between multiple DBs, and TSAN isn't able to track
3837
// acquire/release across multiple mappings of the same underlying memory.
3838
TEST_IF(LangBindHelper_RacingAttachers, !running_with_tsan)
3839
{
1✔
3840
    const int num_attachers = 10;
1✔
3841
    SHARED_GROUP_TEST_PATH(path);
1✔
3842
    ColKey col;
1✔
3843
    {
1✔
3844
        std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3845
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
3846
        auto g = sg->start_write();
1✔
3847
        auto table = g->add_table("table");
1✔
3848
        col = table->add_column(type_Int, "first");
1✔
3849
        for (int i = 0; i < 1000; ++i)
1,001✔
3850
            table->create_object(ObjKey(i));
1,000✔
3851
        g->commit();
1✔
3852
    }
1✔
3853
    std::thread attachers[num_attachers];
1✔
3854
    for (int i = 0; i < num_attachers; ++i) {
11✔
3855
        attachers[i] = std::thread(attacher, std::string(path), col);
10✔
3856
    }
10✔
3857
    for (int i = 0; i < num_attachers; ++i) {
11✔
3858
        attachers[i].join();
10✔
3859
    }
10✔
3860
}
1✔
3861

3862
// This test takes a very long time when running with valgrind
3863
TEST_IF(LangBindHelper_HandoverBetweenThreads, !running_with_valgrind)
3864
{
1✔
3865
    SHARED_GROUP_TEST_PATH(path);
1✔
3866
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3867
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
3868
    auto g = sg->start_write();
1✔
3869
    auto table = g->add_table("table");
1✔
3870
    table->add_column(type_Int, "first");
1✔
3871
    g->commit();
1✔
3872
    g = sg->start_read();
1✔
3873
    table = g->get_table("table");
1✔
3874
    CHECK(bool(table));
1✔
3875
    g->end_read();
1✔
3876

3877
    HandoverControl<Work> control;
1✔
3878
    std::thread querier(handover_querier, &control, std::ref(test_context), sg);
1✔
3879
    std::thread verifier(handover_verifier, &control, std::ref(test_context));
1✔
3880
    querier.join();
1✔
3881
    verifier.join();
1✔
3882
}
1✔
3883

3884

3885
TEST(LangBindHelper_HandoverDependentViews)
3886
{
1✔
3887
    SHARED_GROUP_TEST_PATH(path);
1✔
3888
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3889
    DBRef db = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
3890
    TransactionRef tr;
1✔
3891
    std::unique_ptr<TableView> tv_ov;
1✔
3892
    ColKey col;
1✔
3893
    {
1✔
3894
        // Untyped interface
3895
        {
1✔
3896
            TableView tv1;
1✔
3897
            TableView tv2;
1✔
3898
            auto group_w = db->start_write();
1✔
3899
            TableRef table = group_w->add_table("table2");
1✔
3900
            col = table->add_column(type_Int, "first");
1✔
3901
            for (int i = 0; i < 100; ++i) {
101✔
3902
                table->create_object().set_all(i);
100✔
3903
            }
100✔
3904
            group_w->commit_and_continue_as_read();
1✔
3905
            tv1 = table->where().find_all();
1✔
3906
            tv2 = table->where(&tv1).find_all();
1✔
3907
            CHECK(tv1.is_attached());
1✔
3908
            CHECK(tv2.is_attached());
1✔
3909
            CHECK_EQUAL(100, tv1.size());
1✔
3910
            for (int i = 0; i < 100; ++i) {
101✔
3911
                auto o = tv1.get_object(i);
100✔
3912
                CHECK_EQUAL(i, o.get<int64_t>(col));
100✔
3913
            }
100✔
3914
            CHECK_EQUAL(100, tv2.size());
1✔
3915
            for (int i = 0; i < 100; ++i) {
101✔
3916
                auto o = tv2.get_object(i);
100✔
3917
                CHECK_EQUAL(i, o.get<int64_t>(col));
100✔
3918
            }
100✔
3919
            tr = group_w->duplicate();
1✔
3920
            tv_ov = tr->import_copy_of(tv2, PayloadPolicy::Copy);
1✔
3921
            CHECK(tv1.is_attached());
1✔
3922
            CHECK(tv2.is_attached());
1✔
3923
        }
1✔
3924
        {
1✔
3925
            CHECK(tv_ov->is_in_sync());
1✔
3926
            // CHECK(tv1.is_attached());
3927
            CHECK(tv_ov->is_attached());
1✔
3928
            CHECK_EQUAL(100, tv_ov->size());
1✔
3929
            for (int i = 0; i < 100; ++i) {
101✔
3930
                auto o = tv_ov->get_object(i);
100✔
3931
                CHECK_EQUAL(i, o.get<int64_t>(col));
100✔
3932
            }
100✔
3933
        }
1✔
3934
    }
1✔
3935
}
1✔
3936

3937

3938
TEST(LangBindHelper_HandoverTableViewWithLnkLst)
3939
{
1✔
3940
    // First iteration hands-over a normal valid attached LnkLst. Second
3941
    // iteration hands-over a detached LnkLst.
3942
    for (int detached = 0; detached < 2; detached++) {
3✔
3943
        SHARED_GROUP_TEST_PATH(path);
2✔
3944
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3945
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3946
        ColKey col_link2, col0;
2✔
3947
        ObjKey ok0, ok1, ok2;
2✔
3948
        TransactionRef tr;
2✔
3949
        std::unique_ptr<TableView> tv2;
2✔
3950
        std::unique_ptr<Query> q2;
2✔
3951
        {
2✔
3952
            TableView tv;
2✔
3953
            auto group_w = sg->start_write();
2✔
3954

3955
            TableRef table1 = group_w->add_table("table1");
2✔
3956
            TableRef table2 = group_w->add_table("table2");
2✔
3957

3958
            // add some more columns to table1 and table2
3959
            col0 = table1->add_column(type_Int, "col1");
2✔
3960
            table1->add_column(type_String, "str1");
2✔
3961

3962
            // add some rows
3963
            ok0 = table1->create_object().set_all(300, "delta").get_key();
2✔
3964
            ok1 = table1->create_object().set_all(100, "alfa").get_key();
2✔
3965
            ok2 = table1->create_object().set_all(200, "beta").get_key();
2✔
3966

3967
            col_link2 = table2->add_column_list(*table1, "linklist");
2✔
3968

3969
            auto o = table2->create_object();
2✔
3970
            auto lvr = o.get_linklist(col_link2);
2✔
3971
            lvr.clear();
2✔
3972
            lvr.add(ok0);
2✔
3973
            lvr.add(ok1);
2✔
3974
            lvr.add(ok2);
2✔
3975

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

3978
            // q.m_table = table1
3979
            // q.m_view = lvr
3980
            Query q = table1->where(lvr).and_query(table1->column<Int>(col0) > 100);
2✔
3981

3982
            // Remove the LinkList that the query depends on, to see if a detached
3983
            // LinkList can be handed over correctly
3984
            if (detached == 1)
2✔
3985
                table2->remove_object(o.get_key());
1✔
3986

3987
            tv = q.find_all(); // tv = { 0, 2 } (only first iteration)
2✔
3988
            CHECK(tv.is_in_sync());
2✔
3989
            group_w->commit_and_continue_as_read();
2✔
3990
            tr = group_w->duplicate();
2✔
3991
            CHECK(tv.is_in_sync());
2✔
3992
            tv2 = tr->import_copy_of(tv, PayloadPolicy::Copy);
2✔
3993
            q2 = tr->import_copy_of(q, PayloadPolicy::Copy);
2✔
3994
            auto tv3a = q.find_all();
2✔
3995
            auto tv3b = q2->find_all();
2✔
3996
        }
2✔
3997
        {
2✔
3998
            auto tv3 = q2->find_all();
2✔
3999
            CHECK(tv2->is_in_sync());
2✔
4000
            if (detached == 0) {
2✔
4001
                CHECK_EQUAL(2, tv2->size());
1✔
4002
                CHECK_EQUAL(ok0, tv2->get_key(0));
1✔
4003
                CHECK_EQUAL(ok2, tv2->get_key(1));
1✔
4004
                CHECK_EQUAL(2, tv3.size());
1✔
4005
                CHECK_EQUAL(ok0, tv3.get_key(0));
1✔
4006
                CHECK_EQUAL(ok2, tv3.get_key(1));
1✔
4007
            }
1✔
4008
            else {
1✔
4009
                CHECK_EQUAL(0, tv2->size());
1✔
4010
                CHECK_EQUAL(0, tv3.size());
1✔
4011
            }
1✔
4012
            tr->close();
2✔
4013
        }
2✔
4014
    }
2✔
4015
}
1✔
4016

4017

4018
TEST(LangBindHelper_HandoverTableViewWithQueryOnLink)
4019
{
1✔
4020
    for (int detached = 0; detached < 2; detached++) {
3✔
4021
        SHARED_GROUP_TEST_PATH(path);
2✔
4022
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4023
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4024
        TransactionRef tr;
2✔
4025
        ObjKey target;
2✔
4026
        std::unique_ptr<TableView> tv2;
2✔
4027
        std::unique_ptr<Query> q2;
2✔
4028
        {
2✔
4029
            auto group_w = sg->start_write();
2✔
4030

4031
            TableRef table1 = group_w->add_table("table1");
2✔
4032
            TableRef table2 = group_w->add_table("table2");
2✔
4033
            table1->add_column(type_Int, "col1");
2✔
4034
            auto col_link = table2->add_column(*table1, "link");
2✔
4035

4036
            target = table1->create_object().set_all(300).get_key();
2✔
4037
            auto o = table2->create_object().set_all(target);
2✔
4038
            Query q = table2->where().and_query(table2->column<Link>(col_link) == table1->get_object(target));
2✔
4039

4040
            // Remove the object that the query depends on, to see if a detached
4041
            // object can be handed over correctly
4042
            if (detached == 1)
2✔
4043
                table2->remove_object(o.get_key());
1✔
4044

4045
            auto tv = q.find_all();
2✔
4046
            CHECK(tv.is_in_sync());
2✔
4047
            group_w->commit_and_continue_as_read();
2✔
4048
            tr = group_w->duplicate();
2✔
4049
            CHECK(tv.is_in_sync());
2✔
4050
            tv2 = tr->import_copy_of(tv, PayloadPolicy::Copy);
2✔
4051
            q2 = tr->import_copy_of(q, PayloadPolicy::Copy);
2✔
4052
        }
2✔
4053
        {
2✔
4054
            auto tv3 = q2->find_all();
2✔
4055
            CHECK(tv2->is_in_sync());
2✔
4056
            if (detached == 0) {
2✔
4057
                CHECK_EQUAL(1, tv2->size());
1✔
4058
                CHECK_EQUAL(target, tv2->get_key(0));
1✔
4059
                CHECK_EQUAL(1, tv3.size());
1✔
4060
                CHECK_EQUAL(target, tv3.get_key(0));
1✔
4061
            }
1✔
4062
            else {
1✔
4063
                CHECK_EQUAL(0, tv2->size());
1✔
4064
                CHECK_EQUAL(0, tv3.size());
1✔
4065
            }
1✔
4066
            tr->close();
2✔
4067
        }
2✔
4068
    }
2✔
4069
}
1✔
4070

4071

4072
#ifdef LEGACY_TESTS // (not useful as std unittest)
4073
namespace {
4074

4075
void do_write_work(std::string path, size_t id, size_t num_rows)
4076
{
4077
    const size_t num_iterations = 5000000; // this makes it run for a loooong time
4078
    const size_t payload_length_small = 10;
4079
    const size_t payload_length_large = 5000;   // > 4096 == page_size
4080
    Random random(random_int<unsigned long>()); // Seed from slow global generator
4081
    const char* key = crypt_key(true);
4082
    for (size_t rep = 0; rep < num_iterations; ++rep) {
4083
        std::unique_ptr<Replication> hist(make_in_realm_history());
4084
        DBRef sg = DB::create(*hist, path, DBOptions(key));
4085

4086
        TransactionRef rt = sg->start_read() LangBindHelper::promote_to_write(sg);
4087
        Group& group = const_cast<Group&>(rt.get_group());
4088
        TableRef t = rt->get_table(0);
4089

4090
        for (size_t i = 0; i < num_rows; ++i) {
4091
            const size_t payload_length = i % 10 == 0 ? payload_length_large : payload_length_small;
4092
            const char payload_char = 'a' + static_cast<char>((id + rep + i) % 26);
4093
            std::string std_payload(payload_length, payload_char);
4094
            StringData payload(std_payload);
4095

4096
            t->set_int(0, i, payload.size());
4097
            t->set_string(1, i, StringData(std_payload.c_str(), 1));
4098
            t->set_string(2, i, payload);
4099
        }
4100
        LangBindHelper::commit_and_continue_as_read(sg);
4101
    }
4102
}
4103

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

4141
} // end anonymous namespace
4142

4143

4144
// The following test is long running to try to catch race conditions
4145
// in with many reader writer threads on an encrypted realm and it is
4146
// not suited to automated testing.
4147
TEST_IF(Thread_AsynchronousIODataConsistency, false)
4148
{
4149
    SHARED_GROUP_TEST_PATH(path);
4150
    const int num_writer_threads = 2;
4151
    const int num_reader_threads = 2;
4152
    const int num_rows = 200; // 2 + REALM_MAX_BPNODE_SIZE;
4153
    const char* key = crypt_key(true);
4154
    std::unique_ptr<Replication> hist(make_in_realm_history());
4155
    DBRef sg = DB::create(*hist, path, DBOptions(key));
4156
    {
4157
        WriteTransaction wt(sg);
4158
        Group& group = wt.get_group();
4159
        TableRef t = rt->add_table("class_Table_Emulation_Name");
4160
        // add a column for each thread to write to
4161
        t->add_column(type_Int, "count", true);
4162
        t->add_column(type_String, "char", true);
4163
        t->add_column(type_String, "payload", true);
4164
        t->add_empty_row(num_rows);
4165
        wt.commit();
4166
    }
4167

4168
    Thread writer_threads[num_writer_threads];
4169
    for (int i = 0; i < num_writer_threads; ++i) {
4170
        writer_threads[i].start(std::bind(do_write_work, std::string(path), i, num_rows));
4171
    }
4172
    Thread reader_threads[num_reader_threads];
4173
    for (int i = 0; i < num_reader_threads; ++i) {
4174
        reader_threads[i].start(std::bind(do_read_verify, std::string(path)));
4175
    }
4176
    for (int i = 0; i < num_writer_threads; ++i) {
4177
        writer_threads[i].join();
4178
    }
4179

4180
    {
4181
        WriteTransaction wt(sg);
4182
        Group& group = wt.get_group();
4183
        TableRef t = rt->get_table("class_Table_Emulation_Name");
4184
        t->set_string(1, 0, "stop reading");
4185
        wt.commit();
4186
    }
4187

4188
    for (int i = 0; i < num_reader_threads; ++i) {
4189
        reader_threads[i].join();
4190
    }
4191
}
4192
#endif
4193

4194

4195
TEST(LangBindHelper_HandoverTableRef)
4196
{
1✔
4197
    SHARED_GROUP_TEST_PATH(path);
1✔
4198
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4199
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4200
    TransactionRef reader;
1✔
4201
    TableRef table;
1✔
4202
    {
1✔
4203
        auto writer = sg->start_write();
1✔
4204
        TableRef table1 = writer->add_table("table1");
1✔
4205
        writer->commit_and_continue_as_read();
1✔
4206
        auto vid = writer->get_version_of_current_transaction();
1✔
4207
        reader = sg->start_read(vid);
1✔
4208
        table = reader->import_copy_of(table1);
1✔
4209
    }
1✔
4210
    CHECK(bool(table));
1✔
4211
    CHECK(table->size() == 0);
1✔
4212
}
1✔
4213

4214
TEST(LangBindHelper_HandoverLinkView)
4215
{
1✔
4216
    SHARED_GROUP_TEST_PATH(path);
1✔
4217
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4218
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4219
    TransactionRef reader;
1✔
4220
    ColKey col1;
1✔
4221

4222
    auto writer = sg->start_write();
1✔
4223

4224
    TableRef table1 = writer->add_table("table1");
1✔
4225
    TableRef table2 = writer->add_table("table2");
1✔
4226

4227
    // add some more columns to table1 and table2
4228
    col1 = table1->add_column(type_Int, "col1");
1✔
4229
    table1->add_column(type_String, "str1");
1✔
4230

4231
    // add some rows
4232
    auto to1 = table1->create_object().set_all(300, "delta");
1✔
4233
    auto to2 = table1->create_object().set_all(100, "alfa");
1✔
4234
    auto to3 = table1->create_object().set_all(200, "beta");
1✔
4235

4236
    ColKey col_link2 = table2->add_column_list(*table1, "linklist");
1✔
4237

4238
    auto o1 = table2->create_object();
1✔
4239
    table2->create_object();
1✔
4240
    LnkLstPtr lvr = o1.get_linklist_ptr(col_link2);
1✔
4241
    lvr->clear();
1✔
4242
    lvr->add(to1.get_key());
1✔
4243
    lvr->add(to2.get_key());
1✔
4244
    lvr->add(to3.get_key());
1✔
4245
    writer->commit_and_continue_as_read();
1✔
4246
    reader = writer->duplicate();
1✔
4247
    auto ll = reader->import_copy_of(lvr);
1✔
4248
    {
1✔
4249
        // validate inside reader transaction
4250
        // Return all rows of table1 (the linked-to-table) that match the criteria and is in the LinkList
4251

4252
        // q.m_table = table1
4253
        // q.m_view = lvr
4254
        TableRef table1b = reader->get_table("table1");
1✔
4255
        Query q = table1b->where(*ll).and_query(table1b->column<Int>(col1) > 100);
1✔
4256

4257
        // tv.m_table == table1
4258
        TableView tv = q.find_all(); // tv = { 0, 2 }
1✔
4259

4260

4261
        CHECK_EQUAL(2, tv.size());
1✔
4262
        CHECK_EQUAL(to1.get_key(), tv.get_key(0));
1✔
4263
        CHECK_EQUAL(to3.get_key(), tv.get_key(1));
1✔
4264
    }
1✔
4265
    {
1✔
4266
        // Change table1 and verify that the change does not propagate through the handed-over linkview
4267
        writer->promote_to_write();
1✔
4268
        to1.set<int64_t>(col1, 50);
1✔
4269
        writer->commit_and_continue_as_read();
1✔
4270
    }
1✔
4271
    {
1✔
4272
        TableRef table1b = reader->get_table("table1");
1✔
4273
        Query q = table1b->where(*ll).and_query(table1b->column<Int>(col1) > 100);
1✔
4274

4275
        // tv.m_table == table1
4276
        TableView tv = q.find_all(); // tv = { 0, 2 }
1✔
4277

4278

4279
        CHECK_EQUAL(2, tv.size());
1✔
4280
        CHECK_EQUAL(to1.get_key(), tv.get_key(0));
1✔
4281
        CHECK_EQUAL(to3.get_key(), tv.get_key(1));
1✔
4282
    }
1✔
4283
}
1✔
4284

4285
TEST(LangBindHelper_HandoverDistinctView)
4286
{
1✔
4287
    SHARED_GROUP_TEST_PATH(path);
1✔
4288
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4289
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4290
    TransactionRef reader;
1✔
4291
    std::unique_ptr<TableView> tv2;
1✔
4292
    Obj obj2b;
1✔
4293
    {
1✔
4294
        {
1✔
4295
            TableView tv1;
1✔
4296
            auto writer = sg->start_write();
1✔
4297
            TableRef table = writer->add_table("table2");
1✔
4298
            auto col = table->add_column(type_Int, "first");
1✔
4299
            auto obj1 = table->create_object().set_all(100);
1✔
4300
            table->create_object().set_all(100);
1✔
4301

4302
            writer->commit_and_continue_as_read();
1✔
4303
            tv1 = table->where().find_all();
1✔
4304
            tv1.distinct(col);
1✔
4305
            CHECK(tv1.size() == 1);
1✔
4306
            CHECK(tv1.get_key(0) == obj1.get_key());
1✔
4307
            CHECK(tv1.is_attached());
1✔
4308

4309
            reader = writer->duplicate();
1✔
4310
            tv2 = reader->import_copy_of(tv1, PayloadPolicy::Copy);
1✔
4311
            obj2b = reader->import_copy_of(obj1);
1✔
4312
            CHECK(tv1.is_attached());
1✔
4313
        }
1✔
4314
        {
1✔
4315
            // importing side: working in the context of "reader"
4316
            CHECK(tv2->is_in_sync());
1✔
4317
            CHECK(tv2->is_attached());
1✔
4318

4319
            CHECK_EQUAL(tv2->size(), 1);
1✔
4320
            CHECK_EQUAL(tv2->get_key(0), obj2b.get_key());
1✔
4321

4322
            // distinct property must remain through handover such that second row is kept being omitted
4323
            // after sync_if_needed()
4324
            tv2->sync_if_needed();
1✔
4325
            CHECK_EQUAL(tv2->size(), 1);
1✔
4326
            CHECK_EQUAL(tv2->get_key(0), obj2b.get_key());
1✔
4327
        }
1✔
4328
    }
1✔
4329
}
1✔
4330

4331

4332
TEST(LangBindHelper_HandoverWithReverseDependency)
4333
{
1✔
4334
    SHARED_GROUP_TEST_PATH(path);
1✔
4335
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4336
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4337
    auto trans = sg->start_read();
1✔
4338
    {
1✔
4339
        // Untyped interface
4340
        TableView tv1;
1✔
4341
        TableView tv2;
1✔
4342
        ColKey ck;
1✔
4343
        {
1✔
4344
            trans->promote_to_write();
1✔
4345
            TableRef table = trans->add_table("table2");
1✔
4346
            ck = table->add_column(type_Int, "first");
1✔
4347
            for (int i = 0; i < 100; ++i) {
101✔
4348
                table->create_object().set_all(i);
100✔
4349
            }
100✔
4350
            trans->commit_and_continue_as_read();
1✔
4351
            tv1 = table->where().find_all();
1✔
4352
            tv2 = table->where(&tv1).find_all();
1✔
4353
            CHECK(tv1.is_attached());
1✔
4354
            CHECK(tv2.is_attached());
1✔
4355
            CHECK_EQUAL(100, tv1.size());
1✔
4356
            for (int i = 0; i < 100; ++i)
101✔
4357
                CHECK_EQUAL(i, tv1.get_object(i).get<int64_t>(ck));
100✔
4358
            CHECK_EQUAL(100, tv2.size());
1✔
4359
            for (int i = 0; i < 100; ++i)
101✔
4360
                CHECK_EQUAL(i, tv1.get_object(i).get<int64_t>(ck));
100✔
4361
            auto dummy_trans = trans->duplicate();
1✔
4362
            auto dummy_tv = dummy_trans->import_copy_of(tv1, PayloadPolicy::Copy);
1✔
4363
            CHECK(tv1.is_attached());
1✔
4364
            CHECK(tv2.is_attached());
1✔
4365
        }
1✔
4366
    }
1✔
4367
}
1✔
4368

4369
TEST(LangBindHelper_HandoverTableViewFromBacklink)
4370
{
1✔
4371
    SHARED_GROUP_TEST_PATH(path);
1✔
4372
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4373
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4374
    auto group_w = sg->start_write();
1✔
4375

4376
    TableRef source = group_w->add_table("source");
1✔
4377
    source->add_column(type_Int, "int");
1✔
4378

4379
    TableRef links = group_w->add_table("links");
1✔
4380
    ColKey col = links->add_column(*source, "link");
1✔
4381

4382
    std::vector<ObjKey> dummies;
1✔
4383
    source->create_objects(100, dummies);
1✔
4384
    links->create_objects(100, dummies);
1✔
4385
    auto source_it = source->begin();
1✔
4386
    auto links_it = links->begin();
1✔
4387
    for (int i = 0; i < 100; ++i) {
101✔
4388
        auto obj = source_it->set_all(i);
100✔
4389
        links_it->set(col, obj.get_key());
100✔
4390
        ++source_it;
100✔
4391
        ++links_it;
100✔
4392
    }
100✔
4393
    group_w->commit_and_continue_as_read();
1✔
4394

4395
    for (int i = 0; i < 100; ++i) {
101✔
4396
        TableView tv = source->get_object(i).get_backlink_view(links, col);
100✔
4397
        CHECK(tv.is_attached());
100✔
4398
        CHECK_EQUAL(1, tv.size());
100✔
4399
        ObjKey o_key = source->get_object(i).get_key();
100✔
4400
        CHECK_EQUAL(o_key, tv.get_key(0));
100✔
4401
        auto group = group_w->duplicate();
100✔
4402
        auto tv2 = group->import_copy_of(tv, PayloadPolicy::Copy);
100✔
4403
        CHECK(tv.is_attached());
100✔
4404
        CHECK(tv2->is_attached());
100✔
4405
        CHECK_EQUAL(1, tv2->size());
100✔
4406
        CHECK_EQUAL(o_key, tv2->get_key(0));
100✔
4407
    }
100✔
4408
}
1✔
4409

4410
// Verify that handing over an out-of-sync TableView that represents backlinks
4411
// to a deleted row results in a TableView that can be brought back into sync.
4412
TEST(LangBindHelper_HandoverOutOfSyncTableViewFromBacklinksToDeletedRow)
4413
{
1✔
4414
    SHARED_GROUP_TEST_PATH(path);
1✔
4415
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4416
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4417
    auto group_w = sg->start_write();
1✔
4418

4419
    TableRef target = group_w->add_table("target");
1✔
4420
    target->add_column(type_Int, "int");
1✔
4421

4422
    TableRef links = group_w->add_table("links");
1✔
4423
    auto col = links->add_column(*target, "link");
1✔
4424

4425
    auto obj_t = target->create_object().set_all(0);
1✔
4426

4427
    links->create_object().set_all(obj_t.get_key());
1✔
4428

4429
    TableView tv = obj_t.get_backlink_view(links, col);
1✔
4430
    CHECK_EQUAL(true, tv.is_attached());
1✔
4431
    CHECK_EQUAL(true, tv.is_in_sync());
1✔
4432
    CHECK_EQUAL(false, tv.depends_on_deleted_object());
1✔
4433
    CHECK_EQUAL(1, tv.size());
1✔
4434

4435
    // Bring the view out of sync, and have it depend on a deleted row.
4436
    target->remove_object(obj_t.get_key());
1✔
4437
    CHECK_EQUAL(true, tv.is_attached());
1✔
4438
    CHECK_EQUAL(false, tv.is_in_sync());
1✔
4439
    CHECK_EQUAL(true, tv.depends_on_deleted_object());
1✔
4440
    CHECK_EQUAL(1, tv.size());
1✔
4441
    tv.sync_if_needed();
1✔
4442
    CHECK_EQUAL(0, tv.size());
1✔
4443
    group_w->commit_and_continue_as_read();
1✔
4444
    auto group = group_w->duplicate();
1✔
4445
    auto tv2 = group->import_copy_of(tv, PayloadPolicy::Copy);
1✔
4446
    CHECK_EQUAL(true, tv2->depends_on_deleted_object());
1✔
4447
    CHECK_EQUAL(0, tv2->size());
1✔
4448
}
1✔
4449

4450
// Test that we can handover a query involving links, and that after the
4451
// handover export, the handover is completely decoupled from later changes
4452
// done on accessors belonging to the exporting shared group
4453
TEST(LangBindHelper_HandoverWithLinkQueries)
4454
{
1✔
4455
    SHARED_GROUP_TEST_PATH(path);
1✔
4456
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4457
    DBRef db = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4458
    auto group_w = db->start_write();
1✔
4459
    // First setup data so that we can do a query on links
4460
    TableRef table1 = group_w->add_table("table1");
1✔
4461
    TableRef table2 = group_w->add_table("table2");
1✔
4462
    // add some more columns to table1 and table2
4463
    table1->add_column(type_Int, "col1");
1✔
4464
    table1->add_column(type_String, "str1");
1✔
4465

4466
    table2->add_column(type_Int, "col1");
1✔
4467
    auto col_str = table2->add_column(type_String, "str2");
1✔
4468

4469
    // add some rows
4470
    auto o10 = table1->create_object().set_all(100, "foo");
1✔
4471
    auto o11 = table1->create_object().set_all(200, "!");
1✔
4472
    table1->create_object().set_all(300, "bar");
1✔
4473
    table2->create_object().set_all(400, "hello");
1✔
4474
    auto o21 = table2->create_object().set_all(500, "world");
1✔
4475
    auto o22 = table2->create_object().set_all(600, "!");
1✔
4476

4477
    ColKey col_link2 = table1->add_column_list(*table2, "link");
1✔
4478

4479
    // set some links
4480
    auto links1 = o10.get_linklist(col_link2);
1✔
4481
    CHECK(links1.is_attached());
1✔
4482
    links1.add(o21.get_key());
1✔
4483

4484
    auto links2 = o11.get_linklist(col_link2);
1✔
4485
    CHECK(links2.is_attached());
1✔
4486
    links2.add(o21.get_key());
1✔
4487
    links2.add(o22.get_key());
1✔
4488
    group_w->commit_and_continue_as_read();
1✔
4489

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

4496
    auto rec1 = group_w->duplicate();
1✔
4497
    auto q1 = rec1->import_copy_of(query, PayloadPolicy::Copy);
1✔
4498
    auto rec2 = group_w->duplicate();
1✔
4499
    auto q2 = rec2->import_copy_of(query, PayloadPolicy::Copy);
1✔
4500

4501
    {
1✔
4502
        realm::TableView tv = q1->find_all();
1✔
4503
        CHECK_EQUAL(0, tv.size());
1✔
4504
    }
1✔
4505

4506
    // On the exporting side, change the data such that the query will now have
4507
    // non-zero results if evaluated in that context.
4508
    group_w->promote_to_write();
1✔
4509
    auto o23 = table2->create_object().set_all(700, "nabil");
1✔
4510
    links1.add(o23.get_key());
1✔
4511
    group_w->commit_and_continue_as_read();
1✔
4512
    CHECK_EQUAL(1, query.count());
1✔
4513
    {
1✔
4514
        // Import query and evaluate in the old context. This should *not* be
4515
        // affected by the change done above on the exporting side.
4516
        realm::TableView tv2 = q2->find_all();
1✔
4517
        CHECK_EQUAL(0, tv2.size());
1✔
4518
    }
1✔
4519
}
1✔
4520

4521

4522
TEST(LangBindHelper_HandoverQueryLinksTo)
4523
{
1✔
4524
    SHARED_GROUP_TEST_PATH(path);
1✔
4525
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4526
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4527

4528
    TransactionRef reader;
1✔
4529
    std::unique_ptr<Query> query;
1✔
4530
    std::unique_ptr<Query> queryOr;
1✔
4531
    std::unique_ptr<Query> queryAnd;
1✔
4532
    std::unique_ptr<Query> queryNot;
1✔
4533
    std::unique_ptr<Query> queryAndAndOr;
1✔
4534
    std::unique_ptr<Query> queryWithExpression;
1✔
4535
    std::unique_ptr<Query> queryLinksToDetached;
1✔
4536
    {
1✔
4537
        auto group_w = sg->start_write();
1✔
4538
        TableRef source = group_w->add_table("source");
1✔
4539
        TableRef target = group_w->add_table("target");
1✔
4540

4541
        ColKey col_link = source->add_column(*target, "link");
1✔
4542
        ColKey col_name = target->add_column(type_String, "name");
1✔
4543

4544
        std::vector<ObjKey> keys;
1✔
4545
        target->create_objects(4, keys);
1✔
4546
        target->get_object(0).set(col_name, "A");
1✔
4547
        target->get_object(1).set(col_name, "B");
1✔
4548
        target->get_object(2).set(col_name, "C");
1✔
4549
        target->get_object(3).set(col_name, "D");
1✔
4550

4551
        source->create_object().set_all(keys[0]);
1✔
4552
        source->create_object().set_all(keys[1]);
1✔
4553
        source->create_object().set_all(keys[2]);
1✔
4554

4555
        Obj detached_row = target->get_object(3);
1✔
4556
        target->remove_object(detached_row.get_key());
1✔
4557

4558
        group_w->commit_and_continue_as_read();
1✔
4559

4560
        Query _query = source->column<Link>(col_link) == target->get_object(0);
1✔
4561
        Query _queryOr = source->column<Link>(col_link) == target->get_object(0) ||
1✔
4562
                         source->column<Link>(col_link) == target->get_object(1);
1✔
4563
        Query _queryAnd = source->column<Link>(col_link) == target->get_object(0) &&
1✔
4564
                          source->column<Link>(col_link) == target->get_object(0);
1✔
4565
        Query _queryNot = !(source->column<Link>(col_link) == target->get_object(0)) &&
1✔
4566
                          source->column<Link>(col_link) == target->get_object(1);
1✔
4567
        Query _queryAndAndOr = source->where().group().and_query(_queryOr).end_group().and_query(_queryAnd);
1✔
4568
        Query _queryWithExpression = source->column<Link>(col_link).is_not_null() && _query;
1✔
4569
        Query _queryLinksToDetached = source->where().links_to(col_link, detached_row.get_key());
1✔
4570

4571
        // handover:
4572
        reader = group_w->duplicate();
1✔
4573
        query = reader->import_copy_of(_query, PayloadPolicy::Copy);
1✔
4574
        queryOr = reader->import_copy_of(_queryOr, PayloadPolicy::Copy);
1✔
4575
        queryAnd = reader->import_copy_of(_queryAnd, PayloadPolicy::Copy);
1✔
4576
        queryNot = reader->import_copy_of(_queryNot, PayloadPolicy::Copy);
1✔
4577
        queryAndAndOr = reader->import_copy_of(_queryAndAndOr, PayloadPolicy::Copy);
1✔
4578
        queryWithExpression = reader->import_copy_of(_queryWithExpression, PayloadPolicy::Copy);
1✔
4579
        queryLinksToDetached = reader->import_copy_of(_queryLinksToDetached, PayloadPolicy::Copy);
1✔
4580

4581
        CHECK_EQUAL(1, _query.count());
1✔
4582
        CHECK_EQUAL(2, _queryOr.count());
1✔
4583
        CHECK_EQUAL(1, _queryAnd.count());
1✔
4584
        CHECK_EQUAL(1, _queryNot.count());
1✔
4585
        CHECK_EQUAL(1, _queryAndAndOr.count());
1✔
4586
        CHECK_EQUAL(1, _queryWithExpression.count());
1✔
4587
        CHECK_EQUAL(0, _queryLinksToDetached.count());
1✔
4588
    }
1✔
4589
    {
1✔
4590
        CHECK_EQUAL(1, query->count());
1✔
4591
        CHECK_EQUAL(2, queryOr->count());
1✔
4592
        CHECK_EQUAL(1, queryAnd->count());
1✔
4593
        CHECK_EQUAL(1, queryNot->count());
1✔
4594
        CHECK_EQUAL(1, queryAndAndOr->count());
1✔
4595
        CHECK_EQUAL(1, queryWithExpression->count());
1✔
4596
        CHECK_EQUAL(0, queryLinksToDetached->count());
1✔
4597

4598

4599
        // Remove the linked-to row.
4600
        {
1✔
4601
            auto group_w = sg->start_write();
1✔
4602
            TableRef target = group_w->get_table("target");
1✔
4603
            target->remove_object(target->begin()->get_key());
1✔
4604
            group_w->commit();
1✔
4605
        }
1✔
4606

4607
        // Verify that the queries against the read-only shared group gives the same results.
4608
        CHECK_EQUAL(1, query->count());
1✔
4609
        CHECK_EQUAL(2, queryOr->count());
1✔
4610
        CHECK_EQUAL(1, queryAnd->count());
1✔
4611
        CHECK_EQUAL(1, queryNot->count());
1✔
4612
        CHECK_EQUAL(1, queryAndAndOr->count());
1✔
4613
        CHECK_EQUAL(1, queryWithExpression->count());
1✔
4614
        CHECK_EQUAL(0, queryLinksToDetached->count());
1✔
4615
    }
1✔
4616
}
1✔
4617

4618

4619
TEST(LangBindHelper_HandoverQuerySubQuery)
4620
{
1✔
4621
    SHARED_GROUP_TEST_PATH(path);
1✔
4622
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4623
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4624

4625
    TransactionRef reader;
1✔
4626
    std::unique_ptr<Query> query;
1✔
4627
    {
1✔
4628
        auto group_w = sg->start_write();
1✔
4629

4630
        TableRef source = group_w->add_table("source");
1✔
4631
        TableRef target = group_w->add_table("target");
1✔
4632

4633
        ColKey col_link = source->add_column(*target, "link");
1✔
4634
        ColKey col_name = target->add_column(type_String, "name");
1✔
4635

4636
        std::vector<ObjKey> keys;
1✔
4637
        target->create_objects(3, keys);
1✔
4638
        target->get_object(keys[0]).set(col_name, "A");
1✔
4639
        target->get_object(keys[1]).set(col_name, "B");
1✔
4640
        target->get_object(keys[2]).set(col_name, "C");
1✔
4641

4642
        source->create_object().set_all(keys[0]);
1✔
4643
        source->create_object().set_all(keys[1]);
1✔
4644
        source->create_object().set_all(keys[2]);
1✔
4645

4646
        group_w->commit_and_continue_as_read();
1✔
4647

4648
        realm::Query query_2 = source->column<Link>(col_link, target->column<String>(col_name) == "C").count() == 1;
1✔
4649
        reader = group_w->duplicate();
1✔
4650
        query = reader->import_copy_of(query_2, PayloadPolicy::Copy);
1✔
4651
    }
1✔
4652

4653
    CHECK_EQUAL(1, query->count());
1✔
4654

4655
    // Remove the linked-to row.
4656
    {
1✔
4657
        auto group_w = sg->start_write();
1✔
4658

4659
        TableRef target = group_w->get_table("target");
1✔
4660
        target->clear();
1✔
4661
        group_w->commit_and_continue_as_read();
1✔
4662
    }
1✔
4663

4664
    // Verify that the queries against the read-only shared group gives the same results.
4665
    CHECK_EQUAL(1, query->count());
1✔
4666
}
1✔
4667

4668
TEST(LangBindHelper_VersionControl)
4669
{
1✔
4670
    Random random(random_int<unsigned long>());
1✔
4671

4672
    const int num_versions = 10;
1✔
4673
    const int num_random_tests = 100;
1✔
4674
    DB::VersionID versions[num_versions];
1✔
4675
    std::vector<TransactionRef> trs;
1✔
4676
    SHARED_GROUP_TEST_PATH(path);
1✔
4677
    {
1✔
4678
        // Create a new shared db
4679
        std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4680
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4681
        // first create 'num_version' versions
4682
        ColKey col;
1✔
4683
        auto reader = sg->start_read();
1✔
4684
        {
1✔
4685
            WriteTransaction wt(sg);
1✔
4686
            col = wt.get_or_add_table("test")->add_column(type_Int, "a");
1✔
4687
            wt.commit();
1✔
4688
        }
1✔
4689
        for (int i = 0; i < num_versions; ++i) {
11✔
4690
            {
10✔
4691
                WriteTransaction wt(sg);
10✔
4692
                auto t = wt.get_table("test");
10✔
4693
                t->create_object().set_all(i);
10✔
4694
                wt.commit();
10✔
4695
            }
10✔
4696
            {
10✔
4697
                auto rt = sg->start_read();
10✔
4698
                trs.push_back(rt->duplicate());
10✔
4699
                versions[i] = rt->get_version_of_current_transaction();
10✔
4700
            }
10✔
4701
        }
10✔
4702

4703
        // do steps of increasing size from the first version to the last,
4704
        // including a "step on the spot" (from version 0 to 0)
4705
        {
1✔
4706
            for (int k = 0; k < num_versions; ++k) {
11✔
4707
                // std::cerr << "Advancing from initial version to version " << k << std::endl;
4708
                auto g = sg->start_read(versions[0]);
10✔
4709
                auto t = g->get_table("test");
10✔
4710
                CHECK(versions[k] >= versions[0]);
10✔
4711
                g->verify();
10✔
4712
                g->advance_read(versions[k]);
10✔
4713
                g->verify();
10✔
4714
                auto o = *(t->begin() + k);
10✔
4715
                CHECK_EQUAL(k, o.get<int64_t>(col));
10✔
4716
            }
10✔
4717
        }
1✔
4718

4719
        // step through the versions backward:
4720
        for (int i = num_versions - 1; i >= 0; --i) {
11✔
4721
            // std::cerr << "Jumping directly to version " << i << std::endl;
4722

4723
            auto g = sg->start_read(versions[i]);
10✔
4724
            g->verify();
10✔
4725
            auto t = g->get_table("test");
10✔
4726
            auto o = *(t->begin() + i);
10✔
4727
            CHECK_EQUAL(i, o.get<int64_t>(col));
10✔
4728
        }
10✔
4729

4730
        // then advance through the versions going forward
4731
        {
1✔
4732
            auto g = sg->start_read(versions[0]);
1✔
4733
            g->verify();
1✔
4734
            auto t = g->get_table("test");
1✔
4735
            for (int k = 0; k < num_versions; ++k) {
11✔
4736
                // std::cerr << "Advancing to version " << k << std::endl;
4737
                CHECK(k == 0 || versions[k] >= versions[k - 1]);
10✔
4738

4739
                g->advance_read(versions[k]);
10✔
4740
                g->verify();
10✔
4741
                auto o = *(t->begin() + k);
10✔
4742
                CHECK_EQUAL(k, o.get<int64_t>(col));
10✔
4743
            }
10✔
4744
        }
1✔
4745
        // sync to a randomly selected version - use advance_read when going
4746
        // forward in time, but begin_read when going back in time
4747
        int old_version = 0;
1✔
4748
        auto g = sg->start_read(versions[old_version]);
1✔
4749
        auto t = g->get_table("test");
1✔
4750
        for (int k = num_random_tests; k; --k) {
101✔
4751
            int new_version = random.draw_int_mod(num_versions);
100✔
4752
            // std::cerr << "Random jump: version " << old_version << " -> " << new_version << std::endl;
4753
            if (new_version < old_version) {
100✔
4754
                CHECK(versions[new_version] < versions[old_version]);
41✔
4755
                g->end_read();
41✔
4756
                g = sg->start_read(versions[new_version]);
41✔
4757
                g->verify();
41✔
4758
                t = g->get_table("test");
41✔
4759
                auto o = *(t->begin() + new_version);
41✔
4760
                CHECK_EQUAL(new_version, o.get<int64_t>(col));
41✔
4761
            }
41✔
4762
            else {
59✔
4763
                CHECK(versions[new_version] >= versions[old_version]);
59✔
4764
                g->verify();
59✔
4765
                g->advance_read(versions[new_version]);
59✔
4766
                g->verify();
59✔
4767
                auto o = *(t->begin() + new_version);
59✔
4768
                CHECK_EQUAL(new_version, o.get<int64_t>(col));
59✔
4769
            }
59✔
4770
            old_version = new_version;
100✔
4771
        }
100✔
4772
        trs.clear();
1✔
4773
        g->end_read();
1✔
4774
        // release the first readlock and commit something to force a cleanup
4775
        // we need to commit twice, because cleanup is done before the actual
4776
        // commit, so during the first commit, the last of the previous versions
4777
        // will still be kept. To get rid of it, we must commit once more.
4778
        reader->end_read();
1✔
4779
        g = sg->start_write();
1✔
4780
        g->commit();
1✔
4781
        g = sg->start_write();
1✔
4782
        g->commit();
1✔
4783

4784
        // Validate that all the versions are now unreachable
4785
        for (int i = 0; i < num_versions; ++i)
11✔
4786
            CHECK_THROW(sg->start_read(versions[i]), DB::BadVersion);
10✔
4787
    }
1✔
4788
}
1✔
4789

4790
TEST(LangBindHelper_RollbackToInitialState1)
4791
{
1✔
4792
    SHARED_GROUP_TEST_PATH(path);
1✔
4793
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
4794
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
4795
    auto trans = sg_w->start_read();
1✔
4796
    trans->promote_to_write();
1✔
4797
    trans->rollback_and_continue_as_read();
1✔
4798
}
1✔
4799

4800

4801
TEST(LangBindHelper_RollbackToInitialState2)
4802
{
1✔
4803
    SHARED_GROUP_TEST_PATH(path);
1✔
4804
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
4805
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
4806
    auto trans = sg_w->start_write();
1✔
4807
    trans->rollback();
1✔
4808
}
1✔
4809

4810
// non-concurrent because we test the filesystem which may
4811
// be used by other tests at the same time otherwise
4812
NONCONCURRENT_TEST(LangBindHelper_Compact)
4813
{
1✔
4814
    SHARED_GROUP_TEST_PATH(path);
1✔
4815
    size_t N = 100;
1✔
4816
    std::string dir_path = File::parent_dir(path);
1✔
4817
    dir_path = dir_path.empty() ? "." : dir_path;
1✔
4818
    auto dir_has_tmp_compaction = [&dir_path]() -> size_t {
3✔
4819
        DirScanner dir(dir_path);
3✔
4820
        std::string name;
3✔
4821
        while (dir.next(name)) {
93✔
4822
            if (name.find("tmp_compaction_space") != std::string::npos) {
90✔
4823
                return true;
×
4824
            }
×
4825
        }
90✔
4826
        return false;
3✔
4827
    };
3✔
4828

4829
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4830
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
4831
    {
1✔
4832
        WriteTransaction w(sg);
1✔
4833
        TableRef table = w.get_or_add_table("test");
1✔
4834
        table->add_column(type_Int, "int");
1✔
4835
        for (size_t i = 0; i < N; ++i) {
101✔
4836
            table->create_object().set_all(static_cast<signed>(i));
100✔
4837
        }
100✔
4838
        w.commit();
1✔
4839
    }
1✔
4840
    {
1✔
4841
        ReadTransaction r(sg);
1✔
4842
        ConstTableRef table = r.get_table("test");
1✔
4843
        CHECK_EQUAL(N, table->size());
1✔
4844
        CHECK(File::exists(dir_path));
1✔
4845
        CHECK(File::is_dir(dir_path));
1✔
4846
        CHECK(!dir_has_tmp_compaction());
1✔
4847
    }
1✔
4848
    {
1✔
4849
        CHECK_EQUAL(true, sg->compact());
1✔
4850
        CHECK(!dir_has_tmp_compaction());
1✔
4851
    }
1✔
4852
    {
1✔
4853
        ReadTransaction r(sg);
1✔
4854
        ConstTableRef table = r.get_table("test");
1✔
4855
        CHECK_EQUAL(N, table->size());
1✔
4856
    }
1✔
4857
    {
1✔
4858
        WriteTransaction w(sg);
1✔
4859
        TableRef table = w.get_or_add_table("test");
1✔
4860
        table->create_object().set_all(0);
1✔
4861
        w.commit();
1✔
4862
    }
1✔
4863
    {
1✔
4864
        CHECK_EQUAL(true, sg->compact());
1✔
4865
        CHECK(!dir_has_tmp_compaction());
1✔
4866
    }
1✔
4867
}
1✔
4868

4869
TEST(LangBindHelper_CompactLargeEncryptedFile)
4870
{
1✔
4871
    SHARED_GROUP_TEST_PATH(path);
1✔
4872

4873
    std::vector<char> data(realm::util::page_size());
1✔
4874
    const size_t N = 32;
1✔
4875

4876
    {
1✔
4877
        std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4878
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key(true)));
1✔
4879
        WriteTransaction wt(sg);
1✔
4880
        TableRef table = wt.get_or_add_table("test");
1✔
4881
        table->add_column(type_String, "string");
1✔
4882
        for (size_t i = 0; i < N; ++i) {
33✔
4883
            table->create_object().set_all(StringData(data.data(), data.size()));
32✔
4884
        }
32✔
4885
        wt.commit();
1✔
4886

4887
        CHECK_EQUAL(true, sg->compact());
1✔
4888

4889
        sg->close();
1✔
4890
    }
1✔
4891

4892
    {
1✔
4893
        std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4894
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key(true)));
1✔
4895
        ReadTransaction r(sg);
1✔
4896
        ConstTableRef table = r.get_table("test");
1✔
4897
        CHECK_EQUAL(N, table->size());
1✔
4898
    }
1✔
4899
}
1✔
4900

4901
TEST(LangBindHelper_CloseDBvsTransactions)
4902
{
1✔
4903
    SHARED_GROUP_TEST_PATH(path);
1✔
4904
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
4905
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key(true)));
1✔
4906
    auto tr0 = sg->start_read();
1✔
4907
    auto tr1 = sg->start_write();
1✔
4908
    CHECK(tr1->add_table("possible"));
1✔
4909
    // write transactions must be closed (one way or the other) before DB::close
4910
    CHECK_THROW(sg->close(), LogicError);
1✔
4911
    tr1->rollback();
1✔
4912
    // closing the DB explicitly while there are open read transactions will fail
4913
    CHECK_THROW(sg->close(), LogicError);
1✔
4914
    // unless we explicitly ask for it to succeed()
4915
    sg->close(true);
1✔
4916
    CHECK(!sg->is_attached());
1✔
4917
    CHECK(!tr0->is_attached());
1✔
4918
    CHECK(!tr1->is_attached());
1✔
4919
    CHECK_THROW(sg->start_read(), LogicError);
1✔
4920
}
1✔
4921

4922
TEST(LangBindHelper_TableViewAggregateAfterAdvanceRead)
4923
{
1✔
4924
    SHARED_GROUP_TEST_PATH(path);
1✔
4925

4926
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
4927
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
4928
    ColKey col;
1✔
4929
    {
1✔
4930
        WriteTransaction w(sg_w);
1✔
4931
        TableRef table = w.add_table("test");
1✔
4932
        col = table->add_column(type_Double, "double");
1✔
4933
        table->create_object().set_all(1234.0);
1✔
4934
        table->create_object().set_all(-5678.0);
1✔
4935
        table->create_object().set_all(1000.0);
1✔
4936
        w.commit();
1✔
4937
    }
1✔
4938

4939
    auto reader = sg_w->start_read();
1✔
4940
    auto table_r = reader->get_table("test");
1✔
4941

4942
    // Create a table view with all refs detached.
4943
    TableView view = table_r->where().find_all();
1✔
4944
    {
1✔
4945
        WriteTransaction w(sg_w);
1✔
4946
        w.get_table("test")->clear();
1✔
4947
        w.commit();
1✔
4948
    }
1✔
4949
    reader->advance_read();
1✔
4950

4951
    // Verify that an aggregate on the view with detached refs gives the expected result.
4952
    CHECK_EQUAL(false, view.is_in_sync());
1✔
4953
    ObjKey res;
1✔
4954
    CHECK(view.min(col, &res)->is_null());
1✔
4955
    CHECK_EQUAL(ObjKey(), res);
1✔
4956

4957
    // Sync the view to discard the detached refs.
4958
    view.sync_if_needed();
1✔
4959

4960
    // Verify that an aggregate on the view still gives the expected result.
4961
    res = ObjKey();
1✔
4962
    CHECK(view.min(col, &res)->is_null());
1✔
4963
    CHECK_EQUAL(ObjKey(), res);
1✔
4964
}
1✔
4965

4966
// Tests handover of a Query. Especially it tests if next-gen-syntax nodes are deep copied correctly by
4967
// executing an imported query multiple times in parallel
4968
TEST_IF(LangBindHelper_HandoverFuzzyTest, TEST_DURATION > 0)
4969
{
×
4970
    SHARED_GROUP_TEST_PATH(path);
×
4971

4972
    const size_t threads = 5;
×
4973

4974
    size_t numberOfOwner = 100;
×
4975
    size_t numberOfDogsPerOwner = 20;
×
4976

4977
    std::atomic<bool> end_signal(false);
×
4978
    std::unique_ptr<Replication> hist(make_in_realm_history());
×
4979
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
×
4980

4981
    std::vector<TransactionRef> vids;
×
4982
    std::vector<std::unique_ptr<Query>> qs;
×
4983
    std::mutex vector_mutex;
×
4984

4985
    ColKey c0, c1, c2, c3;
×
4986
    {
×
4987
        auto rt = sg->start_write();
×
4988

4989
        TableRef owner = rt->add_table("Owner");
×
4990
        TableRef dog = rt->add_table("Dog");
×
4991

4992
        c0 = owner->add_column(type_String, "name");
×
4993
        c1 = owner->add_column_list(*dog, "link");
×
4994

4995
        c2 = dog->add_column(type_String, "name");
×
4996
        c3 = dog->add_column(*owner, "link");
×
4997

4998
        for (size_t i = 0; i < numberOfOwner; i++) {
×
4999

5000
            auto o = owner->create_object();
×
5001
            std::string owner_str(std::string("owner") + to_string(i));
×
5002
            o.set<StringData>(c0, owner_str);
×
5003

5004
            for (size_t j = 0; j < numberOfDogsPerOwner; j++) {
×
5005
                auto o_d = dog->create_object();
×
5006
                std::string dog_str(std::string("dog") + to_string(i * numberOfOwner + j));
×
5007
                o_d.set<StringData>(c2, dog_str);
×
5008
                o_d.set(c3, o.get_key());
×
5009
                auto ll = o.get_linklist(c1);
×
5010
                ll.add(o_d.get_key());
×
5011
            }
×
5012
        }
×
5013
        rt->verify();
×
5014
        {
×
5015
            realm::Query query = dog->link(c3).column<String>(c0) == "owner" + to_string(rand() % numberOfOwner);
×
5016
            query.find_all(); // <-- fails
×
5017
        }
×
5018
        rt->commit();
×
5019
    }
×
5020

5021
    auto async = [&]() {
×
5022
        // Async thread
5023
        //************************************************************************************************
5024
        while (!end_signal) {
×
5025
            millisleep(10);
×
5026

5027
            vector_mutex.lock();
×
5028
            if (qs.size() > 0) {
×
5029

5030
                auto t = vids[0];
×
5031
                vids.erase(vids.begin());
×
5032
                auto q = std::move(qs[0]);
×
5033
                qs.erase(qs.begin());
×
5034
                vector_mutex.unlock();
×
5035

5036
                realm::TableView tv = q->find_all();
×
5037
            }
×
5038
            else {
×
5039
                vector_mutex.unlock();
×
5040
            }
×
5041
        }
×
5042
        //************************************************************************************************
5043
    };
×
5044

5045
    auto rt = sg->start_read();
×
5046
    // Create and export query
5047
    TableRef dog = rt->get_table("Dog");
×
5048

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

5052
    std::thread slaves[threads];
×
5053
    for (int i = 0; i != threads; ++i) {
×
5054
        slaves[i] = std::thread(async);
×
5055
    }
×
5056

5057
    // Main thread
5058
    //************************************************************************************************
5059
    for (size_t iter = 0; iter < 20 + TEST_DURATION * TEST_DURATION * 500; iter++) {
×
5060
        vector_mutex.lock();
×
5061
        rt->promote_to_write();
×
5062
        rt->commit_and_continue_as_read();
×
5063
        if (qs.size() < 100) {
×
5064
            for (size_t t = 0; t < 5; t++) {
×
5065
                auto t2 = rt->duplicate();
×
5066
                qs.push_back(t2->import_copy_of(query, PayloadPolicy::Move));
×
5067
                vids.push_back(t2);
×
5068
            }
×
5069
        }
×
5070
        vector_mutex.unlock();
×
5071

5072
        millisleep(100);
×
5073
    }
×
5074
    //************************************************************************************************
5075

5076
    end_signal = true;
×
5077
    for (int i = 0; i != threads; ++i)
×
5078
        slaves[i].join();
×
5079
}
×
5080

5081

5082
// TableView::clear() was originally reported to be slow when table was indexed and had links, but performance
5083
// has now doubled. This test is just a short sanity test that clear() still works.
5084
TEST(LangBindHelper_TableViewClear)
5085
{
1✔
5086
    SHARED_GROUP_TEST_PATH(path);
1✔
5087

5088
    int64_t number_of_history = 1000;
1✔
5089
    int64_t number_of_line = 18;
1✔
5090

5091
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
5092
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
5093
    TransactionRef tr;
1✔
5094
    ColKey col0, col1, col2, colA, colB;
1✔
5095
    // set up tables:
5096
    // history : ["id" (int), "parent" (int), "lines" (list(line))]
5097
    // line    : ["id" (int), "parent" (int)]
5098
    {
1✔
5099
        tr = sg->start_write();
1✔
5100

5101
        TableRef history = tr->add_table("history");
1✔
5102
        TableRef line = tr->add_table("line");
1✔
5103

5104
        col0 = history->add_column(type_Int, "id");
1✔
5105
        col1 = history->add_column(type_Int, "parent");
1✔
5106
        col2 = history->add_column_list(*line, "lines");
1✔
5107
        history->add_search_index(col1);
1✔
5108

5109
        colA = line->add_column(type_Int, "id");
1✔
5110
        colB = line->add_column(type_Int, "parent");
1✔
5111
        line->add_search_index(colB);
1✔
5112
        tr->commit_and_continue_as_read();
1✔
5113
    }
1✔
5114

5115
    {
1✔
5116
        tr->promote_to_write();
1✔
5117

5118
        TableRef history = tr->get_table("history");
1✔
5119
        TableRef line = tr->get_table("line");
1✔
5120

5121
        auto obj = history->create_object();
1✔
5122
        obj.set(col0, 1);
1✔
5123
        auto ll = obj.get_linklist(col2);
1✔
5124
        for (int64_t j = 0; j < number_of_line; ++j) {
19✔
5125
            Obj o = line->create_object().set_all(j, 0);
18✔
5126
            ll.add(o.get_key());
18✔
5127
        }
18✔
5128

5129
        for (int64_t i = 1; i < number_of_history; ++i) {
1,000✔
5130
            history->create_object().set_all(i, i + 1);
999✔
5131
            int64_t rj = i * number_of_line;
999✔
5132
            for (int64_t j = 1; j <= number_of_line; ++j) {
18,981✔
5133
                line->create_object().set_all(rj, j);
17,982✔
5134
                ++rj;
17,982✔
5135
            }
17,982✔
5136
        }
999✔
5137
        tr->commit_and_continue_as_read();
1✔
5138
        CHECK_EQUAL(number_of_history, history->size());
1✔
5139
        CHECK_EQUAL(number_of_history * number_of_line, line->size());
1✔
5140
    }
1✔
5141

5142
    // query and delete
5143
    {
1✔
5144
        tr->promote_to_write();
1✔
5145

5146
        TableRef line = tr->get_table("line");
1✔
5147

5148
        //    number_of_line = 2;
5149
        for (int64_t i = 1; i <= number_of_line; ++i) {
19✔
5150
            TableView tv = (line->column<Int>(colB) == i).find_all();
18✔
5151
            tv.clear();
18✔
5152
        }
18✔
5153
        tr->commit_and_continue_as_read();
1✔
5154
    }
1✔
5155

5156
    {
1✔
5157
        TableRef history = tr->get_table("history");
1✔
5158
        TableRef line = tr->get_table("line");
1✔
5159

5160
        CHECK_EQUAL(number_of_history, history->size());
1✔
5161
        CHECK_EQUAL(number_of_line, line->size());
1✔
5162
    }
1✔
5163
}
1✔
5164

5165

5166
TEST(LangBindHelper_SessionHistoryConsistency)
5167
{
1✔
5168
    // Check that we can reliably detect inconsist history
5169
    // types across concurrent session participants.
5170

5171
    // Errors of this kind are considered as incorrect API usage, and will lead
5172
    // to throwing of LogicError exceptions.
5173

5174
    SHARED_GROUP_TEST_PATH(path);
1✔
5175

5176
    // When starting with an empty Realm, all history types are allowed, but all
5177
    // session participants must still agree
5178
    {
1✔
5179
        // No history
5180
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
1✔
5181

5182
        // Out-of-Realm history
5183
        std::unique_ptr<Replication> hist = realm::make_in_realm_history();
1✔
5184
        CHECK_RUNTIME_ERROR(DB::create(*hist, path, DBOptions(crypt_key())), ErrorCodes::IncompatibleSession);
1✔
5185
    }
1✔
5186
}
1✔
5187

5188

5189
TEST(LangBindHelper_InRealmHistory_Upgrade)
5190
{
1✔
5191
    SHARED_GROUP_TEST_PATH(path_1);
1✔
5192
    {
1✔
5193
        // Out-of-Realm history
5194
        std::unique_ptr<Replication> hist = make_in_realm_history();
1✔
5195
        DBRef sg = DB::create(*hist, path_1, DBOptions(crypt_key()));
1✔
5196
        WriteTransaction wt(sg);
1✔
5197
        wt.commit();
1✔
5198
    }
1✔
5199
    {
1✔
5200
        // In-Realm history
5201
        std::unique_ptr<Replication> hist = make_in_realm_history();
1✔
5202
        DBRef sg = DB::create(*hist, path_1, DBOptions(crypt_key()));
1✔
5203
        WriteTransaction wt(sg);
1✔
5204
        wt.commit();
1✔
5205
    }
1✔
5206
    SHARED_GROUP_TEST_PATH(path_2);
1✔
5207
    {
1✔
5208
        // No history
5209
        DBRef sg = DB::create(path_2, DBOptions(crypt_key()));
1✔
5210
        WriteTransaction wt(sg);
1✔
5211
        wt.commit();
1✔
5212
    }
1✔
5213
    {
1✔
5214
        // In-Realm history
5215
        std::unique_ptr<Replication> hist = make_in_realm_history();
1✔
5216
        DBRef sg = DB::create(*hist, path_2, DBOptions(crypt_key()));
1✔
5217
        WriteTransaction wt(sg);
1✔
5218
        wt.commit();
1✔
5219
    }
1✔
5220
}
1✔
5221

5222
TEST(LangBindHelper_InRealmHistory_Downgrade)
5223
{
1✔
5224
    SHARED_GROUP_TEST_PATH(path);
1✔
5225
    {
1✔
5226
        // In-Realm history
5227
        std::unique_ptr<Replication> hist = make_in_realm_history();
1✔
5228
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
5229
        WriteTransaction wt(sg);
1✔
5230
        wt.commit();
1✔
5231
    }
1✔
5232
    {
1✔
5233
        // No history
5234
        CHECK_THROW(DB::create(path, DBOptions(crypt_key())), IncompatibleHistories);
1✔
5235
    }
1✔
5236
}
1✔
5237

5238
// Trigger erase_rows with num_rows == 0 by inserting zero rows
5239
// and then rolling back the transaction. There was a problem
5240
// where accessors were not updated correctly in this case because
5241
// of an early out when num_rows_to_erase is zero.
5242
TEST(LangBindHelper_RollbackInsertZeroRows)
5243
{
1✔
5244
    SHARED_GROUP_TEST_PATH(path)
1✔
5245
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
5246
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
5247
    auto g = sg->start_write();
1✔
5248

5249
    auto t0 = g->add_table("t0");
1✔
5250
    auto t1 = g->add_table("t1");
1✔
5251

5252
    auto col = t0->add_column(*t1, "t0_link_to_t1");
1✔
5253
    t0->create_object();
1✔
5254
    auto o1 = t0->create_object();
1✔
5255
    t1->create_object();
1✔
5256
    auto v1 = t1->create_object();
1✔
5257
    o1.set(col, v1.get_key());
1✔
5258

5259
    CHECK_EQUAL(t0->size(), 2);
1✔
5260
    CHECK_EQUAL(t1->size(), 2);
1✔
5261
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
1✔
5262

5263
    g->commit_and_continue_as_read();
1✔
5264
    g->promote_to_write();
1✔
5265

5266
    std::vector<ObjKey> keys;
1✔
5267
    t1->create_objects(0, keys); // Insert zero rows
1✔
5268

5269
    CHECK_EQUAL(t0->size(), 2);
1✔
5270
    CHECK_EQUAL(t1->size(), 2);
1✔
5271
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
1✔
5272

5273
    g->rollback_and_continue_as_read();
1✔
5274
    g->verify();
1✔
5275

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

5281

5282
TEST(LangBindHelper_RollbackRemoveZeroRows)
5283
{
1✔
5284
    SHARED_GROUP_TEST_PATH(path)
1✔
5285
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
5286
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
1✔
5287
    auto g = sg->start_write();
1✔
5288

5289
    auto t0 = g->add_table("t0");
1✔
5290
    auto t1 = g->add_table("t1");
1✔
5291

5292
    auto col = t0->add_column(*t1, "t0_link_to_t1");
1✔
5293
    t0->create_object();
1✔
5294
    auto o1 = t0->create_object();
1✔
5295
    t1->create_object();
1✔
5296
    auto v1 = t1->create_object();
1✔
5297
    o1.set(col, v1.get_key());
1✔
5298

5299
    CHECK_EQUAL(t0->size(), 2);
1✔
5300
    CHECK_EQUAL(t1->size(), 2);
1✔
5301
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
1✔
5302

5303
    g->commit_and_continue_as_read();
1✔
5304
    g->promote_to_write();
1✔
5305

5306
    t1->clear();
1✔
5307

5308
    CHECK_EQUAL(t0->size(), 2);
1✔
5309
    CHECK_EQUAL(t1->size(), 0);
1✔
5310
    CHECK_EQUAL(o1.get<ObjKey>(col), ObjKey());
1✔
5311

5312
    g->rollback_and_continue_as_read();
1✔
5313
    g->verify();
1✔
5314

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

5320
// Bug found by AFL during development of TimestampColumn
5321
TEST_TYPES(LangBindHelper_AddEmptyRowsAndRollBackTimestamp, std::true_type, std::false_type)
5322
{
2✔
5323
    constexpr bool nullable_toggle = TEST_TYPE::value;
2✔
5324
    SHARED_GROUP_TEST_PATH(path);
2✔
5325
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5326
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
5327
    auto g = sg_w->start_write();
2✔
5328
    TableRef t = g->add_table("");
2✔
5329
    t->add_column(type_Int, "", nullable_toggle);
2✔
5330
    t->add_column(type_Timestamp, "gnyf", nullable_toggle);
2✔
5331
    g->commit_and_continue_as_read();
2✔
5332
    g->promote_to_write();
2✔
5333
    std::vector<ObjKey> keys;
2✔
5334
    t->create_objects(224, keys);
2✔
5335
    g->rollback_and_continue_as_read();
2✔
5336
    g->verify();
2✔
5337
}
2✔
5338

5339
// Another bug found by AFL during development of TimestampColumn
5340
TEST_TYPES(LangBindHelper_EmptyWrites, std::true_type, std::false_type)
5341
{
2✔
5342
    constexpr bool nullable_toggle = TEST_TYPE::value;
2✔
5343
    SHARED_GROUP_TEST_PATH(path);
2✔
5344
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5345
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
5346
    auto g = sg_w->start_write();
2✔
5347
    TableRef t = g->add_table("");
2✔
5348
    t->add_column(type_Timestamp, "gnyf", nullable_toggle);
2✔
5349

5350
    for (int i = 0; i < 27; ++i) {
56✔
5351
        g->commit_and_continue_as_read();
54✔
5352
        g->promote_to_write();
54✔
5353
    }
54✔
5354

5355
    t->create_object();
2✔
5356
}
2✔
5357

5358

5359
// Found by AFL
5360
TEST_TYPES(LangBindHelper_SetTimestampRollback, std::true_type, std::false_type)
5361
{
2✔
5362
    constexpr bool nullable_toggle = TEST_TYPE::value;
2✔
5363
    SHARED_GROUP_TEST_PATH(path);
2✔
5364
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5365
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
5366
    auto g = sg->start_write();
2✔
5367
    auto table = g->add_table("");
2✔
5368
    table->add_column(type_Timestamp, "gnyf", nullable_toggle);
2✔
5369
    table->create_object().set_all(Timestamp(-1, -1));
2✔
5370
    g->rollback_and_continue_as_read();
2✔
5371
    g->verify();
2✔
5372
}
2✔
5373

5374

5375
// Found by AFL, probably related to the rollback version above
5376
TEST_TYPES(LangBindHelper_SetTimestampAdvanceRead, std::true_type, std::false_type)
5377
{
2✔
5378
    constexpr bool nullable_toggle = TEST_TYPE::value;
2✔
5379
    SHARED_GROUP_TEST_PATH(path);
2✔
5380
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5381
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5382
    auto g_r = sg->start_read();
2✔
5383
    auto g_w = sg->start_write();
2✔
5384
    auto table = g_w->add_table("");
2✔
5385
    table->add_column(type_Timestamp, "gnyf", nullable_toggle);
2✔
5386
    table->create_object().set_all(Timestamp(-1, -1));
2✔
5387
    g_w->commit_and_continue_as_read();
2✔
5388
    g_w->verify();
2✔
5389
    g_r->advance_read();
2✔
5390
    g_r->verify();
2✔
5391
}
2✔
5392

5393

5394
// Found by AFL.
5395
TEST(LangbindHelper_BoolSearchIndexCommitPromote)
5396
{
1✔
5397
    SHARED_GROUP_TEST_PATH(path);
1✔
5398
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
5399
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
5400
    auto g = sg->start_write();
1✔
5401
    auto t = g->add_table("");
1✔
5402
    auto col = t->add_column(type_Bool, "gnyf", true);
1✔
5403
    std::vector<ObjKey> keys;
1✔
5404
    t->create_objects(5, keys);
1✔
5405
    t->get_object(keys[0]).set(col, false);
1✔
5406
    t->add_search_index(col);
1✔
5407
    g->commit_and_continue_as_read();
1✔
5408
    g->promote_to_write();
1✔
5409
    t->create_objects(5, keys);
1✔
5410
    t->remove_object(keys[8]);
1✔
5411
}
1✔
5412

5413

5414
// Found by AFL.
5415
TEST(LangbindHelper_GroupWriter_EdgeCaseAssert)
5416
{
1✔
5417
    SHARED_GROUP_TEST_PATH(path);
1✔
5418
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
5419
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
5420
    auto g_r = sg->start_read();
1✔
5421
    auto g_w = sg->start_write();
1✔
5422

5423
    auto t1 = g_w->add_table("dgrpnpgmjbchktdgagmqlihjckcdhpjccsjhnqlcjnbterse");
1✔
5424
    auto t2 = g_w->add_table("pknglaqnckqbffehqfgjnrepcfohoedkhiqsiedlotmaqitm");
1✔
5425
    t1->add_column(type_Double, "ggotpkoshbrcrmmqbagbfjetajlrrlbpjhhqrngfgdteilmj", true);
1✔
5426
    t2->add_column_list(*t1, "dtkiipajqdsfglbptieibknaoeeohqdlhftqmlriphobspjr");
1✔
5427
    std::vector<ObjKey> keys;
1✔
5428
    t1->create_objects(375, keys);
1✔
5429
    g_w->add_table("pnsidlijqeddnsgaesiijrrqedkdktmfekftogjccerhpeil");
1✔
5430
    g_r->close();
1✔
5431
    g_w->commit();
1✔
5432
    REALM_ASSERT_RELEASE(sg->compact());
1✔
5433
    g_w = sg->start_write();
1✔
5434
    g_r = sg->start_read();
1✔
5435
    g_r->verify();
1✔
5436
    g_w->add_table("citdgiaclkfbbksfaqegcfiqcserceaqmttkilnlbknoadtb");
1✔
5437
    g_w->add_table("tqtnnikpggeakeqcqhfqtshmimtjqkchgbnmbpttbetlahfi");
1✔
5438
    g_w->add_table("hkesaecjqbkemmmkffctacsnskekjbtqmpoetjnqkpactenf");
1✔
5439
    g_r->close();
1✔
5440
    g_w->commit();
1✔
5441
}
1✔
5442

5443
TEST(LangBindHelper_Bug2321)
5444
{
1✔
5445
    SHARED_GROUP_TEST_PATH(path);
1✔
5446
    ShortCircuitHistory hist;
1✔
5447
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
1✔
5448
    int i;
1✔
5449
    std::vector<ObjKey> target_keys;
1✔
5450
    std::vector<ObjKey> origin_keys;
1✔
5451
    ColKey col;
1✔
5452
    {
1✔
5453
        WriteTransaction wt(sg);
1✔
5454
        Group& group = wt.get_group();
1✔
5455
        TableRef target = group.add_table("target");
1✔
5456
        target->add_column(type_Int, "data");
1✔
5457
        target->create_objects(REALM_MAX_BPNODE_SIZE + 2, target_keys);
1✔
5458
        TableRef origin = group.add_table("origin");
1✔
5459
        col = origin->add_column_list(*target, "_link");
1✔
5460
        origin->create_objects(2, origin_keys);
1✔
5461
        wt.commit();
1✔
5462
    }
1✔
5463

5464
    {
1✔
5465
        WriteTransaction wt(sg);
1✔
5466
        Group& group = wt.get_group();
1✔
5467
        TableRef origin = group.get_table("origin");
1✔
5468
        auto lv0 = origin->begin()->get_linklist(col);
1✔
5469
        for (i = 0; i < (REALM_MAX_BPNODE_SIZE - 1); i++) {
1,000✔
5470
            lv0.add(target_keys[i]);
999✔
5471
        }
999✔
5472
        wt.commit();
1✔
5473
    }
1✔
5474

5475
    auto reader = sg->start_read();
1✔
5476
    auto lv1 = reader->get_table("origin")->begin()->get_linklist(col);
1✔
5477
    {
1✔
5478
        WriteTransaction wt(sg);
1✔
5479
        Group& group = wt.get_group();
1✔
5480
        TableRef origin = group.get_table("origin");
1✔
5481
        auto lv0 = origin->begin()->get_linklist(col);
1✔
5482
        lv0.add(target_keys[i++]);
1✔
5483
        lv0.add(target_keys[i++]);
1✔
5484
        wt.commit();
1✔
5485
    }
1✔
5486

5487
    // If MAX_BPNODE_SIZE is 4 and we run in debug mode, then the LinkView
5488
    // accessor was not refreshed correctly. It would still be a leaf class,
5489
    // but the header flags would tell it is a node.
5490
    reader->advance_read();
1✔
5491
    CHECK_EQUAL(lv1.size(), i);
1✔
5492
}
1✔
5493

5494
TEST(LangBindHelper_Bug2295)
5495
{
1✔
5496
    SHARED_GROUP_TEST_PATH(path);
1✔
5497
    ShortCircuitHistory hist;
1✔
5498
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
1✔
5499
    int i;
1✔
5500
    std::vector<ObjKey> target_keys;
1✔
5501
    std::vector<ObjKey> origin_keys;
1✔
5502
    ColKey col;
1✔
5503
    {
1✔
5504
        WriteTransaction wt(sg);
1✔
5505
        Group& group = wt.get_group();
1✔
5506
        TableRef target = group.add_table("target");
1✔
5507
        target->add_column(type_Int, "data");
1✔
5508
        target->create_objects(REALM_MAX_BPNODE_SIZE + 2, target_keys);
1✔
5509
        TableRef origin = group.add_table("origin");
1✔
5510
        col = origin->add_column_list(*target, "_link");
1✔
5511
        origin->create_objects(2, origin_keys);
1✔
5512
        wt.commit();
1✔
5513
    }
1✔
5514

5515
    {
1✔
5516
        WriteTransaction wt(sg);
1✔
5517
        Group& group = wt.get_group();
1✔
5518
        TableRef origin = group.get_table("origin");
1✔
5519
        auto lv0 = origin->begin()->get_linklist(col);
1✔
5520
        for (i = 0; i < (REALM_MAX_BPNODE_SIZE - 1); i++) {
1,000✔
5521
            lv0.add(target_keys[i]);
999✔
5522
        }
999✔
5523
        wt.commit();
1✔
5524
    }
1✔
5525

5526
    auto reader = sg->start_read();
1✔
5527
    auto lv1 = reader->get_table("origin")->begin()->get_linklist(col);
1✔
5528
    CHECK_EQUAL(lv1.size(), i);
1✔
5529
    {
1✔
5530
        WriteTransaction wt(sg);
1✔
5531
        Group& group = wt.get_group();
1✔
5532
        TableRef origin = group.get_table("origin");
1✔
5533
        // With the error present, this will cause some areas to be freed
5534
        // that has already been freed in the above transaction
5535
        auto lv0 = origin->begin()->get_linklist(col);
1✔
5536
        lv0.add(target_keys[i++]);
1✔
5537
        wt.commit();
1✔
5538
    }
1✔
5539
    reader->promote_to_write();
1✔
5540
    // Here we write the duplicates to the free list
5541
    reader->commit_and_continue_as_read();
1✔
5542
    reader->verify();
1✔
5543
    CHECK_EQUAL(lv1.size(), i);
1✔
5544
}
1✔
5545

5546
#ifdef LEGACY_TESTS // FIXME: Requires get_at() method to be available on Obj.
5547
ONLY(LangBindHelper_BigBinary)
5548
{
5549
    SHARED_GROUP_TEST_PATH(path);
5550
    ShortCircuitHistory hist;
5551
    DBRef sg = DB::create(hist, path);
5552
    std::string big_data(0x1000000, 'x');
5553
    auto rt = sg->start_read();
5554
    auto wt = sg->start_write();
5555

5556
    std::string data(16777362, 'y');
5557
    TableRef target = wt->add_table("big");
5558
    auto col = target->add_column(type_Binary, "data");
5559
    target->create_object().set(col, BinaryData(data.data(), data.size()));
5560
    wt->commit();
5561
    rt->advance_read();
5562
    {
5563
        WriteTransaction wt(sg);
5564
        TableRef t = wt.get_table("big");
5565
        t->begin()->set(col, BinaryData(big_data.data(), big_data.size()));
5566
        wt.get_group().verify();
5567
        wt.commit();
5568
    }
5569
    rt->advance_read();
5570
    auto t = rt->get_table("big");
5571
    size_t pos = 0;
5572
    BinaryData bin = t->begin()->get_at(col, pos); // <---- not there yet?
5573
    CHECK_EQUAL(memcmp(big_data.data(), bin.data(), bin.size()), 0);
5574
}
5575
#endif
5576

5577
TEST(LangBindHelper_CopyOnWriteOverflow)
5578
{
1✔
5579
    SHARED_GROUP_TEST_PATH(path);
1✔
5580
    ShortCircuitHistory hist;
1✔
5581
    DBRef sg = DB::create(hist, path);
1✔
5582
    auto g = sg->start_write();
1✔
5583
    auto table = g->add_table("big");
1✔
5584
    auto obj = table->create_object();
1✔
5585
    auto col = table->add_column(type_Binary, "data");
1✔
5586
    std::string data(0xfffff0, 'x');
1✔
5587
    obj.set(col, BinaryData(data.data(), data.size()));
1✔
5588
    g->commit();
1✔
5589
    g = sg->start_write();
1✔
5590
    g->get_table("big")->begin()->set(col, BinaryData{"Hello", 5});
1✔
5591
    g->verify();
1✔
5592
    g->commit();
1✔
5593
}
1✔
5594

5595

5596
TEST(LangBindHelper_RollbackOptimize)
5597
{
1✔
5598
    SHARED_GROUP_TEST_PATH(path);
1✔
5599
    const char* key = crypt_key();
1✔
5600
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
5601
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(key));
1✔
5602
    auto g = sg_w->start_write();
1✔
5603

5604
    auto table = g->add_table("t0");
1✔
5605
    auto col = table->add_column(type_String, "str_col_0", true);
1✔
5606
    g->commit_and_continue_as_read();
1✔
5607
    g->verify();
1✔
5608
    g->promote_to_write();
1✔
5609
    g->verify();
1✔
5610
    std::vector<ObjKey> keys;
1✔
5611
    table->create_objects(198, keys);
1✔
5612
    table->enumerate_string_column(col);
1✔
5613
    g->rollback_and_continue_as_read();
1✔
5614
    g->verify();
1✔
5615
}
1✔
5616

5617

5618
TEST(LangBindHelper_BinaryReallocOverMax)
5619
{
1✔
5620
    SHARED_GROUP_TEST_PATH(path);
1✔
5621
    const char* key = crypt_key();
1✔
5622
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
5623
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(key));
1✔
5624
    auto g = sg_w->start_write();
1✔
5625
    auto table = g->add_table("table");
1✔
5626
    auto col = table->add_column(type_Binary, "binary_col", false);
1✔
5627
    auto obj = table->create_object();
1✔
5628

5629
    // The sizes of these binaries were found with AFL. Essentially we must hit
5630
    // the case where doubling the allocated memory goes above max_array_payload
5631
    // and hits the condition to clamp to the maximum.
5632
    std::string blob1(8877637, static_cast<unsigned char>(133));
1✔
5633
    std::string blob2(15994373, static_cast<unsigned char>(133));
1✔
5634
    BinaryData dataAlloc(blob1);
1✔
5635
    BinaryData dataRealloc(blob2);
1✔
5636

5637
    obj.set(col, dataAlloc);
1✔
5638
    obj.set(col, dataRealloc);
1✔
5639
    g->verify();
1✔
5640
}
1✔
5641

5642

5643
// This test verifies that small unencrypted files are treated correctly if
5644
// opened as encrypted.
5645
#if REALM_ENABLE_ENCRYPTION
5646
TEST(LangBindHelper_OpenAsEncrypted)
5647
{
1✔
5648
    SHARED_GROUP_TEST_PATH(path);
1✔
5649
    {
1✔
5650
        ShortCircuitHistory hist;
1✔
5651
        DBRef sg_clear = DB::create(hist, path);
1✔
5652

5653
        {
1✔
5654
            WriteTransaction wt(sg_clear);
1✔
5655
            TableRef target = wt.add_table("table");
1✔
5656
            target->add_column(type_String, "mixed_col");
1✔
5657
            target->create_object();
1✔
5658
            wt.commit();
1✔
5659
        }
1✔
5660
    }
1✔
5661
    {
1✔
5662
        const char* key = crypt_key(true);
1✔
5663
        std::unique_ptr<Replication> hist_encrypt(make_in_realm_history());
1✔
5664
        CHECK_THROW(DB::create(*hist_encrypt, path, DBOptions(key)), InvalidDatabase);
1✔
5665
    }
1✔
5666
}
1✔
5667
#endif
5668

5669

5670
// Test case generated in [realm-core-4.0.4] on Mon Dec 18 13:33:24 2017.
5671
// Adding 0 rows to a StringEnumColumn would add the default value to the keys
5672
// but not the indexes creating an inconsistency.
5673
TEST(LangBindHelper_EnumColumnAddZeroRows)
5674
{
1✔
5675
    SHARED_GROUP_TEST_PATH(path);
1✔
5676
    const char* key = nullptr;
1✔
5677
    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
5678
    DBRef sg = DB::create(*hist, path, DBOptions(key));
1✔
5679
    auto g = sg->start_write();
1✔
5680
    auto g_r = sg->start_read();
1✔
5681
    auto table = g->add_table("");
1✔
5682

5683
    auto col = table->add_column(DataType(2), "table", false);
1✔
5684
    table->enumerate_string_column(col);
1✔
5685
    g->commit_and_continue_as_read();
1✔
5686
    g->verify();
1✔
5687
    g->promote_to_write();
1✔
5688
    g->verify();
1✔
5689
    table->create_object();
1✔
5690
    g->commit_and_continue_as_read();
1✔
5691
    g_r->advance_read();
1✔
5692
    g_r->verify();
1✔
5693
    g->verify();
1✔
5694
}
1✔
5695

5696

5697
TEST(LangBindHelper_RemoveObject)
5698
{
1✔
5699
    SHARED_GROUP_TEST_PATH(path);
1✔
5700
    ShortCircuitHistory hist;
1✔
5701
    DBRef sg = DB::create(hist, path);
1✔
5702
    ColKey col;
1✔
5703
    auto rt = sg->start_read();
1✔
5704
    {
1✔
5705
        auto wt = sg->start_write();
1✔
5706
        TableRef t = wt->add_table("Foo");
1✔
5707
        col = t->add_column(type_Int, "int");
1✔
5708
        t->create_object(ObjKey(123)).set(col, 1);
1✔
5709
        t->create_object(ObjKey(456)).set(col, 2);
1✔
5710
        wt->commit();
1✔
5711
    }
1✔
5712

5713
    rt->advance_read();
1✔
5714
    auto table = rt->get_table("Foo");
1✔
5715
    const Obj o1 = table->get_object(ObjKey(123));
1✔
5716
    const Obj o2 = table->get_object(ObjKey(456));
1✔
5717
    CHECK_EQUAL(o1.get<int64_t>(col), 1);
1✔
5718
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
1✔
5719

5720
    {
1✔
5721
        auto wt = sg->start_write();
1✔
5722
        TableRef t = wt->get_table("Foo");
1✔
5723
        t->remove_object(ObjKey(123));
1✔
5724
        wt->commit();
1✔
5725
    }
1✔
5726
    rt->advance_read();
1✔
5727
    CHECK_THROW(o1.get<int64_t>(col), KeyNotFound);
1✔
5728
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
1✔
5729
}
1✔
5730

5731
TEST(LangBindHelper_callWithLock)
5732
{
1✔
5733
    SHARED_GROUP_TEST_PATH(path);
1✔
5734
    auto callback = [&](const std::string& realm_path) {
2✔
5735
        CHECK(realm_path.compare(path) == 0);
2✔
5736
    };
2✔
5737

5738
    auto callback_not_called = [&](const std::string&) {
1✔
5739
        CHECK(false);
×
5740
    };
×
5741

5742
    // call_with_lock should run the callback if the lock file doesn't exist.
5743
    CHECK_NOT(File::exists(path.get_lock_path()));
1✔
5744
    CHECK(DB::call_with_lock(path, callback));
1✔
5745
    CHECK(File::exists(path.get_lock_path()));
1✔
5746

5747
    {
1✔
5748
        std::unique_ptr<Replication> hist_w(make_in_realm_history());
1✔
5749
        DBRef sg_w = DB::create(*hist_w, path);
1✔
5750
        WriteTransaction wt(sg_w);
1✔
5751
        CHECK_NOT(DB::call_with_lock(path, callback_not_called));
1✔
5752
        wt.commit();
1✔
5753
        CHECK_NOT(DB::call_with_lock(path, callback_not_called));
1✔
5754
    }
1✔
5755
    CHECK(DB::call_with_lock(path, callback));
1✔
5756
}
1✔
5757

5758
TEST(LangBindHelper_AdvanceReadCluster)
5759
{
1✔
5760
    SHARED_GROUP_TEST_PATH(path);
1✔
5761
    ShortCircuitHistory hist;
1✔
5762
    DBRef sg = DB::create(hist, path);
1✔
5763

5764
    auto rt = sg->start_read();
1✔
5765
    {
1✔
5766
        auto wt = sg->start_write();
1✔
5767
        TableRef t = wt->add_table("Foo");
1✔
5768
        auto int_col = t->add_column(type_Int, "int");
1✔
5769
        for (int64_t i = 0; i < 100; i++) {
101✔
5770
            t->create_object(ObjKey(i)).set(int_col, i);
100✔
5771
        }
100✔
5772
        wt->commit();
1✔
5773
    }
1✔
5774

5775
    rt->advance_read();
1✔
5776
    auto table = rt->get_table("Foo");
1✔
5777
    auto col = table->get_column_key("int");
1✔
5778
    for (int64_t i = 0; i < 100; i++) {
101✔
5779
        const Obj o = table->get_object(ObjKey(i));
100✔
5780
        CHECK_EQUAL(o.get<int64_t>(col), i);
100✔
5781
    }
100✔
5782
}
1✔
5783

5784
TEST(LangBindHelper_ImportDetachedLinkList)
5785
{
1✔
5786
    SHARED_GROUP_TEST_PATH(path);
1✔
5787
    auto hist = make_in_realm_history();
1✔
5788
    DBRef db = DB::create(*hist, path);
1✔
5789
    std::unique_ptr<TableView> tv_1;
1✔
5790

5791
    ColKey col_pet;
1✔
5792
    ColKey col_addr;
1✔
5793
    ColKey col_name;
1✔
5794
    ColKey col_age;
1✔
5795

5796
    {
1✔
5797
        WriteTransaction wt(db);
1✔
5798
        auto persons = wt.add_table("person");
1✔
5799
        auto dogs = wt.add_table("dog");
1✔
5800
        col_pet = persons->add_column_list(*dogs, "pet");
1✔
5801
        col_addr = persons->add_column_list(type_String, "address");
1✔
5802
        col_name = dogs->add_column(type_String, "name");
1✔
5803
        col_age = dogs->add_column(type_Int, "age");
1✔
5804

5805
        auto tago = dogs->create_object().set(col_name, "Tago").set(col_age, 9);
1✔
5806
        auto hector = dogs->create_object().set(col_name, "Hector").set(col_age, 7);
1✔
5807

5808
        auto me = persons->create_object();
1✔
5809
        me.set_list_values<String>(col_addr, {"Paradisæblevej 5", "2500 Andeby"});
1✔
5810
        auto pets = me.get_linklist(col_pet);
1✔
5811
        pets.add(tago.get_key());
1✔
5812
        pets.add(hector.get_key());
1✔
5813
        wt.commit();
1✔
5814
    }
1✔
5815

5816
    auto rt = db->start_read();
1✔
5817
    auto persons = rt->get_table("person");
1✔
5818
    auto dogs = rt->get_table("dog");
1✔
5819
    Obj me = *persons->begin();
1✔
5820
    auto my_pets = me.get_linklist(col_pet);
1✔
5821
    Query q = dogs->where(my_pets).equal(col_age, 7);
1✔
5822
    auto tv = q.find_all();
1✔
5823
    CHECK_EQUAL(tv.size(), 1);
1✔
5824
    auto my_address = me.get_listbase_ptr(col_addr);
1✔
5825
    CHECK_EQUAL(my_address->size(), 2);
1✔
5826

5827
    {
1✔
5828
        // Delete the person.
5829
        WriteTransaction wt(db);
1✔
5830
        wt.get_table("person")->begin()->remove();
1✔
5831
        wt.commit();
1✔
5832
    }
1✔
5833

5834
    {
1✔
5835
        auto read_transaction = db->start_read();
1✔
5836

5837
        // The link_list that is embedded in the query imported here should be detached
5838
        auto local_tv = read_transaction->import_copy_of(tv, PayloadPolicy::Stay);
1✔
5839
        local_tv->sync_if_needed();
1✔
5840
        CHECK_EQUAL(local_tv->size(), 0);
1✔
5841

5842
        // Check that we can import a detached link_list back
5843
        tv_1 = rt->import_copy_of(*local_tv, PayloadPolicy::Move);
1✔
5844

5845
        // The list imported here should be null
5846
        CHECK_NOT(read_transaction->import_copy_of(*my_address));
1✔
5847
    }
1✔
5848

5849
    CHECK_EQUAL(tv_1->size(), 0);
1✔
5850
}
1✔
5851

5852
TEST(LangBindHelper_SearchIndexAccessor)
5853
{
1✔
5854
    SHARED_GROUP_TEST_PATH(path);
1✔
5855
    auto hist = make_in_realm_history();
1✔
5856
    DBRef db = DB::create(*hist, path);
1✔
5857
    ColKey col_name;
1✔
5858

5859
    auto tr = db->start_write();
1✔
5860
    {
1✔
5861
        auto persons = tr->add_table("person");
1✔
5862
        col_name = persons->add_column(type_String, "name");
1✔
5863
        persons->add_search_index(col_name);
1✔
5864
        persons->create_object().set(col_name, "Per");
1✔
5865
    }
1✔
5866
    tr->commit_and_continue_as_read();
1✔
5867

5868
    tr->promote_to_write();
1✔
5869
    {
1✔
5870
        auto persons = tr->get_table("person");
1✔
5871
        persons->remove_column(col_name);
1✔
5872
        auto col_age = persons->add_column(type_Int, "age");
1✔
5873
        persons->add_search_index(col_age);
1✔
5874
        // Index referring to col_age is now at position 0
5875
        persons->create_object().set(col_age, 47);
1✔
5876
    }
1✔
5877
    // The index accessor must be refreshed with old ColKey (col_name)
5878
    tr->rollback_and_continue_as_read();
1✔
5879

5880
    tr->promote_to_write();
1✔
5881
    {
1✔
5882
        auto persons = tr->get_table("person");
1✔
5883
        // Index accssor uses its ColKey to find value in table
5884
        persons->create_object().set(col_name, "Poul");
1✔
5885
    }
1✔
5886
    tr->commit();
1✔
5887
}
1✔
5888

5889
TEST(LangBindHelper_ArrayXoverMapping)
5890
{
1✔
5891
    SHARED_GROUP_TEST_PATH(path);
1✔
5892
    auto hist = make_in_realm_history();
1✔
5893
    DBRef db = DB::create(*hist, path);
1✔
5894
    ColKey my_col;
1✔
5895
    {
1✔
5896
        auto tr = db->start_write();
1✔
5897
        auto tbl = tr->add_table("my_table");
1✔
5898
        my_col = tbl->add_column(type_String, "my_col");
1✔
5899
        std::string s(1'000'000, 'a');
1✔
5900
        for (auto i = 0; i < 100; ++i)
101✔
5901
            tbl->create_object().set_all(s);
100✔
5902
        tr->commit();
1✔
5903
    }
1✔
5904
    REALM_ASSERT(db->compact());
1✔
5905
    {
1✔
5906
        auto tr = db->start_read();
1✔
5907
        auto tbl = tr->get_table("my_table");
1✔
5908
        for (auto i = 0; i < 100; ++i) {
101✔
5909
            auto o = tbl->get_object(i);
100✔
5910
            StringData str = o.get<String>(my_col);
100✔
5911
            for (auto j = 0; j < 1'000'000; ++j)
100,000,100✔
5912
                REALM_ASSERT(str[j] == 'a');
100,000,000✔
5913
        }
100✔
5914
    }
1✔
5915
}
1✔
5916

5917
TEST(LangBindHelper_SchemaChangeNotification)
5918
{
1✔
5919
    SHARED_GROUP_TEST_PATH(path);
1✔
5920
    auto hist = make_in_realm_history();
1✔
5921
    DBRef db = DB::create(*hist, path);
1✔
5922

5923
    auto rt = db->start_read();
1✔
5924
    bool handler_called;
1✔
5925
    rt->set_schema_change_notification_handler([&handler_called]() {
2✔
5926
        handler_called = true;
2✔
5927
    });
2✔
5928
    CHECK(rt->has_schema_change_notification_handler());
1✔
5929

5930
    {
1✔
5931
        auto tr = db->start_write();
1✔
5932
        tr->add_table("my_table");
1✔
5933
        tr->commit();
1✔
5934
    }
1✔
5935
    handler_called = false;
1✔
5936
    rt->advance_read();
1✔
5937
    CHECK(handler_called);
1✔
5938

5939
    {
1✔
5940
        auto tr = db->start_write();
1✔
5941
        auto table = tr->get_table("my_table");
1✔
5942
        table->add_column(type_Int, "integer");
1✔
5943
        tr->commit();
1✔
5944
    }
1✔
5945
    handler_called = false;
1✔
5946
    rt->advance_read();
1✔
5947
    CHECK(handler_called);
1✔
5948
}
1✔
5949

5950
TEST(LangBindHelper_InMemoryDB)
5951
{
1✔
5952
    DBRef sg = DB::create(make_in_realm_history());
1✔
5953
    ColKey col;
1✔
5954
    auto rt = sg->start_read();
1✔
5955
    {
1✔
5956
        auto wt = sg->start_write();
1✔
5957
        TableRef t = wt->add_table("Foo");
1✔
5958
        col = t->add_column(type_Int, "int");
1✔
5959
        t->create_object(ObjKey(123)).set(col, 1);
1✔
5960
        t->create_object(ObjKey(456)).set(col, 2);
1✔
5961
        wt->commit();
1✔
5962
    }
1✔
5963

5964
    rt->advance_read();
1✔
5965
    auto table = rt->get_table("Foo");
1✔
5966
    const Obj o1 = table->get_object(ObjKey(123));
1✔
5967
    const Obj o2 = table->get_object(ObjKey(456));
1✔
5968
    CHECK_EQUAL(o1.get<int64_t>(col), 1);
1✔
5969
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
1✔
5970

5971
    {
1✔
5972
        auto wt = sg->start_write();
1✔
5973
        TableRef t = wt->get_table("Foo");
1✔
5974
        t->remove_object(ObjKey(123));
1✔
5975
        wt->commit();
1✔
5976
    }
1✔
5977
    rt->advance_read();
1✔
5978
    CHECK_THROW(o1.get<int64_t>(col), KeyNotFound);
1✔
5979
    CHECK_EQUAL(o2.get<int64_t>(col), 2);
1✔
5980
}
1✔
5981

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