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

realm / realm-core / 2109

07 Mar 2024 01:56PM UTC coverage: 90.918% (+0.01%) from 90.908%
2109

push

Evergreen

web-flow
Fix querying with a path into nested collections with wildcards (#7404)

Comparing a collection with a list could fail if there was wildcards
in the path and therefore multiple collections to compare with right
hand list.

Linklist is implicitly having wildcard in the path, so if linklists is
in the path there will be a similar problem.  Do not merge values
from different objects into a common list in queries.

93972 of 173176 branches covered (54.26%)

323 of 332 new or added lines in 6 files covered. (97.29%)

91 existing lines in 18 files now uncovered.

238503 of 262328 relevant lines covered (90.92%)

6065347.74 hits per line

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

94.24
/test/test_lang_bind_helper.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <map>
20
#include <sstream>
21
#include <mutex>
22
#include <condition_variable>
23
#include <atomic>
24
#include <chrono>
25
#include <thread>
26
#include "testsettings.hpp"
27
#ifdef TEST_LANG_BIND_HELPER
28

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

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

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

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

74
namespace {
75

76
void work_on_frozen(TestContext& test_context, TransactionRef frozen)
77
{
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) {
162,894✔
85
        sum += i.get<int64_t>(col);
162,894✔
86
    }
162,894✔
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);
197✔
125
        });
197✔
126
    for (int j = 0; j < num_threads; ++j)
202✔
127
        frozen_workers[j].join();
200✔
128
}
2✔
129

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

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

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

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

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

264

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

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

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

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

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

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

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

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

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

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

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

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

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

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

473

474
private:
475
    MyHistory m_history;
476
};
477

478
} // anonymous namespace
479

480

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

725

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

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

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

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

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

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

762

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

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

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

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

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

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

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

825

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

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

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

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

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

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

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

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

903

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

991

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1263
namespace {
1264

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1462

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1781

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

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

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

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

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

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

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

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

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

1✔
1841

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

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

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

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

1871

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2015
} // unnamed namespace
2016

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2200

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

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

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

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

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

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

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

2259

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

2313

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

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

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

2372

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

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

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

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

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

2439

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

2470

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

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

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

2505

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

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

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

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

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

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

2621

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

2653

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

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

2689

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

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

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

2724

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

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

2772

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

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

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

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

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

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

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

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

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

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

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

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

2836

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

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

2886

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

2901

2902
namespace {
2903

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

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

2958
} // anonymous namespace
2959

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

1✔
2965
    SHARED_GROUP_TEST_PATH(path);
2✔
2966

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

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

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

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

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

3021
#if !REALM_ANDROID && !REALM_IOS
3022

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

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

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

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

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

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

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

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

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

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

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

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

3154

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

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

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

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

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

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

3186

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

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

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

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

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

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

3222

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3402

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

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

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

3483

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

3515

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3818
} // anonymous namespace
3819

3820
namespace {
3821

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

3844

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

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

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

3900

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

3953

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

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

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

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

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

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

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

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

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

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

4033

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

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

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

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

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

4087

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

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

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

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

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

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

4157
} // end anonymous namespace
4158

4159

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

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

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

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

4210

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

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

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

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

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

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

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

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

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

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

1✔
4276

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

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

1✔
4294

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

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

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

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

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

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

4347

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4537

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

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

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

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

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

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

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

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

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

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

1✔
4614

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

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

4634

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4816

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4988
    const size_t threads = 5;
×
4989

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5099

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5183

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

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

1✔
5192
    SHARED_GROUP_TEST_PATH(path);
2✔
5193

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

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

5206

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

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

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

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

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

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

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

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

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

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

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

5299

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

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

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

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

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

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

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

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

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

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

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

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

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

5376

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

5392

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

5411

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

5431

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

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

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

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

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

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

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

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

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

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

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

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

5613

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

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

5635

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

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

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

5660

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

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

5687

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

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

5714

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6000
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc