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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

94.27
/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
{
196✔
78
    CHECK(frozen->is_frozen());
196✔
79
    CHECK_THROW(frozen->promote_to_write(), LogicError);
196✔
80
    auto table = frozen->get_table("my_table");
196✔
81
    CHECK(table->is_frozen());
196✔
82
    auto col = table->get_column_key("my_col_1");
196✔
83
    int64_t sum = 0;
196✔
84
    for (auto i : *table) {
161,463✔
85
        sum += i.get<int64_t>(col);
161,463✔
86
    }
161,463✔
87
    CHECK_EQUAL(sum, 1000 / 2 * 999);
196✔
88
    TableView tv = table->where().not_equal(col, 42).find_all();
196✔
89
    CHECK(tv.is_frozen());
196✔
90
}
196✔
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);
193✔
125
        });
193✔
126
    for (int j = 0; j < num_threads; ++j)
202✔
127
        frozen_workers[j].join();
200✔
128
}
2✔
129

130

131
TEST(Transactions_ConcurrentFrozenTableGetByName)
132
{
2✔
133
    SHARED_GROUP_TEST_PATH(path);
2✔
134
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
135
    DBRef db = DB::create(*hist_w, path);
2✔
136
    TransactionRef frozen;
2✔
137
    std::string table_names[3000];
2✔
138
    {
2✔
139
        auto wt = db->start_write();
2✔
140
        for (int j = 0; j < 3000; ++j) {
6,002✔
141
            std::string name = "Table" + to_string(j);
6,000✔
142
            table_names[j] = name;
6,000✔
143
            wt->add_table(name);
6,000✔
144
        }
6,000✔
145
        wt->commit_and_continue_as_read();
2✔
146
        frozen = wt->freeze();
2✔
147
    }
2✔
148
    auto runner = [&](int first, int last) {
1,975✔
149
        millisleep(1);
1,975✔
150
        for (int j = first; j < last; ++j) {
1,913,965✔
151
            frozen->get_table(table_names[j]);
1,911,990✔
152
        }
1,911,990✔
153
    };
1,975✔
154
    std::thread threads[1000];
2✔
155
    for (int j = 0; j < 1000; ++j) {
2,002✔
156
        threads[j] = std::thread(runner, j * 2, j * 2 + 1000);
2,000✔
157
    }
2,000✔
158
    for (int j = 0; j < 1000; ++j)
2,002✔
159
        threads[j].join();
2,000✔
160
}
2✔
161

162
TEST(Transactions_ReclaimFrozen)
163
{
2✔
164
    struct Entry {
2✔
165
        TransactionRef frozen;
2✔
166
        Obj o;
2✔
167
        int64_t value;
2✔
168
    };
2✔
169
    int num_pending_transactions = 100;
2✔
170
    int num_transactions_created = 1000;
2✔
171
    int num_objects = 200;
2✔
172
    int num_checks_pr_trans = 10;
2✔
173

1✔
174
    SHARED_GROUP_TEST_PATH(path);
2✔
175
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
176
    DBRef db = DB::create(*hist_w, path);
2✔
177
    std::vector<Entry> refs;
2✔
178
    refs.resize(num_pending_transactions);
2✔
179
    Random random(random_int<unsigned long>());
2✔
180

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

217
TEST(Transactions_ConcurrentFrozenTableGetByKey)
218
{
2✔
219
    SHARED_GROUP_TEST_PATH(path);
2✔
220
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
221
    DBRef db = DB::create(*hist_w, path);
2✔
222
    TransactionRef frozen;
2✔
223
    TableKey table_keys[3000];
2✔
224
    {
2✔
225
        auto wt = db->start_write();
2✔
226
        for (int j = 0; j < 3000; ++j) {
6,002✔
227
            std::string name = "Table" + to_string(j);
6,000✔
228
            auto table = wt->add_table(name);
6,000✔
229
            table_keys[j] = table->get_key();
6,000✔
230
        }
6,000✔
231
        wt->commit_and_continue_as_read();
2✔
232
        frozen = wt->freeze();
2✔
233
    }
2✔
234
    auto runner = [&](int first, int last) {
1,956✔
235
        millisleep(1);
1,956✔
236
        for (int j = first; j < last; ++j) {
1,474,456✔
237
            auto table = frozen->get_table(table_keys[j]);
1,472,500✔
238
            CHECK(table->get_key() == table_keys[j]);
1,472,500✔
239
        }
1,472,500✔
240
    };
1,956✔
241
    std::thread threads[1000];
2✔
242
    for (int j = 0; j < 1000; ++j) {
2,002✔
243
        threads[j] = std::thread(runner, j * 2, j * 2 + 1000);
2,000✔
244
    }
2,000✔
245
    for (int j = 0; j < 1000; ++j)
2,002✔
246
        threads[j].join();
2,000✔
247
}
2✔
248

249

250
TEST(Transactions_ConcurrentFrozenQueryAndObj)
251
{
2✔
252
    SHARED_GROUP_TEST_PATH(path);
2✔
253
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
254
    DBRef db = DB::create(*hist_w, path);
2✔
255
    TransactionRef frozen;
2✔
256
    ObjKey obj_keys[1000];
2✔
257
    {
2✔
258
        auto wt = db->start_write();
2✔
259
        auto table = wt->add_table("MyTable");
2✔
260
        table->add_column(type_Int, "MyCol");
2✔
261
        for (int i = 0; i < 1000; ++i) {
2,002✔
262
            obj_keys[i] = table->create_object().set_all(i).get_key();
2,000✔
263
        }
2,000✔
264
        wt->commit_and_continue_as_read();
2✔
265
        frozen = wt->freeze();
2✔
266
    }
2✔
267
    auto runner = [&](int first, int last) {
991✔
268
        millisleep(1);
991✔
269
        auto table = frozen->get_table("MyTable");
991✔
270
        auto col = table->get_column_key("MyCol");
991✔
271
        for (int j = first; j < last; ++j) {
435,218✔
272
            // loads of concurrent queries created and executed:
191,842✔
273
            TableView tb = table->where().equal(col, j).find_all();
434,227✔
274
            CHECK(tb.size() == 1);
434,227✔
275
            CHECK(tb.get_key(0) == obj_keys[j]);
434,227✔
276
            // concurrent reads from results are just fine:
191,842✔
277
            auto obj = tb[0];
434,227✔
278
            CHECK(obj.get<Int>(col) == j);
434,227✔
279
        }
434,227✔
280
    };
991✔
281
    std::thread threads[500];
2✔
282
    for (int j = 0; j < 500; ++j) {
1,002✔
283
        threads[j] = std::thread(runner, j, j + 500);
1,000✔
284
    }
1,000✔
285
    for (int j = 0; j < 500; ++j)
1,002✔
286
        threads[j].join();
1,000✔
287
}
2✔
288

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

346
class MyHistory : public _impl::History {
347
public:
348
    MyHistory(const MyHistory&) = delete;
349
    explicit MyHistory(MyHistory* write_history = nullptr)
350
        : m_write_history(write_history)
351
    {
104✔
352
    }
104✔
353
    std::vector<char> m_incoming_changeset;
354
    version_type m_incoming_version;
355
    struct ChangeSet {
356
        std::vector<char> changes;
357
        bool finalized = false;
358
    };
359
    std::map<uint_fast64_t, ChangeSet> m_changesets;
360
    MyHistory* m_write_history = nullptr;
361

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

407
    void verify() const override
408
    {
50✔
409
        // No-op
25✔
410
    }
50✔
411
};
412

413
class ShortCircuitHistory : public Replication {
414
public:
415
    using version_type = _impl::History::version_type;
416

417
    version_type prepare_changeset(const char* data, size_t size, version_type orig_version) override
418
    {
5,124✔
419
        return m_history.add_changeset(data, size, orig_version); // Throws
5,124✔
420
    }
5,124✔
421

422
    void finalize_changeset() noexcept override
423
    {
5,124✔
424
        m_history.finalize();
5,124✔
425
    }
5,124✔
426

427
    HistoryType get_history_type() const noexcept override
428
    {
20,286✔
429
        return hist_InRealm;
20,286✔
430
    }
20,286✔
431

432
    _impl::History* _get_history_write() override
433
    {
10,248✔
434
        return &m_history;
10,248✔
435
    }
10,248✔
436

437
    std::unique_ptr<_impl::History> _create_history_read() override
438
    {
74✔
439
        return std::make_unique<MyHistory>(&m_history);
74✔
440
    }
74✔
441

442
    int get_history_schema_version() const noexcept override
443
    {
38✔
444
        return 0;
38✔
445
    }
38✔
446

447
    bool is_upgradable_history_schema(int) const noexcept override
448
    {
×
449
        REALM_ASSERT(false);
×
450
        return false;
×
451
    }
×
452

453
    void upgrade_history_schema(int) override
454
    {
×
455
        REALM_ASSERT(false);
×
456
    }
×
457

458

459
private:
460
    MyHistory m_history;
461
};
462

463
} // anonymous namespace
464

465

466
TEST(LangBindHelper_AdvanceReadTransact_Basics)
467
{
2✔
468
    SHARED_GROUP_TEST_PATH(path);
2✔
469
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
470
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
471

1✔
472
    // Start a read transaction (to be repeatedly advanced)
1✔
473
    TransactionRef rt = sg->start_read();
2✔
474
    CHECK_EQUAL(0, rt->size());
2✔
475

1✔
476
    // Try to advance without anything having happened
1✔
477
    rt->advance_read();
2✔
478
    rt->verify();
2✔
479
    CHECK_EQUAL(0, rt->size());
2✔
480

1✔
481
    // Try to advance after an empty write transaction
1✔
482
    {
2✔
483
        WriteTransaction wt(sg);
2✔
484
        wt.commit();
2✔
485
    }
2✔
486
    rt->advance_read();
2✔
487
    rt->verify();
2✔
488
    CHECK_EQUAL(0, rt->size());
2✔
489

1✔
490
    // Try to advance after a superfluous rollback
1✔
491
    {
2✔
492
        WriteTransaction wt(sg);
2✔
493
        // Implicit rollback
1✔
494
    }
2✔
495
    rt->advance_read();
2✔
496
    rt->verify();
2✔
497
    CHECK_EQUAL(0, rt->size());
2✔
498

1✔
499
    // Try to advance after a propper rollback
1✔
500
    {
2✔
501
        WriteTransaction wt(sg);
2✔
502
        wt.add_table("bad");
2✔
503
        // Implicit rollback
1✔
504
    }
2✔
505
    rt->advance_read();
2✔
506
    rt->verify();
2✔
507
    CHECK_EQUAL(0, rt->size());
2✔
508

1✔
509
    // Create a table via the other SharedGroup
1✔
510
    ObjKey k0;
2✔
511
    {
2✔
512
        WriteTransaction wt(sg);
2✔
513
        TableRef foo_w = wt.add_table("foo");
2✔
514
        foo_w->add_column(type_Int, "i");
2✔
515
        k0 = foo_w->create_object().get_key();
2✔
516
        wt.commit();
2✔
517
    }
2✔
518

1✔
519
    rt->advance_read();
2✔
520
    rt->verify();
2✔
521
    CHECK_EQUAL(1, rt->size());
2✔
522
    ConstTableRef foo = rt->get_table("foo");
2✔
523
    CHECK_EQUAL(1, foo->get_column_count());
2✔
524
    auto cols = foo->get_column_keys();
2✔
525
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
526
    CHECK_EQUAL(1, foo->size());
2✔
527
    CHECK_EQUAL(0, foo->get_object(k0).get<int64_t>(cols[0]));
2✔
528
    uint_fast64_t version = foo->get_content_version();
2✔
529

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

1✔
578
    // Again, with no change
1✔
579
    rt->advance_read();
2✔
580
    rt->verify();
2✔
581
    CHECK_EQUAL(10, foo->get_column_count());
2✔
582
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
583
    CHECK_EQUAL(type_String, foo->get_column_type(cols[1]));
2✔
584
    CHECK_EQUAL(2, foo->size());
2✔
585
    CHECK_EQUAL(1, obj0.get<int64_t>(cols[0]));
2✔
586
    CHECK_EQUAL(2, obj1.get<int64_t>(cols[0]));
2✔
587
    CHECK_EQUAL("a", obj0.get<StringData>(cols[1]));
2✔
588
    CHECK_EQUAL("b", obj1.get<StringData>(cols[1]));
2✔
589
    CHECK_EQUAL(foo, rt->get_table("foo"));
2✔
590

1✔
591
    // Perform several write transactions before advancing the read transaction
1✔
592
    {
2✔
593
        WriteTransaction wt(sg);
2✔
594
        TableRef bar_w = wt.add_table("bar");
2✔
595
        bar_w->add_column(type_Int, "a");
2✔
596
        wt.commit();
2✔
597
    }
2✔
598
    {
2✔
599
        WriteTransaction wt(sg);
2✔
600
        wt.commit();
2✔
601
    }
2✔
602
    {
2✔
603
        WriteTransaction wt(sg);
2✔
604
        TableRef bar_w = wt.get_table("bar");
2✔
605
        bar_w->add_column(type_Float, "b");
2✔
606
        wt.commit();
2✔
607
    }
2✔
608
    {
2✔
609
        WriteTransaction wt(sg);
2✔
610
        // Implicit rollback
1✔
611
    }
2✔
612
    {
2✔
613
        WriteTransaction wt(sg);
2✔
614
        TableRef bar_w = wt.get_table("bar");
2✔
615
        bar_w->add_column(type_Double, "c");
2✔
616
        wt.commit();
2✔
617
    }
2✔
618

1✔
619
    rt->advance_read();
2✔
620
    rt->verify();
2✔
621
    CHECK_EQUAL(2, rt->size());
2✔
622
    CHECK_EQUAL(10, foo->get_column_count());
2✔
623
    cols = foo->get_column_keys();
2✔
624
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
625
    CHECK_EQUAL(type_String, foo->get_column_type(cols[1]));
2✔
626
    CHECK_EQUAL(2, foo->size());
2✔
627
    CHECK_EQUAL(1, obj0.get<int64_t>(cols[0]));
2✔
628
    CHECK_EQUAL(2, obj1.get<int64_t>(cols[0]));
2✔
629
    CHECK_EQUAL("a", obj0.get<StringData>(cols[1]));
2✔
630
    CHECK_EQUAL("b", obj1.get<StringData>(cols[1]));
2✔
631
    CHECK_EQUAL(foo, rt->get_table("foo"));
2✔
632
    ConstTableRef bar = rt->get_table("bar");
2✔
633
    cols = bar->get_column_keys();
2✔
634
    CHECK_EQUAL(3, bar->get_column_count());
2✔
635
    CHECK_EQUAL(type_Int, bar->get_column_type(cols[0]));
2✔
636
    CHECK_EQUAL(type_Float, bar->get_column_type(cols[1]));
2✔
637
    CHECK_EQUAL(type_Double, bar->get_column_type(cols[2]));
2✔
638

1✔
639
    // Clear tables - not supported before backlinks work again
1✔
640
    {
2✔
641
        WriteTransaction wt(sg);
2✔
642
        TableRef foo_w = wt.get_table("foo");
2✔
643
        foo_w->clear();
2✔
644
        TableRef bar_w = wt.get_table("bar");
2✔
645
        bar_w->clear();
2✔
646
        wt.commit();
2✔
647
    }
2✔
648
    rt->advance_read();
2✔
649
    rt->verify();
2✔
650

1✔
651
    size_t free_space, used_space;
2✔
652
    sg->get_stats(free_space, used_space);
2✔
653

1✔
654
    CHECK_EQUAL(2, rt->size());
2✔
655
    CHECK(foo);
2✔
656
    cols = foo->get_column_keys();
2✔
657
    CHECK_EQUAL(10, foo->get_column_count());
2✔
658
    CHECK_EQUAL(type_Int, foo->get_column_type(cols[0]));
2✔
659
    CHECK_EQUAL(type_String, foo->get_column_type(cols[1]));
2✔
660
    CHECK_EQUAL(0, foo->size());
2✔
661
    CHECK(bar);
2✔
662
    cols = bar->get_column_keys();
2✔
663
    CHECK_EQUAL(3, bar->get_column_count());
2✔
664
    CHECK_EQUAL(type_Int, bar->get_column_type(cols[0]));
2✔
665
    CHECK_EQUAL(type_Float, bar->get_column_type(cols[1]));
2✔
666
    CHECK_EQUAL(type_Double, bar->get_column_type(cols[2]));
2✔
667
    CHECK_EQUAL(0, bar->size());
2✔
668
    CHECK_EQUAL(foo, rt->get_table("foo"));
2✔
669
    CHECK_EQUAL(bar, rt->get_table("bar"));
2✔
670
}
2✔
671

672
TEST(LangBindHelper_AdvanceReadTransact_AddTableWithFreshSharedGroup)
673
{
2✔
674
    SHARED_GROUP_TEST_PATH(path);
2✔
675

1✔
676
    // Testing that a foreign transaction, that adds a table, can be applied to
1✔
677
    // a freshly created SharedGroup, even when another table existed in the
1✔
678
    // group prior to the one being added in the mentioned transaction. This
1✔
679
    // test is relevant because of the way table accesors are created and
1✔
680
    // managed inside a SharedGroup, in particular because table accessors are
1✔
681
    // created lazily, and will therefore not be present in a freshly created
1✔
682
    // SharedGroup instance.
1✔
683

1✔
684
    // Add the first table
1✔
685
    {
2✔
686
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
687
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
688
        WriteTransaction wt(sg_w);
2✔
689
        wt.add_table("table_1");
2✔
690
        wt.commit();
2✔
691
    }
2✔
692

1✔
693
    // Create a SharedGroup to which we can apply a foreign transaction
1✔
694
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
695
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
696
    TransactionRef rt = sg->start_read();
2✔
697

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

1✔
707
    rt->advance_read();
2✔
708
}
2✔
709

710

711
TEST(LangBindHelper_AdvanceReadTransact_RemoveTableWithFreshSharedGroup)
712
{
2✔
713
    SHARED_GROUP_TEST_PATH(path);
2✔
714

1✔
715
    // Testing that a foreign transaction, that removes a table, can be applied
1✔
716
    // to a freshly created Sharedrt-> This test is relevant because of the
1✔
717
    // way table accesors are created and managed inside a SharedGroup, in
1✔
718
    // particular because table accessors are created lazily, and will therefore
1✔
719
    // not be present in a freshly created SharedGroup instance.
1✔
720

1✔
721
    // Add the table
1✔
722
    {
2✔
723
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
724
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
725
        WriteTransaction wt(sg_w);
2✔
726
        wt.add_table("table");
2✔
727
        wt.commit();
2✔
728
    }
2✔
729

1✔
730
    // Create a SharedGroup to which we can apply a foreign transaction
1✔
731
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
732
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
733
    TransactionRef rt = sg->start_read();
2✔
734

1✔
735
    // remove the table in a "foreign" transaction
1✔
736
    {
2✔
737
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
738
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
739
        WriteTransaction wt(sg_w);
2✔
740
        wt.get_group().remove_table("table");
2✔
741
        wt.commit();
2✔
742
    }
2✔
743

1✔
744
    rt->advance_read();
2✔
745
}
2✔
746

747

748
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_CreateManyTables, testing_supports_spawn_process)
749
{
2✔
750
    SHARED_GROUP_TEST_PATH(path);
2✔
751
    SHARED_GROUP_TEST_PATH(path2);
2✔
752

1✔
753
    if (SpawnedProcess::is_parent()) {
2✔
754
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
755
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
756
        WriteTransaction wt(sg_w);
2✔
757
        wt.add_table("table");
2✔
758
        wt.commit();
2✔
759
    }
2✔
760

1✔
761
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
762
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
763
    TransactionRef rt = sg->start_read();
2✔
764

1✔
765
    auto process = test_util::spawn_process(test_context.test_details.test_name, "make_many_tables");
2✔
766
    if (process->is_child()) {
2✔
767
        size_t free_space, used_space;
×
768
        {
×
769
            std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
×
770
            DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
×
771

772
            WriteTransaction wt(sg_w);
×
773
            for (int i = 0; i < 16; ++i) {
×
774
                wt.add_table(util::format("table_%1", i));
×
775
            }
×
776
            wt.commit();
×
777
            sg_w->get_stats(free_space, used_space);
×
778
        }
×
779
        {
×
780
            std::unique_ptr<Replication> hist_w2(realm::make_in_realm_history());
×
781
            DBRef sg_w2 = DB::create(*hist_w2, path2, DBOptions(crypt_key()));
×
782
            WriteTransaction wt(sg_w2);
×
783
            auto table = wt.add_table("stats");
×
784
            ColKey col = table->add_column(type_Int, "used_space");
×
785
            table->create_object().set<int64_t>(col, used_space);
×
786
            wt.commit();
×
787
        }
×
788

789
        exit(0);
×
790
    }
×
791
    else {
2✔
792
        process->wait_for_child_to_finish();
2✔
793
    }
2✔
794
    size_t reported_used_space = 0;
2✔
795
    {
2✔
796
        std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
797
        DBRef sg = DB::create(*hist, path2, DBOptions(crypt_key()));
2✔
798
        WriteTransaction wt(sg);
2✔
799
        auto table = wt.get_table("stats");
2✔
800
        CHECK(table);
2✔
801
        CHECK_EQUAL(table->size(), 1);
2✔
802
        reported_used_space = size_t(table->begin()->get<int64_t>("used_space"));
2✔
803
    }
2✔
804

1✔
805
    rt->advance_read();
2✔
806
    auto used_space1 = rt->get_used_space();
2✔
807
    CHECK_EQUAL(reported_used_space, used_space1);
2✔
808
}
2✔
809

810

811
TEST(LangBindHelper_AdvanceReadTransact_PinnedSize)
812
{
2✔
813
    SHARED_GROUP_TEST_PATH(path);
2✔
814
    constexpr int num_rows = 1000;
2✔
815
    constexpr int iterations = 10;
2✔
816
    constexpr int rows_per_iteration = num_rows / iterations;
2✔
817

1✔
818
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
819
    auto sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
820
    ObjKeys keys;
2✔
821

1✔
822
    // Create some data
1✔
823
    {
2✔
824
        {
2✔
825
            WriteTransaction wt(sg);
2✔
826
            auto table = wt.add_table("table");
2✔
827
            table->add_column(type_Int, "int");
2✔
828
            wt.commit();
2✔
829
        }
2✔
830
        for (size_t i = 0; i < iterations; i++) {
22✔
831
            WriteTransaction wt(sg);
20✔
832
            auto table = wt.get_table("table");
20✔
833
            auto col = table->get_column_key("int");
20✔
834
            for (int j = 0; j < rows_per_iteration; j++) {
2,020✔
835
                auto k = table->create_object().set(col, j).get_key();
2,000✔
836
                keys.push_back(k);
2,000✔
837
            }
2,000✔
838
            wt.commit();
20✔
839
        }
20✔
840
    }
2✔
841

1✔
842
    // Pin this version
1✔
843
    auto rt = sg->start_read();
2✔
844
    size_t free_space, used_space, locked_space;
2✔
845

1✔
846
    // Make some more versions
1✔
847
    {
2✔
848
        for (int i = 0; i < iterations; i++) {
22✔
849
            WriteTransaction wt(sg);
20✔
850
            auto table = wt.get_table("table");
20✔
851
            auto col = table->get_column_key("int");
20✔
852
            for (int j = 0; j < rows_per_iteration; j++) {
2,020✔
853
                int ndx = rows_per_iteration * i + j;
2,000✔
854
                table->get_object(keys[ndx]).set(col, 2 * ndx);
2,000✔
855
            }
2,000✔
856
            wt.commit();
20✔
857
        }
20✔
858
        sg->get_stats(free_space, used_space, &locked_space);
2✔
859
    }
2✔
860

1✔
861
    CHECK_GREATER(locked_space, 0);
2✔
862
    CHECK_LESS(locked_space, free_space);
2✔
863

1✔
864
    // Cancel read transaction
1✔
865
    rt = nullptr;
2✔
866
    size_t new_locked_space;
2✔
867
    {
2✔
868
        WriteTransaction wt(sg);
2✔
869
        wt.commit();
2✔
870
        // Large history entries are freed here
1✔
871
    }
2✔
872
    {
2✔
873
        WriteTransaction wt(sg);
2✔
874
        wt.commit();
2✔
875
        // History entries still held by previous commit
1✔
876
    }
2✔
877
    {
2✔
878
        WriteTransaction wt(sg);
2✔
879
        wt.commit();
2✔
880
        // History entries now finally free
1✔
881
    }
2✔
882
    sg->get_stats(free_space, used_space, &new_locked_space);
2✔
883

1✔
884
    // Some space must have been released
1✔
885
    CHECK_LESS(new_locked_space, locked_space);
2✔
886
}
2✔
887

888

889
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_InsertTable, testing_supports_spawn_process)
890
{
2✔
891
    SHARED_GROUP_TEST_PATH(path);
2✔
892

1✔
893
    if (test_util::SpawnedProcess::is_parent()) {
2✔
894
        std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
2✔
895
        DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
2✔
896
        WriteTransaction wt(sg_w);
2✔
897

1✔
898
        TableRef table = wt.add_table("table1");
2✔
899
        table->add_column(type_Int, "col");
2✔
900

1✔
901
        table = wt.add_table("table2");
2✔
902
        table->add_column(type_Float, "col1");
2✔
903
        table->add_column(type_Float, "col2");
2✔
904

1✔
905
        wt.commit();
2✔
906
    }
2✔
907

1✔
908
    std::unique_ptr<Replication> hist(realm::make_in_realm_history());
2✔
909
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
910
    TransactionRef rt = sg->start_read();
2✔
911

1✔
912
    ConstTableRef table1 = rt->get_table("table1");
2✔
913
    ConstTableRef table2 = rt->get_table("table2");
2✔
914

1✔
915
    auto process = test_util::spawn_process(test_context.test_details.test_name, "add_table");
2✔
916
    if (process->is_child()) {
2✔
917
        {
×
918
            std::unique_ptr<Replication> hist_w(realm::make_in_realm_history());
×
919
            DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
×
920
            WriteTransaction wt(sg_w);
×
921
            wt.get_group().add_table("new table");
×
922
            wt.get_table("table1")->create_object();
×
923
            wt.get_table("table2")->create_object();
×
924
            wt.get_table("table2")->create_object();
×
925
            wt.commit();
×
926
        } // clean up sg before exit
×
927
        exit(0);
×
928
    }
×
929
    else {
2✔
930
        process->wait_for_child_to_finish();
2✔
931
    }
2✔
932

1✔
933
    rt->advance_read();
2✔
934

1✔
935
    CHECK_EQUAL(table1->size(), 1);
2✔
936
    CHECK_EQUAL(table2->size(), 2);
2✔
937
    CHECK_EQUAL(rt->get_table("new table")->size(), 0);
2✔
938
}
2✔
939

940
TEST(LangBindHelper_AdvanceReadTransact_LinkColumnInNewTable)
941
{
2✔
942
    // Verify that the table accessor of a link-opposite table is refreshed even
1✔
943
    // when the origin table is created in the same transaction as the link
1✔
944
    // column is added to it. This case is slightly involved, as there is a rule
1✔
945
    // that requires the two opposite table accessors of a link column (origin
1✔
946
    // and target sides) to either both exist or both not exist. On the other
1✔
947
    // hand, tables accessors are normally not created during
1✔
948
    // Group::advance_transact() for newly created tables.
1✔
949

1✔
950
    SHARED_GROUP_TEST_PATH(path);
2✔
951
    ShortCircuitHistory hist;
2✔
952
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
953
    DBRef sg_w = DB::create(hist, path, DBOptions(crypt_key()));
2✔
954
    {
2✔
955
        WriteTransaction wt(sg_w);
2✔
956
        wt.get_or_add_table("a");
2✔
957
        wt.commit();
2✔
958
    }
2✔
959

1✔
960
    TransactionRef rt = sg->start_read();
2✔
961
    ConstTableRef a_r = rt->get_table("a");
2✔
962

1✔
963
    {
2✔
964
        WriteTransaction wt(sg_w);
2✔
965
        TableRef a_w = wt.get_table("a");
2✔
966
        TableRef b_w = wt.get_or_add_table("b");
2✔
967
        b_w->add_column(*a_w, "foo");
2✔
968
        wt.commit();
2✔
969
    }
2✔
970

1✔
971
    rt->advance_read();
2✔
972
    CHECK(a_r);
2✔
973
    rt->verify();
2✔
974
}
2✔
975

976

977
TEST(LangBindHelper_AdvanceReadTransact_EnumeratedStrings)
978
{
2✔
979
    SHARED_GROUP_TEST_PATH(path);
2✔
980
    ShortCircuitHistory hist;
2✔
981
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
982
    ColKey c0, c1, c2;
2✔
983

1✔
984
    // Start a read transaction (to be repeatedly advanced)
1✔
985
    auto rt = sg->start_read();
2✔
986
    CHECK_EQUAL(0, rt->size());
2✔
987

1✔
988
    // Create 3 string columns, one primed for conversion to "unique string
1✔
989
    // enumeration" representation
1✔
990
    {
2✔
991
        WriteTransaction wt(sg);
2✔
992
        TableRef table_w = wt.add_table("t");
2✔
993
        c0 = table_w->add_column(type_String, "a");
2✔
994
        c1 = table_w->add_column(type_String, "b");
2✔
995
        c2 = table_w->add_column(type_String, "c");
2✔
996
        for (int i = 0; i < 1000; ++i) {
2,002✔
997
            std::ostringstream out;
2,000✔
998
            out << i;
2,000✔
999
            std::string str = out.str();
2,000✔
1000
            table_w->create_object(ObjKey{}, {{c0, str}, {c1, "foo"}, {c2, str}});
2,000✔
1001
        }
2,000✔
1002
        wt.commit();
2✔
1003
    }
2✔
1004
    rt->advance_read();
2✔
1005
    rt->verify();
2✔
1006
    ConstTableRef table = rt->get_table("t");
2✔
1007
    CHECK_EQUAL(0, table->get_num_unique_values(c0));
2✔
1008
    CHECK_EQUAL(0, table->get_num_unique_values(c1)); // Not yet "optimized"
2✔
1009
    CHECK_EQUAL(0, table->get_num_unique_values(c2));
2✔
1010

1✔
1011
    // Optimize
1✔
1012
    {
2✔
1013
        WriteTransaction wt(sg);
2✔
1014
        TableRef table_w = wt.get_table("t");
2✔
1015
        table_w->enumerate_string_column(c1);
2✔
1016
        wt.commit();
2✔
1017
    }
2✔
1018
    rt->advance_read();
2✔
1019
    rt->verify();
2✔
1020
    CHECK_EQUAL(0, table->get_num_unique_values(c0));
2✔
1021
    CHECK_NOT_EQUAL(0, table->get_num_unique_values(c1)); // Must be "optimized" now
2✔
1022
    CHECK_EQUAL(0, table->get_num_unique_values(c2));
2✔
1023
}
2✔
1024

1025
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_SearchIndex, testing_supports_spawn_process)
1026
{
2✔
1027
    SHARED_GROUP_TEST_PATH(path);
2✔
1028
    if (test_util::SpawnedProcess::is_parent()) {
2✔
1029
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1030
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1031

1✔
1032
        // Start a read transaction (to be repeatedly advanced)
1✔
1033
        TransactionRef rt = sg->start_read();
2✔
1034
        CHECK_EQUAL(0, rt->size());
2✔
1035
    }
2✔
1036
    // Create 5 columns, and make 3 of them indexed
1✔
1037
    auto process = test_util::spawn_process(test_context.test_details.test_name, "init");
2✔
1038
    if (process->is_child()) {
2✔
1039
        {
×
1040
            std::vector<ObjKey> keys;
×
1041
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1042
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1043
            WriteTransaction wt(sg_w);
×
1044
            TableRef table_w = wt.add_table("t");
×
1045
            ColKey col_int = table_w->add_column(type_Int, "i0");
×
1046
            table_w->add_column(type_String, "s1");
×
1047
            ColKey col_str2 = table_w->add_column(type_String, "s2");
×
1048
            table_w->add_column(type_Int, "i3");
×
1049
            ColKey col_int4 = table_w->add_column(type_Int, "i4");
×
1050
            table_w->add_search_index(col_int);
×
1051
            table_w->add_search_index(col_str2);
×
1052
            table_w->add_search_index(col_int4);
×
1053
            table_w->create_objects(8, keys);
×
1054
            wt.commit();
×
1055
        } // clean up sg before exit
×
1056
        exit(0);
×
1057
    }
×
1058

1✔
1059
    if (process->is_parent()) {
2✔
1060
        process->wait_for_child_to_finish();
2✔
1061

1✔
1062
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1063
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1064

1✔
1065
        // Start a read transaction (to be repeatedly advanced)
1✔
1066
        TransactionRef rt = sg->start_read();
2✔
1067
        rt->advance_read();
2✔
1068
        rt->verify();
2✔
1069
        ConstTableRef table = rt->get_table("t");
2✔
1070
        CHECK(table->has_search_index(table->get_column_key("i0")));
2✔
1071
        CHECK_NOT(table->has_search_index(table->get_column_key("s1")));
2✔
1072
        CHECK(table->has_search_index(table->get_column_key("s2")));
2✔
1073
        CHECK_NOT(table->has_search_index(table->get_column_key("i3")));
2✔
1074
        CHECK(table->has_search_index(table->get_column_key("i4")));
2✔
1075
    }
2✔
1076

1✔
1077
    // Remove the previous search indexes and add 2 new ones
1✔
1078
    process = test_util::spawn_process(test_context.test_details.test_name, "change_indexes");
2✔
1079
    if (process->is_child()) {
2✔
1080
        {
×
1081
            std::vector<ObjKey> keys;
×
1082
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1083
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1084
            WriteTransaction wt(sg_w);
×
1085
            TableRef table_w = wt.get_table("t");
×
1086
            table_w->create_objects(8, keys);
×
1087
            table_w->remove_search_index(table_w->get_column_key("s2"));
×
1088
            table_w->add_search_index(table_w->get_column_key("i3"));
×
1089
            table_w->remove_search_index(table_w->get_column_key("i0"));
×
1090
            table_w->add_search_index(table_w->get_column_key("s1"));
×
1091
            table_w->remove_search_index(table_w->get_column_key("i4"));
×
1092
            wt.commit();
×
1093
        }
×
1094
        exit(0);
×
1095
    }
×
1096

1✔
1097
    if (process->is_parent()) {
2✔
1098
        process->wait_for_child_to_finish();
2✔
1099

1✔
1100
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1101
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1102

1✔
1103
        // Start a read transaction (to be repeatedly advanced)
1✔
1104
        TransactionRef rt = sg->start_read();
2✔
1105
        ConstTableRef table = rt->get_table("t");
2✔
1106
        rt->advance_read();
2✔
1107
        rt->verify();
2✔
1108
        CHECK_NOT(table->has_search_index(table->get_column_key("i0")));
2✔
1109
        CHECK(table->has_search_index(table->get_column_key("s1")));
2✔
1110
        CHECK_NOT(table->has_search_index(table->get_column_key("s2")));
2✔
1111
        CHECK(table->has_search_index(table->get_column_key("i3")));
2✔
1112
        CHECK_NOT(table->has_search_index(table->get_column_key("i4")));
2✔
1113
    }
2✔
1114

1✔
1115
    // Add some searchable contents
1✔
1116
    process = test_util::spawn_process(test_context.test_details.test_name, "add_content");
2✔
1117
    if (process->is_child()) {
2✔
1118
        {
×
1119
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1120
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1121
            WriteTransaction wt(sg_w);
×
1122
            TableRef table_w = wt.get_table("t");
×
1123
            int_fast64_t v = 7;
×
1124
            for (auto obj : *table_w) {
×
1125
                std::string out(util::to_string(v));
×
1126
                obj.set(table_w->get_column_key("s1"), StringData(out));
×
1127
                obj.set(table_w->get_column_key("i3"), v);
×
1128
                v = (v + 1581757577LL) % 1000;
×
1129
            }
×
1130
            wt.commit();
×
1131
        }
×
1132
        exit(0);
×
1133
    }
×
1134
    if (process->is_parent()) {
2✔
1135
        process->wait_for_child_to_finish();
2✔
1136

1✔
1137
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1138
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1139

1✔
1140
        // Start a read transaction (to be repeatedly advanced)
1✔
1141
        TransactionRef rt = sg->start_read();
2✔
1142
        ConstTableRef table = rt->get_table("t");
2✔
1143
        rt->advance_read();
2✔
1144
        rt->verify();
2✔
1145

1✔
1146
        CHECK_NOT(table->has_search_index(table->get_column_key("i0")));
2✔
1147
        CHECK(table->has_search_index(table->get_column_key("s1")));
2✔
1148
        CHECK_NOT(table->has_search_index(table->get_column_key("s2")));
2✔
1149
        CHECK(table->has_search_index(table->get_column_key("i3")));
2✔
1150
        CHECK_NOT(table->has_search_index(table->get_column_key("i4")));
2✔
1151
        CHECK_EQUAL(ObjKey(12), table->find_first_string(table->get_column_key("s1"), "931"));
2✔
1152
        CHECK_EQUAL(ObjKey(4), table->find_first_int(table->get_column_key("i3"), 315));
2✔
1153
        CHECK_EQUAL(ObjKey(13), table->find_first_int(table->get_column_key("i3"), 508));
2✔
1154
    }
2✔
1155
    // Move the indexed columns by removal
1✔
1156
    process = test_util::spawn_process(test_context.test_details.test_name, "move_and_remove");
2✔
1157
    if (process->is_child()) {
2✔
1158
        {
×
1159
            std::unique_ptr<Replication> hist = make_in_realm_history();
×
1160
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1161
            WriteTransaction wt(sg_w);
×
1162
            TableRef table_w = wt.get_table("t");
×
1163
            table_w->remove_column(table_w->get_column_key("i0"));
×
1164
            table_w->remove_column(table_w->get_column_key("s2"));
×
1165
            wt.commit();
×
1166
        }
×
1167
        exit(0);
×
1168
    }
×
1169
    if (process->is_parent()) {
2✔
1170
        process->wait_for_child_to_finish();
2✔
1171

1✔
1172
        std::unique_ptr<Replication> hist_r = make_in_realm_history();
2✔
1173
        DBRef sg = DB::create(*hist_r, path, DBOptions(crypt_key()));
2✔
1174

1✔
1175
        // Start a read transaction (to be repeatedly advanced)
1✔
1176
        TransactionRef rt = sg->start_read();
2✔
1177
        ConstTableRef table = rt->get_table("t");
2✔
1178
        rt->advance_read();
2✔
1179
        rt->verify();
2✔
1180
        CHECK(table->has_search_index(table->get_column_key("s1")));
2✔
1181
        CHECK(table->has_search_index(table->get_column_key("i3")));
2✔
1182
        CHECK_NOT(table->has_search_index(table->get_column_key("i4")));
2✔
1183
        CHECK_EQUAL(ObjKey(3), table->find_first_string(table->get_column_key("s1"), "738"));
2✔
1184
        CHECK_EQUAL(ObjKey(13), table->find_first_int(table->get_column_key("i3"), 508));
2✔
1185
    }
2✔
1186
}
2✔
1187

1188
TEST(LangBindHelper_AdvanceReadTransact_LinkView)
1189
{
2✔
1190
    SHARED_GROUP_TEST_PATH(path);
2✔
1191
    ShortCircuitHistory hist;
2✔
1192
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1193
    DBRef sg_w = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1194
    DBRef sg_q = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1195

1✔
1196
    // Start a continuous read transaction
1✔
1197
    TransactionRef rt = sg->start_read();
2✔
1198

1✔
1199
    // Add some tables and rows.
1✔
1200
    {
2✔
1201
        WriteTransaction wt(sg_w);
2✔
1202
        TableRef origin = wt.add_table("origin");
2✔
1203
        TableRef target = wt.add_table("target");
2✔
1204
        target->add_column(type_Int, "value");
2✔
1205
        auto col = origin->add_column_list(*target, "list");
2✔
1206

1✔
1207
        std::vector<ObjKey> keys;
2✔
1208
        target->create_objects(10, keys);
2✔
1209

1✔
1210
        Obj o0 = origin->create_object(ObjKey(0));
2✔
1211
        Obj o1 = origin->create_object(ObjKey(1));
2✔
1212

1✔
1213
        o0.get_linklist(col).add(keys[1]);
2✔
1214
        o1.get_linklist(col).add(keys[2]);
2✔
1215
        // state:
1✔
1216
        // origin[0].ll[0] -> target[1]
1✔
1217
        // origin[1].ll[0] -> target[2]
1✔
1218
        wt.commit();
2✔
1219
    }
2✔
1220
    rt->advance_read();
2✔
1221
    rt->verify();
2✔
1222

1✔
1223
    // Grab references to the LinkViews
1✔
1224
    auto origin = rt->get_table("origin");
2✔
1225
    auto col_link = origin->get_column_key("list");
2✔
1226
    const Obj obj0 = origin->get_object(ObjKey(0));
2✔
1227
    const Obj obj1 = origin->get_object(ObjKey(1));
2✔
1228

1✔
1229
    auto ll1 = obj0.get_linklist(col_link); // lv1[0] -> target[1]
2✔
1230
    auto ll2 = obj1.get_linklist(col_link); // lv2[0] -> target[2]
2✔
1231
    CHECK_EQUAL(ll1.size(), 1);
2✔
1232
    CHECK_EQUAL(ll2.size(), 1);
2✔
1233

1✔
1234
    ObjKey ll1_target = ll1.get_object(0).get_key();
2✔
1235
    CHECK_EQUAL(ll1.find_first(ll1_target), 0);
2✔
1236

1✔
1237
    {
2✔
1238
        WriteTransaction wt(sg_w);
2✔
1239
        wt.get_table("origin")->get_object(ObjKey(0)).get_linklist(col_link).clear();
2✔
1240
        wt.commit();
2✔
1241
    }
2✔
1242
    rt->advance_read();
2✔
1243
    rt->verify();
2✔
1244

1✔
1245
    CHECK_EQUAL(ll1.find_first(ll1_target), not_found);
2✔
1246
}
2✔
1247

1248
namespace {
1249

1250
template <typename T>
1251
class ConcurrentQueue {
1252
public:
1253
    ConcurrentQueue(size_t size)
1254
        : sz(size)
1255
    {
2✔
1256
        data.reset(new T[sz]);
2✔
1257
    }
2✔
1258
    inline bool is_full()
1259
    {
199,998✔
1260
        return writer - reader == sz;
199,998✔
1261
    }
199,998✔
1262
    inline bool is_empty()
1263
    {
220,030✔
1264
        return writer - reader == 0;
220,030✔
1265
    }
220,030✔
1266
    void put(T& e)
1267
    {
100,000✔
1268
        std::unique_lock<std::mutex> lock(mutex);
100,000✔
1269
        while (is_full())
100,001✔
1270
            not_full.wait(lock);
1✔
1271
        if (is_empty())
100,000✔
1272
            not_empty_or_closed.notify_all();
32,406✔
1273
        data[writer++ % sz] = std::move(e);
100,000✔
1274
    }
100,000✔
1275

1276
    bool get(T& e)
1277
    {
99,999✔
1278
        std::unique_lock<std::mutex> lock(mutex);
99,999✔
1279
        while (is_empty() && !closed)
120,030✔
1280
            not_empty_or_closed.wait(lock);
20,031✔
1281
        if (closed)
99,999✔
1282
            return false;
2✔
1283
        if (is_full())
99,997✔
1284
            not_full.notify_all();
1✔
1285
        e = std::move(data[reader++ % sz]);
99,997✔
1286
        return true;
99,997✔
1287
    }
99,997✔
1288

1289
    void reopen()
1290
    {
1291
        // no concurrent access allowed here
1292
        closed = false;
1293
    }
1294

1295
    void close()
1296
    {
2✔
1297
        std::unique_lock<std::mutex> lock(mutex);
2✔
1298
        closed = true;
2✔
1299
        not_empty_or_closed.notify_all();
2✔
1300
    }
2✔
1301

1302
private:
1303
    std::mutex mutex;
1304
    std::condition_variable not_full;
1305
    std::condition_variable not_empty_or_closed;
1306
    size_t reader = 0;
1307
    size_t writer = 0;
1308
    bool closed = false;
1309
    size_t sz;
1310
    std::unique_ptr<T[]> data;
1311
};
1312

1313
// Background thread for test below.
1314
void deleter_thread(ConcurrentQueue<LnkLstPtr>& queue)
1315
{
2✔
1316
    Random random(random_int<unsigned long>());
2✔
1317
    bool closed = false;
2✔
1318
    while (!closed) {
100,001✔
1319
        LnkLstPtr r;
99,999✔
1320
        // prevent the compiler from eliminating a loop:
49,999✔
1321
        volatile int delay = random.draw_int_mod(10000);
99,999✔
1322
        closed = !queue.get(r);
99,999✔
1323
        // random delay goes *after* get(), so that it comes
49,999✔
1324
        // after the potentially synchronizing locking
49,999✔
1325
        // operation inside queue.get()
49,999✔
1326
        while (delay > 0)
500,383,564✔
1327
            delay = delay - 1;
500,283,565✔
1328
        // just let 'r' die
49,999✔
1329
    }
99,999✔
1330
}
2✔
1331
} // namespace
1332

1333
TEST(LangBindHelper_ConcurrentLinkViewDeletes)
1334
{
2✔
1335
    // This tests checks concurrent deletion of LinkViews.
1✔
1336
    // It is structured as a mutator which creates and uses
1✔
1337
    // LinkView accessors, and a background deleter which
1✔
1338
    // consumes LinkViewRefs and makes them go out of scope
1✔
1339
    // concurrently with the new references being created.
1✔
1340

1✔
1341
    // Number of table entries (and hence, max number of accessors)
1✔
1342
    const int table_size = 1000;
2✔
1343

1✔
1344
    // Number of references produced (some will refer to the same
1✔
1345
    // accessor)
1✔
1346
    const int max_refs = 50000;
2✔
1347

1✔
1348
    // Frequency of references that are used to change the
1✔
1349
    // database during the test.
1✔
1350
    const int change_frequency_per_mill = 50000; // 5pct changes
2✔
1351

1✔
1352
    // Number of references that may be buffered for communication
1✔
1353
    // between main thread and deleter thread. Should be large enough
1✔
1354
    // to allow considerable overlap.
1✔
1355
    const int buffer_size = 2000;
2✔
1356

1✔
1357
    Random random(random_int<unsigned long>());
2✔
1358

1✔
1359
    // setup two tables with empty linklists inside
1✔
1360
    SHARED_GROUP_TEST_PATH(path);
2✔
1361
    ShortCircuitHistory hist;
2✔
1362
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1363

1✔
1364
    // Start a read transaction (to be repeatedly advanced)
1✔
1365
    std::vector<ObjKey> o_keys;
2✔
1366
    std::vector<ObjKey> t_keys;
2✔
1367
    ColKey ck;
2✔
1368
    auto rt = sg->start_read();
2✔
1369
    {
2✔
1370
        // setup tables with empty linklists
1✔
1371
        WriteTransaction wt(sg);
2✔
1372
        TableRef origin = wt.add_table("origin");
2✔
1373
        TableRef target = wt.add_table("target");
2✔
1374
        ck = origin->add_column_list(*target, "ll");
2✔
1375
        origin->create_objects(table_size, o_keys);
2✔
1376
        target->create_objects(table_size, t_keys);
2✔
1377
        wt.commit();
2✔
1378
    }
2✔
1379
    rt->advance_read();
2✔
1380

1✔
1381
    // Create accessors for random entries in the table.
1✔
1382
    // occasionally modify the database through the accessor.
1✔
1383
    // feed the accessor refs to the background thread for
1✔
1384
    // later deletion.
1✔
1385
    util::Thread deleter;
2✔
1386
    ConcurrentQueue<LnkLstPtr> queue(buffer_size);
2✔
1387
    deleter.start([&] {
2✔
1388
        deleter_thread(queue);
2✔
1389
    });
2✔
1390
    for (int i = 0; i < max_refs; ++i) {
100,002✔
1391
        TableRef origin = rt->get_table("origin");
100,000✔
1392
        int ndx = random.draw_int_mod(table_size);
100,000✔
1393
        Obj o = origin->get_object(o_keys[ndx]);
100,000✔
1394
        LnkLstPtr lw = o.get_linklist_ptr(ck);
100,000✔
1395
        bool will_add = change_frequency_per_mill > random.draw_int_mod(1000000);
100,000✔
1396
        if (will_add) {
100,000✔
1397
            rt->promote_to_write();
5,040✔
1398
            lw->add(t_keys[ndx]);
5,040✔
1399
            rt->commit_and_continue_as_read();
5,040✔
1400
        }
5,040✔
1401
        queue.put(lw);
100,000✔
1402
    }
100,000✔
1403
    queue.close();
2✔
1404
    deleter.join();
2✔
1405
}
2✔
1406

1407
TEST(LangBindHelper_AdvanceReadTransact_InsertLink)
1408
{
2✔
1409
    // This test checks that Table::insert_link() works across transaction
1✔
1410
    // boundaries (advance transaction).
1✔
1411

1✔
1412
    SHARED_GROUP_TEST_PATH(path);
2✔
1413
    ShortCircuitHistory hist;
2✔
1414
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1415

1✔
1416
    // Start a read transaction (to be repeatedly advanced)
1✔
1417
    TransactionRef rt = sg->start_read();
2✔
1418
    CHECK_EQUAL(0, rt->size());
2✔
1419
    ColKey col;
2✔
1420
    ObjKey target_key;
2✔
1421
    {
2✔
1422
        WriteTransaction wt(sg);
2✔
1423
        TableRef origin_w = wt.add_table("origin");
2✔
1424
        TableRef target_w = wt.add_table("target");
2✔
1425
        col = origin_w->add_column(*target_w, "");
2✔
1426
        target_w->add_column(type_Int, "");
2✔
1427
        target_key = target_w->create_object().get_key();
2✔
1428
        wt.commit();
2✔
1429
    }
2✔
1430
    rt->advance_read();
2✔
1431
    rt->verify();
2✔
1432
    ConstTableRef origin = rt->get_table("origin");
2✔
1433
    ConstTableRef target = rt->get_table("target");
2✔
1434
    {
2✔
1435
        WriteTransaction wt(sg);
2✔
1436
        TableRef origin_w = wt.get_table("origin");
2✔
1437
        auto obj = origin_w->create_object();
2✔
1438
        obj.set(col, target_key);
2✔
1439
        wt.commit();
2✔
1440
    }
2✔
1441
    rt->advance_read();
2✔
1442
    CHECK(origin);
2✔
1443
    CHECK(target);
2✔
1444
    rt->verify();
2✔
1445
}
2✔
1446

1447

1448
TEST(LangBindHelper_AdvanceReadTransact_LinkToNeighbour)
1449
{
2✔
1450
    // This test checks that you can insert a link to an object that resides
1✔
1451
    // in the same cluster as the origin object.
1✔
1452

1✔
1453
    SHARED_GROUP_TEST_PATH(path);
2✔
1454
    ShortCircuitHistory hist;
2✔
1455
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1456

1✔
1457
    // Start a read transaction (to be repeatedly advanced)
1✔
1458
    TransactionRef rt = sg->start_read();
2✔
1459
    CHECK_EQUAL(0, rt->size());
2✔
1460
    ColKey col;
2✔
1461
    std::vector<ObjKey> keys;
2✔
1462
    {
2✔
1463
        WriteTransaction wt(sg);
2✔
1464
        TableRef table = wt.add_table("table");
2✔
1465
        table->add_column(type_Int, "integers");
2✔
1466
        col = table->add_column(*table, "links");
2✔
1467
        table->create_objects(10, keys);
2✔
1468
        wt.commit();
2✔
1469
    }
2✔
1470
    rt->advance_read();
2✔
1471
    rt->verify();
2✔
1472
    {
2✔
1473
        WriteTransaction wt(sg);
2✔
1474
        TableRef table = wt.get_table("table");
2✔
1475
        table->get_object(keys[0]).set(col, keys[1]);
2✔
1476
        table->get_object(keys[1]).set(col, keys[2]);
2✔
1477
        wt.commit();
2✔
1478
    }
2✔
1479
    rt->advance_read();
2✔
1480
    rt->verify();
2✔
1481
}
2✔
1482

