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

realm / realm-core / 2492

15 Jul 2024 06:38PM UTC coverage: 90.993% (+0.01%) from 90.98%
2492

push

Evergreen

web-flow
RCORE-2192 RCORE-2193 Fix FLX download progress reporting (#7870)

* Fix FLX download progress reporting

We need to store the download progress for each batch of a bootstrap and not
just at the end for it to be useful in any way.

The server will sometimes send us DOWNLOAD messages with a non-one estimate
followed by a one estimate where the byte-level information is the same (as the
final message is empty). When this happens we need to report the download
completion to the user, so add the estimate to the fields checked for changes.

A subscription change which doesn't actually change what set of objects is in
view can result in an empty DOWNLOAD message with no changes other than the
query version, and we should report that too.

* Fix a comment

* Pass the DownloadMessage to process_flx_bootstrap_message()

* Report steady-state download progress

102388 of 180586 branches covered (56.7%)

247 of 257 new or added lines in 10 files covered. (96.11%)

44 existing lines in 13 files now uncovered.

215408 of 236730 relevant lines covered (90.99%)

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

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

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

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

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

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

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

264

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

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

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

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

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

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

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

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

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

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

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

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

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

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

473

474
private:
475
    MyHistory m_history;
476
};
477

478
} // anonymous namespace
479

480

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

725

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

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

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

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

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

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

762

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

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

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

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

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

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

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

825

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

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

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

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

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

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

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

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

903

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

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

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

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

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

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

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

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

948
    rt->advance_read();
2✔
949

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

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

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

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

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

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

991

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1263
namespace {
1264

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

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

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

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

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

1328
// Background thread for test below.
1329
void deleter_thread(ConcurrentQueue<LnkLstPtr>& queue)
1330
{
2✔
1331
    Random random(random_int<unsigned long>());
2✔
1332
    bool closed = false;
2✔
1333
    while (!closed) {
99,985✔
1334
        LnkLstPtr r;
99,983✔
1335
        // prevent the compiler from eliminating a loop:
1336
        volatile int delay = random.draw_int_mod(10000);
99,983✔
1337
        closed = !queue.get(r);
99,983✔
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)
499,603,713✔
1342
            delay = delay - 1;
499,503,730✔
1343
        // just let 'r' die
1344
    }
99,983✔
1345
}
2✔
1346
} // namespace
1347

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

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

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

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

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

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

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

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

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

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

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

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

1461

1462
TEST(LangBindHelper_AdvanceReadTransact_LinkToNeighbour)
1463
{
2✔
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);
2✔
1468
    ShortCircuitHistory hist;
2✔
1469
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1470

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

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

1504
        // Start a read transaction (to be repeatedly advanced)
1505
        TransactionRef rt = sg->start_read();
2✔
1506
        CHECK_EQUAL(0, rt->size());
2✔
1507
    }
2✔
1508
    auto process = test_util::spawn_process(test_context.test_details.test_name, "initial_write");
2✔
1509
    if (process->is_child()) {
2✔
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()) {
2✔
1529
        process->wait_for_child_to_finish();
2✔
1530

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

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

1539
        CHECK_EQUAL(5, rt->size());
2✔
1540
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1541
        ConstTableRef beta = rt->get_table("beta");
2✔
1542
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1543
        ConstTableRef delta = rt->get_table("delta");
2✔
1544
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1545
        CHECK(alpha);
2✔
1546
        CHECK(beta);
2✔
1547
        CHECK(gamma);
2✔
1548
        CHECK(delta);
2✔
1549
        CHECK(epsilon);
2✔
1550
    }
2✔
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");
2✔
1554
    if (process->is_child()) {
2✔
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()) {
2✔
1565
        process->wait_for_child_to_finish();
2✔
1566

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

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

1580
        CHECK_EQUAL(4, rt->size());
2✔
1581
        CHECK_NOT(alpha);
2✔
1582
        CHECK(beta);
2✔
1583
        CHECK(gamma);
2✔
1584
        CHECK(delta);
2✔
1585
        CHECK(epsilon);
2✔
1586
    }
2✔
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");
2✔
1589
    if (process->is_child()) {
2✔
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()) {
2✔
1600
        process->wait_for_child_to_finish();
2✔
1601

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

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

1610
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1611
        ConstTableRef beta = rt->get_table("beta");
2✔
1612
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1613
        ConstTableRef delta = rt->get_table("delta");
2✔
1614
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1615
        CHECK_EQUAL(3, rt->size());
2✔
1616
        CHECK_NOT(alpha);
2✔
1617
        CHECK_NOT(beta);
2✔
1618
        CHECK(gamma);
2✔
1619
        CHECK(delta);
2✔
1620
        CHECK(epsilon);
2✔
1621
    }
2✔
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");
2✔
1625
    if (process->is_child()) {
2✔
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()) {
2✔
1636
        process->wait_for_child_to_finish();
2✔
1637

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

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

1646
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1647
        ConstTableRef beta = rt->get_table("beta");
2✔
1648
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1649
        ConstTableRef delta = rt->get_table("delta");
2✔
1650
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1651
        CHECK_EQUAL(2, rt->size());
2✔
1652
        CHECK_NOT(alpha);
2✔
1653
        CHECK_NOT(beta);
2✔
1654
        CHECK_NOT(gamma);
2✔
1655
        CHECK(delta);
2✔
1656
        CHECK(epsilon);
2✔
1657
    }
