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

realm / realm-core / thomas.goyne_232

13 Mar 2024 01:00AM UTC coverage: 91.787% (+0.9%) from 90.924%
thomas.goyne_232

Pull #7402

Evergreen

tgoyne
Add more UpdateIfNeeded tests
Pull Request #7402: Make Obj trivial and add a separate ObjCollectionParent type

94460 of 174600 branches covered (54.1%)

496 of 559 new or added lines in 21 files covered. (88.73%)

848 existing lines in 34 files now uncovered.

242761 of 264484 relevant lines covered (91.79%)

6342666.36 hits per line

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

94.24
/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
{
195✔
78
    CHECK(frozen->is_frozen());
195✔
79
    CHECK_THROW(frozen->promote_to_write(), LogicError);
195✔
80
    auto table = frozen->get_table("my_table");
195✔
81
    CHECK(table->is_frozen());
195✔
82
    auto col = table->get_column_key("my_col_1");
195✔
83
    int64_t sum = 0;
195✔
84
    for (auto i : *table) {
163,808✔
85
        sum += i.get<int64_t>(col);
163,808✔
86
    }
163,808✔
87
    CHECK_EQUAL(sum, 1000 / 2 * 999);
195✔
88
    TableView tv = table->where().not_equal(col, 42).find_all();
195✔
89
    CHECK(tv.is_frozen());
195✔
90
}
195✔
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
1✔
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);
190✔
125
        });
190✔
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,973✔
156
        millisleep(1);
1,973✔
157
        for (int j = first; j < last; ++j) {
1,990,075✔
158
            frozen->get_table(table_names[j]);
1,988,102✔
159
        }
1,988,102✔
160
    };
1,973✔
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

1✔
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

1✔
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();
1,000✔
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,973✔
213
            }
17,973✔
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();
1✔
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,966✔
250
        millisleep(1);
1,966✔
251
        for (int j = first; j < last; ++j) {
1,637,082✔
252
            auto table = frozen->get_table(table_keys[j]);
1,635,116✔
253
            CHECK(table->get_key() == table_keys[j]);
1,635,116✔
254
        }
1,635,116✔
255
    };
1,966✔
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) {
987✔
283
        millisleep(1);
987✔
284
        auto table = frozen->get_table("MyTable");
987✔
285
        auto col = table->get_column_key("MyCol");
987✔
286
        for (int j = first; j < last; ++j) {
447,140✔
287
            // loads of concurrent queries created and executed:
210,813✔
288
            TableView tb = table->where().equal(col, j).find_all();
446,153✔
289
            CHECK(tb.size() == 1);
446,153✔
290
            CHECK(tb.get_key(0) == obj_keys[j]);
446,153✔
291
            // concurrent reads from results are just fine:
210,813✔
292
            auto obj = tb[0];
446,153✔
293
            CHECK(obj.get<Int>(col) == j);
446,153✔
294
        }
446,153✔
295
    };
987✔
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)
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,099✔
388
        m_incoming_changeset.assign(data, data + size); // Throws
5,099✔
389
        version_type new_version = orig_version + 1;
5,099✔
390
        m_incoming_version = new_version;
5,099✔
391
        // Allocate space for the new changeset in m_changesets such that we can
2,528✔
392
        // be sure no exception will be thrown whan adding the changeset in
2,528✔
393
        // finalize_changeset().
2,528✔
394
        m_changesets[new_version]; // Throws
5,099✔
395
        return new_version;
5,099✔
396
    }
5,099✔
397
    void finalize()
398
    {
5,099✔
399
        // The following operation will not throw due to the space reservation
2,528✔
400
        // carried out in prepare_new_changeset().
2,528✔
401
        m_changesets[m_incoming_version].changes = std::move(m_incoming_changeset);
5,099✔
402
        m_changesets[m_incoming_version].finalized = true;
5,099✔
403
    }
5,099✔
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,099✔
419
        // No-op
2,528✔
420
    }
5,099✔
421

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

437
    void finalize_changeset() noexcept override
438
    {
5,099✔
439
        m_history.finalize();
5,099✔
440
    }
5,099✔
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,198✔
449
        return &m_history;
10,198✔
450
    }
10,198✔
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

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

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

1✔
496
    // Try to advance after an empty write transaction
1✔
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

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

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

1✔
524
    // Create a table via the other SharedGroup