1483
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_RemoveTableWithColumns, testing_supports_spawn_process)
1484
{
2✔
1485
    SHARED_GROUP_TEST_PATH(path);
2✔
1486
    if (test_util::SpawnedProcess::is_parent()) {
2✔
1487
        std::unique_ptr<Replication> hist_parent(make_in_realm_history());
2✔
1488
        DBRef sg = DB::create(*hist_parent, path, DBOptions(crypt_key()));
2✔
1489

1✔
1490
        // Start a read transaction (to be repeatedly advanced)
1✔
1491
        TransactionRef rt = sg->start_read();
2✔
1492
        CHECK_EQUAL(0, rt->size());
2✔
1493
    }
2✔
1494
    auto process = test_util::spawn_process(test_context.test_details.test_name, "initial_write");
2✔
1495
    if (process->is_child()) {
2✔
1496
        {
×
1497
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1498
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1499
            WriteTransaction wt(sg_w);
×
1500
            TableRef alpha_w = wt.add_table("alpha");
×
1501
            TableRef beta_w = wt.add_table("beta");
×
1502
            TableRef gamma_w = wt.add_table("gamma");
×
1503
            TableRef delta_w = wt.add_table("delta");
×
1504
            TableRef epsilon_w = wt.add_table("epsilon");
×
1505
            alpha_w->add_column(type_Int, "alpha-1");
×
1506
            beta_w->add_column(*delta_w, "beta-1");
×
1507
            gamma_w->add_column(*gamma_w, "gamma-1");
×
1508
            delta_w->add_column(type_Int, "delta-1");
×
1509
            epsilon_w->add_column(*delta_w, "epsilon-1");
×
1510
            wt.commit();
×
1511
        } // clean up sg before exit
×
1512
        exit(0);
×
1513
    }
×
1514
    else if (process->is_parent()) {
2✔
1515
        process->wait_for_child_to_finish();
2✔
1516

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

1✔
1520
        // Start a read transaction (to be repeatedly advanced)
1✔
1521
        TransactionRef rt = sg->start_read();
2✔
1522
        rt->advance_read();
2✔
1523
        rt->verify();
2✔
1524

1✔
1525
        CHECK_EQUAL(5, rt->size());
2✔
1526
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1527
        ConstTableRef beta = rt->get_table("beta");
2✔
1528
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1529
        ConstTableRef delta = rt->get_table("delta");
2✔
1530
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1531
        CHECK(alpha);
2✔
1532
        CHECK(beta);
2✔
1533
        CHECK(gamma);
2✔
1534
        CHECK(delta);
2✔
1535
        CHECK(epsilon);
2✔
1536
    }
2✔
1537
    // Remove table with columns, but no link columns, and table is not a link
1✔
1538
    // target.
1✔
1539
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_alpha");
2✔
1540
    if (process->is_child()) {
2✔
1541
        {
×
1542
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1543
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1544
            WriteTransaction wt(sg_w);
×
1545
            wt.get_group().remove_table("alpha");
×
1546
            wt.commit();
×
1547
        }
×
1548
        exit(0);
×
1549
    }
×
1550
    else if (process->is_parent()) {
2✔
1551
        process->wait_for_child_to_finish();
2✔
1552

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

1✔
1556
        // Start a read transaction (to be repeatedly advanced)
1✔
1557
        TransactionRef rt = sg->start_read();
2✔
1558
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1559
        ConstTableRef beta = rt->get_table("beta");
2✔
1560
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1561
        ConstTableRef delta = rt->get_table("delta");
2✔
1562
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1563
        rt->advance_read();
2✔
1564
        rt->verify();
2✔
1565

1✔
1566
        CHECK_EQUAL(4, rt->size());
2✔
1567
        CHECK_NOT(alpha);
2✔
1568
        CHECK(beta);
2✔
1569
        CHECK(gamma);
2✔
1570
        CHECK(delta);
2✔
1571
        CHECK(epsilon);
2✔
1572
    }
2✔
1573
    // Remove table with link column, and table is not a link target.
1✔
1574
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_beta");
2✔
1575
    if (process->is_child()) {
2✔
1576
        {
×
1577
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1578
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1579
            WriteTransaction wt(sg_w);
×
1580
            wt.get_group().remove_table("beta");
×
1581
            wt.commit();
×
1582
        }
×
1583
        exit(0);
×
1584
    }
×
1585
    else if (process->is_parent()) {
2✔
1586
        process->wait_for_child_to_finish();
2✔
1587

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

1✔
1591
        // Start a read transaction (to be repeatedly advanced)
1✔
1592
        TransactionRef rt = sg->start_read();
2✔
1593
        rt->advance_read();
2✔
1594
        rt->verify();
2✔
1595

1✔
1596
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1597
        ConstTableRef beta = rt->get_table("beta");
2✔
1598
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1599
        ConstTableRef delta = rt->get_table("delta");
2✔
1600
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1601
        CHECK_EQUAL(3, rt->size());
2✔
1602
        CHECK_NOT(alpha);
2✔
1603
        CHECK_NOT(beta);
2✔
1604
        CHECK(gamma);
2✔
1605
        CHECK(delta);
2✔
1606
        CHECK(epsilon);
2✔
1607
    }
2✔
1608
    // Remove table with self-link column, and table is not a target of link
1✔
1609
    // columns of other tables.
1✔
1610
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_gamma");
2✔
1611
    if (process->is_child()) {
2✔
1612
        {
×
1613
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1614
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1615
            WriteTransaction wt(sg_w);
×
1616
            wt.get_group().remove_table("gamma");
×
1617
            wt.commit();
×
1618
        }
×
1619
        exit(0);
×
1620
    }
×
1621
    else if (process->is_parent()) {
2✔
1622
        process->wait_for_child_to_finish();
2✔
1623

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

1✔
1627
        // Start a read transaction (to be repeatedly advanced)
1✔
1628
        TransactionRef rt = sg->start_read();
2✔
1629
        rt->advance_read();
2✔
1630
        rt->verify();
2✔
1631

1✔
1632
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1633
        ConstTableRef beta = rt->get_table("beta");
2✔
1634
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1635
        ConstTableRef delta = rt->get_table("delta");
2✔
1636
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1637
        CHECK_EQUAL(2, rt->size());
2✔
1638
        CHECK_NOT(alpha);
2✔
1639
        CHECK_NOT(beta);
2✔
1640
        CHECK_NOT(gamma);
2✔
1641
        CHECK(delta);
2✔
1642
        CHECK(epsilon);
2✔
1643
    }
2✔
1644
    // Try, but fail to remove table which is a target of link columns of other
1✔
1645
    // tables.
1✔
1646
    process = test_util::spawn_process(test_context.test_details.test_name, "remove_delta");
2✔
1647
    if (process->is_child()) {
2✔
1648
        {
×
1649
            std::unique_ptr<Replication> hist(make_in_realm_history());
×
1650
            DBRef sg_w = DB::create(*hist, path, DBOptions(crypt_key()));
×
1651
            WriteTransaction wt(sg_w);
×
1652
            CHECK_THROW(wt.get_group().remove_table("delta"), CrossTableLinkTarget);
×
1653
            wt.commit();
×
1654
        }
×
1655
        exit(0);
×
1656
    }
×
1657
    else if (process->is_parent()) {
2✔
1658
        process->wait_for_child_to_finish();
2✔
1659

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

1✔
1663
        // Start a read transaction (to be repeatedly advanced)
1✔
1664
        TransactionRef rt = sg->start_read();
2✔
1665
        rt->advance_read();
2✔
1666
        rt->verify();
2✔
1667
        ConstTableRef alpha = rt->get_table("alpha");
2✔
1668
        ConstTableRef beta = rt->get_table("beta");
2✔
1669
        ConstTableRef gamma = rt->get_table("gamma");
2✔
1670
        ConstTableRef delta = rt->get_table("delta");
2✔
1671
        ConstTableRef epsilon = rt->get_table("epsilon");
2✔
1672

1✔
1673
        CHECK_EQUAL(2, rt->size());
2✔
1674
        CHECK_NOT(alpha);
2✔
1675
        CHECK_NOT(beta);
2✔
1676
        CHECK_NOT(gamma);
2✔
1677
        CHECK(delta);
2✔
1678
        CHECK(epsilon);
2✔
1679
    }
2✔
1680
}
2✔
1681

1682
TEST(LangBindHelper_AdvanceReadTransact_CascadeRemove_ColumnLink)
1683
{
2✔
1684
    SHARED_GROUP_TEST_PATH(path);
2✔
1685
    ShortCircuitHistory hist;
2✔
1686
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1687

1✔
1688
    ColKey col;
2✔
1689
    {
2✔
1690
        WriteTransaction wt(sg);
2✔
1691
        auto origin = wt.add_table("origin");
2✔
1692
        auto target = wt.add_table("target", Table::Type::Embedded);
2✔
1693
        col = origin->add_column(*target, "o_1");
2✔
1694
        target->add_column(type_Int, "t_1");
2✔
1695
        wt.commit();
2✔
1696
    }
2✔
1697

1✔
1698
    // Start a read transaction (to be repeatedly advanced)
1✔
1699
    auto rt = sg->start_read();
2✔
1700
    auto target = rt->get_table("target");
2✔
1701

1✔
1702
    ObjKey target_key0, target_key1;
2✔
1703
    Obj target_obj0, target_obj1;
2✔
1704

1✔
1705
    auto perform_change = [&](util::FunctionRef<void(Table&)> func) {
6✔
1706
        // Ensure there are two rows in each table, with each row in `origin`
3✔
1707
        // pointing to the corresponding row in `target`
3✔
1708
        {
6✔
1709
            WriteTransaction wt(sg);
6✔
1710
            auto origin_w = wt.get_table("origin");
6✔
1711
            auto target_w = wt.get_table("target");
6✔
1712

3✔
1713
            origin_w->clear();
6✔
1714
            target_w->clear();
6✔
1715
            auto o0 = origin_w->create_object();
6✔
1716
            auto o1 = origin_w->create_object();
6✔
1717
            target_key0 = o0.create_and_set_linked_object(col).get_key();
6✔
1718
            target_key1 = o1.create_and_set_linked_object(col).get_key();
6✔
1719
            wt.commit();
6✔
1720
        }
6✔
1721

3✔
1722
        // Grab the row accessors before applying the modification being tested
3✔
1723
        rt->advance_read();
6✔
1724
        rt->verify();
6✔
1725
        target_obj0 = target->get_object(target_key0);
6✔
1726
        target_obj1 = target->get_object(target_key1);
6✔
1727

3✔
1728
        // Perform the modification
3✔
1729
        {
6✔
1730
            WriteTransaction wt(sg);
6✔
1731
            func(*wt.get_table("origin"));
6✔
1732
            wt.commit();
6✔
1733
        }
6✔
1734

3✔
1735
        rt->advance_read();
6✔
1736
        rt->verify();
6✔
1737
        // Leave `group` and the target accessors in a state which can be tested
3✔
1738
        // with the changes applied
3✔
1739
    };
6✔
1740

1✔
1741
    // Break link by clearing table
1✔
1742
    perform_change([](Table& origin) {
2✔
1743
        origin.clear();
2✔
1744
    });
2✔
1745
    CHECK(!target_obj0.is_valid());
2✔
1746
    CHECK(!target_obj1.is_valid());
2✔
1747
    CHECK_EQUAL(target->size(), 0);
2✔
1748

1✔
1749
    // Break link by nullifying
1✔
1750
    perform_change([&](Table& origin) {
2✔
1751
        origin.get_object(1).set_null(col);
2✔
1752
    });
2✔
1753
    CHECK(target_obj0.is_valid());
2✔
1754
    CHECK(!target_obj1.is_valid());
2✔
1755
    CHECK_EQUAL(target->size(), 1);
2✔
1756

1✔
1757
    // Break link by reassign
1✔
1758
    perform_change([&](Table& origin) {
2✔
1759
        origin.get_object(1).create_and_set_linked_object(col);
2✔
1760
    });
2✔
1761
    CHECK(target_obj0.is_valid());
2✔
1762
    CHECK(!target_obj1.is_valid());
2✔
1763
    CHECK_EQUAL(target->size(), 2);
2✔
1764
}
2✔
1765

1766

1767
TEST(LangBindHelper_AdvanceReadTransact_CascadeRemove_ColumnLinkList)
1768
{
2✔
1769
    SHARED_GROUP_TEST_PATH(path);
2✔
1770
    ShortCircuitHistory hist;
2✔
1771
    DBRef sg = DB::create(hist, path, DBOptions(crypt_key()));
2✔
1772

1✔
1773
    ColKey col;
2✔
1774
    {
2✔
1775
        WriteTransaction wt(sg);
2✔
1776
        auto origin = wt.add_table("origin");
2✔
1777
        auto target = wt.add_table("target", Table::Type::Embedded);
2✔
1778
        col = origin->add_column_list(*target, "o_1");
2✔
1779
        target->add_column(type_Int, "t_1");
2✔
1780
        wt.commit();
2✔
1781
    }
2✔
1782

1✔
1783
    // Start a read transaction (to be repeatedly advanced)
1✔
1784
    auto rt = sg->start_read();
2✔
1785
    auto target = rt->get_table("target");
2✔
1786

1✔
1787
    ObjKey target_key0, target_key1;
2✔
1788
    Obj target_obj0, target_obj1;
2✔
1789

1✔
1790
    auto perform_change = [&](util::FunctionRef<void(Table&)> func) {
8✔
1791
        // Ensure there are two rows in each table, with each row in `origin`
4✔
1792
        // pointing to the corresponding row in `target`
4✔
1793
        {
8✔
1794
            WriteTransaction wt(sg);
8✔
1795
            auto origin_w = wt.get_table("origin");
8✔
1796
            auto target_w = wt.get_table("target");
8✔
1797

4✔
1798
            origin_w->clear();
8✔
1799
            target_w->clear();
8✔
1800
            auto o0 = origin_w->create_object();
8✔
1801
            auto o1 = origin_w->create_object();
8✔
1802
            target_key0 = o0.get_linklist(col).create_and_insert_linked_object(0).get_key();
8✔
1803
            target_key1 = o1.get_linklist(col).create_and_insert_linked_object(0).get_key();
8✔
1804
            wt.commit();
8✔
1805
        }
8✔
1806

4✔
1807
        // Grab the row accessors before applying the modification being tested
4✔
1808
        rt->advance_read();
8✔
1809
        rt->verify();
8✔
1810
        target_obj0 = target->get_object(target_key0);
8✔
1811
        target_obj1 = target->get_object(target_key1);
8✔
1812

4✔
1813
        // Perform the modification
4✔
1814
        {
8✔
1815
            WriteTransaction wt(sg);
8✔
1816
            func(*wt.get_table("origin"));
8✔
1817
            wt.commit();
8✔
1818
        }
8✔
1819

4✔
1820
        rt->advance_read();
8✔
1821
        rt->verify();
8✔
1822
        // Leave `group` and the target accessors in a state which can be tested
4✔
1823
        // with the changes applied
4✔
1824
    };
8✔
1825

1✔
1826

1✔
1827
    // Break link by clearing list
1✔
1828
    perform_change([&](Table& origin) {
2✔
1829
        origin.get_object(1).get_linklist(col).clear();
2✔
1830
    });
2✔
1831
    CHECK(target_obj0.is_valid() && !target_obj1.is_valid());
2✔
1832
    CHECK_EQUAL(target->size(), 1);
2✔
1833

1✔
1834
    // Break link by removal from list
1✔
1835
    perform_change([&](Table& origin) {
2✔
1836
        origin.get_object(1).get_linklist(col).remove(0);
2✔
1837
    });
2✔
1838
    CHECK(target_obj0.is_valid() && !target_obj1.is_valid());
2✔
1839
    CHECK_EQUAL(target->size(), 1);
2✔
1840

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

1✔
1848
    // Break link by clearing table
1✔
1849
    perform_change([](Table& origin) {
2✔
1850
        origin.clear();
2✔
1851
    });
2✔
1852
    CHECK(!target_obj0.is_valid() && !target_obj1.is_valid());
2✔
1853
    CHECK_EQUAL(target->size(), 0);
2✔
1854
}
2✔
1855

1856

1857
TEST(LangBindHelper_AdvanceReadTransact_IntIndex)
1858
{
2✔
1859
    SHARED_GROUP_TEST_PATH(path);
2✔
1860

1✔
1861
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
1862
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
1863
    auto g = sg->start_read();
2✔
1864
    g->promote_to_write();
2✔
1865

1✔
1866
    TableRef target = g->add_table("target");
2✔
1867
    auto col = target->add_column(type_Int, "pk");
2✔
1868
    target->add_search_index(col);
2✔
1869

1✔
1870
    std::vector<ObjKey> obj_keys;
2✔
1871
    target->create_objects(REALM_MAX_BPNODE_SIZE + 1, obj_keys);
2✔
1872

1✔
1873
    g->commit_and_continue_as_read();
2✔
1874

1✔
1875
    // open a second copy that'll be advanced over the write
1✔
1876
    auto g_r = sg->start_read();
2✔
1877
    TableRef t_r = g_r->get_table("target");
2✔
1878

1✔
1879
    g->promote_to_write();
2✔
1880

1✔
1881
    // Ensure that the index has a different bptree layout so that failing to
1✔
1882
    // refresh it will do bad things
1✔
1883
    int i = 0;
2✔
1884
    for (auto it = target->begin(); it != target->end(); ++it)
2,004✔
1885
        it->set(col, i++);
2,002✔
1886

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

1✔
1889
    g_r->promote_to_write();
2✔
1890
    // Crashes if index has an invalid parent ref
1✔
1891
    t_r->clear();
2✔
1892
}
2✔
1893

1894
NONCONCURRENT_TEST_IF(LangBindHelper_AdvanceReadTransact_TableClear, testing_supports_spawn_process)
1895
{
2✔
1896
    SHARED_GROUP_TEST_PATH(path);
2✔
1897

1✔
1898
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
1899
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
1900
    ColKey col;
2✔
1901
    if (SpawnedProcess::is_parent()) {
2✔
1902
        WriteTransaction wt(sg);
2✔
1903
        TableRef table = wt.add_table("table");
2✔
1904
        col = table->add_column(type_Int, "col");
2✔
1905
        table->create_object();
2✔
1906
        wt.commit();
2✔
1907
    }
2✔
1908

1✔
1909
    auto reader = sg->start_read();
2✔
1910
    auto table = reader->get_table("table");
2✔
1911
    TableView tv = table->where().find_all();
2✔
1912
    auto obj = *table->begin();
2✔
1913
    CHECK(obj.is_valid());
2✔
1914

1✔
1915
    auto process = test_util::spawn_process(test_context.test_details.test_name, "external_clear");
2✔
1916
    if (process->is_child()) {
2✔
1917
        {
×
1918
            std::unique_ptr<Replication> hist_w(make_in_realm_history());
×
1919
            DBRef sg_w = DB::create(*hist_w, path, DBOptions(crypt_key()));
×
1920
            WriteTransaction wt(sg_w);
×
1921
            wt.get_table("table")->clear();
×
1922
            wt.commit();
×
1923
        }
×
1924
        exit(0);
×
1925
    }
×
1926
    else if (process->is_parent()) {
2✔
1927
        process->wait_for_child_to_finish();
2✔
1928

1✔
1929
        reader->advance_read();
2✔
1930

1✔
1931
        CHECK(!obj.is_valid());
2✔
1932

1✔
1933
        CHECK_EQUAL(tv.size(), 1);
2✔
1934
        CHECK(!tv.is_in_sync());
2✔
1935
        // key is still there...
1✔
1936
        CHECK(tv.get_key(0));
2✔
1937
        // but no obj for that key...
1✔
1938
        CHECK_NOT(tv.get_object(0).is_valid());
2✔
1939

1✔
1940
        tv.sync_if_needed();
2✔
1941
        CHECK_EQUAL(tv.size(), 0);
2✔
1942
    }
2✔
1943
}
2✔
1944

1945
TEST(LangBindHelper_AdvanceReadTransact_UnorderedTableViewClear)
1946
{
2✔
1947
    SHARED_GROUP_TEST_PATH(path);
2✔
1948

1✔
1949
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
1950
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
1951
    ObjKey first_obj, last_obj;
2✔
1952
    ColKey col;
2✔
1953
    {
2✔
1954
        WriteTransaction wt(sg);
2✔
1955
        TableRef table = wt.add_table("table");
2✔
1956
        col = table->add_column(type_Int, "col");
2✔
1957
        first_obj = table->create_object().set_all(0).get_key();
2✔
1958
        table->create_object().set_all(1);
2✔
1959
        last_obj = table->create_object().set_all(2).get_key();
2✔
1960
        wt.commit();
2✔
1961
    }
2✔
1962

1✔
1963
    auto reader = sg->start_read();
2✔
1964
    auto table = reader->get_table("table");
2✔
1965
    auto obj = table->get_object(last_obj);
2✔
1966
    CHECK_EQUAL(obj.get<int64_t>(col), 2);
2✔
1967

1✔
1968
    {
2✔
1969
        // Remove the first row via unordered removal, resulting in the '2' row
1✔
1970
        // moving to index 0 (with ordered removal it would instead move to index 1)
1✔
1971
        WriteTransaction wt(sg);
2✔
1972
        wt.get_table("table")->where().equal(col, 0).find_all().clear();
2✔
1973
        wt.commit();
2✔
1974
    }
2✔
1975

1✔
1976
    reader->advance_read();
2✔
1977

1✔
1978
    CHECK(obj.is_valid());
2✔
1979
    CHECK_EQUAL(obj.get<int64_t>(col), 2);
2✔
1980
}
2✔
1981