2✔
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");
2✔
1661
    if (process->is_child()) {
2✔
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()) {
2✔
1672
        process->wait_for_child_to_finish();
2✔
1673

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1780

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

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

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

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

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

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

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

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

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

1840

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

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

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

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

1870

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

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

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

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

1887
    g->commit_and_continue_as_read();
2✔
1888

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

1893
    g->promote_to_write();
2✔
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;
2✔
1898
    for (auto it = target->begin(); it != target->end(); ++it)
2,004✔
1899
        it->set(col, i++);
2,002✔
1900

1901
    g->commit_and_continue_as_read();
2✔
1902

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

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

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

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

1929
    auto process = test_util::spawn_process(test_context.test_details.test_name, "external_clear");
2✔
1930
    if (process->is_child()) {
2✔
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()) {
2✔
1941
        process->wait_for_child_to_finish();
2✔
1942

1943
        reader->advance_read();
2✔
1944

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

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

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

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

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

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

1982
    {
2✔
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);
2✔
1986
        wt.get_table("table")->where().equal(col, 0).find_all().clear();
2✔
1987
        wt.commit();
2✔
1988
    }
2✔
1989

1990
    reader->advance_read();
2✔
1991

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

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

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

2014
} // unnamed namespace
2015

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2199

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

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

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

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

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

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

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

2257

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

2311

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

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

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

2370

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

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

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

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

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

2437

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

2468

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

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

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

2503

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

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

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

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

2619

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

2651

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

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

2687

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

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

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

2722

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

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

2770

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

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

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

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

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

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

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

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

2814
    std::vector<ObjKey> keys;
2✔
2815
    target->create_objects(REALM_MAX_BPNODE_SIZE + 1, keys);
2✔
2816
    g->commit_and_continue_as_read();
2✔
2817
    g->promote_to_write();
2✔
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();
2✔
2822
    for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i) {
2,004✔
2823
        it->set<int64_t>(col, i);
2,002✔
2824
        ++it;
2,002✔
2825
    }
2,002✔
2826

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

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

2834

2835
TEST(LangBindHelper_ImplicitTransactions_OverSharedGroupDestruction)
2836
{
2✔
2837
    SHARED_GROUP_TEST_PATH(path);
2✔
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());
2✔
2841
    {
2✔
2842
        DBRef sg = DB::create(*hist1, path, DBOptions(crypt_key()));
2✔
2843
        {
2✔
2844
            WriteTransaction wt(sg);
2✔
2845
            TableRef tr = wt.add_table("table");
2✔
2846
            tr->add_column(type_Int, "first");
2✔
2847
            for (int i = 0; i < 20; i++)
42✔
2848
                tr->create_object();
40✔
2849
            wt.commit();
2✔
2850
        }
2✔
2851
        // no valid shared group anymore
2852
    }
2✔
2853
    {
2✔
2854
        std::unique_ptr<Replication> hist2(make_in_realm_history());
2✔
2855
        DBRef sg = DB::create(*hist2, path, DBOptions(crypt_key()));
2✔
2856
        {
2✔
2857
            WriteTransaction wt(sg);
2✔
2858
            TableRef tr = wt.get_table("table");
2✔
2859
            for (int i = 0; i < 20; i++)
42✔
2860
                tr->create_object();
40✔
2861
            wt.commit();
2✔
2862
        }
2✔
2863
    }
2✔
2864
}
2✔
2865

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

2884

2885
TEST(LangBindHelper_ImplicitTransactions_StringIndex)
2886
{
2✔
2887
    SHARED_GROUP_TEST_PATH(path);
2✔
2888
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2889
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2890
    auto group = sg->start_write();
2✔
2891
    TableRef table = group->add_table("a");
2✔
2892
    auto col = table->add_column(type_String, "b");
2✔
2893
    table->add_search_index(col);
2✔
2894
    group->verify();
2✔
2895
    group->commit_and_continue_as_read();
2✔
2896
    group->verify();
2✔
2897
}
2✔
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
{
14✔
2911
    // insert new random values
2912
    Random random(random_int<unsigned long>());
14✔
2913
    for (int i = 0; i < 10; ++i) {
154✔
2914
        WriteTransaction wt(db);
140✔
2915
        auto ta = wt.get_table("A");
140✔
2916
        auto col = ta->get_column_keys()[0];
140✔
2917
        for (auto it = ta->begin(); it != ta->end(); ++it) {
28,140✔
2918
            auto e = *it;
28,000✔
2919
            e.set(col, random.draw_int_mod(200));
28,000✔
2920
        }
28,000✔
2921
        auto tb = wt.get_table("B");
140✔
2922
        auto count = ta->where().greater(col, 100).count();
140✔
2923
        tb->begin()->set<int64_t>(tb->get_column_keys()[0], count);
140✔
2924
        wt.commit();
140✔
2925
    }
140✔
2926
}
14✔
2927

2928
void multiple_trackers_reader_thread(TestContext& test_context, DBRef db)
2929
{
6✔
2930
    // verify that consistency is maintained as we advance_read through a
2931
    // stream of transactions
2932
    auto g = db->start_read();
6✔
2933
    auto ta = g->get_table("A");
6✔
2934
    auto tb = g->get_table("B");
6✔
2935
    auto tc = g->get_table("C");
6✔
2936
    auto col = ta->get_column_keys()[0];
6✔
2937
    auto b_col = tb->get_column_keys()[0];
6✔
2938
    TableView tv = ta->where().greater(col, 100).find_all();
6✔
2939
    const auto wait_start = std::chrono::steady_clock::now();
6✔
2940
    std::chrono::seconds max_wait_seconds = std::chrono::seconds(1050);
6✔
2941
    while (tc->size() == 0) {
44,199✔
2942
        auto count = tb->begin()->get<int64_t>(b_col);
44,193✔
2943
        tv.sync_if_needed();
44,193✔
2944
        CHECK_EQUAL(tv.size(), count);
44,193✔
2945
        std::this_thread::yield();
44,193✔
2946
        g->advance_read();
44,193✔
2947
        if (std::chrono::steady_clock::now() - wait_start > max_wait_seconds) {
44,193✔
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
    }
44,193✔
2954
}
6✔
2955

2956
} // anonymous namespace
2957

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

2963
    SHARED_GROUP_TEST_PATH(path);
2✔
2964

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

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

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

2998
    // signal to all readers to complete
2999
    {
2✔
3000
        WriteTransaction wt(sg);
2✔
3001
        TableRef tr = wt.get_table("C");
2✔
3002
        tr->create_object();
2✔
3003
        wt.commit();
2✔
3004
    }
2✔
3005
    // Wait for all reader threads to complete
3006
    for (int i = 0; i < read_thread_count; ++i)
8✔
3007
        threads[write_thread_count + i].join();
6✔
3008
}
2✔
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
{
2✔
3026
    const int write_process_count = 7;
2✔
3027
    const int read_process_count = 3;
2✔
3028

3029
    std::vector<std::unique_ptr<SpawnedProcess>> readers;
2✔
3030
    std::vector<std::unique_ptr<SpawnedProcess>> writers;
2✔
3031
    SHARED_GROUP_TEST_PATH(path);
2✔
3032
    auto key = crypt_key();
2✔
3033
    auto process = test_util::spawn_process(test_context.test_details.test_name, "populate");
2✔
3034
    if (process->is_child()) {
2✔
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()) {
2✔
3059
        process->wait_for_child_to_finish();
2✔
3060
    }
2✔
3061

3062
    // intialization complete. Start writers:
3063
    for (int i = 0; i < write_process_count; ++i) {
16✔
3064
        writers.push_back(
14✔
3065
            test_util::spawn_process(test_context.test_details.test_name, util::format("writer[%1]", i)));
14✔
3066
        if (writers.back()->is_child()) {
14✔
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
    }
14✔
3077
    std::this_thread::yield();
2✔
3078
    // then start readers:
3079
    for (int i = 0; i < read_process_count; ++i) {
8✔
3080
        readers.push_back(
6✔
3081
            test_util::spawn_process(test_context.test_details.test_name, util::format("reader[%1]", i)));
6✔
3082
        if (readers[i]->is_child()) {
6✔
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
    }
6✔
3093

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

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

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

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

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

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

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

3135
// the miminum filesize (after a commit) is one or two pages, depending on the
3136
// page size.
3137
#if REALM_ENABLE_ENCRYPTION
2✔
3138
    if (crypt_key())
2✔
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
2✔
3142
        CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size());
2✔
3143
#else
3144
    CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size());
3145
#endif // REALM_ENABLE_ENCRYPTION
3146
}
2✔
3147

3148

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

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

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

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

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

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

3180

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

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

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

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

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

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

3216

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3374
        reader = writer->duplicate();
2✔
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
    }
2✔
3394
}
2✔
3395

3396

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

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

3436
TEST(LangBindHelper_HandoverPartialQuery)
3437
{
2✔
3438
    SHARED_GROUP_TEST_PATH(path);
2✔
3439
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3440
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3441
    std::unique_ptr<Query> qq2;
2✔
3442
    TransactionRef reader;
2✔
3443
    ColKey col0;
2✔
3444
    {
2✔
3445
        {
2✔
3446
            TableView tv1;
2✔
3447
            auto writer = sg->start_write();
2✔
3448
            TableRef table = writer->add_table("table2");
2✔
3449
            col0 = table->add_column(type_Int, "first");
2✔
3450
            auto col1 = table->add_column(type_Bool, "even");
2✔
3451
            for (int i = 0; i < 100; ++i) {
202✔
3452
                auto obj = table->create_object();
200✔
3453
                obj.set<int>(col0, i);
200✔
3454
                bool isEven = ((i % 2) == 0);
200✔
3455
                obj.set<bool>(col1, isEven);
200✔
3456
            }
200✔
3457
            writer->commit_and_continue_as_read();
2✔
3458
            tv1 = table->where().less_equal(col0, 50).find_all();
2✔
3459
            Query qq = tv1.get_parent()->where(&tv1);
2✔
3460
            reader = writer->duplicate();
2✔
3461
            qq2 = reader->import_copy_of(qq, PayloadPolicy::Copy);
2✔
3462
            CHECK(tv1.is_attached());
2✔
3463
            CHECK_EQUAL(51, tv1.size());
2✔
3464
        }
2✔
3465
        {
2✔
3466
            TableView tv = qq2->greater(col0, 48).find_all();
2✔
3467
            CHECK(tv.is_attached());
2✔
3468
            CHECK_EQUAL(2, tv.size());
2✔
3469
            auto obj = tv.get_object(0);
2✔
3470
            CHECK_EQUAL(49, obj.get<int64_t>(col0));
2✔
3471
            obj = tv.get_object(1);
2✔
3472
            CHECK_EQUAL(50, obj.get<int64_t>(col0));
2✔
3473
        }
2✔
3474
    }
2✔
3475
}
2✔
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
{
2✔
3482
    SHARED_GROUP_TEST_PATH(path);
2✔
3483
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3484
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3485
    {
2✔
3486
        TransactionRef reader;
2✔
3487
        std::unique_ptr<TableView> tv;
2✔
3488
        {
2✔
3489
            auto writer = sg->start_write();
2✔
3490
            TableRef table = writer->add_table("table2");
2✔
3491
            auto col = table->add_column(type_Int, "first");
2✔
3492
            for (int i = 0; i < 100; ++i) {
202✔
3493
                table->create_object().set_all(i);
200✔
3494
            }
200✔
3495
            writer->commit_and_continue_as_read();
2✔
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();
2✔
3498
            TableView tv2 = tv1.get_parent()->where(&tv1).greater(col, 25).find_all();
2✔
3499
            CHECK(tv2.is_in_sync());
2✔
3500
            reader = writer->duplicate();
2✔
3501
            tv = reader->import_copy_of(tv2, PayloadPolicy::Move);
2✔
3502
        }
2✔
3503
        CHECK(tv->is_in_sync());
2✔
3504
        CHECK(tv->is_attached());
2✔
3505
        CHECK_EQUAL(25, tv->size());
2✔
3506
    }
2✔
3507
}
2✔
3508

3509

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3606
TEST(LangBindHelper_TableViewAndTransactionBoundaries)
3607
{
2✔
3608
    SHARED_GROUP_TEST_PATH(path);
2✔
3609
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3610
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3611
    ColKey col;
2✔
3612
    {
2✔
3613
        WriteTransaction wt(sg);
2✔
3614
        auto table = wt.add_table("myTable");
2✔
3615
        col = table->add_column(type_Int, "myColumn");
2✔
3616
        table->create_object().set_all(42);
2✔
3617
        wt.commit();
2✔
3618
    }
2✔
3619
    auto rt = sg->start_read();
2✔
3620
    auto tv = rt->get_table("myTable")->where().greater(col, 40).find_all();
2✔
3621
    CHECK(tv.is_in_sync());
2✔
3622
    {
2✔
3623
        WriteTransaction wt(sg);
2✔
3624
        wt.commit();
2✔
3625
    }
2✔
3626
    rt->advance_read();
2✔
3627
    CHECK(tv.is_in_sync());
2✔
3628
    {
2✔
3629
        WriteTransaction wt(sg);
2✔
3630
        wt.commit();
2✔
3631
    }
2✔
3632
    rt->promote_to_write();
2✔
3633
    CHECK(tv.is_in_sync());
2✔
3634
    rt->commit_and_continue_as_read();
2✔
3635
    CHECK(tv.is_in_sync());
2✔
3636
    {
2✔
3637
        WriteTransaction wt(sg);
2✔
3638
        auto table = wt.get_table("myTable");
2✔
3639
        table->begin()->set_all(41);
2✔
3640
        wt.commit();
2✔
3641
    }
2✔
3642
    rt->advance_read();
2✔
3643
    CHECK(!tv.is_in_sync());
2✔
3644
    tv.sync_if_needed();
2✔
3645
    CHECK(tv.is_in_sync());
2✔
3646
    rt->advance_read();
2✔
3647
    CHECK(tv.is_in_sync());
2✔
3648
}
2✔
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
    {
3,431✔
3667
        LockGuard lg(m_lock);
3,431✔
3668
        // std::cout << "put " << h << std::endl;
3669
        while (m_handover != nullptr)
3,431✔
3670
            m_changed.wait(lg);
×
3671
        // std::cout << " -- put " << h << std::endl;
3672
        m_handover = std::move(h);
3,431✔
3673
        m_changed.notify_all();
3,431✔
3674
    }
3,431✔
3675
    void get(std::unique_ptr<T>& h)
3676
    {
3,431✔
3677
        LockGuard lg(m_lock);
3,431✔
3678
        // std::cout << "get " << std::endl;
3679
        while (m_handover == nullptr)
4,663✔
3680
            m_changed.wait(lg);
1,232✔
3681
        // std::cout << " -- get " << m_handover << std::endl;
3682
        h = std::move(m_handover);
3,431✔
3683
        m_handover = nullptr;
3,431✔
3684
        m_changed.notify_all();
3,431✔
3685
    }
3,431✔
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
    {
3,431✔
3698
        LockGuard lg(m_lock);
3,431✔
3699
        m_has_feedback = true;
3,431✔
3700
        m_changed.notify_all();
3,431✔
3701
    }
3,431✔
3702
    void wait_feedback()
3703
    {
3,431✔
3704
        LockGuard lg(m_lock);
3,431✔
3705
        while (!m_has_feedback)
7,355✔
3706
            m_changed.wait(lg);
3,924✔
3707
        m_has_feedback = false;
3,431✔
3708
    }
3,431✔
3709
    HandoverControl(const HandoverControl&) = delete;
3710
    HandoverControl() {}
2✔
3711
};
3712

3713
void handover_writer(DBRef db)
3714
{
2✔
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();
2✔
3718
    auto table = g->get_table("table");
2✔
3719
    Random random(random_int<unsigned long>());
2✔
3720
    for (int i = 1; i < 5000; ++i) {
10,000✔
3721
        g->promote_to_write();
9,998✔
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));
9,998✔
3725
        g->commit_and_continue_as_read();
9,998✔
3726
        // improve chance of consumers running concurrently with
3727
        // new writes:
3728
        for (int n = 0; n < 10; ++n)
109,978✔
3729
            std::this_thread::yield();
99,980✔
3730
    }
9,998✔
3731
    g->promote_to_write();
2✔
3732
    table->create_object().set_all(0); // <---- signals other threads to stop
2✔
3733
    g->commit();
2✔
3734
}
2✔
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
{
2✔
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();
2✔
3747
    std::thread writer(handover_writer, db);
2✔
3748
    TableRef table = g->get_table("table");
2✔
3749
    ColKeys cols = table->get_column_keys();
2✔
3750
    TableView tv = table->where().greater(cols[0], 50).find_all();
2✔
3751
    for (;;) {
3,288,314✔
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)) {
3,288,314✔
3755
            std::this_thread::yield();
3,284,883✔
3756
            continue;
3,284,883✔
3757
        }
3,284,883✔
3758

3759
        g->advance_read();
3,431✔
3760
        CHECK(!tv.is_in_sync());
3,431✔
3761
        tv.sync_if_needed();
3,431✔
3762
        CHECK(tv.is_in_sync());
3,431✔
3763
        auto ref = g->duplicate();
3,431✔
3764
        std::unique_ptr<Work> h = std::make_unique<Work>();
3,431✔
3765
        h->tr = ref;
3,431✔
3766
        h->tv = ref->import_copy_of(tv, PayloadPolicy::Move);
3,431✔
3767
        control->put(std::move(h));
3,431✔
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();
3,431✔
3772
        std::this_thread::yield();
3,431✔
3773

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

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

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

3809
} // anonymous namespace
3810

3811
namespace {
3812

3813
void attacher(std::string path, ColKey col)
3814
{
20✔
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());
20✔
3819
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
20✔
3820
    for (int i = 0; i < 100; ++i) {
2,020✔
3821
        auto g = sg->start_read();
2,000✔
3822
        g->verify();
2,000✔
3823
        auto table = g->get_table("table");
2,000✔
3824
        g->promote_to_write();
2,000✔
3825
        auto o = table->get_object(ObjKey(i));
2,000✔
3826
        auto o2 = table->get_object(ObjKey(i * 10));
2,000✔
3827
        o.set<int64_t>(col, 1 + o2.get<int64_t>(col));
2,000✔
3828
        g->commit_and_continue_as_read();
2,000✔
3829
        g->verify();
2,000✔
3830
        g->end_read();
2,000✔
3831
    }
2,000✔
3832
}
20✔
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
{
2✔
3840
    const int num_attachers = 10;
2✔
3841
    SHARED_GROUP_TEST_PATH(path);
2✔
3842
    ColKey col;
2✔
3843
    {
2✔
3844
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3845
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3846
        auto g = sg->start_write();
2✔
3847
        auto table = g->add_table("table");
2✔
3848
        col = table->add_column(type_Int, "first");
2✔
3849
        for (int i = 0; i < 1000; ++i)
2,002✔
3850
            table->create_object(ObjKey(i));
2,000✔
3851
        g->commit();
2✔
3852
    }
2✔
3853
    std::thread attachers[num_attachers];
2✔
3854
    for (int i = 0; i < num_attachers; ++i) {
22✔
3855
        attachers[i] = std::thread(attacher, std::string(path), col);
20✔
3856
    }
20✔
3857
    for (int i = 0; i < num_attachers; ++i) {
22✔
3858
        attachers[i].join();
20✔
3859
    }
20✔
3860
}
2✔
3861

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

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

3884

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

3937

3938
TEST(LangBindHelper_HandoverTableViewWithLnkLst)
3939
{
2✔
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++) {
6✔
3943
        SHARED_GROUP_TEST_PATH(path);
4✔
3944
        std::unique_ptr<Replication> hist(make_in_realm_history());
4✔
3945
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4✔
3946
        ColKey col_link2, col0;
4✔
3947
        ObjKey ok0, ok1, ok2;
4✔
3948
        TransactionRef tr;
4✔
3949
        std::unique_ptr<TableView> tv2;
4✔
3950
        std::unique_ptr<Query> q2;
4✔
3951
        {
4✔
3952
            TableView tv;
4✔
3953
            auto group_w = sg->start_write();
4✔
3954

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

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

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

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

3969
            auto o = table2->create_object();
4✔
3970
            auto lvr = o.get_linklist(col_link2);
4✔
3971
            lvr.clear();
4✔
3972
            lvr.add(ok0);
4✔
3973
            lvr.add(ok1);
4✔
3974
            lvr.add(ok2);
4✔
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);
4✔
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)
4✔
3985
                table2->remove_object(o.get_key());
2✔
3986

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

4017

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

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

4036
            target = table1->create_object().set_all(300).get_key();
4✔
4037
            auto o = table2->create_object().set_all(target);
4✔
4038
            Query q = table2->where().and_query(table2->column<Link>(col_link) == table1->get_object(target));
4✔
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)
4✔
4043
                table2->remove_object(o.get_key());
2✔
4044

4045
            auto tv = q.find_all();
4✔
4046
            CHECK(tv.is_in_sync());
4✔
4047
            group_w->commit_and_continue_as_read();
4✔
4048
            tr = group_w->duplicate();
4✔
4049
            CHECK(tv.is_in_sync());
4✔
4050
            tv2 = tr->import_copy_of(tv, PayloadPolicy::Copy);
4✔
4051
            q2 = tr->import_copy_of(q, PayloadPolicy::Copy);
4✔
4052
        }
4✔
4053
        {
4✔
4054
            auto tv3 = q2->find_all();
4✔
4055
            CHECK(tv2->is_in_sync());
4✔
4056
            if (detached == 0) {
4✔
4057
                CHECK_EQUAL(1, tv2->size());
2✔
4058
                CHECK_EQUAL(target, tv2->get_key(0));
2✔
4059
                CHECK_EQUAL(1, tv3.size());
2✔
4060
                CHECK_EQUAL(target, tv3.get_key(0));
2✔
4061
            }
2✔
4062
            else {
2✔
4063
                CHECK_EQUAL(0, tv2->size());
2✔
4064
                CHECK_EQUAL(0, tv3.size());
2✔
4065
            }
2✔
4066
            tr->close();
4✔
4067
        }
4✔
4068
    }
4✔
4069
}
2✔
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
{
2✔
4197
    SHARED_GROUP_TEST_PATH(path);
2✔
4198
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4199
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4200
    TransactionRef reader;
2✔
4201
    TableRef table;
2✔
4202
    {
2✔
4203
        auto writer = sg->start_write();
2✔
4204
        TableRef table1 = writer->add_table("table1");
2✔
4205
        writer->commit_and_continue_as_read();
2✔
4206
        auto vid = writer->get_version_of_current_transaction();
2✔
4207
        reader = sg->start_read(vid);
2✔
4208
        table = reader->import_copy_of(table1);
2✔
4209
    }
2✔
4210
    CHECK(bool(table));
2✔
4211
    CHECK(table->size() == 0);
2✔
4212
}
2✔
4213

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

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

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

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

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

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

4238
    auto o1 = table2->create_object();
2✔
4239
    table2->create_object();
2✔
4240
    LnkLstPtr lvr = o1.get_linklist_ptr(col_link2);
2✔
4241
    lvr->clear();
2✔
4242
    lvr->add(to1.get_key());
2✔
4243
    lvr->add(to2.get_key());
2✔
4244
    lvr->add(to3.get_key());
2✔
4245
    writer->commit_and_continue_as_read();
2✔
4246
    reader = writer->duplicate();
2✔
4247
    auto ll = reader->import_copy_of(lvr);
2✔
4248
    {
2✔
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");
2✔
4255
        Query q = table1b->where(*ll).and_query(table1b->column<Int>(col1) > 100);
2✔
4256

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

4260

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

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

4278

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

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

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

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

4319
            CHECK_EQUAL(tv2->size(), 1);
2✔
4320
            CHECK_EQUAL(tv2->get_key(0), obj2b.get_key());
2✔
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();
2✔
4325
            CHECK_EQUAL(tv2->size(), 1);
2✔
4326
            CHECK_EQUAL(tv2->get_key(0), obj2b.get_key());
2✔
4327
        }
2✔
4328
    }
2✔
4329
}
2✔
4330

4331

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

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

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

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

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

4395
    for (int i = 0; i < 100; ++i) {
202✔
4396
        TableView tv = source->get_object(i).get_backlink_view(links, col);
200✔
4397
        CHECK(tv.is_attached());
200✔
4398
        CHECK_EQUAL(1, tv.size());
200✔
4399
        ObjKey o_key = source->get_object(i).get_key();
200✔
4400
        CHECK_EQUAL(o_key, tv.get_key(0));
200✔
4401
        auto group = group_w->duplicate();
200✔
4402
        auto tv2 = group->import_copy_of(tv, PayloadPolicy::Copy);
200✔
4403
        CHECK(tv.is_attached());
200✔
4404
        CHECK(tv2->is_attached());
200✔
4405
        CHECK_EQUAL(1, tv2->size());
200✔
4406
        CHECK_EQUAL(o_key, tv2->get_key(0));
200✔
4407
    }
200✔
4408
}
2✔
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
{
2✔
4414
    SHARED_GROUP_TEST_PATH(path);
2✔
4415
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4416
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4417
    auto group_w = sg->start_write();
2✔
4418

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

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

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

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

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

4435
    // Bring the view out of sync, and have it depend on a deleted row.
4436
    target->remove_object(obj_t.get_key());
2✔
4437
    CHECK_EQUAL(true, tv.is_attached());
2✔
4438
    CHECK_EQUAL(false, tv.is_in_sync());
2✔
4439
    CHECK_EQUAL(true, tv.depends_on_deleted_object());
2✔
4440
    CHECK_EQUAL(1, tv.size());
2✔
4441
    tv.sync_if_needed();
2✔
4442
    CHECK_EQUAL(0, tv.size());
2✔
4443
    group_w->commit_and_continue_as_read();
2✔
4444
    auto group = group_w->duplicate();
2✔
4445
    auto tv2 = group->import_copy_of(tv, PayloadPolicy::Copy);
2✔
4446
    CHECK_EQUAL(true, tv2->depends_on_deleted_object());
2✔
4447
    CHECK_EQUAL(0, tv2->size());
2✔
4448
}
2✔
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
{
2✔
4455
    SHARED_GROUP_TEST_PATH(path);
2✔
4456
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4457
    DBRef db = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4458
    auto group_w = db->start_write();
2✔
4459
    // First setup data so that we can do a query on links
4460
    TableRef table1 = group_w->add_table("table1");
2✔
4461
    TableRef table2 = group_w->add_table("table2");
2✔
4462
    // add some more columns to table1 and table2
4463
    table1->add_column(type_Int, "col1");
2✔
4464
    table1->add_column(type_String, "str1");
2✔
4465

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

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

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

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

4484
    auto links2 = o11.get_linklist(col_link2);
2✔
4485
    CHECK(links2.is_attached());
2✔
4486
    links2.add(o21.get_key());
2✔
4487
    links2.add(o22.get_key());
2✔
4488
    group_w->commit_and_continue_as_read();
2✔
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";
2✔
4494
    realm::TableView tv4 = query.find_all();
2✔
4495

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

4501
    {
2✔
4502
        realm::TableView tv = q1->find_all();
2✔
4503
        CHECK_EQUAL(0, tv.size());
2✔
4504
    }
2✔
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();
2✔
4509
    auto o23 = table2->create_object().set_all(700, "nabil");
2✔
4510
    links1.add(o23.get_key());
2✔
4511
    group_w->commit_and_continue_as_read();
2✔
4512
    CHECK_EQUAL(1, query.count());
2✔
4513
    {
2✔
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();
2✔
4517
        CHECK_EQUAL(0, tv2.size());
2✔
4518
    }
2✔
4519
}
2✔
4520

4521

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

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

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

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

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

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

4558
        group_w->commit_and_continue_as_read();
2✔
4559

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

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

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

4598

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

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

4618

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

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

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

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

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

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

4646
        group_w->commit_and_continue_as_read();
2✔
4647

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

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

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

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

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

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

4672
    const int num_versions = 10;
2✔
4673
    const int num_random_tests = 100;
2✔
4674
    DB::VersionID versions[num_versions];
2✔
4675
    std::vector<TransactionRef> trs;
2✔
4676
    SHARED_GROUP_TEST_PATH(path);
2✔
4677
    {
2✔
4678
        // Create a new shared db
4679
        std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4680
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4681
        // first create 'num_version' versions
4682
        ColKey col;
2✔
4683
        auto reader = sg->start_read();
2✔
4684
        {
2✔
4685
            WriteTransaction wt(sg);
2✔
4686
            col = wt.get_or_add_table("test")->add_column(type_Int, "a");
2✔
4687
            wt.commit();
2✔
4688
        }
2✔
4689
        for (int i = 0; i < num_versions; ++i) {
22✔
4690
            {
20✔
4691
                WriteTransaction wt(sg);
20✔
4692
                auto t = wt.get_table("test");
20✔
4693
                t->create_object().set_all(i);
20✔
4694
                wt.commit();
20✔
4695
            }
20✔
4696
            {
20✔
4697
                auto rt = sg->start_read();
20✔
4698
                trs.push_back(rt->duplicate());
20✔
4699
                versions[i] = rt->get_version_of_current_transaction();
20✔
4700
            }
20✔
4701
        }
20✔
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
        {
2✔
4706
            for (int k = 0; k < num_versions; ++k) {
22✔
4707
                // std::cerr << "Advancing from initial version to version " << k << std::endl;
4708
                auto g = sg->start_read(versions[0]);
20✔
4709
                auto t = g->get_table("test");
20✔
4710
                CHECK(versions[k] >= versions[0]);
20✔
4711
                g->verify();
20✔
4712
                g->advance_read(versions[k]);
20✔
4713
                g->verify();
20✔
4714
                auto o = *(t->begin() + k);
20✔
4715
                CHECK_EQUAL(k, o.get<int64_t>(col));
20✔
4716
            }
20✔
4717
        }
2✔
4718

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

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

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

4739
                g->advance_read(versions[k]);
20✔
4740
                g->verify();
20✔
4741
                auto o = *(t->begin() + k);
20✔
4742
                CHECK_EQUAL(k, o.get<int64_t>(col));
20✔
4743
            }