1✔
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

1✔
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

1✔
545
    // Modify the table via the other SharedGroup
1✔
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

1✔
593
    // Again, with no change
1✔
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

1✔
606
    // Perform several write transactions before advancing the read transaction
1✔
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
1✔
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

1✔
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

1✔
654
    // Clear tables - not supported before backlinks work again
1✔
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

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

1✔
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

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

1✔
699
    // Add the first table
1✔
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

1✔
708
    // Create a SharedGroup to which we can apply a foreign transaction
1✔
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

1✔
713
    // Add the second table in a "foreign" transaction
1✔
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

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

725

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

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

1✔
736
    // Add the table
1✔
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

1✔
745
    // Create a SharedGroup to which we can apply a foreign transaction
1✔
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

1✔
750
    // remove the table in a "foreign" transaction
1✔
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

1✔
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

1✔
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

1✔
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

1✔
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

1✔
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

1✔
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

1✔
837
    // Create some data
1✔
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

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

1✔
861
    // Make some more versions
1✔
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

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

1✔
879
    // Cancel read transaction
1✔
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
1✔
886
    }
2✔
887
    {
2✔
888
        WriteTransaction wt(sg);
2✔
889
        wt.commit();
2✔
890
        // History entries still held by previous commit
1✔
891
    }
2✔
892
    {
2✔
893
        WriteTransaction wt(sg);
2✔
894
        wt.commit();
2✔
895
        // History entries now finally free
1✔
896
    }
2✔
897
    sg->get_stats(free_space, used_space, &new_locked_space);
2✔
898

1✔
899
    // Some space must have been released
1✔
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

1✔
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

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

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

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

1✔
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

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

1✔
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

1✔
948
    rt->advance_read();
2✔
949

1✔
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
1✔
958
    // when the origin table is created in the same transaction as the link
1✔
959
    // column is added to it. This case is slightly involved, as there is a rule
1✔
960
    // that requires the two opposite table accessors of a link column (origin
1✔
961
    // and target sides) to either both exist or both not exist. On the other
1✔
962
    // hand, tables accessors are normally not created during
1✔
963
    // Group::advance_transact() for newly created tables.
1✔
964

1✔
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

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

1✔
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

1✔
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

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

1✔
1003
    // Create 3 string columns, one primed for conversion to "unique string
1✔
1004
    // enumeration" representation
1✔
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

1✔
1026
    // Optimize
1✔
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

1✔
1047
        // Start a read transaction (to be repeatedly advanced)
1✔
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
1✔
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

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

1✔
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

1✔
1080
        // Start a read transaction (to be repeatedly advanced)
1✔
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

1✔
1092
    // Remove the previous search indexes and add 2 new ones
1✔
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

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

1✔
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

1✔
1118
        // Start a read transaction (to be repeatedly advanced)
1✔
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

1✔
1130
    // Add some searchable contents
1✔
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

1✔
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

1✔
1155
        // Start a read transaction (to be repeatedly advanced)
1✔
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

1✔
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
1✔
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

1✔
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

1✔
1190
        // Start a read transaction (to be repeatedly advanced)
1✔
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

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

1✔
1214
    // Add some tables and rows.
1✔
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

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

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

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

1✔
1238
    // Grab references to the LinkViews
1✔
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

1✔
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

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

1✔
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

1✔
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)
1270
    {
2✔
1271
        data.reset(new T[sz]);
2✔
1272
    }
2✔
1273
    inline bool is_full()
1274
    {
199,989✔
1275
        return writer - reader == sz;
199,989✔
1276
    }
199,989✔
1277
    inline bool is_empty()
1278
    {
214,220✔
1279
        return writer - reader == 0;
214,220✔
1280
    }
214,220✔
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();
28,119✔
1288
        data[writer++ % sz] = std::move(e);
100,000✔
1289
    }
100,000✔
1290

1291
    bool get(T& e)
1292
    {
99,991✔
1293
        std::unique_lock<std::mutex> lock(mutex);
99,991✔
1294
        while (is_empty() && !closed)
114,220✔
1295
            not_empty_or_closed.wait(lock);
14,229✔
1296
        if (closed)
99,991✔
1297
            return false;
2✔
1298
        if (is_full())
99,989✔
UNCOV
1299
            not_full.notify_all();
×
1300
        e = std::move(data[reader++ % sz]);
99,989✔
1301
        return true;
99,989✔
1302
    }