1982
namespace {
1983
struct AdvanceReadTransact {
1984
    template <typename Func>
1985
    static void call(TransactionRef tr, Func* func)
1986
    {
10✔
1987
        tr->advance_read(func);
10✔
1988
    }
10✔
1989
};
1990

1991
struct PromoteThenRollback {
1992
    template <typename Func>
1993
    static void call(TransactionRef tr, Func* func)
1994
    {
10✔
1995
        tr->promote_to_write(func);
10✔
1996
        tr->rollback_and_continue_as_read();
10✔
1997
    }
10✔
1998
};
1999

2000
} // unnamed namespace
2001

2002
TEST_TYPES(LangBindHelper_AdvanceReadTransact_TransactLog, AdvanceReadTransact, PromoteThenRollback)
2003
{
4✔
2004
    SHARED_GROUP_TEST_PATH(path);
4✔
2005
    std::unique_ptr<Replication> hist(make_in_realm_history());
4✔
2006
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4✔
2007
    ColKey c0, c1;
4✔
2008
    {
4✔
2009
        WriteTransaction wt(sg);
4✔
2010
        c0 = wt.add_table("table 1")->add_column(type_Int, "int");
4✔
2011
        c1 = wt.add_table("table 2")->add_column(type_Int, "int");
4✔
2012
        wt.commit();
4✔
2013
    }
4✔
2014

2✔
2015
    auto tr = sg->start_read();
4✔
2016

2✔
2017
    {
4✔
2018
        // With no changes, the handler should not be called at all
2✔
2019
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2020
            TestContext& test_context;
4✔
2021
            Parser(TestContext& context)
4✔
2022
                : test_context(context)
4✔
2023
            {
4✔
2024
            }
4✔
2025
            void parse_complete()
4✔
2026
            {
2✔
UNCOV
2027
                CHECK(false);
×
UNCOV
2028
            }
×
2029
        } parser(test_context);
4✔
2030
        TEST_TYPE::call(tr, &parser);
4✔
2031
    }
4✔
2032

2✔
2033
    {
4✔
2034
        // With an empty change, parse_complete() and nothing else should be called
2✔
2035
        auto wt = sg->start_write();
4✔
2036
        wt->commit();
4✔
2037

2✔
2038
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2039
            TestContext& test_context;
4✔
2040
            bool called = false;
4✔
2041

2✔
2042
            Parser(TestContext& context)
4✔
2043
                : test_context(context)
4✔
2044
            {
4✔
2045
            }
4✔
2046

2✔
2047
            void parse_complete()
4✔
2048
            {
4✔
2049
                called = true;
4✔
2050
            }
4✔
2051
        } parser(test_context);
4✔
2052
        TEST_TYPE::call(tr, &parser);
4✔
2053
        CHECK(parser.called);
4✔
2054
    }
4✔
2055
    ObjKey o0, o1;
4✔
2056
    {
4✔
2057
        // Make a simple modification and verify that the appropriate handler is called
2✔
2058
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2059
            TestContext& test_context;
4✔
2060
            size_t expected_table = 0;
4✔
2061
            TableKey t1;
4✔
2062
            TableKey t2;
4✔
2063

2✔
2064
            Parser(TestContext& context)
4✔
2065
                : test_context(context)
4✔
2066
            {
4✔
2067
            }
4✔
2068

2✔
2069
            bool create_object(ObjKey)
4✔
2070
            {
8✔
2071
                CHECK_EQUAL(expected_table ? t2 : t1, get_current_table());
8✔
2072
                ++expected_table;
8✔
2073

4✔
2074
                return true;
8✔
2075
            }
8✔
2076
        } parser(test_context);
4✔
2077

2✔
2078
        WriteTransaction wt(sg);
4✔
2079
        parser.t1 = wt.get_table("table 1")->get_key();
4✔
2080
        parser.t2 = wt.get_table("table 2")->get_key();
4✔
2081
        o0 = wt.get_table("table 1")->create_object().get_key();
4✔
2082
        o1 = wt.get_table("table 2")->create_object().get_key();
4✔
2083
        wt.commit();
4✔
2084

2✔
2085
        TEST_TYPE::call(tr, &parser);
4✔
2086
        CHECK_EQUAL(2, parser.expected_table);
4✔
2087
    }
4✔
2088
    ColKey c2, c3;
4✔
2089
    ObjKey okey;
4✔
2090
    {
4✔
2091
        // Add a table with some links
2✔
2092
        WriteTransaction wt(sg);
4✔
2093
        TableRef table = wt.add_table("link origin");
4✔
2094
        c2 = table->add_column(*wt.get_table("table 1"), "link");
4✔
2095
        c3 = table->add_column_list(*wt.get_table("table 2"), "linklist");
4✔
2096
        Obj o = table->create_object();
4✔
2097
        o.set(c2, o.get_key());
4✔
2098
        o.get_linklist(c3).add(o.get_key());
4✔
2099
        okey = o.get_key();
4✔
2100
        wt.commit();
4✔
2101

2✔
2102
        tr->advance_read();
4✔
2103
    }
4✔
2104
    {
4✔
2105
        // Verify that deleting the targets of the links logs link nullifications
2✔
2106
        WriteTransaction wt(sg);
4✔
2107
        wt.get_table("table 1")->remove_object(o0);
4✔
2108
        wt.get_table("table 2")->remove_object(o1);
4✔
2109
        wt.commit();
4✔
2110

2✔
2111
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2112
            TestContext& test_context;
4✔
2113
            Parser(TestContext& context)
4✔
2114
                : test_context(context)
4✔
2115
            {
4✔
2116
            }
4✔
2117

2✔
2118
            bool remove_object(ObjKey o)
4✔
2119
            {
8✔
2120
                CHECK(o == o1 || o == o0);
8!
2121
                return true;
8✔
2122
            }
8✔
2123
            bool select_collection(ColKey col, ObjKey o, const StablePath&)
4✔
2124
            {
4✔
2125
                CHECK(col == link_list_col);
4✔
2126
                CHECK(o == okey);
4✔
2127
                return true;
4✔
2128
            }
4✔
2129
            bool collection_erase(size_t ndx)
4✔
2130
            {
4✔
2131
                CHECK(ndx == 0);
4✔
2132
                return true;
4✔
2133
            }
4✔
2134

2✔
2135
            bool modify_object(ColKey col, ObjKey obj)
4✔
2136
            {
4✔
2137
                CHECK(col == link_col && obj == okey);
4✔
2138
                return true;
4✔
2139
            }
4✔
2140
            ObjKey o0, o1, okey;
4✔
2141
            ColKey link_col, link_list_col;
4✔
2142
        } parser(test_context);
4✔
2143
        parser.o1 = o1;
4✔
2144
        parser.o0 = o0;
4✔
2145
        parser.okey = okey;
4✔
2146
        parser.link_col = c2;
4✔
2147
        parser.link_list_col = c3;
4✔
2148
        TEST_TYPE::call(tr, &parser);
4✔
2149
    }
4✔
2150
    {
4✔
2151
        // Verify that clear() logs the correct rows
2✔
2152
        WriteTransaction wt(sg);
4✔
2153
        std::vector<ObjKey> keys;
4✔
2154
        wt.get_table("table 2")->create_objects(10, keys);
4✔
2155

2✔
2156
        auto lv = wt.get_table("link origin")->begin()->get_linklist(c3);
4✔
2157
        lv.add(keys[1]);
4✔
2158
        lv.add(keys[3]);
4✔
2159
        lv.add(keys[5]);
4✔
2160

2✔
2161
        wt.commit();
4✔
2162
        tr->advance_read();
4✔
2163
    }
4✔
2164
    {
4✔
2165
        WriteTransaction wt(sg);
4✔
2166
        wt.get_table("link origin")->begin()->get_linklist(c3).clear();
4✔
2167
        wt.commit();
4✔
2168
        struct Parser : _impl::NoOpTransactionLogParser {
4✔
2169
            TestContext& test_context;
4✔
2170
            Parser(TestContext& context)
4✔
2171
                : test_context(context)
4✔
2172
            {
4✔
2173
            }
4✔
2174

2✔
2175
            bool collection_clear(size_t old_size) const
4✔
2176
            {
4✔
2177
                CHECK_EQUAL(3, old_size);
4✔
2178
                return true;
4✔
2179
            }
4✔
2180
        } parser(test_context);
4✔
2181
        TEST_TYPE::call(tr, &parser);
4✔
2182
    }
4✔
2183
}
4✔
2184

2185

2186
TEST(LangBindHelper_AdvanceReadTransact_ErrorInObserver)
2187
{
2✔
2188
    SHARED_GROUP_TEST_PATH(path);
2✔
2189
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2190
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2191
    ColKey col;
2✔
2192
    Obj obj;
2✔
2193
    // Add some initial data and then begin a read transaction at that version
1✔
2194
    auto wt1 = sg->start_write();
2✔
2195
    TableRef table = wt1->add_table("Table");
2✔
2196
    col = table->add_column(type_Int, "int");
2✔
2197
    auto obj2 = table->create_object().set_all(10);
2✔
2198
    wt1->commit_and_continue_as_read();
2✔
2199

1✔
2200
    auto g = sg->start_read();     // must follow commit, to see table just created
2✔
2201
    obj = g->import_copy_of(obj2); // cannot be imported if table does not exist
2✔
2202
    wt1->end_read();               // wt1 must live long enough to support import_copy_of of obj2
2✔
2203
    // Modify the data with a different SG so that we can determine which version
1✔
2204
    // the read transaction is using
1✔
2205
    {
2✔
2206
        auto wt = sg->start_write();
2✔
2207
        Obj o2 = wt->import_copy_of(obj);
2✔
2208
        o2.set<int64_t>(col, 20);
2✔
2209
        wt->commit();
2✔
2210
    }
2✔
2211

1✔
2212
    struct ObserverError {
2✔
2213
    };
2✔
2214
    try {
2✔
2215
        struct Parser : _impl::NoOpTransactionLogParser {
2✔
2216
            TestContext& test_context;
2✔
2217
            Parser(TestContext& context)
2✔
2218
                : test_context(context)
2✔
2219
            {
2✔
2220
            }
2✔
2221

1✔
2222
            bool modify_object(ColKey, ObjKey) const
2✔
2223
            {
2✔
2224
                throw ObserverError();
2✔
2225
            }
2✔
2226
        } parser(test_context);
2✔
2227
        g->advance_read(&parser);
2✔
2228
        CHECK(false); // Should not be reached
2✔
2229
    }
2✔
2230
    catch (ObserverError) {
2✔
2231
    }
2✔
2232

1✔
2233
    // Should still see data from old version
1✔
2234
    auto o = g->import_copy_of(obj);
2✔
2235
    CHECK_EQUAL(10, o.get<int64_t>(col));
2✔
2236

1✔
2237
    // Should be able to advance to the new version still
1✔
2238
    g->advance_read();
2✔
2239

1✔
2240
    // And see that version's data
1✔
2241
    CHECK_EQUAL(20, o.get<int64_t>(col));
2✔
2242
}
2✔
2243

2244

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

2298

2299
TEST(LangBindHelper_RollbackAndContinueAsRead)
2300
{
2✔
2301
    SHARED_GROUP_TEST_PATH(path);
2✔
2302
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2303
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2304
    {
2✔
2305
        ObjKey key;
2✔
2306
        ColKey col;
2✔
2307
        auto group = sg->start_read();
2✔
2308
        {
2✔
2309
            group->promote_to_write();
2✔
2310
            TableRef origin = group->get_or_add_table("origin");
2✔
2311
            col = origin->add_column(type_Int, "");
2✔
2312
            key = origin->create_object().set_all(42).get_key();
2✔
2313
            group->commit_and_continue_as_read();
2✔
2314
        }
2✔
2315
        group->verify();
2✔
2316
        {
2✔
2317
            // rollback of group level table insertion
1✔
2318
            group->promote_to_write();
2✔
2319
            group->get_or_add_table("nullermand");
2✔
2320
            TableRef o2 = group->get_table("nullermand");
2✔
2321
            REALM_ASSERT(o2);
2✔
2322
            group->rollback_and_continue_as_read();
2✔
2323
            TableRef o3 = group->get_table("nullermand");
2✔
2324
            REALM_ASSERT(!o3);
2✔
2325
            REALM_ASSERT(!o2);
2✔
2326
        }
2✔
2327

1✔
2328
        TableRef origin = group->get_table("origin");
2✔
2329
        Obj row = origin->get_object(key);
2✔
2330
        CHECK_EQUAL(42, row.get<int64_t>(col));
2✔
2331

1✔
2332
        {
2✔
2333
            group->promote_to_write();
2✔
2334
            auto row2 = origin->create_object().set_all(5746);
2✔
2335
            CHECK_EQUAL(42, row.get<int64_t>(col));
2✔
2336
            CHECK_EQUAL(5746, row2.get<int64_t>(col));
2✔
2337
            CHECK_EQUAL(2, origin->size());
2✔
2338
            group->verify();
2✔
2339
            group->rollback_and_continue_as_read();
2✔
2340
        }
2✔
2341
        CHECK_EQUAL(1, origin->size());
2✔
2342
        group->verify();
2✔
2343
        CHECK_EQUAL(42, row.get<int64_t>(col));
2✔
2344
        Obj row2;
2✔
2345
        {
2✔
2346
            group->promote_to_write();
2✔
2347
            row2 = origin->create_object().set_all(42);
2✔
2348
            group->commit_and_continue_as_read();
2✔
2349
        }
2✔
2350
        CHECK_EQUAL(2, origin->size());
2✔
2351
        group->verify();
2✔
2352
        CHECK_EQUAL(42, row2.get<int64_t>(col));
2✔
2353
        group->end_read();
2✔
2354
    }
2✔
2355
}
2✔
2356

2357

2358
TEST(LangBindHelper_RollbackAndContinueAsReadGroupLevelTableRemoval)
2359
{
2✔
2360
    SHARED_GROUP_TEST_PATH(path);
2✔
2361
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2362
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2363
    auto reader = sg->start_read();
2✔
2364
    {
2✔
2365
        reader->promote_to_write();
2✔
2366
        reader->get_or_add_table("a_table");
2✔
2367
        reader->commit_and_continue_as_read();
2✔
2368
    }
2✔
2369
    reader->verify();
2✔
2370
    {
2✔
2371
        // rollback of group level table delete
1✔
2372
        reader->promote_to_write();
2✔
2373
        TableRef o2 = reader->get_table("a_table");
2✔
2374
        REALM_ASSERT(o2);
2✔
2375
        reader->remove_table("a_table");
2✔
2376
        TableRef o3 = reader->get_table("a_table");
2✔
2377
        REALM_ASSERT(!o3);
2✔
2378
        reader->rollback_and_continue_as_read();
2✔
2379
        TableRef o4 = reader->get_table("a_table");
2✔
2380
        REALM_ASSERT(o4);
2✔
2381
    }
2✔
2382
    reader->verify();
2✔
2383
}
2✔
2384

2385
TEST(LangBindHelper_RollbackCircularReferenceRemoval)
2386
{
2✔
2387
    SHARED_GROUP_TEST_PATH(path);
2✔
2388
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2389
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2390
    ColKey ca, cb;
2✔
2391
    auto group = sg->start_read();
2✔
2392
    {
2✔
2393
        group->promote_to_write();
2✔
2394
        TableRef alpha = group->get_or_add_table("alpha");
2✔
2395
        TableRef beta = group->get_or_add_table("beta");
2✔
2396
        ca = alpha->add_column(*beta, "beta-1");
2✔
2397
        cb = beta->add_column(*alpha, "alpha-1");
2✔
2398
        group->commit_and_continue_as_read();
2✔
2399
    }
2✔
2400
    group->verify();
2✔
2401
    {
2✔
2402
        group->promote_to_write();
2✔
2403
        CHECK_EQUAL(2, group->size());
2✔
2404
        TableRef alpha = group->get_table("alpha");
2✔
2405
        TableRef beta = group->get_table("beta");
2✔
2406

1✔
2407
        CHECK_THROW(group->remove_table("alpha"), CrossTableLinkTarget);
2✔
2408
        beta->remove_column(cb);
2✔
2409
        alpha->remove_column(ca);
2✔
2410
        group->remove_table("beta");
2✔
2411
        CHECK_NOT(group->has_table("beta"));
2✔
2412

1✔
2413
        // Version 1: This crashes
1✔
2414
        group->rollback_and_continue_as_read();
2✔
2415
        CHECK_EQUAL(2, group->size());
2✔
2416

1✔
2417
        //        // Version 2: This works
1✔
2418
        //        LangBindHelper::commit_and_continue_as_read(sg);
1✔
2419
        //        CHECK_EQUAL(1, group->size());
1✔
2420
    }
2✔
2421
    group->verify();
2✔
2422
}
2✔
2423

2424

2425
TEST(LangBindHelper_RollbackAndContinueAsReadColumnAdd)
2426
{
2✔
2427
    SHARED_GROUP_TEST_PATH(path);
2✔
2428
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2429
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2430
    auto group = sg->start_read();
2✔
2431
    TableRef t;
2✔
2432
    {
2✔
2433
        group->promote_to_write();
2✔
2434
        t = group->get_or_add_table("a_table");
2✔
2435
        t->add_column(type_Int, "lorelei");
2✔
2436
        t->create_object().set_all(43);
2✔
2437
        CHECK_EQUAL(1, t->get_column_count());
2✔
2438
        group->commit_and_continue_as_read();
2✔
2439
    }
2✔
2440
    group->verify();
2✔
2441
    {
2✔
2442
        // add a column and regret it again
1✔
2443
        group->promote_to_write();
2✔
2444
        auto col = t->add_column(type_Int, "riget");
2✔
2445
        t->begin()->set(col, 44);
2✔
2446
        CHECK_EQUAL(2, t->get_column_count());
2✔
2447
        group->verify();
2✔
2448
        group->rollback_and_continue_as_read();
2✔
2449
        group->verify();
2✔
2450
        CHECK_EQUAL(1, t->get_column_count());
2✔
2451
    }
2✔
2452
    group->verify();
2✔
2453
}
2✔
2454

2455

2456
// This issue was uncovered while looking into the RollbackCircularReferenceRemoval issue
2457
TEST(LangBindHelper_TableLinkingRemovalIssue)
2458
{
2✔
2459
    SHARED_GROUP_TEST_PATH(path);
2✔
2460
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2461
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2462
    auto group = sg->start_read();
2✔
2463
    {
2✔
2464
        group->promote_to_write();
2✔
2465
        TableRef t1 = group->get_or_add_table("t1");
2✔
2466
        TableRef t2 = group->get_or_add_table("t2");
2✔
2467
        TableRef t3 = group->get_or_add_table("t3");
2✔
2468
        TableRef t4 = group->get_or_add_table("t4");
2✔
2469
        t1->add_column(*t2, "l12");
2✔
2470
        t2->add_column(*t3, "l23");
2✔
2471
        t3->add_column(*t4, "l34");
2✔
2472
        group->commit_and_continue_as_read();
2✔
2473
    }
2✔
2474
    group->verify();
2✔
2475
    {
2✔
2476
        group->promote_to_write();
2✔
2477
        CHECK_EQUAL(4, group->size());
2✔
2478

1✔
2479
        group->remove_table("t1");
2✔
2480
        group->remove_table("t2");
2✔
2481
        group->remove_table("t3"); // CRASHES HERE
2✔
2482
        group->remove_table("t4");
2✔
2483

1✔
2484
        group->rollback_and_continue_as_read();
2✔
2485
        CHECK_EQUAL(4, group->size());
2✔
2486
    }
2✔
2487
    group->verify();
2✔
2488
}
2✔
2489