20✔
4744
        }
2✔
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;
2✔
4748
        auto g = sg->start_read(versions[old_version]);
2✔
4749
        auto t = g->get_table("test");
2✔
4750
        for (int k = num_random_tests; k; --k) {
202✔
4751
            int new_version = random.draw_int_mod(num_versions);
200✔
4752
            // std::cerr << "Random jump: version " << old_version << " -> " << new_version << std::endl;
4753
            if (new_version < old_version) {
200✔
4754
                CHECK(versions[new_version] < versions[old_version]);
90✔
4755
                g->end_read();
90✔
4756
                g = sg->start_read(versions[new_version]);
90✔
4757
                g->verify();
90✔
4758
                t = g->get_table("test");
90✔
4759
                auto o = *(t->begin() + new_version);
90✔
4760
                CHECK_EQUAL(new_version, o.get<int64_t>(col));
90✔
4761
            }
90✔
4762
            else {
110✔
4763
                CHECK(versions[new_version] >= versions[old_version]);
110✔
4764
                g->verify();
110✔
4765
                g->advance_read(versions[new_version]);
110✔
4766
                g->verify();
110✔
4767
                auto o = *(t->begin() + new_version);
110✔
4768
                CHECK_EQUAL(new_version, o.get<int64_t>(col));
110✔
4769
            }
110✔
4770
            old_version = new_version;
200✔
4771
        }
200✔
4772
        trs.clear();
2✔
4773
        g->end_read();
2✔
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();
2✔
4779
        g = sg->start_write();
2✔
4780
        g->commit();
2✔
4781
        g = sg->start_write();
2✔
4782
        g->commit();
2✔
4783

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

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

4800

4801
TEST(LangBindHelper_RollbackToInitialState2)
4802
{
2✔
4803
    SHARED_GROUP_TEST_PATH(path);
2✔
4804
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
4805
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
4806
    auto trans = sg_w->start_write();
2✔
4807
    trans->rollback();
2✔
4808
}
2✔
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
{
2✔
4814
    SHARED_GROUP_TEST_PATH(path);
2✔
4815
    size_t N = 100;
2✔
4816
    std::string dir_path = File::parent_dir(path);
2✔
4817
    dir_path = dir_path.empty() ? "." : dir_path;
2✔
4818
    auto dir_has_tmp_compaction = [&dir_path]() -> size_t {
6✔
4819
        DirScanner dir(dir_path);
6✔
4820
        std::string name;
6✔
4821
        while (dir.next(name)) {
165✔
4822
            if (name.find("tmp_compaction_space") != std::string::npos) {
159✔
4823
                return true;
×
4824
            }
×
4825
        }
159✔
4826
        return false;
6✔
4827
    };
6✔
4828

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4960
    // Verify that an aggregate on the view still gives the expected result.
4961
    res = ObjKey();
2✔
4962
    CHECK(view.min(col, &res)->is_null());
2✔
4963
    CHECK_EQUAL(ObjKey(), res);
2✔
4964
}
2✔
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
{
2✔
5086
    SHARED_GROUP_TEST_PATH(path);
2✔
5087

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5165

5166
TEST(LangBindHelper_SessionHistoryConsistency)
5167
{
2✔
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);
2✔
5175

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

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

5188

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

5222
TEST(LangBindHelper_InRealmHistory_Downgrade)
5223
{
2✔
5224
    SHARED_GROUP_TEST_PATH(path);
2✔
5225
    {
2✔
5226
        // In-Realm history
5227
        std::unique_ptr<Replication> hist = make_in_realm_history();
2✔
5228
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5229
        WriteTransaction wt(sg);
2✔
5230
        wt.commit();
2✔
5231
    }
2✔
5232
    {
2✔
5233
        // No history
5234
        CHECK_THROW(DB::create(path, DBOptions(crypt_key())), IncompatibleHistories);
2✔
5235
    }
2✔
5236
}
2✔
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
{
2✔
5244
    SHARED_GROUP_TEST_PATH(path)
2✔
5245
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5246
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
5247
    auto g = sg->start_write();
2✔
5248

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

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

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

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

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

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

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

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

5281

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

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

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

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

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

5306
    t1->clear();
2✔
5307

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

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

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

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

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

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

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

5358

5359
// Found by AFL
5360
TEST_TYPES(LangBindHelper_SetTimestampRollback, std::true_type, std::false_type)
5361
{
4✔
5362
    constexpr bool nullable_toggle = TEST_TYPE::value;
4✔
5363
    SHARED_GROUP_TEST_PATH(path);
4✔
5364
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
4✔
5365
    DBRef sg = DB::create(*hist_w, path, DBOptions(crypt_key()));
4✔
5366
    auto g = sg->start_write();
4✔
5367
    auto table = g->add_table("");
4✔
5368
    table->add_column(type_Timestamp, "gnyf", nullable_toggle);
4✔
5369
    table->create_object().set_all(Timestamp(-1, -1));
4✔
5370
    g->rollback_and_continue_as_read();
4✔
5371
    g->verify();
4✔
5372
}
4✔
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
{
4✔
5378
    constexpr bool nullable_toggle = TEST_TYPE::value;
4✔
5379
    SHARED_GROUP_TEST_PATH(path);
4✔
5380
    std::unique_ptr<Replication> hist(make_in_realm_history());
4✔
5381
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4✔
5382
    auto g_r = sg->start_read();
4✔
5383
    auto g_w = sg->start_write();
4✔
5384
    auto table = g_w->add_table("");
4✔
5385
    table->add_column(type_Timestamp, "gnyf", nullable_toggle);
4✔
5386
    table->create_object().set_all(Timestamp(-1, -1));
4✔
5387
    g_w->commit_and_continue_as_read();
4✔
5388
    g_w->verify();
4✔
5389
    g_r->advance_read();
4✔
5390
    g_r->verify();
4✔
5391
}
4✔
5392

5393

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

5413

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

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

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

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

5475
    auto reader = sg->start_read();
2✔
5476
    auto lv1 = reader->get_table("origin")->begin()->get_linklist(col);
2✔
5477
    {
2✔
5478
        WriteTransaction wt(sg);
2✔
5479
        Group& group = wt.get_group();
2✔
5480
        TableRef origin = group.get_table("origin");
2✔
5481
        auto lv0 = origin->begin()->get_linklist(col);
2✔
5482
        lv0.add(target_keys[i++]);
2✔
5483
        lv0.add(target_keys[i++]);
2✔
5484
        wt.commit();
2✔
5485
    }
2✔
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();
2✔
5491
    CHECK_EQUAL(lv1.size(), i);
2✔
5492
}
2✔
5493

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

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

5526
    auto reader = sg->start_read();
2✔
5527
    auto lv1 = reader->get_table("origin")->begin()->get_linklist(col);
2✔
5528
    CHECK_EQUAL(lv1.size(), i);
2✔
5529
    {
2✔
5530
        WriteTransaction wt(sg);
2✔
5531
        Group& group = wt.get_group();
2✔
5532
        TableRef origin = group.get_table("origin");
2✔
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);
2✔
5536
        lv0.add(target_keys[i++]);
2✔
5537
        wt.commit();
2✔
5538
    }