99,989✔
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,993✔
1334
        LnkLstPtr r;
99,991✔
1335
        // prevent the compiler from eliminating a loop:
49,993✔
1336
        volatile int delay = random.draw_int_mod(10000);
99,991✔
1337
        closed = !queue.get(r);
99,991✔
1338
        // random delay goes *after* get(), so that it comes
49,993✔
1339
        // after the potentially synchronizing locking
49,993✔
1340
        // operation inside queue.get()
49,993✔
1341
        while (delay > 0)
498,878,266✔
1342
            delay = delay - 1;
498,778,275✔
1343
        // just let 'r' die
49,993✔
1344
    }
99,991✔
1345
}
2✔
1346
} // namespace
1347

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

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

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

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

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

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

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

1✔
1379
    // Start a read transaction (to be repeatedly advanced)
1✔
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
1✔
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

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

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

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

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

1462

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1781

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

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

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

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

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

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

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

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

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

1✔
1841

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

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

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

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

1871

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

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

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

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

1✔
1888
    g->commit_and_continue_as_read();
2✔
1889

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

1✔
1894
    g->promote_to_write();
2✔
1895

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

1✔
1902
    g->commit_and_continue_as_read();
2✔
1903

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

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

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

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

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

1✔
1944
        reader->advance_read();
2✔
1945

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

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

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

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

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

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

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

1✔
1991
    reader->advance_read();
2✔
1992

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

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

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

2015
} // unnamed namespace
2016

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2200

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

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

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

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

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

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

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

2259

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

2313

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

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

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

2372

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

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

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

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

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

2439

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

2470

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

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

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

2505

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

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

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

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

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

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

2621

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

2653

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

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

2689

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

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

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

2724

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

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

2772

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

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

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

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

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

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

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

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

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

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

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

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

2836

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

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

2886

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

2901

2902
namespace {
2903

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

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

2958
} // anonymous namespace
2959

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

1✔
2965
    SHARED_GROUP_TEST_PATH(path);
2✔
2966

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

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

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

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

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

3021
#if !REALM_ANDROID && !REALM_IOS
3022

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

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

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

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

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

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

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

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

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

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

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

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

3154

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

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

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

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

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

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

3186

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

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

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

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

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

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

3222

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3402

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

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

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

3483

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

3515

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3818
} // anonymous namespace
3819

3820
namespace {
3821

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

3844

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

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

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

3900

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

3953

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

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

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

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

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

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

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

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

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

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

4033

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

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

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

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

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

4087

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

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

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

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

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

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

4157
} // end anonymous namespace
4158

4159

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

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

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

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

4210

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

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

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

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

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

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

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

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

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

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

1✔
4276

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

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

1✔
4294

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

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

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

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

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

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

4347

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4537

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

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

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

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

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

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

1✔
4574
        group_w->commit_and_continue_as_read();
2✔
4575

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

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

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

1✔
4614

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

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

4634

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

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

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

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

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

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

1✔
4662
        group_w->commit_and_continue_as_read();
2✔
4663

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4816

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

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

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

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

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

1✔
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
        WriteTransaction wt(sg);
2✔
4896
        TableRef table = wt.get_or_add_table("test");
2✔
4897
        table->add_column(type_String, "string");
2✔
4898
        for (size_t i = 0; i < N; ++i) {
66✔
4899
            table->create_object().set_all(StringData(data.data(), data.size()));
64✔
4900
        }
64✔
4901
        wt.commit();
2✔
4902

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

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

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

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

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

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

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

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

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

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

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

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

4988
    const size_t threads = 5;
×
4989

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5099

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

1✔
5106
    int64_t number_of_history = 1000;
2✔
5107
    int64_t number_of_line = 18;
2✔
5108

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

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

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

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

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

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

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

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

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

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

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

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

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

5183

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

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

1✔
5192
    SHARED_GROUP_TEST_PATH(path);
2✔
5193

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

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

5206

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

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

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

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

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

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

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

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

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

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

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

5299

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

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

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

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

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

1✔
5324
    t1->clear();
2✔
5325

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

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

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

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

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

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

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

5376

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

5392

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

5411

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

5431

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

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

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

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

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

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

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

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

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

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

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

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

5613

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

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

5635

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

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

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

5660

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

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

5687

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

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

5714

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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