2490

2491
// This issue was uncovered while looking into the RollbackCircularReferenceRemoval issue
2492
TEST(LangBindHelper_RollbackTableRemove)
2493
{
2✔
2494
    SHARED_GROUP_TEST_PATH(path);
2✔
2495
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2496
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2497
    auto group = sg->start_read();
2✔
2498
    {
2✔
2499
        group->promote_to_write();
2✔
2500
        TableRef alpha = group->get_or_add_table("alpha");
2✔
2501
        TableRef beta = group->get_or_add_table("beta");
2✔
2502
        beta->add_column(*alpha, "alpha-1");
2✔
2503
        group->commit_and_continue_as_read();
2✔
2504
    }
2✔
2505
    group->verify();
2✔
2506
    {
2✔
2507
        group->promote_to_write();
2✔
2508
        CHECK_EQUAL(2, group->size());
2✔
2509
        TableRef alpha = group->get_table("alpha");
2✔
2510
        TableRef beta = group->get_table("beta");
2✔
2511
        CHECK(alpha);
2✔
2512
        CHECK(beta);
2✔
2513
        group->remove_table("beta");
2✔
2514
        CHECK_NOT(group->has_table("beta"));
2✔
2515
        group->rollback_and_continue_as_read();
2✔
2516
        CHECK_EQUAL(2, group->size());
2✔
2517
    }
2✔
2518
    group->verify();
2✔
2519
}
2✔
2520

2521
TEST(LangBindHelper_RollbackTableRemove2)
2522
{
2✔
2523
    SHARED_GROUP_TEST_PATH(path);
2✔
2524
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2525
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2526
    auto group = sg->start_read();
2✔
2527
    {
2✔
2528
        group->promote_to_write();
2✔
2529
        TableRef a = group->get_or_add_table("a");
2✔
2530
        TableRef b = group->get_or_add_table("b");
2✔
2531
        TableRef c = group->get_or_add_table("c");
2✔
2532
        TableRef d = group->get_or_add_table("d");
2✔
2533
        c->add_column(*a, "a");
2✔
2534
        d->add_column(*b, "b");
2✔
2535
        group->commit_and_continue_as_read();
2✔
2536
    }
2✔
2537
    group->verify();
2✔
2538
    {
2✔
2539
        group->promote_to_write();
2✔
2540
        CHECK_EQUAL(4, group->size());
2✔
2541
        group->remove_table("c");
2✔
2542
        CHECK_NOT(group->has_table("c"));
2✔
2543
        group->verify();
2✔
2544
        group->rollback_and_continue_as_read();
2✔
2545
        CHECK_EQUAL(4, group->size());
2✔
2546
    }
2✔
2547
    group->verify();
2✔
2548
}
2✔
2549

2550
TEST(LangBindHelper_ContinuousTransactions_RollbackTableRemoval)
2551
{
2✔
2552
    // Test that it is possible to modify a table, then remove it from the
1✔
2553
    // group, and then rollback the transaction.
1✔
2554

1✔
2555
    // This triggered a bug in the instruction reverser which would incorrectly
1✔
2556
    // associate the table removal instruction with the table selection
1✔
2557
    // instruction induced by the modification, causing the latter to occur in
1✔
2558
    // the reverse log at a point where the selected table does not yet
1✔
2559
    // exist. The filler table is there to avoid an early-out in
1✔
2560
    // Group::TransactAdvancer::select_table() due to a misinterpretation of the
1✔
2561
    // reason for the missing table accessor entry.
1✔
2562

1✔
2563
    SHARED_GROUP_TEST_PATH(path);
2✔
2564
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2565
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2566
    auto group = sg->start_read();
2✔
2567
    group->promote_to_write();
2✔
2568
    group->get_or_add_table("filler");
2✔
2569
    TableRef table = group->get_or_add_table("table");
2✔
2570
    auto col = table->add_column(type_Int, "i");
2✔
2571
    Obj o = table->create_object();
2✔
2572
    group->commit_and_continue_as_read();
2✔
2573
    group->promote_to_write();
2✔
2574
    o.set<int>(col, 0);
2✔
2575
    group->remove_table("table");
2✔
2576
    group->rollback_and_continue_as_read();
2✔
2577
}
2✔
2578

2579
TEST(LangBindHelper_RollbackAndContinueAsReadLinkColumnRemove)
2580
{
2✔
2581
    SHARED_GROUP_TEST_PATH(path);
2✔
2582
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2583
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2584
    auto group = sg->start_read();
2✔
2585
    TableRef t, t2;
2✔
2586
    ColKey col;
2✔
2587
    {
2✔
2588
        // add a column
1✔
2589
        group->promote_to_write();
2✔
2590
        t = group->get_or_add_table("a_table");
2✔
2591
        t2 = group->get_or_add_table("b_table");
2✔
2592
        col = t->add_column(*t2, "bruno");
2✔
2593
        CHECK_EQUAL(1, t->get_column_count());
2✔
2594
        group->commit_and_continue_as_read();
2✔
2595
    }
2✔
2596
    group->verify();
2✔
2597
    {
2✔
2598
        // ... but then regret it
1✔
2599
        group->promote_to_write();
2✔
2600
        t->remove_column(col);
2✔
2601
        CHECK_EQUAL(0, t->get_column_count());
2✔
2602
        group->rollback_and_continue_as_read();
2✔
2603
    }
2✔
2604
}
2✔
2605

2606

2607
TEST(LangBindHelper_RollbackAndContinueAsReadColumnRemove)
2608
{
2✔
2609
    SHARED_GROUP_TEST_PATH(path);
2✔
2610
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2611
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2612
    auto group = sg->start_read();
2✔
2613
    TableRef t;
2✔
2614
    ColKey col;
2✔
2615
    {
2✔
2616
        group->promote_to_write();
2✔
2617
        t = group->get_or_add_table("a_table");
2✔
2618
        col = t->add_column(type_Int, "lorelei");
2✔
2619
        t->add_column(type_Int, "riget");
2✔
2620
        t->create_object().set_all(43, 44);
2✔
2621
        CHECK_EQUAL(2, t->get_column_count());
2✔
2622
        group->commit_and_continue_as_read();
2✔
2623
    }
2✔
2624
    group->verify();
2✔
2625
    {
2✔
2626
        // remove a column but regret it
1✔
2627
        group->promote_to_write();
2✔
2628
        CHECK_EQUAL(2, t->get_column_count());
2✔
2629
        t->remove_column(col);
2✔
2630
        group->verify();
2✔
2631
        group->rollback_and_continue_as_read();
2✔
2632
        group->verify();
2✔
2633
        CHECK_EQUAL(2, t->get_column_count());
2✔
2634
    }
2✔
2635
    group->verify();
2✔
2636
}
2✔
2637

2638

2639
TEST(LangBindHelper_RollbackAndContinueAsReadLinkList)
2640
{
2✔
2641
    SHARED_GROUP_TEST_PATH(path);
2✔
2642
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2643
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2644
    auto group = sg->start_read();
2✔
2645
    group->promote_to_write();
2✔
2646
    TableRef origin = group->add_table("origin");
2✔
2647
    TableRef target = group->add_table("target");
2✔
2648
    auto col0 = origin->add_column_list(*target, "");
2✔
2649
    target->add_column(type_Int, "");
2✔
2650
    auto o0 = origin->create_object();
2✔
2651
    auto t0 = target->create_object();
2✔
2652
    auto t1 = target->create_object();
2✔
2653
    auto t2 = target->create_object();
2✔
2654

1✔
2655
    auto link_list = o0.get_linklist(col0);
2✔
2656
    link_list.add(t0.get_key());
2✔
2657
    group->commit_and_continue_as_read();
2✔
2658
    CHECK_EQUAL(1, link_list.size());
2✔
2659
    group->verify();
2✔
2660
    // now change a link in link list and roll back the change
1✔
2661
    group->promote_to_write();
2✔
2662
    link_list.add(t1.get_key());
2✔
2663
    link_list.add(t2.get_key());
2✔
2664
    CHECK_EQUAL(3, link_list.size());
2✔
2665
    group->rollback_and_continue_as_read();
2✔
2666
    CHECK_EQUAL(1, link_list.size());
2✔
2667
    group->promote_to_write();
2✔
2668
    link_list.remove(0);
2✔
2669
    CHECK_EQUAL(0, link_list.size());
2✔
2670
    group->rollback_and_continue_as_read();
2✔
2671
    CHECK_EQUAL(1, link_list.size());
2✔
2672
}
2✔
2673

2674

2675
TEST(LangBindHelper_RollbackAndContinueAsRead_Links)
2676
{
2✔
2677
    SHARED_GROUP_TEST_PATH(path);
2✔
2678
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2679
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2680
    auto group = sg->start_read();
2✔
2681
    group->promote_to_write();
2✔
2682
    TableRef origin = group->add_table("origin");
2✔
2683
    TableRef target = group->add_table("target");
2✔
2684
    auto col0 = origin->add_column(*target, "");
2✔
2685
    target->add_column(type_Int, "");
2✔
2686
    auto o0 = origin->create_object();
2✔
2687
    target->create_object();
2✔
2688
    auto t1 = target->create_object();
2✔
2689
    auto t2 = target->create_object();
2✔
2690

1✔
2691
    o0.set(col0, t2.get_key());
2✔
2692
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2693
    group->commit_and_continue_as_read();
2✔
2694

1✔
2695
    // verify that we can revert a link change:
1✔
2696
    group->promote_to_write();
2✔
2697
    o0.set(col0, t1.get_key());
2✔
2698
    CHECK_EQUAL(t1.get_key(), o0.get<ObjKey>(col0));
2✔
2699
    group->rollback_and_continue_as_read();
2✔
2700
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2701
    // verify that we can revert addition of a row in target table
1✔
2702
    group->promote_to_write();
2✔
2703
    target->create_object();
2✔
2704
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2705
    group->rollback_and_continue_as_read();
2✔
2706
    CHECK_EQUAL(t2.get_key(), o0.get<ObjKey>(col0));
2✔
2707
}
2✔
2708

2709

2710
TEST(LangBindHelper_RollbackAndContinueAsRead_LinkLists)
2711
{
2✔
2712
    SHARED_GROUP_TEST_PATH(path);
2✔
2713
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2714
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2715
    auto group = sg->start_read();
2✔
2716
    group->promote_to_write();
2✔
2717
    TableRef origin = group->add_table("origin");
2✔
2718
    TableRef target = group->add_table("target");
2✔
2719
    auto col0 = origin->add_column_list(*target, "");
2✔
2720
    target->add_column(type_Int, "");
2✔
2721
    auto o0 = origin->create_object();
2✔
2722
    auto t0 = target->create_object();
2✔
2723
    auto t1 = target->create_object();
2✔
2724
    auto t2 = target->create_object();
2✔
2725

1✔
2726
    auto link_list = o0.get_linklist(col0);
2✔
2727
    link_list.add(t0.get_key());
2✔
2728
    link_list.add(t1.get_key());
2✔
2729
    link_list.add(t2.get_key());
2✔
2730
    link_list.add(t0.get_key());
2✔
2731
    link_list.add(t2.get_key());
2✔
2732
    group->commit_and_continue_as_read();
2✔
2733
    // verify that we can reverse a LinkView::move()
1✔
2734
    CHECK_EQUAL(5, link_list.size());
2✔
2735
    CHECK_EQUAL(t0.get_key(), link_list.get(0));
2✔
2736
    CHECK_EQUAL(t1.get_key(), link_list.get(1));
2✔
2737
    CHECK_EQUAL(t2.get_key(), link_list.get(2));
2✔
2738
    CHECK_EQUAL(t0.get_key(), link_list.get(3));
2✔
2739
    CHECK_EQUAL(t2.get_key(), link_list.get(4));
2✔
2740
    group->promote_to_write();
2✔
2741
    link_list.move(1, 3);
2✔
2742
    CHECK_EQUAL(5, link_list.size());
2✔
2743
    CHECK_EQUAL(t0.get_key(), link_list.get(0));
2✔
2744
    CHECK_EQUAL(t2.get_key(), link_list.get(1));
2✔
2745
    CHECK_EQUAL(t0.get_key(), link_list.get(2));
2✔
2746
    CHECK_EQUAL(t1.get_key(), link_list.get(3));
2✔
2747
    CHECK_EQUAL(t2.get_key(), link_list.get(4));
2✔
2748
    group->rollback_and_continue_as_read();
2✔
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
}
2✔
2756

2757

2758
TEST(LangBindHelper_RollbackAndContinueAsRead_TableClear)
2759
{
2✔
2760
    SHARED_GROUP_TEST_PATH(path);
2✔
2761
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2762
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2763
    auto group = sg->start_read();
2✔
2764

1✔
2765
    group->promote_to_write();
2✔
2766
    TableRef origin = group->add_table("origin");
2✔
2767
    TableRef target = group->add_table("target");
2✔
2768

1✔
2769
    auto c1 = origin->add_column_list(*target, "linklist");
2✔
2770
    target->add_column(type_Int, "int");
2✔
2771
    auto c2 = origin->add_column(*target, "link");
2✔
2772

1✔
2773
    Obj t = target->create_object();
2✔
2774
    Obj o = origin->create_object();
2✔
2775
    o.set(c2, t.get_key());
2✔
2776
    LnkLst l = o.get_linklist(c1);
2✔
2777
    l.add(t.get_key());
2✔
2778
    group->commit_and_continue_as_read();
2✔
2779

1✔
2780
    group->promote_to_write();
2✔
2781
    CHECK_EQUAL(1, l.size());
2✔
2782
    target->clear();
2✔
2783
    CHECK_EQUAL(0, l.size());
2✔
2784

1✔
2785
    group->rollback_and_continue_as_read();
2✔
2786
    CHECK_EQUAL(1, l.size());
2✔
2787
}
2✔
2788

2789
TEST(LangBindHelper_RollbackAndContinueAsRead_IntIndex)
2790
{
2✔
2791
    SHARED_GROUP_TEST_PATH(path);
2✔
2792
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2793
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2794
    auto g = sg->start_read();
2✔
2795
    g->promote_to_write();
2✔
2796

1✔
2797
    TableRef target = g->add_table("target");
2✔
2798
    ColKey col = target->add_column(type_Int, "pk");
2✔
2799
    target->add_search_index(col);
2✔
2800

1✔
2801
    std::vector<ObjKey> keys;
2✔
2802
    target->create_objects(REALM_MAX_BPNODE_SIZE + 1, keys);
2✔
2803
    g->commit_and_continue_as_read();
2✔
2804
    g->promote_to_write();
2✔
2805

1✔
2806
    // Ensure that the index has a different bptree layout so that failing to
1✔
2807
    // refresh it will do bad things
1✔
2808
    auto it = target->begin();
2✔
2809
    for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i) {
2,004✔
2810
        it->set<int64_t>(col, i);
2,002✔
2811
        ++it;
2,002✔
2812
    }
2,002✔
2813

1✔
2814
    g->rollback_and_continue_as_read();
2✔
2815
    g->promote_to_write();
2✔
2816

1✔
2817
    // Crashes if index has an invalid parent ref
1✔
2818
    target->clear();
2✔
2819
}
2✔
2820

2821

2822
TEST(LangBindHelper_ImplicitTransactions_OverSharedGroupDestruction)
2823
{
2✔
2824
    SHARED_GROUP_TEST_PATH(path);
2✔
2825
    // we hold on to write log collector and registry across a complete
1✔
2826
    // shutdown/initialization of shared rt->
1✔
2827
    std::unique_ptr<Replication> hist1(make_in_realm_history());
2✔
2828
    {
2✔
2829
        DBRef sg = DB::create(*hist1, path, DBOptions(crypt_key()));
2✔
2830
        {
2✔
2831
            WriteTransaction wt(sg);
2✔
2832
            TableRef tr = wt.add_table("table");
2✔
2833
            tr->add_column(type_Int, "first");
2✔
2834
            for (int i = 0; i < 20; i++)
42✔
2835
                tr->create_object();
40✔
2836
            wt.commit();
2✔
2837
        }
2✔
2838
        // no valid shared group anymore
1✔
2839
    }
2✔
2840
    {
2✔
2841
        std::unique_ptr<Replication> hist2(make_in_realm_history());
2✔
2842
        DBRef sg = DB::create(*hist2, path, DBOptions(crypt_key()));
2✔
2843
        {
2✔
2844
            WriteTransaction wt(sg);
2✔
2845
            TableRef tr = wt.get_table("table");
2✔
2846
            for (int i = 0; i < 20; i++)
42✔
2847
                tr->create_object();
40✔
2848
            wt.commit();
2✔
2849
        }
2✔
2850
    }
2✔
2851
}
2✔
2852

2853
TEST(LangBindHelper_ImplicitTransactions_LinkList)
2854
{
2✔
2855
    SHARED_GROUP_TEST_PATH(path);
2✔
2856
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2857
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2858
    auto group = sg->start_write();
2✔
2859
    TableRef origin = group->add_table("origin");
2✔
2860
    TableRef target = group->add_table("target");
2✔
2861
    auto col = origin->add_column_list(*target, "");
2✔
2862
    target->add_column(type_Int, "");
2✔
2863
    auto O0 = origin->create_object();
2✔
2864
    auto T0 = target->create_object();
2✔
2865
    auto link_list = O0.get_linklist(col);
2✔
2866
    link_list.add(T0.get_key());
2✔
2867
    group->commit_and_continue_as_read();
2✔
2868
    group->verify();
2✔
2869
}
2✔
2870

2871

2872
TEST(LangBindHelper_ImplicitTransactions_StringIndex)
2873
{
2✔
2874
    SHARED_GROUP_TEST_PATH(path);
2✔
2875
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2876
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2877
    auto group = sg->start_write();
2✔
2878
    TableRef table = group->add_table("a");
2✔
2879
    auto col = table->add_column(type_String, "b");
2✔
2880
    table->add_search_index(col);
2✔
2881
    group->verify();
2✔
2882
    group->commit_and_continue_as_read();
2✔
2883
    group->verify();
2✔
2884
}
2✔
2885