2✔
5539
    reader->promote_to_write();
2✔
5540
    // Here we write the duplicates to the free list
5541
    reader->commit_and_continue_as_read();
2✔
5542
    reader->verify();
2✔
5543
    CHECK_EQUAL(lv1.size(), i);
2✔
5544
}
2✔
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
{
2✔
5579
    SHARED_GROUP_TEST_PATH(path);
2✔
5580
    ShortCircuitHistory hist;
2✔
5581
    DBRef sg = DB::create(hist, path);
2✔
5582
    auto g = sg->start_write();
2✔
5583
    auto table = g->add_table("big");
2✔
5584
    auto obj = table->create_object();
2✔
5585
    auto col = table->add_column(type_Binary, "data");
2✔
5586
    std::string data(0xfffff0, 'x');
2✔
5587
    obj.set(col, BinaryData(data.data(), data.size()));
2✔
5588
    g->commit();
2✔
5589
    g = sg->start_write();
2✔
5590
    g->get_table("big")->begin()->set(col, BinaryData{"Hello", 5});
2✔
5591
    g->verify();
2✔
5592
    g->commit();
2✔
5593
}
2✔
5594

5595

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

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

5617

5618
TEST(LangBindHelper_BinaryReallocOverMax)
5619
{
2✔
5620
    SHARED_GROUP_TEST_PATH(path);
2✔
5621
    const char* key = crypt_key();
2✔
5622
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
5623
    DBRef sg_w = DB::create(*hist_w, path, DBOptions(key));
2✔
5624
    auto g = sg_w->start_write();
2✔
5625
    auto table = g->add_table("table");
2✔
5626
    auto col = table->add_column(type_Binary, "binary_col", false);
2✔
5627
    auto obj = table->create_object();
2✔
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));
2✔
5633
    std::string blob2(15994373, static_cast<unsigned char>(133));
2✔
5634
    BinaryData dataAlloc(blob1);
2✔
5635
    BinaryData dataRealloc(blob2);
2✔
5636

5637
    obj.set(col, dataAlloc);
2✔
5638
    obj.set(col, dataRealloc);
2✔
5639
    g->verify();
2✔
5640
}
2✔
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
{
2✔
5648
    SHARED_GROUP_TEST_PATH(path);
2✔
5649
    {
2✔
5650
        ShortCircuitHistory hist;
2✔
5651
        DBRef sg_clear = DB::create(hist, path);
2✔
5652

5653
        {
2✔
5654
            WriteTransaction wt(sg_clear);
2✔
5655
            TableRef target = wt.add_table("table");
2✔
5656
            target->add_column(type_String, "mixed_col");
2✔
5657
            target->create_object();
2✔
5658
            wt.commit();
2✔
5659
        }
2✔
5660
    }
2✔
5661
    {
2✔
5662
        const char* key = crypt_key(true);
2✔
5663
        std::unique_ptr<Replication> hist_encrypt(make_in_realm_history());
2✔
5664
        CHECK_THROW(DB::create(*hist_encrypt, path, DBOptions(key)), InvalidDatabase);
2✔
5665
    }
2✔
5666
}
2✔
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
{
2✔
5675
    SHARED_GROUP_TEST_PATH(path);
2✔
5676
    const char* key = nullptr;
2✔
5677
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5678
    DBRef sg = DB::create(*hist, path, DBOptions(key));
2✔
5679
    auto g = sg->start_write();
2✔
5680
    auto g_r = sg->start_read();
2✔
5681
    auto table = g->add_table("");
2✔
5682

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

5696

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

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

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

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

5738
    auto callback_not_called = [&](const std::string&) {
2✔
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()));
2✔
5744
    CHECK(DB::call_with_lock(path, callback));
2✔
5745
    CHECK(File::exists(path.get_lock_path()));
2✔
5746

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

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

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

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

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

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

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

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

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

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

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

5834
    {
2✔
5835
        auto read_transaction = db->start_read();
2✔
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);
2✔
5839
        local_tv->sync_if_needed();
2✔
5840
        CHECK_EQUAL(local_tv->size(), 0);
2✔
5841

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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