2886

2887
namespace {
2888

2889
// Test that multiple trackers (of changes) always see a consistent picture.
2890
// This is done by having multiple writers update a table A, query it, and store
2891
// the count of the query in another table, B. Multiple readers then track the
2892
// changes and verify that the count on their query is consistent with the count
2893
// they read from table B. Terminate verification by signalling through a third
2894
// table, C, after a chosen number of yields, which should allow readers to
2895
// run their verification.
2896
void multiple_trackers_writer_thread(DBRef db)
2897
{
13✔
2898
    // insert new random values
6✔
2899
    Random random(random_int<unsigned long>());
13✔
2900
    for (int i = 0; i < 10; ++i) {
153✔
2901
        WriteTransaction wt(db);
140✔
2902
        auto ta = wt.get_table("A");
140✔
2903
        auto col = ta->get_column_keys()[0];
140✔
2904
        for (auto it = ta->begin(); it != ta->end(); ++it) {
28,140✔
2905
            auto e = *it;
28,000✔
2906
            e.set(col, random.draw_int_mod(200));
28,000✔
2907
        }
28,000✔
2908
        auto tb = wt.get_table("B");
140✔
2909
        auto count = ta->where().greater(col, 100).count();
140✔
2910
        tb->begin()->set<int64_t>(tb->get_column_keys()[0], count);
140✔
2911
        wt.commit();
140✔
2912
    }
140✔
2913
}
13✔
2914

2915
void multiple_trackers_reader_thread(TestContext& test_context, DBRef db)
2916
{
6✔
2917
    // verify that consistency is maintained as we advance_read through a
3✔
2918
    // stream of transactions
3✔
2919
    auto g = db->start_read();
6✔
2920
    auto ta = g->get_table("A");
6✔
2921
    auto tb = g->get_table("B");
6✔
2922
    auto tc = g->get_table("C");
6✔
2923
    auto col = ta->get_column_keys()[0];
6✔
2924
    auto b_col = tb->get_column_keys()[0];
6✔
2925
    TableView tv = ta->where().greater(col, 100).find_all();
6✔
2926
    const auto wait_start = std::chrono::steady_clock::now();
6✔
2927
    std::chrono::seconds max_wait_seconds = std::chrono::seconds(1050);
6✔
2928
    while (tc->size() == 0) {
19,255✔
2929
        auto count = tb->begin()->get<int64_t>(b_col);
19,249✔
2930
        tv.sync_if_needed();
19,249✔
2931
        CHECK_EQUAL(tv.size(), count);
19,249✔
2932
        std::this_thread::yield();
19,249✔
2933
        g->advance_read();
19,249✔
2934
        if (std::chrono::steady_clock::now() - wait_start > max_wait_seconds) {
19,249✔
2935
            // if there is a fatal problem with a writer process we don't want the
2936
            // readers to wait forever as a spawned background processs
UNCOV
2937
            constexpr bool reader_process_timed_out = false;
×
UNCOV
2938
            REALM_ASSERT(reader_process_timed_out);
×
UNCOV
2939
        }
×
2940
    }
19,249✔
2941
}
6✔
2942

2943
} // anonymous namespace
2944

2945
TEST(LangBindHelper_ImplicitTransactions_MultipleTrackers)
2946
{
2✔
2947
    const int write_thread_count = 7;
2✔
2948
    const int read_thread_count = 3;
2✔
2949

1✔
2950
    SHARED_GROUP_TEST_PATH(path);
2✔
2951

1✔
2952
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2953
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2954
    {
2✔
2955
        // initialize table with 200 entries holding 0..200
1✔
2956
        WriteTransaction wt(sg);
2✔
2957
        TableRef tr = wt.add_table("A");
2✔
2958
        auto col = tr->add_column(type_Int, "first");
2✔
2959
        for (int j = 0; j < 200; j++) {
402✔
2960
            tr->create_object().set(col, j);
400✔
2961
        }
400✔
2962
        auto table_b = wt.add_table("B");
2✔
2963
        table_b->add_column(type_Int, "bussemand");
2✔
2964
        table_b->create_object().set_all(99);
2✔
2965
        wt.add_table("C");
2✔
2966
        wt.commit();
2✔
2967
    }
2✔
2968
    // FIXME: Use separate arrays for reader and writer threads for safety and readability.
1✔
2969
    Thread threads[write_thread_count + read_thread_count];
2✔
2970
    for (int i = 0; i < write_thread_count; ++i)
16✔
2971
        threads[i].start([&] {
14✔
2972
            multiple_trackers_writer_thread(sg);
13✔
2973
        });
13✔
2974
    std::this_thread::yield();
2✔
2975
    for (int i = 0; i < read_thread_count; ++i) {
8✔
2976
        threads[write_thread_count + i].start([&] {
6✔
2977
            multiple_trackers_reader_thread(test_context, sg);
6✔
2978
        });
6✔
2979
    }
6✔
2980

1✔
2981
    // Wait for all writer threads to complete
1✔
2982
    for (int i = 0; i < write_thread_count; ++i)
16✔
2983
        threads[i].join();
14✔
2984

1✔
2985
    // Allow readers time to catch up
1✔
2986
    for (int k = 0; k < 100; ++k)
202✔
2987
        std::this_thread::yield();
200✔
2988

1✔
2989
    // signal to all readers to complete
1✔
2990
    {
2✔
2991
        WriteTransaction wt(sg);
2✔
2992
        TableRef tr = wt.get_table("C");
2✔
2993
        tr->create_object();
2✔
2994
        wt.commit();
2✔
2995
    }
2✔
2996
    // Wait for all reader threads to complete
1✔
2997
    for (int i = 0; i < read_thread_count; ++i)
8✔
2998
        threads[write_thread_count + i].join();
6✔
2999
}
2✔
3000

3001
// Interprocess communication does not work with encryption enabled on Apple.
3002
// This is because fork() does not play well with Apple primitives such as
3003
// dispatch_queue_t in ReclaimerThreadStopper. This could possibly be fixed if
3004
// we need more tests like this.
3005

3006
#if !REALM_ANDROID && !REALM_IOS
3007

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

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

1✔
3049
    if (process->is_parent()) {
2✔
3050
        process->wait_for_child_to_finish();
2✔
3051
    }
2✔
3052

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

1✔
3085
    if (process->is_parent()) {
2✔
3086
        // Wait for all writer threads to complete
1✔
3087
        for (int i = 0; i < write_process_count; ++i) {
16✔
3088
            writers[i]->wait_for_child_to_finish();
14✔
3089
        }
14✔
3090

1✔
3091
        // Allow readers time to catch up
1✔
3092
        for (int k = 0; k < 100; ++k)
202✔
3093
            std::this_thread::yield();
200✔
3094

1✔
3095
        // signal to all readers to complete
1✔
3096
        {
2✔
3097
            std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3098
            DBRef sg = DB::create(*hist, path, DBOptions(key));
2✔
3099
            WriteTransaction wt(sg);
2✔
3100
            TableRef tr = wt.get_table("C");
2✔
3101
            tr->create_object();
2✔
3102
            wt.commit();
2✔
3103
        }
2✔
3104

1✔
3105
        // Wait for all reader threads to complete
1✔
3106
        for (int i = 0; i < read_process_count; ++i) {
8✔
3107
            readers[i]->wait_for_child_to_finish();
6✔
3108
        }
6✔
3109
    }
2✔
3110
}
2✔
3111

3112
#endif // !REALM_ANDROID && !REALM_IOS
3113

3114
TEST(LangBindHelper_ImplicitTransactions_NoExtremeFileSpaceLeaks)
3115
{
2✔
3116
    SHARED_GROUP_TEST_PATH(path);
2✔
3117

1✔
3118
    for (int i = 0; i < 100; ++i) {
202✔
3119
        std::unique_ptr<Replication> hist(make_in_realm_history());
200✔
3120
        DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
200✔
3121
        auto trans = sg->start_read();
200✔
3122
        trans->promote_to_write();
200✔
3123
        trans->commit_and_continue_as_read();
200✔
3124
    }
200✔
3125

1✔
3126
// the miminum filesize (after a commit) is one or two pages, depending on the
1✔
3127
// page size.
1✔
3128
#if REALM_ENABLE_ENCRYPTION
2✔
3129
    if (crypt_key())
2✔
3130
        // Encrypted files are always at least a 4096 byte header plus payload
1✔
3131
        CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size() + 4096);
1✔
3132
    else
2✔
3133
        CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size());
2✔
3134
#else
3135
    CHECK_LESS_EQUAL(File(path).get_size(), 2 * page_size());
3136
#endif // REALM_ENABLE_ENCRYPTION
3137
}
2✔
3138

3139

3140
TEST(LangBindHelper_ImplicitTransactions_ContinuedUseOfTable)
3141
{
2✔
3142
    SHARED_GROUP_TEST_PATH(path);
2✔
3143

1✔
3144
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3145
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3146
    auto group = sg->start_read();
2✔
3147
    auto group_w = sg->start_write();
2✔
3148

1✔
3149
    TableRef table_w = group_w->add_table("table");
2✔
3150
    auto col = table_w->add_column(type_Int, "");
2✔
3151
    auto obj = table_w->create_object();
2✔
3152
    group_w->commit_and_continue_as_read();
2✔
3153
    group_w->verify();
2✔
3154

1✔
3155
    group->advance_read();
2✔
3156
    ConstTableRef table = group->get_table("table");
2✔
3157
    CHECK_EQUAL(1, table->size());
2✔
3158
    group->verify();
2✔
3159

1✔
3160
    group_w->promote_to_write();
2✔
3161
    obj.set<int64_t>(col, 1);
2✔
3162
    group_w->commit_and_continue_as_read();
2✔
3163
    group_w->verify();
2✔
3164

1✔
3165
    group->advance_read();
2✔
3166
    auto obj2 = group->import_copy_of(obj);
2✔
3167
    CHECK_EQUAL(1, obj2.get<int64_t>(col));
2✔
3168
    group->verify();
2✔
3169
}
2✔
3170

3171

3172
TEST(LangBindHelper_ImplicitTransactions_ContinuedUseOfLinkList)
3173
{
2✔
3174
    SHARED_GROUP_TEST_PATH(path);
2✔
3175

1✔
3176
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3177
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3178
    auto group = sg->start_read();
2✔
3179
    auto group_w = sg->start_write();
2✔
3180

1✔
3181
    TableRef table_w = group_w->add_table("table");
2✔
3182
    auto col = table_w->add_column_list(*table_w, "flubber");
2✔
3183
    auto obj = table_w->create_object();
2✔
3184
    auto link_list_w = obj.get_linklist(col);
2✔
3185
    link_list_w.add(obj.get_key());
2✔
3186
    // CHECK_EQUAL(1, link_list_w.size()); // avoid this, it hides missing updates
1✔
3187
    group_w->commit_and_continue_as_read();
2✔
3188
    group_w->verify();
2✔
3189

1✔
3190
    group->advance_read();
2✔
3191
    auto link_list = obj.get_linklist(col);
2✔
3192
    CHECK_EQUAL(1, link_list.size());
2✔
3193
    group->verify();
2✔
3194

1✔
3195
    group_w->promote_to_write();
2✔
3196
    // CHECK_EQUAL(1, link_list_w.size()); // avoid this, it hides missing updates
1✔
3197
    link_list_w.add(obj.get_key());
2✔
3198
    CHECK_EQUAL(2, link_list_w.size());
2✔
3199
    group_w->commit_and_continue_as_read();
2✔
3200
    group_w->verify();
2✔
3201

1✔
3202
    group->advance_read();
2✔
3203
    CHECK_EQUAL(2, link_list.size());
2✔
3204
    group->verify();
2✔
3205
}
2✔
3206

3207

3208
TEST(LangBindHelper_MemOnly)
3209
{
2✔
3210
    SHARED_GROUP_TEST_PATH(path);
2✔
3211
    ShortCircuitHistory hist;
2✔
3212
    DBRef sg = DB::create(hist, path, DBOptions(DBOptions::Durability::MemOnly));
2✔
3213

1✔
3214
    // Verify that the db is empty after populating and then re-opening a file
1✔
3215
    {
2✔
3216
        WriteTransaction wt(sg);
2✔
3217
        wt.add_table("table");
2✔
3218
        wt.commit();
2✔
3219
    }
2✔
3220
    {
2✔
3221
        TransactionRef rt = sg->start_read();
2✔
3222
        CHECK(!rt->is_empty());
2✔
3223
    }
2✔
3224
    sg->close();
2✔
3225
    sg = DB::create(hist, path, DBOptions(DBOptions::Durability::MemOnly));
2✔
3226

1✔
3227
    // Verify that basic replication functionality works
1✔
3228
    auto rt = sg->start_read();
2✔
3229
    {
2✔
3230
        WriteTransaction wt(sg);
2✔
3231
        wt.add_table("table");
2✔
3232
        wt.commit();
2✔
3233
    }
2✔
3234

1✔
3235
    CHECK(rt->is_empty());
2✔
3236
    rt->advance_read();
2✔
3237
    CHECK(!rt->is_empty());
2✔
3238
}
2✔
3239

3240
TEST(LangBindHelper_ImplicitTransactions_SearchIndex)
3241
{
2✔
3242
    SHARED_GROUP_TEST_PATH(path);
2✔
3243

1✔
3244
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3245
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3246
    auto rt = sg->start_read();
2✔
3247
    auto group_w = sg->start_read();
2✔
3248

1✔
3249
    // Add initial data
1✔
3250
    group_w->promote_to_write();
2✔
3251
    TableRef table_w = group_w->add_table("table");
2✔
3252
    auto c0 = table_w->add_column(type_Int, "int1");
2✔
3253
    auto c1 = table_w->add_column(type_String, "str");
2✔
3254
    auto c2 = table_w->add_column(type_Int, "int2");
2✔
3255
    auto ok = table_w->create_object(ObjKey{}, {{c1, "2"}, {c0, 1}, {c2, 3}}).get_key();
2✔
3256
    group_w->commit_and_continue_as_read();
2✔
3257
    group_w->verify();
2✔
3258

1✔
3259
    rt->advance_read();
2✔
3260
    ConstTableRef table = rt->get_table("table");
2✔
3261
    auto obj = table->get_object(ok);
2✔
3262
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
2✔
3263
    CHECK_EQUAL("2", obj.get<StringData>(c1));
2✔
3264
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
2✔
3265
    rt->verify();
2✔
3266

1✔
3267
    // Add search index and re-verify
1✔
3268
    group_w->promote_to_write();
2✔
3269
    table_w->add_search_index(c1);
2✔
3270
    group_w->commit_and_continue_as_read();
2✔
3271
    group_w->verify();
2✔
3272

1✔
3273
    rt->advance_read();
2✔
3274
    CHECK_EQUAL(1, obj.get<int64_t>(c0));
2✔
3275
    CHECK_EQUAL("2", obj.get<StringData>(c1));
2✔
3276
    CHECK_EQUAL(3, obj.get<int64_t>(c2));
2✔
3277
    CHECK(table->has_search_index(c1));
2✔
3278
    rt->verify();
2✔
3279

1✔
3280
    // Remove search index and re-verify
1✔
3281
    group_w->promote_to_write();
2✔
3282
    table_w->remove_search_index(c1);
2✔
3283
    group_w->commit_and_continue_as_read();
2✔
3284
    group_w->verify();
2✔
3285

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

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

3336
TEST(LangBindHelper_SubqueryHandoverQueryCreatedFromDeletedLinkView)
3337
{
2✔
3338
    SHARED_GROUP_TEST_PATH(path);
2✔
3339
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3340
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3341
    TransactionRef reader;
2✔
3342
    auto writer = sg->start_write();
2✔
3343
    {
2✔
3344
        TableView tv1;
2✔
3345
        auto table = writer->add_table("table");
2✔
3346
        auto table2 = writer->add_table("table2");
2✔
3347
        table2->add_column(type_Int, "int");
2✔
3348
        auto key = table2->create_object().set_all(42).get_key();
2✔
3349

1✔
3350
        auto col = table->add_column_list(*table2, "first");
2✔
3351
        auto obj = table->create_object();
2✔
3352
        auto link_view = obj.get_linklist(col);
2✔
3353

1✔
3354
        link_view.add(key);
2✔
3355
        writer->commit_and_continue_as_read();
2✔
3356

1✔
3357
        Query qq = table2->where(link_view);
2✔
3358
        CHECK_EQUAL(qq.count(), 1);
2✔
3359
        writer->promote_to_write();
2✔
3360
        table->clear();
2✔
3361
        writer->commit_and_continue_as_read();
2✔
3362
        CHECK_EQUAL(link_view.size(), 0);
2✔
3363
        CHECK_EQUAL(qq.count(), 0);
2✔
3364

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

3380
        CHECK(tv.is_in_sync());
3381
        CHECK(tv.is_attached());
3382
        CHECK_EQUAL(0, tv.size());
3383
#endif
3384
    }
2✔
3385
}
2✔
3386

3387

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

1✔
3420
            CHECK(tv.is_in_sync());
2✔
3421
            CHECK(tv.is_attached());
2✔
3422
            CHECK_EQUAL(26, tv.size()); // BOOM! fail with 50
2✔
3423
        }
2✔
3424
    }
2✔
3425
}
2✔
3426

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

3468

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

3500

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

1✔
3524
        tv = table->where().find_all();
2✔
3525
        CHECK(tv.is_attached());
2✔
3526
        CHECK_EQUAL(100, tv.size());
2✔
3527
        for (int i = 0; i < 100; ++i)
202✔
3528
            CHECK_EQUAL(i, tv.get_object(i).get<Int>(col));
200✔
3529

1✔
3530
        reader = writer->duplicate();
2✔
3531
        tv2 = reader->import_copy_of(tv, PayloadPolicy::Copy);
2✔
3532
        CHECK(tv.is_attached());
2✔
3533
        CHECK(tv.is_in_sync());
2✔
3534

1✔
3535
        tv3 = reader->import_copy_of(tv, PayloadPolicy::Stay);
2✔
3536
        CHECK(tv.is_attached());
2✔
3537
        CHECK(tv.is_in_sync());
2✔
3538

1✔
3539
        tv4 = reader->import_copy_of(tv, PayloadPolicy::Move);
2✔
3540
        CHECK(tv.is_attached());
2✔
3541
        CHECK(!tv.is_in_sync());
2✔
3542

1✔
3543
        // and again, but this time with the source out of sync:
1✔
3544
        tv5 = reader->import_copy_of(tv, PayloadPolicy::Copy);
2✔
3545
        CHECK(tv.is_attached());
2✔
3546
        CHECK(!tv.is_in_sync());
2✔
3547

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

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

1✔
3556
        // and verify, that even though it was out of sync, we can bring it in sync again
1✔
3557
        tv.sync_if_needed();
2✔
3558
        CHECK(tv.is_in_sync());
2✔
3559

1✔
3560
        // Obj handover tested elsewhere
1✔
3561
    }
2✔
3562
    {
2✔
3563
        // now examining stuff handed over to other transaction
1✔
3564
        // with payload:
1✔
3565
        CHECK(tv2->is_attached());
2✔
3566
        CHECK(tv2->is_in_sync());
2✔
3567
        CHECK_EQUAL(100, tv2->size());
2✔
3568
        for (int i = 0; i < 100; ++i)
202✔
3569
            CHECK_EQUAL(i, tv2->get_object(i).get<Int>(col));
200✔
3570
        // importing one without payload:
1✔
3571
        CHECK(tv3->is_attached());
2✔
3572
        CHECK(!tv3->is_in_sync());
2✔
3573
        tv3->sync_if_needed();
2✔
3574
        CHECK_EQUAL(100, tv3->size());
2✔
3575
        for (int i = 0; i < 100; ++i)
202✔
3576
            CHECK_EQUAL(i, tv3->get_object(i).get<Int>(col));
200✔
3577

1✔
3578
        // one with payload:
1✔
3579
        CHECK(tv4->is_attached());
2✔
3580
        CHECK(tv4->is_in_sync());
2✔
3581
        CHECK_EQUAL(100, tv4->size());
2✔
3582
        for (int i = 0; i < 100; ++i)
202✔
3583
            CHECK_EQUAL(i, tv4->get_object(i).get<Int>(col));
200✔
3584

1✔
3585
        // verify that subsequent imports are all without payload:
1✔
3586
        CHECK(tv5->is_attached());
2✔
3587
        CHECK(!tv5->is_in_sync());
2✔
3588

1✔
3589
        CHECK(tv6->is_attached());
2✔
3590
        CHECK(!tv6->is_in_sync());
2✔
3591

1✔
3592
        CHECK(tv7->is_attached());
2✔
3593
        CHECK(!tv7->is_in_sync());
2✔
3594
    }
2✔
3595
}
2✔
3596

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

3641
namespace {
3642
// support threads for handover test. The setup is as follows:
3643
// thread A writes a stream of updates to the database,
3644
// thread B listens and continously does advance_read to see the updates.
3645
// thread B also has a table view, which it continuosly keeps in sync in response
3646
// to the updates. It then hands over the result to thread C.
3647
// thread C continuously recieves copies of the results obtained in thead B and
3648
// verifies them (by comparing with its own local, but identical query)
3649

3650
template <typename T>
3651
struct HandoverControl {
3652
    Mutex m_lock;
3653
    CondVar m_changed;
3654
    std::unique_ptr<T> m_handover;
3655
    bool m_has_feedback = false;
3656
    void put(std::unique_ptr<T> h)
3657
    {
1,593✔
3658
        LockGuard lg(m_lock);
1,593✔
3659
        // std::cout << "put " << h << std::endl;
767✔
3660
        while (m_handover != nullptr)
1,593✔
UNCOV
3661
            m_changed.wait(lg);
×
3662
        // std::cout << " -- put " << h << std::endl;
767✔
3663
        m_handover = std::move(h);
1,593✔
3664
        m_changed.notify_all();
1,593✔
3665
    }
1,593✔
3666
    void get(std::unique_ptr<T>& h)
3667
    {
1,593✔
3668
        LockGuard lg(m_lock);
1,593✔
3669
        // std::cout << "get " << std::endl;
767✔
3670
        while (m_handover == nullptr)
2,010✔
3671
            m_changed.wait(lg);
417✔
3672
        // std::cout << " -- get " << m_handover << std::endl;
767✔
3673
        h = std::move(m_handover);
1,593✔
3674
        m_handover = nullptr;
1,593✔
3675
        m_changed.notify_all();
1,593✔
3676
    }
1,593✔
3677
    bool try_get(std::unique_ptr<T>& h)
3678
    {
3679
        LockGuard lg(m_lock);
3680
        if (m_handover == nullptr)
3681
            return false;
3682
        h = std::move(m_handover);
3683
        m_handover = nullptr;
3684
        m_changed.notify_all();
3685
        return true;
3686
    }
3687
    void signal_feedback()
3688
    {
1,593✔
3689
        LockGuard lg(m_lock);
1,593✔
3690
        m_has_feedback = true;
1,593✔
3691
        m_changed.notify_all();
1,593✔
3692
    }
1,593✔
3693
    void wait_feedback()
3694
    {
1,593✔
3695
        LockGuard lg(m_lock);
1,593✔
3696
        while (!m_has_feedback)
3,343✔
3697
            m_changed.wait(lg);
1,750✔
3698
        m_has_feedback = false;
1,593✔
3699
    }
1,593✔
3700
    HandoverControl(const HandoverControl&) = delete;
3701
    HandoverControl() {}
2✔
3702
};
3703

3704
void handover_writer(DBRef db)
3705
{
2✔
3706
    //    std::unique_ptr<Replication> hist(make_in_realm_history());
1✔
3707
    //    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
1✔
3708
    auto g = db->start_read();
2✔
3709
    auto table = g->get_table("table");
2✔
3710
    Random random(random_int<unsigned long>());
2✔
3711
    for (int i = 1; i < 5000; ++i) {
10,000✔
3712
        g->promote_to_write();
9,998✔
3713
        // table holds random numbers >= 1, until the writing process
4,999✔
3714
        // finishes, after n new entry with value 0 is added to signal termination
4,999✔
3715
        table->create_object().set_all(1 + random.draw_int_mod(100));
9,998✔
3716
        g->commit_and_continue_as_read();
9,998✔
3717
        // improve chance of consumers running concurrently with
4,999✔
3718
        // new writes:
4,999✔
3719
        for (int n = 0; n < 10; ++n)
109,978✔
3720
            std::this_thread::yield();
99,980✔
3721
    }
9,998✔
3722
    g->promote_to_write();
2✔
3723
    table->create_object().set_all(0); // <---- signals other threads to stop
2✔
3724
    g->commit();
2✔
3725
}
2✔
3726

3727
struct Work {
3728
    TransactionRef tr;
3729
    std::unique_ptr<TableView> tv;
3730
};
3731

3732
void handover_querier(HandoverControl<Work>* control, TestContext& test_context, DBRef db)
3733
{
2✔
3734
    // We need to ensure that the initial version observed is *before* the final
1✔
3735
    // one written by the writer thread. We do this (simplisticly) by locking on
1✔
3736
    // to the initial version before even starting the writer.
1✔
3737
    auto g = db->start_read();
2✔
3738
    Thread writer;
2✔
3739
    writer.start([&] {
2✔
3740
        handover_writer(db);
2✔
3741
    });
2✔
3742
    TableRef table = g->get_table("table");
2✔
3743
    ColKeys cols = table->get_column_keys();
2✔
3744
    TableView tv = table->where().greater(cols[0], 50).find_all();
2✔
3745
    for (;;) {
536,498✔
3746
        // wait here for writer to change the database. Kind of wasteful, but wait_for_change()
141,723✔
3747
        // is not available on osx.
141,723✔
3748
        if (!db->has_changed(g)) {
536,498✔
3749
            std::this_thread::yield();
534,905✔
3750
            continue;
534,905✔
3751
        }
534,905✔
3752

767✔
3753
        g->advance_read();
1,593✔
3754
        CHECK(!tv.is_in_sync());
1,593✔
3755
        tv.sync_if_needed();
1,593✔
3756
        CHECK(tv.is_in_sync());
1,593✔
3757
        auto ref = g->duplicate();
1,593✔
3758
        std::unique_ptr<Work> h = std::make_unique<Work>();
1,593✔
3759
        h->tr = ref;
1,593✔
3760
        h->tv = ref->import_copy_of(tv, PayloadPolicy::Move);
1,593✔
3761
        control->put(std::move(h));
1,593✔
3762

767✔
3763
        // here we need to allow the reciever to get hold on the proper version before
767✔
3764
        // we go through the loop again and advance_read().
767✔
3765
        control->wait_feedback();
1,593✔
3766
        std::this_thread::yield();
1,593✔
3767

767✔
3768
        if (table->where().equal(cols[0], 0).count() >= 1)
1,593✔
3769
            break;
2✔
3770
    }
1,593✔
3771
    g->end_read();
2✔
3772
    writer.join();
2✔
3773
}
2✔
3774

3775
void handover_verifier(HandoverControl<Work>* control, TestContext& test_context)
3776
{
2✔
3777
    bool not_done = true;
2✔
3778
    while (not_done) {
1,595✔
3779
        std::unique_ptr<Work> work;
1,593✔
3780
        control->get(work);
1,593✔
3781

767✔
3782
        auto g = work->tr;
1,593✔
3783
        control->signal_feedback();
1,593✔
3784
        TableRef table = g->get_table("table");
1,593✔
3785
        ColKeys cols = table->get_column_keys();
1,593✔
3786
        TableView tv = table->where().greater(cols[0], 50).find_all();
1,593✔
3787
        CHECK(tv.is_in_sync());
1,593✔
3788
        std::unique_ptr<TableView> tv2 = std::move(work->tv);
1,593✔
3789
        CHECK(tv.is_in_sync());
1,593✔
3790
        CHECK(tv2->is_in_sync());
1,593✔
3791
        CHECK_EQUAL(tv.size(), tv2->size());
1,593✔
3792
        for (size_t k = 0; k < tv.size(); ++k) {
1,125,876✔
3793
            auto o = tv.get_object(k);
1,124,283✔
3794
            auto o2 = tv2->get_object(k);
1,124,283✔
3795
            CHECK_EQUAL(o.get<int64_t>(cols[0]), o2.get<int64_t>(cols[0]));
1,124,283✔
3796
        }
1,124,283✔
3797
        if (table->where().equal(cols[0], 0).count() >= 1)
1,593✔
3798
            not_done = false;
2✔
3799
        g->close();
1,593✔
3800
    }
1,593✔
3801
}
2✔
3802

3803
} // anonymous namespace
3804

3805
namespace {
3806

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

3829

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

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

1✔
3873
    HandoverControl<Work> control;
2✔
3874
    Thread querier, verifier;
2✔
3875
    querier.start([&] {
2✔
3876
        handover_querier(&control, test_context, sg);
2✔
3877
    });
2✔
3878
    verifier.start([&] {
2✔
3879
        handover_verifier(&control, test_context);
2✔
3880
    });
2✔
3881
    querier.join();
2✔
3882
    verifier.join();
2✔
3883
}
2✔
3884

3885

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

3938

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

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

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

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

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

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

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

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

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

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

4018

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

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

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

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

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

4072

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

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

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

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

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

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

4142
} // end anonymous namespace
4143

4144

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

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

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

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

4195

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

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

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

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

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

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

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

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

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

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

1✔
4261

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

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

1✔
4279

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

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

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

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

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

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

4332

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4522

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

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

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

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

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

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

1✔
4559
        group_w->commit_and_continue_as_read();
2✔
4560

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

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

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

1✔
4599

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

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

4619

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

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

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

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

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

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

1✔
4647
        group_w->commit_and_continue_as_read();
2✔
4648

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4801

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
4973
    const size_t threads = 5;
×
4974

UNCOV
4975
    size_t numberOfOwner = 100;
×
UNCOV
4976
    size_t numberOfDogsPerOwner = 20;
×
4977

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
5053
    Thread slaves[threads];
×
UNCOV
5054
    for (int i = 0; i != threads; ++i) {
×
UNCOV
5055
        slaves[i].start([=] {
×
5056
            async();
×
5057
        });
×
5058
    }
×
5059

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

5075
        millisleep(100);
×
5076
    }
×
5077
    //************************************************************************************************
5078

5079
    end_signal = true;
×
5080
    for (int i = 0; i != threads; ++i)
×
5081
        slaves[i].join();
×
5082
}
×
5083

5084

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

1✔
5091
    int64_t number_of_history = 1000;
2✔
5092
    int64_t number_of_line = 18;
2✔
5093

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

1✔
5104
        TableRef history = tr->add_table("history");
2✔
5105
        TableRef line = tr->add_table("line");
2✔
5106

1✔
5107
        col0 = history->add_column(type_Int, "id");
2✔
5108
        col1 = history->add_column(type_Int, "parent");
2✔
5109
        col2 = history->add_column_list(*line, "lines");
2✔
5110
        history->add_search_index(col1);
2✔
5111

1✔
5112
        colA = line->add_column(type_Int, "id");
2✔
5113
        colB = line->add_column(type_Int, "parent");
2✔
5114
        line->add_search_index(colB);
2✔
5115
        tr->commit_and_continue_as_read();
2✔
5116
    }
2✔
5117

1✔
5118
    {
2✔
5119
        tr->promote_to_write();
2✔
5120

1✔
5121
        TableRef history = tr->get_table("history");
2✔
5122
        TableRef line = tr->get_table("line");
2✔
5123

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

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

1✔
5145
    // query and delete
1✔
5146
    {
2✔
5147
        tr->promote_to_write();
2✔
5148

1✔
5149
        TableRef line = tr->get_table("line");
2✔
5150

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

1✔
5159
    {
2✔
5160
        TableRef history = tr->get_table("history");
2✔
5161
        TableRef line = tr->get_table("line");
2✔
5162

1✔
5163
        CHECK_EQUAL(number_of_history, history->size());
2✔
5164
        CHECK_EQUAL(number_of_line, line->size());
2✔
5165
    }
2✔
5166
}
2✔
5167

5168

5169
TEST(LangBindHelper_SessionHistoryConsistency)
5170
{
2✔
5171
    // Check that we can reliably detect inconsist history
1✔
5172
    // types across concurrent session participants.
1✔
5173

1✔
5174
    // Errors of this kind are considered as incorrect API usage, and will lead
1✔
5175
    // to throwing of LogicError exceptions.
1✔
5176

1✔
5177
    SHARED_GROUP_TEST_PATH(path);
2✔
5178

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

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

5191

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

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

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

1✔
5252
    auto t0 = g->add_table("t0");
2✔
5253
    auto t1 = g->add_table("t1");
2✔
5254

1✔
5255
    auto col = t0->add_column(*t1, "t0_link_to_t1");
2✔
5256
    t0->create_object();
2✔
5257
    auto o1 = t0->create_object();
2✔
5258
    t1->create_object();
2✔
5259
    auto v1 = t1->create_object();
2✔
5260
    o1.set(col, v1.get_key());
2✔
5261

1✔
5262
    CHECK_EQUAL(t0->size(), 2);
2✔
5263
    CHECK_EQUAL(t1->size(), 2);
2✔
5264
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5265

1✔
5266
    g->commit_and_continue_as_read();
2✔
5267
    g->promote_to_write();
2✔
5268

1✔
5269
    std::vector<ObjKey> keys;
2✔
5270
    t1->create_objects(0, keys); // Insert zero rows
2✔
5271

1✔
5272
    CHECK_EQUAL(t0->size(), 2);
2✔
5273
    CHECK_EQUAL(t1->size(), 2);
2✔
5274
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5275

1✔
5276
    g->rollback_and_continue_as_read();
2✔
5277
    g->verify();
2✔
5278

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

5284

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

1✔
5292
    auto t0 = g->add_table("t0");
2✔
5293
    auto t1 = g->add_table("t1");
2✔
5294

1✔
5295
    auto col = t0->add_column(*t1, "t0_link_to_t1");
2✔
5296
    t0->create_object();
2✔
5297
    auto o1 = t0->create_object();
2✔
5298
    t1->create_object();
2✔
5299
    auto v1 = t1->create_object();
2✔
5300
    o1.set(col, v1.get_key());
2✔
5301

1✔
5302
    CHECK_EQUAL(t0->size(), 2);
2✔
5303
    CHECK_EQUAL(t1->size(), 2);
2✔
5304
    CHECK_EQUAL(o1.get<ObjKey>(col), v1.get_key());
2✔
5305

1✔
5306
    g->commit_and_continue_as_read();
2✔
5307
    g->promote_to_write();
2✔
5308

1✔
5309
    t1->clear();
2✔
5310

1✔
5311
    CHECK_EQUAL(t0->size(), 2);
2✔
5312
    CHECK_EQUAL(t1->size(), 0);
2✔
5313
    CHECK_EQUAL(o1.get<ObjKey>(col), ObjKey());
2✔
5314

1✔
5315
    g->rollback_and_continue_as_read();
2✔
5316
    g->verify();
2✔
5317

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

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

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

2✔
5353
    for (int i = 0; i < 27; ++i) {
112✔
5354
        g->commit_and_continue_as_read();
108✔
5355
        g->promote_to_write();
108✔
5356
    }
108✔
5357

2✔
5358
    t->create_object();
4✔
5359
}
4✔
5360

5361

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

5377

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

5396

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

5416

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

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

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

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

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

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

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

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

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

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

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

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

5598

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

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

5620

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

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

1✔
5640
    obj.set(col, dataAlloc);
2✔
5641
    obj.set(col, dataRealloc);
2✔
5642
    g->verify();
2✔
5643
}
2✔
5644

5645

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

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

5672

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

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

5699

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

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

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

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

1✔
5741
    auto callback_not_called = [&](const std::string&) {
1✔
UNCOV
5742
        CHECK(false);
×
UNCOV
5743
    };
×
5744

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

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

5761
TEST(LangBindHelper_AdvanceReadCluster)
5762
{
2✔
5763
    SHARED_GROUP_TEST_PATH(path);
2✔
5764
    ShortCircuitHistory hist;
2✔
5765
    DBRef sg = DB::create(hist, path);
2✔
5766

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

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

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

1✔
5794
    ColKey col_pet;
2✔
5795
    ColKey col_addr;
2✔
5796
    ColKey col_name;
2✔
5797
    ColKey col_age;
2✔
5798

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

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

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

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

1✔
5830
    {
2✔
5831
        // Delete the person.
1✔
5832
        WriteTransaction wt(db);
2✔
5833
        wt.get_table("person")->begin()->remove();
2✔
5834
        wt.commit();
2✔
5835
    }
2✔
5836

1✔
5837
    {
2✔
5838
        auto read_transaction = db->start_read();
2✔
5839

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

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

1✔
5848
        // The list imported here should be null
1✔
5849
        CHECK_NOT(read_transaction->import_copy_of(*my_address));
2✔
5850
    }
2✔
5851

1✔
5852
    CHECK_EQUAL(tv_1->size(), 0);
2✔
5853
}
2✔
5854

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

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

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

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

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

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

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

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

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

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

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

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

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