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

realm / realm-core / michael.wilkersonbarker_1094

06 May 2024 04:51PM UTC coverage: 90.761% (+0.3%) from 90.422%
michael.wilkersonbarker_1094

Pull #7675

Evergreen

michael-wb
Added test to create 2 user and 2 rules when only 1 updated
Pull Request #7675: RCORE-1973 Add role/permissions tests for new bootstrap feature

102046 of 180410 branches covered (56.56%)

13 of 15 new or added lines in 2 files covered. (86.67%)

47 existing lines in 9 files now uncovered.

212810 of 234473 relevant lines covered (90.76%)

5670520.72 hits per line

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

99.51
/test/test_table.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 "testsettings.hpp"
20
#ifdef TEST_TABLE
21

22
#include <algorithm>
23
#include <cmath>
24
#include <limits>
25
#include <string>
26
#include <fstream>
27
#include <ostream>
28
#include <set>
29
#include <chrono>
30

31
using namespace std::chrono;
32

33
#include <realm.hpp>
34
#include <realm/util/buffer.hpp>
35
#include <realm/util/to_string.hpp>
36
#include <realm/util/base64.hpp>
37
#include <realm/array_bool.hpp>
38
#include <realm/array_string.hpp>
39
#include <realm/array_timestamp.hpp>
40
#include <realm/index_string.hpp>
41

42
#include "util/misc.hpp"
43

44
#include "test.hpp"
45
#include "test_table_helper.hpp"
46
#include "test_types_helper.hpp"
47

48
// #include <valgrind/callgrind.h>
49
// #define PERFORMACE_TESTING
50

51
using namespace realm;
52
using namespace realm::util;
53
using namespace realm::test_util;
54
using unit_test::TestContext;
55

56
#ifndef CALLGRIND_START_INSTRUMENTATION
57
#define CALLGRIND_START_INSTRUMENTATION
58
#endif
59

60
#ifndef CALLGRIND_STOP_INSTRUMENTATION
61
#define CALLGRIND_STOP_INSTRUMENTATION
62
#endif
63

64
// Test independence and thread-safety
65
// -----------------------------------
66
//
67
// All tests must be thread safe and independent of each other. This
68
// is required because it allows for both shuffling of the execution
69
// order and for parallelized testing.
70
//
71
// In particular, avoid using std::rand() since it is not guaranteed
72
// to be thread safe. Instead use the API offered in
73
// `test/util/random.hpp`.
74
//
75
// All files created in tests must use the TEST_PATH macro (or one of
76
// its friends) to obtain a suitable file system path. See
77
// `test/util/test_path.hpp`.
78
//
79
//
80
// Debugging and the ONLY() macro
81
// ------------------------------
82
//
83
// A simple way of disabling all tests except one called `Foo`, is to
84
// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the
85
// test suite. Note that you can also use filtering by setting the
86
// environment varible `UNITTEST_FILTER`. See `README.md` for more on
87
// this.
88
//
89
// Another way to debug a particular test, is to copy that test into
90
// `experiments/testcase.cpp` and then run `sh build.sh
91
// check-testcase` (or one of its friends) from the command line.
92

93
extern unsigned int unit_test_random_seed;
94

95
namespace {
96

97
// copy and convert values between nullable/not nullable as expressed by types
98
// both non-nullable:
99
template <typename T1, typename T2>
100
struct value_copier {
101
    value_copier(bool) {}
72✔
102
    T2 operator()(T1 from_value, bool = false)
103
    {
36,360✔
104
        return from_value;
36,360✔
105
    }
36,360✔
106
};
107

108
// copy from non-nullable to nullable
109
template <typename T1, typename T2>
110
struct value_copier<T1, Optional<T2>> {
111
    value_copier(bool throw_on_null)
112
        : internal_copier(throw_on_null)
10✔
113
    {
20✔
114
    }
20✔
115
    value_copier<T1, T2> internal_copier; // we need state for strings and binaries.
116
    Optional<T2> operator()(T1 from_value, bool)
117
    {
10,100✔
118
        return Optional<T2>(internal_copier(from_value));
10,100✔
119
    }
10,100✔
120
};
121

122
// copy from nullable to non-nullable - nulls may trigger exception or become default value
123
template <typename T1, typename T2>
124
struct value_copier<Optional<T1>, T2> {
125
    value_copier(bool throw_on_null)
126
        : m_throw_on_null(throw_on_null)
10✔
127
    {
20✔
128
    }
20✔
129
    bool m_throw_on_null;
130
    T2 operator()(Optional<T1> from_value, bool)
131
    {
10,100✔
132
        if (bool(from_value))
10,100✔
133
            return *from_value;
9,083✔
134
        else {
1,017✔
135
            if (m_throw_on_null)
1,017✔
136
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null found");
×
137
            else
1,017✔
138
                return T2(); // default value for type
1,017✔
139
        }
1,017✔
140
    }
10,100✔
141
};
142

143
// identical to non-specialized case, but specialization needed to avoid capture by 2 previous decls
144
template <typename T1, typename T2>
145
struct value_copier<Optional<T1>, Optional<T2>> {
146
    value_copier(bool) {}
20✔
147
    Optional<T2> operator()(Optional<T1> from_value, bool)
148
    {
10,100✔
149
        return from_value;
10,100✔
150
    }
10,100✔
151
};
152

153
// Specialization for StringData, BinaryData and Timestamp.
154
// these types do not encode/express nullability.
155

156
template <>
157
struct value_copier<StringData, StringData> {
158
    value_copier(bool throw_on_null)
159
        : m_throw_on_null(throw_on_null)
8✔
160
    {
16✔
161
    }
16✔
162
    bool m_throw_on_null;
163
    std::vector<char> data; // we need to make a local copy because writing may invalidate the argument
164
    StringData operator()(StringData from_value, bool to_optional)
165
    {
8,080✔
166
        if (from_value.is_null()) {
8,080✔
167
            if (to_optional)
411✔
168
                return StringData();
206✔
169

170
            if (m_throw_on_null) {
205✔
171
                // possibly incorrect - may need to convert to default value for non-nullable entries instead
172
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null found");
×
173
            }
×
174
            else
205✔
175
                return StringData("", 0);
205✔
176
        }
205✔
177
        const char* p = from_value.data();
7,669✔
178
        const char* limit = p + from_value.size();
7,669✔
179
        data.clear();
7,669✔
180
        data.reserve(from_value.size());
7,669✔
181
        while (p != limit)
245,408✔
182
            data.push_back(*p++);
237,739✔
183
        return StringData(&data[0], from_value.size());
7,669✔
184
    }
8,080✔
185
};
186

187
template <>
188
struct value_copier<BinaryData, BinaryData> {
189
    value_copier(bool throw_on_null)
190
        : m_throw_on_null(throw_on_null)
8✔
191
    {
16✔
192
    }
16✔
193
    bool m_throw_on_null;
194
    std::vector<char> data; // we need to make a local copy because writing may invalidate the argument
195
    BinaryData operator()(BinaryData from_value, bool to_optional)
196
    {
8,080✔
197
        if (from_value.is_null()) {
8,080✔
198
            if (to_optional)
393✔
199
                return BinaryData();
193✔
200

201
            if (m_throw_on_null) {
200✔
202
                // possibly incorrect - may need to convert to default value for non-nullable entries instead
203
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null Found");
×
204
            }
×
205
            else
200✔
206
                return BinaryData("", 0);
200✔
207
        }
200✔
208
        const char* p = from_value.data();
7,687✔
209
        const char* limit = p + from_value.size();
7,687✔
210
        data.clear();
7,687✔
211
        data.reserve(from_value.size());
7,687✔
212
        while (p != limit)
245,984✔
213
            data.push_back(*p++);
238,297✔
214
        return BinaryData(&data[0], from_value.size());
7,687✔
215
    }
8,080✔
216
};
217

218
template <>
219
struct value_copier<Timestamp, Timestamp> {
220
    value_copier(bool throw_on_null)
221
        : m_throw_on_null(throw_on_null)
8✔
222
    {
16✔
223
    }
16✔
224
    bool m_throw_on_null;
225
    Timestamp operator()(Timestamp from_value, bool to_optional)
226
    {
8,080✔
227
        if (from_value.is_null()) {
8,080✔
228
            if (to_optional)
395✔
229
                return Timestamp();
193✔
230

231
            if (m_throw_on_null)
202✔
232
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null found");
×
233
            else
202✔
234
                return Timestamp(0, 0);
202✔
235
        }
202✔
236
        return from_value;
7,685✔
237
    }
8,080✔
238
};
239
} // namespace
240

241
#ifdef JAVA_MANY_COLUMNS_CRASH
242

243
REALM_TABLE_3(SubtableType, year, Int, daysSinceLastVisit, Int, conceptId, String)
244

245
REALM_TABLE_7(MainTableType, patientId, String, gender, Int, ethnicity, Int, yearOfBirth, Int, yearOfDeath, Int,
246
              zipCode, String, events, Subtable<SubtableType>)
247

248
TEST(Table_ManyColumnsCrash2)
249
{
250
    // Trying to reproduce Java crash.
251
    for (int a = 0; a < 10; a++) {
252
        Group group;
253

254
        MainTableType::Ref mainTable = group.add_table<MainTableType>("PatientTable");
255
        TableRef dynPatientTable = group.add_table("PatientTable");
256
        dynPatientTable->add_empty_row();
257

258
        for (int counter = 0; counter < 20000; counter++) {
259
#if 0
260
            // Add row to subtable through typed interface
261
            SubtableType::Ref subtable = mainTable[0].events->get_table_ref();
262
            REALM_ASSERT(subtable->is_attached());
263
            subtable->add(0, 0, "");
264
            REALM_ASSERT(subtable->is_attached());
265

266
#else
267
            // Add row to subtable through dynamic interface. This mimics Java closest
268
            TableRef subtable2 = dynPatientTable->get_subtable(6, 0);
269
            REALM_ASSERT(subtable2->is_attached());
270
            size_t subrow = subtable2->add_empty_row();
271
            REALM_ASSERT(subtable2->is_attached());
272

273
#endif
274
            if ((counter % 1000) == 0) {
275
                //     std::cerr << counter << "\n";
276
            }
277
        }
278
    }
279
}
280

281
#endif // JAVA_MANY_COLUMNS_CRASH
282

283
TEST(Table_Null)
284
{
2✔
285
    {
2✔
286
        // Check that add_empty_row() adds NULL string as default
287
        Group group;
2✔
288
        TableRef table = group.add_table("test");
2✔
289

290
        table->add_column(type_String, "name", true); // nullable = true
2✔
291
        Obj obj = table->create_object();
2✔
292

293
        CHECK(obj.get<String>("name").is_null());
2✔
294
    }
2✔
295

296
    {
2✔
297
        // Check that add_empty_row() adds empty string as default
298
        Group group;
2✔
299
        TableRef table = group.add_table("test");
2✔
300

301
        auto col = table->add_column(type_String, "name");
2✔
302
        CHECK(!table->is_nullable(col));
2✔
303

304
        Obj obj = table->create_object();
2✔
305
        CHECK(!obj.get<String>(col).is_null());
2✔
306

307
        // Test that inserting null in non-nullable column will throw
308
        CHECK_LOGIC_ERROR(obj.set_null(col), ErrorCodes::PropertyNotNullable);
2✔
309
    }
2✔
310

311
    {
2✔
312
        // Check that add_empty_row() adds null integer as default
313
        Group group;
2✔
314
        TableRef table = group.add_table("table");
2✔
315
        auto col = table->add_column(type_Int, "age", true /*nullable*/);
2✔
316
        CHECK(table->is_nullable(col));
2✔
317

318
        Obj obj = table->create_object();
2✔
319
        CHECK(obj.is_null(col));
2✔
320

321
        // Check that you can obtain a non null value through get<Int>
322
        obj.set(col, 7);
2✔
323
        CHECK_NOT(obj.is_null(col));
2✔
324
        CHECK_EQUAL(obj.get<Int>(col), 7);
2✔
325
    }
2✔
326

327
    {
2✔
328
        // Check that add_empty_row() adds 0 integer as default.
329
        Group group;
2✔
330
        TableRef table = group.add_table("test");
2✔
331
        auto col = table->add_column(type_Int, "age");
2✔
332
        CHECK(!table->is_nullable(col));
2✔
333

334
        Obj obj = table->create_object();
2✔
335
        CHECK(!obj.is_null(col));
2✔
336
        CHECK_EQUAL(0, obj.get<Int>(col));
2✔
337

338
        // Check that inserting null in non-nullable column will throw
339
        CHECK_LOGIC_ERROR(obj.set_null(col), ErrorCodes::PropertyNotNullable);
2✔
340
    }
2✔
341

342
    {
2✔
343
        // Check that add_empty_row() adds NULL binary as default
344
        Group group;
2✔
345
        TableRef table = group.add_table("test");
2✔
346

347
        auto col = table->add_column(type_Binary, "bin", true /*nullable*/);
2✔
348
        CHECK(table->is_nullable(col));
2✔
349

350
        Obj obj = table->create_object();
2✔
351
        CHECK(obj.is_null(col));
2✔
352
    }
2✔
353

354
    {
2✔
355
        // Check that add_empty_row() adds empty binary as default
356
        Group group;
2✔
357
        TableRef table = group.add_table("test");
2✔
358

359
        auto col = table->add_column(type_Binary, "name");
2✔
360
        CHECK(!table->is_nullable(col));
2✔
361

362
        Obj obj = table->create_object();
2✔
363
        CHECK(!obj.get<Binary>(col).is_null());
2✔
364

365
        // Test that inserting null in non-nullable column will throw
366
        CHECK_THROW_ANY(obj.set_null(col));
2✔
367
    }
2✔
368

369
    {
2✔
370
        // Check that link columns are nullable.
371
        Group group;
2✔
372
        TableRef target = group.add_table("target");
2✔
373
        TableRef table = group.add_table("table");
2✔
374

375
        auto col_int = target->add_column(type_Int, "int");
2✔
376
        auto col_link = table->add_column(*target, "link");
2✔
377
        CHECK(table->is_nullable(col_link));
2✔
378
        CHECK(!target->is_nullable(col_int));
2✔
379
    }
2✔
380

381
    {
2✔
382
        // Check that linklist columns are not nullable.
383
        Group group;
2✔
384
        TableRef target = group.add_table("target");
2✔
385
        TableRef table = group.add_table("table");
2✔
386

387
        auto col_int = target->add_column(type_Int, "int");
2✔
388
        auto col_link = table->add_column_list(*target, "link");
2✔
389
        CHECK(!table->is_nullable(col_link));
2✔
390
        CHECK(!target->is_nullable(col_int));
2✔
391
    }
2✔
392
}
2✔
393

394
TEST(Table_DeleteCrash)
395
{
2✔
396
    Group group;
2✔
397
    TableRef table = group.add_table("test");
2✔
398

399
    table->add_column(type_String, "name");
2✔
400
    table->add_column(type_Int, "age");
2✔
401

402
    ObjKey k0 = table->create_object().set_all("Alice", 17).get_key();
2✔
403
    ObjKey k1 = table->create_object().set_all("Bob", 50).get_key();
2✔
404
    table->create_object().set_all("Peter", 44);
2✔
405

406
    table->remove_object(k0);
2✔
407

408
    table->remove_object(k1);
2✔
409
}
2✔
410

411
TEST(Table_OptimizeCrash)
412
{
2✔
413
    // This will crash at the .add() method
414
    Table ttt;
2✔
415
    ttt.add_column(type_Int, "first");
2✔
416
    auto col = ttt.add_column(type_String, "second");
2✔
417
    ttt.enumerate_string_column(col);
2✔
418
    ttt.add_search_index(col);
2✔
419
    ttt.clear();
2✔
420
    ttt.create_object().set_all(1, "AA");
2✔
421
}
2✔
422

423
TEST(Table_DateTimeMinMax)
424
{
2✔
425
    Group g;
2✔
426
    TableRef table = g.add_table("test_table");
2✔
427

428
    auto col = table->add_column(type_Timestamp, "time", true);
2✔
429

430
    // We test different code paths of the internal Core minmax method. First a null value as initial "best
431
    // candidate", then non-null first. For each case we then try both a substitution of best candidate, then
432
    // non-substitution. 4 permutations in total.
433

434
    std::vector<Obj> objs(3);
2✔
435
    objs[0] = table->create_object();
2✔
436
    objs[1] = table->create_object();
2✔
437
    objs[2] = table->create_object();
2✔
438

439
    objs[0].set_null(col);
2✔
440
    objs[1].set(col, Timestamp{0, 0});
2✔
441
    objs[2].set(col, Timestamp{2, 2});
2✔
442

443
    CHECK_EQUAL(table->max(col)->get_timestamp(), Timestamp(2, 2));
2✔
444
    CHECK_EQUAL(table->min(col)->get_timestamp(), Timestamp(0, 0));
2✔
445

446
    objs[0].set(col, Timestamp{0, 0});
2✔
447
    objs[1].set_null(col);
2✔
448
    objs[2].set(col, Timestamp{2, 2});
2✔
449

450
    ObjKey idx; // tableview entry that points at the max/min value
2✔
451

452
    CHECK_EQUAL(table->max(col, &idx)->get_timestamp(), Timestamp(2, 2));
2✔
453
    CHECK_EQUAL(idx, objs[2].get_key());
2✔
454
    CHECK_EQUAL(table->min(col, &idx)->get_timestamp(), Timestamp(0, 0));
2✔
455
    CHECK_EQUAL(idx, objs[0].get_key());
2✔
456

457
    objs[0].set_null(col);
2✔
458
    objs[1].set(col, Timestamp{2, 2});
2✔
459
    objs[2].set(col, Timestamp{0, 0});
2✔
460

461
    CHECK_EQUAL(table->max(col)->get_timestamp(), Timestamp(2, 2));
2✔
462
    CHECK_EQUAL(table->min(col)->get_timestamp(), Timestamp(0, 0));
2✔
463

464
    objs[0].set(col, Timestamp{2, 2});
2✔
465
    objs[1].set_null(col);
2✔
466
    objs[2].set(col, Timestamp{0, 0});
2✔
467

468
    CHECK_EQUAL(table->max(col, &idx)->get_timestamp(), Timestamp(2, 2));
2✔
469
    CHECK_EQUAL(idx, objs[0].get_key());
2✔
470
    CHECK_EQUAL(table->min(col, &idx)->get_timestamp(), Timestamp(0, 0));
2✔
471
    CHECK_EQUAL(idx, objs[2].get_key());
2✔
472
}
2✔
473

474

475
TEST(Table_MinMaxSingleNullRow)
476
{
2✔
477
    // To illustrate/document behaviour
478
    Group g;
2✔
479
    TableRef table = g.add_table("test_table");
2✔
480

481
    auto date_col = table->add_column(type_Timestamp, "time", true);
2✔
482
    auto int_col = table->add_column(type_Int, "int", true);
2✔
483
    auto float_col = table->add_column(type_Float, "float", true);
2✔
484
    table->create_object();
2✔
485

486
    ObjKey key;
2✔
487

488
    // Maximum
489
    {
2✔
490
        table->max(date_col, &key); // max on table
2✔
491
        CHECK(key == null_key);
2✔
492
        table->where().find_all().max(date_col, &key); // max on tableview
2✔
493
        CHECK(key == null_key);
2✔
494
        table->where().max(date_col, &key); // max on query
2✔
495
        CHECK(key == null_key);
2✔
496

497
        table->max(int_col, &key); // max on table
2✔
498
        CHECK(key == null_key);
2✔
499
        table->where().find_all().max(int_col, &key); // max on tableview
2✔
500
        CHECK(key == null_key);
2✔
501
        table->where().max(int_col, &key); // max on query
2✔
502
        CHECK(key == null_key);
2✔
503

504
        table->max(float_col, &key); // max on table
2✔
505
        CHECK(key == null_key);
2✔
506
        table->where().find_all().max(float_col, &key); // max on tableview
2✔
507
        CHECK(key == null_key);
2✔
508
        table->where().max(float_col, &key); // max on query
2✔
509
        CHECK(key == null_key);
2✔
510

511
        table->create_object();
2✔
512

513
        CHECK(table->max(date_col)->is_null());        // max on table
2✔
514
        table->where().find_all().max(date_col, &key); // max on tableview
2✔
515
        CHECK(key == null_key);
2✔
516
        table->where().max(date_col, &key); // max on query
2✔
517
        CHECK(key == null_key);
2✔
518
    }
2✔
519

520
    // Minimum
521
    {
2✔
522
        table->min(date_col, &key); // max on table
2✔
523
        CHECK(key == null_key);
2✔
524
        table->where().find_all().min(date_col, &key); // max on tableview
2✔
525
        CHECK(key == null_key);
2✔
526
        table->where().min(date_col, &key); // max on query
2✔
527
        CHECK(key == null_key);
2✔
528

529
        table->min(int_col, &key); // max on table
2✔
530
        CHECK(key == null_key);
2✔
531
        table->where().find_all().min(int_col, &key); // max on tableview
2✔
532
        CHECK(key == null_key);
2✔
533
        table->where().min(int_col, &key); // max on query
2✔
534
        CHECK(key == null_key);
2✔
535

536
        table->min(float_col, &key); // max on table
2✔
537
        CHECK(key == null_key);
2✔
538
        table->where().find_all().min(float_col, &key); // max on tableview
2✔
539
        CHECK(key == null_key);
2✔
540
        table->where().min(float_col, &key); // max on query
2✔
541
        CHECK(key == null_key);
2✔
542

543
        table->create_object();
2✔
544

545
        CHECK(table->min(date_col)->is_null());        // max on table
2✔
546
        table->where().find_all().min(date_col, &key); // max on tableview
2✔
547
        CHECK(key == null_key);
2✔
548
        table->where().min(date_col, &key); // max on query
2✔
549
        CHECK(key == null_key);
2✔
550
    }
2✔
551
}
2✔
552

553

554
TEST(TableView_AggregateBugs)
555
{
2✔
556
    // Tests against various aggregate bugs on TableViews: https://github.com/realm/realm-core/pull/2360
557
    {
2✔
558
        Table table;
2✔
559
        auto int_col = table.add_column(type_Int, "ints", true);
2✔
560
        auto double_col = table.add_column(type_Double, "doubles", true);
2✔
561

562
        table.create_object().set_all(1, 1.);
2✔
563
        table.create_object().set_all(2, 2.);
2✔
564
        table.create_object();
2✔
565
        table.create_object().set_all(42, 42.);
2✔
566

567
        auto tv = table.where().not_equal(int_col, 42).find_all();
2✔
568
        CHECK_EQUAL(tv.size(), 3);
2✔
569
        CHECK_EQUAL(tv.max(int_col), 2);
2✔
570

571
        // average == sum / rows, where rows does *not* include values with null.
572
        size_t vc; // number of non-null values that the average was computed from
2✔
573
        CHECK_APPROXIMATELY_EQUAL(table.avg(int_col, &vc)->get_double(), double(1 + 2 + 42) / 3, 0.001);
2✔
574
        CHECK_EQUAL(vc, 3);
2✔
575

576
        // There are currently 3 ways of doing average: on tableview, table and query:
577
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().avg(int_col, &vc)->get_double());
2✔
578
        CHECK_EQUAL(vc, 3);
2✔
579
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().find_all().avg(int_col, &vc)->get_double());
2✔
580
        CHECK_EQUAL(vc, 3);
2✔
581

582
        // Core has an optimization where it executes average directly on the column if there
583
        // are no query conditions. Bypass that here.
584
        CHECK_APPROXIMATELY_EQUAL(table.where().not_equal(int_col, 1).find_all().avg(int_col, &vc)->get_double(),
2✔
585
                                  double(2 + 42) / 2, 0.001);
2✔
586
        CHECK_EQUAL(vc, 2);
2✔
587

588
        // Now doubles
589
        tv = table.where().not_equal(double_col, 42.).find_all();
2✔
590
        CHECK_EQUAL(tv.size(), 3);
2✔
591
        CHECK_EQUAL(tv.max(double_col), 2.);
2✔
592

593
        // average == sum / rows, where rows does *not* include values with null.
594
        CHECK_APPROXIMATELY_EQUAL(table.avg(double_col, &vc)->get_double(), double(1. + 2. + 42.) / 3, 0.001);
2✔
595
        CHECK_EQUAL(vc, 3);
2✔
596

597
        // There are currently 3 ways of doing average: on tableview, table and query:
598
        CHECK_APPROXIMATELY_EQUAL(table.avg(double_col)->get_double(),
2✔
599
                                  table.where().avg(double_col, &vc)->get_double(), 0.001);
2✔
600
        CHECK_EQUAL(vc, 3);
2✔
601

602
        CHECK_APPROXIMATELY_EQUAL(table.avg(double_col)->get_double(),
2✔
603
                                  table.where().find_all().avg(double_col, &vc)->get_double(), 0.001);
2✔
604
        CHECK_EQUAL(vc, 3);
2✔
605

606
        // Core has an optimization where it executes average directly on the column if there
607
        // are no query conditions. Bypass that here.
608
        CHECK_APPROXIMATELY_EQUAL(
2✔
609
            table.where().not_equal(double_col, 1.).find_all().avg(double_col, &vc)->get_double(), (2. + 42.) / 2,
2✔
610
            0.001);
2✔
611
        CHECK_EQUAL(vc, 2);
2✔
612
    }
2✔
613

614
    // Same as above, with null entry first
615
    {
2✔
616
        Table table;
2✔
617
        auto int_col = table.add_column(type_Int, "ints", true);
2✔
618

619
        table.create_object();
2✔
620
        table.create_object().set_all(1);
2✔
621
        table.create_object().set_all(2);
2✔
622
        table.create_object().set_all(42);
2✔
623

624
        auto tv = table.where().not_equal(int_col, 42).find_all();
2✔
625
        CHECK_EQUAL(tv.size(), 3);
2✔
626
        CHECK_EQUAL(tv.max(int_col), 2);
2✔
627

628
        // average == sum / rows, where rows does *not* include values with null.
629
        CHECK_APPROXIMATELY_EQUAL(table.avg(int_col)->get_double(), double(1 + 2 + 42) / 3, 0.001);
2✔
630

631
        // There are currently 3 ways of doing average: on tableview, table and query:
632
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().avg(int_col)->get_double());
2✔
633
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().find_all().avg(int_col)->get_double());
2✔
634

635
        // Core has an optimization where it executes average directly on the column if there
636
        // are no query conditions. Bypass that here.
637
        CHECK_APPROXIMATELY_EQUAL(table.where().not_equal(int_col, 1).find_all().avg(int_col)->get_double(),
2✔
638
                                  double(2 + 42) / 2, 0.001);
2✔
639
    }
2✔
640
}
2✔
641

642

643
TEST(Table_AggregateFuzz)
644
{
2✔
645
    // Tests sum, avg, min, max on Table, TableView, Query, for types float, Timestamp, int
646
    for (int iter = 0; iter < 50 + 1000 * TEST_DURATION; iter++) {
102✔
647
        Group g;
100✔
648
        TableRef table = g.add_table("test_table");
100✔
649

650
        auto date_col = table->add_column(type_Timestamp, "time", true);
100✔
651
        auto int_col = table->add_column(type_Int, "int", true);
100✔
652
        auto float_col = table->add_column(type_Float, "float", true);
100✔
653

654
        size_t rows = size_t(fastrand(10));
100✔
655
        std::vector<ObjKey> keys;
100✔
656
        table->create_objects(rows, keys);
100✔
657
        int64_t largest = 0;
100✔
658
        int64_t smallest = 0;
100✔
659
        ObjKey largest_pos = null_key;
100✔
660
        ObjKey smallest_pos = null_key;
100✔
661

662
        double avg = 0;
100✔
663
        int64_t sum = 0;
100✔
664
        size_t nulls = 0;
100✔
665

666
        // Create some rows with values and some rows with just nulls
667
        for (size_t t = 0; t < rows; t++) {
568✔
668
            bool null = (fastrand(1) == 0);
468✔
669
            if (!null) {
468✔
670
                int64_t value = fastrand(10);
258✔
671
                sum += value;
258✔
672
                if (largest_pos == null_key || value > largest) {
258✔
673
                    largest = value;
145✔
674
                    largest_pos = keys[t];
145✔
675
                }
145✔
676
                if (smallest_pos == null_key || value < smallest) {
258✔
677
                    smallest = value;
127✔
678
                    smallest_pos = keys[t];
127✔
679
                }
127✔
680
                table->get_object(keys[t]).set_all(Timestamp(value, 0), value, float(value));
258✔
681
            }
258✔
682
            else {
210✔
683
                nulls++;
210✔
684
            }
210✔
685
        }
468✔
686

687
        avg = double(sum) / (rows - nulls == 0 ? 1 : rows - nulls);
100✔
688

689
        ObjKey key;
100✔
690
        size_t cnt;
100✔
691
        int64_t i;
100✔
692
        Mixed m;
100✔
693

694
        // Test methods on Table
695
        {
100✔
696
            // Table::max
697
            key = ObjKey(123);
100✔
698
            m = *table->max(float_col, &key);
100✔
699
            CHECK_EQUAL(key, largest_pos);
100✔
700
            if (largest_pos != null_key)
100✔
701
                CHECK_EQUAL(m.get_float(), table->get_object(largest_pos).get<float>(float_col));
82✔
702

703
            key = ObjKey(123);
100✔
704
            m = *table->max(int_col, &key);
100✔
705
            CHECK_EQUAL(key, largest_pos);
100✔
706
            if (largest_pos != null_key)
100✔
707
                CHECK_EQUAL(m.get_int(), table->get_object(largest_pos).get<util::Optional<Int>>(int_col));
82✔
708

709
            key = ObjKey(123);
100✔
710
            m = *table->max(date_col, &key);
100✔
711
            CHECK_EQUAL(key, largest_pos);
100✔
712
            if (largest_pos != null_key)
100✔
713
                CHECK_EQUAL(m.get_timestamp(), table->get_object(largest_pos).get<Timestamp>(date_col));
82✔
714

715
            // Table::min
716
            key = ObjKey(123);
100✔
717
            m = *table->min(float_col, &key);
100✔
718
            CHECK_EQUAL(key, smallest_pos);
100✔
719
            if (smallest_pos != null_key)
100✔
720
                CHECK_EQUAL(m.get_float(), table->get_object(smallest_pos).get<float>(float_col));
82✔
721

722
            key = ObjKey(123);
100✔
723
            m = *table->min(int_col, &key);
100✔
724
            CHECK_EQUAL(key, smallest_pos);
100✔
725
            if (smallest_pos != null_key)
100✔
726
                CHECK_EQUAL(m.get_int(), table->get_object(smallest_pos).get<util::Optional<Int>>(int_col));
82✔
727

728
            key = ObjKey(123);
100✔
729
            m = *table->min(date_col, &key);
100✔
730
            CHECK_EQUAL(key, smallest_pos);
100✔
731
            if (smallest_pos != null_key)
100✔
732
                CHECK_EQUAL(m.get_timestamp(), table->get_object(smallest_pos).get<Timestamp>(date_col));
82✔
733

734
            // Table::avg
735
            double d;
100✔
736

737
            // number of non-null values used in computing the avg or sum
738
            cnt = 123;
100✔
739

740
            // Table::avg
741
            m = *table->avg(float_col, &cnt);
100✔
742
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
743
            if (cnt != 0)
100✔
744
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
82✔
745

746
            cnt = 123;
100✔
747
            m = *table->avg(int_col, &cnt);
100✔
748
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
749
            if (cnt != 0)
100✔
750
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
82✔
751

752
            // Table::sum
753
            d = table->sum(float_col)->get_double();
100✔
754
            CHECK_APPROXIMATELY_EQUAL(d, double(sum), 0.001);
100✔
755

756
            i = table->sum(int_col)->get_int();
100✔
757
            CHECK_EQUAL(i, sum);
100✔
758
        }
100✔
759

760
        // Test methods on TableView
761
        {
100✔
762
            // TableView::max
763
            key = ObjKey(123);
100✔
764
            m = *table->where().find_all().max(float_col, &key);
100✔
765
            CHECK_EQUAL(key, largest_pos);
100✔
766
            if (largest_pos != null_key)
100✔
767
                CHECK_EQUAL(m, table->get_object(largest_pos).get<float>(float_col));
82✔
768

769
            key = ObjKey(123);
100✔
770
            m = *table->where().find_all().max(int_col, &key);
100✔
771
            CHECK_EQUAL(key, largest_pos);
100✔
772
            if (largest_pos != null_key)
100✔
773
                CHECK_EQUAL(m, table->get_object(largest_pos).get<util::Optional<Int>>(int_col));
82✔
774

775
            key = ObjKey(123);
100✔
776
            m = *table->where().find_all().max(date_col, &key);
100✔
777
            CHECK_EQUAL(key, largest_pos);
100✔
778
            if (largest_pos != null_key)
100✔
779
                CHECK_EQUAL(m, table->get_object(largest_pos).get<Timestamp>(date_col));
82✔
780

781
            // TableView::min
782
            key = ObjKey(123);
100✔
783
            m = *table->where().find_all().min(float_col, &key);
100✔
784
            CHECK_EQUAL(key, smallest_pos);
100✔
785
            if (smallest_pos != null_key)
100✔
786
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<float>(float_col));
82✔
787

788
            key = ObjKey(123);
100✔
789
            m = *table->where().find_all().min(int_col, &key);
100✔
790
            CHECK_EQUAL(key, smallest_pos);
100✔
791
            if (smallest_pos != null_key)
100✔
792
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<util::Optional<Int>>(int_col));
82✔
793

794
            key = ObjKey(123);
100✔
795
            m = *table->where().find_all().min(date_col, &key);
100✔
796
            CHECK_EQUAL(key, smallest_pos);
100✔
797
            if (smallest_pos != null_key)
100✔
798
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<Timestamp>(date_col));
82✔
799

800
            // TableView::avg
801
            double d;
100✔
802

803
            // number of non-null values used in computing the avg or sum
804
            key = ObjKey(123);
100✔
805

806
            // TableView::avg
807
            m = *table->where().find_all().avg(float_col, &cnt);
100✔
808
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
809
            if (cnt != 0)
100✔
810
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
82✔
811

812
            cnt = 123;
100✔
813
            m = *table->where().find_all().avg(int_col, &cnt);
100✔
814
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
815
            if (cnt != 0)
100✔
816
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
82✔
817

818
            // TableView::sum
819
            d = table->where().find_all().sum(float_col)->get_double();
100✔
820
            CHECK_APPROXIMATELY_EQUAL(d, double(sum), 0.001);
100✔
821

822
            i = table->where().find_all().sum(int_col)->get_int();
100✔
823
            CHECK_EQUAL(i, sum);
100✔
824
        }
100✔
825

826
        // Test methods on Query
827
        {
100✔
828
            // TableView::max
829
            key = ObjKey(123);
100✔
830
            m = *table->where().max(float_col, &key);
100✔
831
            CHECK_EQUAL(key, largest_pos);
100✔
832
            if (largest_pos != null_key)
100✔
833
                CHECK_EQUAL(m, table->get_object(largest_pos).get<float>(float_col));
82✔
834

835
            key = ObjKey(123);
100✔
836
            m = *table->where().max(int_col, &key);
100✔
837
            CHECK_EQUAL(key, largest_pos);
100✔
838
            if (largest_pos != null_key)
100✔
839
                CHECK_EQUAL(m, table->get_object(largest_pos).get<util::Optional<Int>>(int_col));
82✔
840

841
            key = ObjKey(123);
100✔
842
            m = *table->where().max(date_col, &key);
100✔
843
            CHECK_EQUAL(key, largest_pos);
100✔
844
            if (largest_pos != null_key)
100✔
845
                CHECK_EQUAL(m, table->get_object(largest_pos).get<Timestamp>(date_col));
82✔
846

847
            // TableView::min
848
            key = ObjKey(123);
100✔
849
            m = *table->where().min(float_col, &key);
100✔
850
            CHECK_EQUAL(key, smallest_pos);
100✔
851
            if (smallest_pos != null_key)
100✔
852
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<float>(float_col));
82✔
853

854
            key = ObjKey(123);
100✔
855
            m = *table->where().min(int_col, &key);
100✔
856
            CHECK_EQUAL(key, smallest_pos);
100✔
857
            if (smallest_pos != null_key)
100✔
858
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<util::Optional<Int>>(int_col));
82✔
859

860
            key = ObjKey(123);
100✔
861
            m = *table->where().min(date_col, &key);
100✔
862
            CHECK_EQUAL(key, smallest_pos);
100✔
863
            if (smallest_pos != null_key)
100✔
864
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<Timestamp>(date_col));
82✔
865

866
            // TableView::avg
867
            double d;
100✔
868

869
            // number of non-null values used in computing the avg or sum
870
            cnt = 123;
100✔
871

872
            // TableView::avg
873
            m = *table->where().avg(float_col, &cnt);
100✔
874
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
875
            if (cnt != 0)
100✔
876
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
82✔
877

878
            cnt = 123;
100✔
879
            m = *table->where().avg(int_col, &cnt);
100✔
880
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
881
            if (cnt != 0)
100✔
882
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
82✔
883

884
            // TableView::sum
885
            d = table->where().sum(float_col)->get_double();
100✔
886
            CHECK_APPROXIMATELY_EQUAL(d, double(sum), 0.001);
100✔
887

888
            m = *table->where().sum(int_col);
100✔
889
            CHECK_EQUAL(m, sum);
100✔
890
        }
100✔
891
    }
100✔
892
}
2✔
893

894
TEST(Table_ColumnNameTooLong)
895
{
2✔
896
    Group group;
2✔
897
    TableRef table = group.add_table("foo");
2✔
898
    const size_t buf_size = 64;
2✔
899
    char buf[buf_size];
2✔
900
    memset(buf, 'A', buf_size);
2✔
901
    CHECK_LOGIC_ERROR(table->add_column(type_Int, StringData(buf, buf_size)), ErrorCodes::InvalidName);
2✔
902
    CHECK_LOGIC_ERROR(table->add_column_list(type_Int, StringData(buf, buf_size)), ErrorCodes::InvalidName);
2✔
903
    CHECK_LOGIC_ERROR(table->add_column(*table, StringData(buf, buf_size)), ErrorCodes::InvalidName);
2✔
904

905
    table->add_column(type_Int, StringData(buf, buf_size - 1));
2✔
906
    memset(buf, 'B', buf_size); // Column names must be unique
2✔
907
    table->add_column_list(type_Int, StringData(buf, buf_size - 1));
2✔
908
    memset(buf, 'C', buf_size);
2✔
909
    table->add_column(*table, StringData(buf, buf_size - 1));
2✔
910
}
2✔
911

912
TEST(Table_StringOrBinaryTooBig)
913
{
2✔
914
    Table table;
2✔
915
    auto col_string = table.add_column(type_String, "s");
2✔
916
    auto col_binary = table.add_column(type_Binary, "b");
2✔
917
    Obj obj = table.create_object();
2✔
918

919
    obj.set(col_string, "01234567");
2✔
920

921
    size_t large_bin_size = 0xFFFFF1;
2✔
922
    size_t large_str_size = 0xFFFFF0; // null-terminate reduces max size by 1
2✔
923
    std::unique_ptr<char[]> large_buf(new char[large_bin_size]);
2✔
924
    CHECK_LOGIC_ERROR(obj.set(col_string, StringData(large_buf.get(), large_str_size)), ErrorCodes::LimitExceeded);
2✔
925
    CHECK_LOGIC_ERROR(obj.set(col_binary, BinaryData(large_buf.get(), large_bin_size)), ErrorCodes::LimitExceeded);
2✔
926
    obj.set(col_string, StringData(large_buf.get(), large_str_size - 1));
2✔
927
    obj.set(col_binary, BinaryData(large_buf.get(), large_bin_size - 1));
2✔
928
}
2✔
929

930

931
TEST(Table_Floats)
932
{
2✔
933
    Table table;
2✔
934
    auto float_col = table.add_column(type_Float, "first");
2✔
935
    auto double_col = table.add_column(type_Double, "second");
2✔
936

937
    CHECK_EQUAL(type_Float, table.get_column_type(float_col));
2✔
938
    CHECK_EQUAL(type_Double, table.get_column_type(double_col));
2✔
939
    CHECK_EQUAL("first", table.get_column_name(float_col));
2✔
940
    CHECK_EQUAL("second", table.get_column_name(double_col));
2✔
941

942
    // Test adding a single empty row
943
    // and filling it with values
944
    Obj obj = table.create_object().set_all(1.12f, 102.13);
2✔
945

946
    CHECK_EQUAL(1.12f, obj.get<float>(float_col));
2✔
947
    CHECK_EQUAL(102.13, obj.get<double>(double_col));
2✔
948

949
    // Test adding multiple rows
950
    std::vector<ObjKey> keys;
2✔
951
    table.create_objects(7, keys);
2✔
952
    for (size_t i = 0; i < 7; ++i) {
16✔
953
        table.get_object(keys[i]).set(float_col, 1.12f + 100 * i).set(double_col, 102.13 * 200 * i);
14✔
954
    }
14✔
955

956
    for (size_t i = 0; i < 7; ++i) {
16✔
957
        const float v1 = 1.12f + 100 * i;
14✔
958
        const double v2 = 102.13 * 200 * i;
14✔
959
        Obj o = table.get_object(keys[i]);
14✔
960
        CHECK_EQUAL(v1, o.get<float>(float_col));
14✔
961
        CHECK_EQUAL(v2, o.get<double>(double_col));
14✔
962
    }
14✔
963

964
    table.verify();
2✔
965
}
2✔
966

967
TEST(Table_Delete)
968
{
2✔
969
    Table table;
2✔
970

971
    auto col_int = table.add_column(type_Int, "ints");
2✔
972

973
    for (int i = 0; i < 10; ++i) {
22✔
974
        table.create_object(ObjKey(i)).set(col_int, i);
20✔
975
    }
20✔
976

977
    table.remove_object(ObjKey(0));
2✔
978
    table.remove_object(ObjKey(4));
2✔
979
    table.remove_object(ObjKey(7));
2✔
980

981
    CHECK_EQUAL(1, table.get_object(ObjKey(1)).get<int64_t>(col_int));
2✔
982
    CHECK_EQUAL(2, table.get_object(ObjKey(2)).get<int64_t>(col_int));
2✔
983
    CHECK_EQUAL(3, table.get_object(ObjKey(3)).get<int64_t>(col_int));
2✔
984
    CHECK_EQUAL(5, table.get_object(ObjKey(5)).get<int64_t>(col_int));
2✔
985
    CHECK_EQUAL(6, table.get_object(ObjKey(6)).get<int64_t>(col_int));
2✔
986
    CHECK_EQUAL(8, table.get_object(ObjKey(8)).get<int64_t>(col_int));
2✔
987
    CHECK_EQUAL(9, table.get_object(ObjKey(9)).get<int64_t>(col_int));
2✔
988

989
#ifdef REALM_DEBUG
2✔
990
    table.verify();
2✔
991
#endif
2✔
992

993
    // Delete all items one at a time
994
    for (size_t i = 0; i < 10; ++i) {
22✔
995
        try {
20✔
996
            table.remove_object(ObjKey(i));
20✔
997
        }
20✔
998
        catch (...) {
20✔
999
        }
6✔
1000
    }
20✔
1001

1002
    CHECK(table.is_empty());
2✔
1003
    CHECK_EQUAL(0, table.size());
2✔
1004

1005
#ifdef REALM_DEBUG
2✔
1006
    table.verify();
2✔
1007
#endif
2✔
1008
}
2✔
1009

1010

1011
TEST(Table_GetName)
1012
{
2✔
1013
    // Freestanding tables have no names
1014
    {
2✔
1015
        Table table;
2✔
1016
        CHECK_EQUAL("", table.get_name());
2✔
1017
    }
2✔
1018

1019
    // Direct members of groups do have names
1020
    {
2✔
1021
        Group group;
2✔
1022
        TableRef table = group.add_table("table");
2✔
1023
        CHECK_EQUAL("table", table->get_name());
2✔
1024
    }
2✔
1025
    {
2✔
1026
        Group group;
2✔
1027
        TableRef foo = group.add_table("foo");
2✔
1028
        TableRef bar = group.add_table("bar");
2✔
1029
        CHECK_EQUAL("foo", foo->get_name());
2✔
1030
        CHECK_EQUAL("bar", bar->get_name());
2✔
1031
    }
2✔
1032
}
2✔
1033

1034

1035
namespace {
1036

1037
void setup_multi_table(Table& table, size_t rows, std::vector<ObjKey>& keys, std::vector<ColKey>& column_keys)
1038
{
4✔
1039
    // Create table with all column types
1040
    auto int_col = table.add_column(type_Int, "int");                        //  0
4✔
1041
    auto bool_col = table.add_column(type_Bool, "bool");                     //  1
4✔
1042
    auto float_col = table.add_column(type_Float, "float");                  //  2
4✔
1043
    auto double_col = table.add_column(type_Double, "double");               //  3
4✔
1044
    auto string_col = table.add_column(type_String, "string");               //  4
4✔
1045
    auto string_long_col = table.add_column(type_String, "string_long");     //  5
4✔
1046
    auto string_big_col = table.add_column(type_String, "string_big_blobs"); //  6
4✔
1047
    auto string_enum_col = table.add_column(type_String, "string_enum");     //  7 - becomes StringEnumColumn
4✔
1048
    auto bin_col = table.add_column(type_Binary, "binary");                  //  8
4✔
1049
    auto int_null_col = table.add_column(type_Int, "int_null", true);        //  9, nullable = true
4✔
1050
    column_keys.push_back(int_col);
4✔
1051
    column_keys.push_back(bool_col);
4✔
1052
    column_keys.push_back(float_col);
4✔
1053
    column_keys.push_back(double_col);
4✔
1054
    column_keys.push_back(string_col);
4✔
1055
    column_keys.push_back(string_long_col);
4✔
1056
    column_keys.push_back(string_big_col);
4✔
1057
    column_keys.push_back(string_enum_col);
4✔
1058
    column_keys.push_back(bin_col);
4✔
1059
    column_keys.push_back(int_null_col);
4✔
1060

1061
    std::vector<std::string> strings;
4✔
1062
    for (size_t i = 0; i < rows; ++i) {
64✔
1063
        std::stringstream out;
60✔
1064
        out << "string" << i;
60✔
1065
        strings.push_back(out.str());
60✔
1066
    }
60✔
1067

1068
    for (size_t i = 0; i < rows; ++i) {
64✔
1069
        Obj obj = table.create_object();
60✔
1070
        keys.push_back(obj.get_key());
60✔
1071

1072
        int64_t sign = (i % 2 == 0) ? 1 : -1;
60✔
1073

1074
        // int
1075
        obj.set(int_col, int64_t(i * sign));
60✔
1076

1077
        if (i % 4 == 0) {
60✔
1078
            obj.set_null(int_null_col);
16✔
1079
        }
16✔
1080
        else {
44✔
1081
            obj.set(int_null_col, int64_t(i * sign));
44✔
1082
        }
44✔
1083
        // bool
1084
        obj.set(bool_col, (i % 2 ? true : false));
60✔
1085
        // float
1086
        obj.set(float_col, 123.456f * sign);
60✔
1087
        // double
1088
        obj.set(double_col, 9876.54321 * sign);
60✔
1089
        // strings
1090
        std::string str_i(strings[i] + " very long string.........");
60✔
1091
        obj.set(string_col, StringData(strings[i]));
60✔
1092
        obj.set(string_long_col, StringData(str_i));
60✔
1093
        switch (i % 2) {
60✔
1094
            case 0: {
32✔
1095
                std::string s = strings[i];
32✔
1096
                s += " very long string.........";
32✔
1097
                for (int j = 0; j != 4; ++j)
160✔
1098
                    s += " big blobs big blobs big blobs"; // +30
128✔
1099
                obj.set(string_big_col, StringData(s));
32✔
1100
                break;
32✔
1101
            }
×
1102
            case 1:
28✔
1103
                obj.set(string_big_col, StringData(""));
28✔
1104
                break;
28✔
1105
        }
60✔
1106
        // enum
1107
        switch (i % 3) {
60✔
1108
            case 0:
20✔
1109
                obj.set(string_enum_col, "enum1");
20✔
1110
                break;
20✔
1111
            case 1:
20✔
1112
                obj.set(string_enum_col, "enum2");
20✔
1113
                break;
20✔
1114
            case 2:
20✔
1115
                obj.set(string_enum_col, "enum3");
20✔
1116
                break;
20✔
1117
        }
60✔
1118
        obj.set(bin_col, BinaryData("binary", 7));
60✔
1119
    }
60✔
1120

1121
    // We also want a StringEnumColumn
1122
    table.enumerate_string_column(string_enum_col);
4✔
1123
}
4✔
1124

1125
} // anonymous namespace
1126

1127

1128
TEST(Table_DeleteAllTypes)
1129
{
2✔
1130
    Table table;
2✔
1131
    std::vector<ObjKey> keys;
2✔
1132
    std::vector<ColKey> column_keys;
2✔
1133
    setup_multi_table(table, 15, keys, column_keys);
2✔
1134

1135
    // Test Deletes
1136
    table.remove_object(keys[14]);
2✔
1137
    table.remove_object(keys[0]);
2✔
1138
    table.remove_object(keys[5]);
2✔
1139

1140
    CHECK_EQUAL(12, table.size());
2✔
1141

1142
#ifdef REALM_DEBUG
2✔
1143
    table.verify();
2✔
1144
#endif
2✔
1145

1146
    // Test Clear
1147
    table.clear();
2✔
1148
    CHECK_EQUAL(0, table.size());
2✔
1149

1150
#ifdef REALM_DEBUG
2✔
1151
    table.verify();
2✔
1152
#endif
2✔
1153
}
2✔
1154

1155

1156
TEST(Table_MoveAllTypes)
1157
{
2✔
1158
    Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
1159

1160
    Table table;
2✔
1161
    std::vector<ObjKey> keys;
2✔
1162
    std::vector<ColKey> column_keys;
2✔
1163
    setup_multi_table(table, 15, keys, column_keys);
2✔
1164
    table.add_search_index(column_keys[6]);
2✔
1165
    while (!table.is_empty()) {
32✔
1166
        size_t size = keys.size();
30✔
1167
        auto it = keys.begin() + random.draw_int_mod(size);
30✔
1168
        table.remove_object(*it);
30✔
1169
        keys.erase(it);
30✔
1170
        table.verify();
30✔
1171
    }
30✔
1172
}
2✔
1173

1174
TEST(Table_FindAllInt)
1175
{
2✔
1176
    Table table;
2✔
1177

1178
    auto col_int = table.add_column(type_Int, "integers");
2✔
1179

1180
    table.create_object(ObjKey(0)).set(col_int, 10);
2✔
1181
    table.create_object(ObjKey(1)).set(col_int, 20);
2✔
1182
    table.create_object(ObjKey(2)).set(col_int, 10);
2✔
1183
    table.create_object(ObjKey(3)).set(col_int, 20);
2✔
1184
    table.create_object(ObjKey(4)).set(col_int, 10);
2✔
1185
    table.create_object(ObjKey(5)).set(col_int, 20);
2✔
1186
    table.create_object(ObjKey(6)).set(col_int, 10);
2✔
1187
    table.create_object(ObjKey(7)).set(col_int, 20);
2✔
1188
    table.create_object(ObjKey(8)).set(col_int, 10);
2✔
1189
    table.create_object(ObjKey(9)).set(col_int, 20);
2✔
1190

1191
    // Search for a value that does not exits
1192
    auto v0 = table.find_all_int(col_int, 5);
2✔
1193
    CHECK_EQUAL(0, v0.size());
2✔
1194

1195
    // Search for a value with several matches
1196
    auto v = table.find_all_int(col_int, 20);
2✔
1197

1198
    CHECK_EQUAL(5, v.size());
2✔
1199
    CHECK_EQUAL(ObjKey(1), v.get_key(0));
2✔
1200
    CHECK_EQUAL(ObjKey(3), v.get_key(1));
2✔
1201
    CHECK_EQUAL(ObjKey(5), v.get_key(2));
2✔
1202
    CHECK_EQUAL(ObjKey(7), v.get_key(3));
2✔
1203
    CHECK_EQUAL(ObjKey(9), v.get_key(4));
2✔
1204

1205
#ifdef REALM_DEBUG
2✔
1206
    table.verify();
2✔
1207
#endif
2✔
1208
}
2✔
1209

1210
TEST(Table_SortedInt)
1211
{
2✔
1212
    Table table;
2✔
1213

1214
    auto col_int = table.add_column(type_Int, "integers");
2✔
1215

1216
    table.create_object(ObjKey(0)).set(col_int, 10); // 0: 4
2✔
1217
    table.create_object(ObjKey(1)).set(col_int, 20); // 1: 7
2✔
1218
    table.create_object(ObjKey(2)).set(col_int, 0);  // 2: 0
2✔
1219
    table.create_object(ObjKey(3)).set(col_int, 40); // 3: 8
2✔
1220
    table.create_object(ObjKey(4)).set(col_int, 15); // 4: 6
2✔
1221
    table.create_object(ObjKey(5)).set(col_int, 11); // 5: 5
2✔
1222
    table.create_object(ObjKey(6)).set(col_int, 6);  // 6: 3
2✔
1223
    table.create_object(ObjKey(7)).set(col_int, 4);  // 7: 2
2✔
1224
    table.create_object(ObjKey(8)).set(col_int, 99); // 8: 9
2✔
1225
    table.create_object(ObjKey(9)).set(col_int, 2);  // 9: 1
2✔
1226

1227
    // Search for a value that does not exits
1228
    auto v = table.get_sorted_view(col_int);
2✔
1229
    CHECK_EQUAL(table.size(), v.size());
2✔
1230

1231
    CHECK_EQUAL(ObjKey(2), v.get_key(0));
2✔
1232
    CHECK_EQUAL(ObjKey(9), v.get_key(1));
2✔
1233
    CHECK_EQUAL(ObjKey(7), v.get_key(2));
2✔
1234
    CHECK_EQUAL(ObjKey(6), v.get_key(3));
2✔
1235
    CHECK_EQUAL(ObjKey(0), v.get_key(4));
2✔
1236
    CHECK_EQUAL(ObjKey(5), v.get_key(5));
2✔
1237
    CHECK_EQUAL(ObjKey(4), v.get_key(6));
2✔
1238
    CHECK_EQUAL(ObjKey(1), v.get_key(7));
2✔
1239
    CHECK_EQUAL(ObjKey(3), v.get_key(8));
2✔
1240
    CHECK_EQUAL(ObjKey(8), v.get_key(9));
2✔
1241

1242
#ifdef REALM_DEBUG
2✔
1243
    table.verify();
2✔
1244
#endif
2✔
1245
}
2✔
1246

1247

1248
TEST(Table_Sorted_Query_where)
1249
{
2✔
1250
    Table table;
2✔
1251

1252
    auto col_dummy = table.add_column(type_Int, "dummmy");
2✔
1253
    auto col_int = table.add_column(type_Int, "integers");
2✔
1254
    auto col_bool = table.add_column(type_Bool, "booleans");
2✔
1255

1256
    table.create_object(ObjKey(0)).set(col_int, 10).set(col_bool, true);  // 0: 4
2✔
1257
    table.create_object(ObjKey(1)).set(col_int, 20).set(col_bool, false); // 1: 7
2✔
1258
    table.create_object(ObjKey(2)).set(col_int, 0).set(col_bool, false);  // 2: 0
2✔
1259
    table.create_object(ObjKey(3)).set(col_int, 40).set(col_bool, false); // 3: 8
2✔
1260
    table.create_object(ObjKey(4)).set(col_int, 15).set(col_bool, false); // 4: 6
2✔
1261
    table.create_object(ObjKey(5)).set(col_int, 11).set(col_bool, true);  // 5: 5
2✔
1262
    table.create_object(ObjKey(6)).set(col_int, 6).set(col_bool, true);   // 6: 3
2✔
1263
    table.create_object(ObjKey(7)).set(col_int, 4).set(col_bool, true);   // 7: 2
2✔
1264
    table.create_object(ObjKey(8)).set(col_int, 99).set(col_bool, true);  // 8: 9
2✔
1265
    table.create_object(ObjKey(9)).set(col_int, 2).set(col_bool, true);   // 9: 1
2✔
1266

1267
    // Get a view containing the complete table
1268
    auto v = table.find_all_int(col_dummy, 0);
2✔
1269
    CHECK_EQUAL(table.size(), v.size());
2✔
1270

1271
    // Count booleans
1272
    size_t count_view = table.where(&v).equal(col_bool, false).count();
2✔
1273
    CHECK_EQUAL(4, count_view);
2✔
1274

1275
    auto v_sorted = table.get_sorted_view(col_int);
2✔
1276
    CHECK_EQUAL(table.size(), v_sorted.size());
2✔
1277

1278
#ifdef REALM_DEBUG
2✔
1279
    table.verify();
2✔
1280
#endif
2✔
1281
}
2✔
1282

1283
namespace realm {
1284
template <class T>
1285
T nan(const char* tag)
1286
{
1,200✔
1287
    typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type i;
1,200✔
1288
    uint64_t double_nan = 0x7ff8000000000000;
1,200✔
1289
    i = std::is_same<T, float>::value ? 0x7fc00000 : static_cast<decltype(i)>(double_nan);
1,200✔
1290
    i += *tag;
1,200✔
1291
    return type_punning<T>(i);
1,200✔
1292
}
1,200✔
1293
template <>
1294
Decimal128 nan(const char* init)
1295
{
600✔
1296
    return Decimal128::nan(init);
600✔
1297
}
600✔
1298

1299
template <typename T>
1300
inline bool isnan(T val)
1301
{
1,200✔
1302
    return std::isnan(val);
1,200✔
1303
}
1,200✔
1304
inline bool isnan(Decimal128 val)
1305
{
600✔
1306
    return val.is_nan();
600✔
1307
}
600✔
1308

1309
} // namespace realm
1310

1311
TEST_TYPES(Table_SortFloat, float, double, Decimal128)
1312
{
6✔
1313
    Table table;
6✔
1314
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
6✔
1315
    auto col = table.add_column(type, "value", true);
6✔
1316
    ObjKeys keys;
6✔
1317
    table.create_objects(900, keys);
6✔
1318
    for (size_t i = 0; i < keys.size(); i += 3) {
1,806✔
1319
        table.get_object(keys[i]).set(col, TEST_TYPE(-500.0 + i));
1,800✔
1320
        table.get_object(keys[i + 1]).set_null(col);
1,800✔
1321
        const char nan_tag[] = {char('0' + i % 10), 0};
1,800✔
1322
        table.get_object(keys[i + 2]).set(col, realm::nan<TEST_TYPE>(nan_tag));
1,800✔
1323
    }
1,800✔
1324

1325
    TableView sorted = table.get_sorted_view(SortDescriptor{{{col}}, {true}});
6✔
1326
    CHECK_EQUAL(table.size(), sorted.size());
6✔
1327

1328
    // nulls should appear first,
1329
    // followed by nans, folllowed by the rest of the values in ascending order
1330
    for (size_t i = 0; i < 300; ++i) {
1,806✔
1331
        CHECK(sorted.get_object(i).is_null(col));
1,800✔
1332
    }
1,800✔
1333
    for (size_t i = 300; i < 600; ++i) {
1,806✔
1334
        CHECK(realm::isnan(sorted.get_object(i).get<TEST_TYPE>(col)));
1,800✔
1335
    }
1,800✔
1336
    for (size_t i = 600; i + 1 < 900; ++i) {
1,800✔
1337
        CHECK_GREATER(sorted.get_object(i + 1).get<TEST_TYPE>(col), sorted.get_object(i).get<TEST_TYPE>(col));
1,794✔
1338
    }
1,794✔
1339
}
6✔
1340

1341
TEST_TYPES(Table_Multi_Sort, int64_t, float, double, Decimal128)
1342
{
8✔
1343
    Table table;
8✔
1344
    auto col_0 = table.add_column(ColumnTypeTraits<TEST_TYPE>::id, "first");
8✔
1345
    auto col_1 = table.add_column(ColumnTypeTraits<TEST_TYPE>::id, "second");
8✔
1346

1347
    table.create_object(ObjKey(0)).set_all(TEST_TYPE(1), TEST_TYPE(10));
8✔
1348
    table.create_object(ObjKey(1)).set_all(TEST_TYPE(2), TEST_TYPE(10));
8✔
1349
    table.create_object(ObjKey(2)).set_all(TEST_TYPE(0), TEST_TYPE(10));
8✔
1350
    table.create_object(ObjKey(3)).set_all(TEST_TYPE(2), TEST_TYPE(14));
8✔
1351
    table.create_object(ObjKey(4)).set_all(TEST_TYPE(1), TEST_TYPE(14));
8✔
1352

1353
    std::vector<std::vector<ExtendedColumnKey>> col_ndx1 = {{col_0}, {col_1}};
8✔
1354
    std::vector<bool> asc = {true, true};
8✔
1355

1356
    // (0, 10); (1, 10); (1, 14); (2, 10); (2; 14)
1357
    TableView v_sorted1 = table.get_sorted_view(SortDescriptor{col_ndx1, asc});
8✔
1358
    CHECK_EQUAL(table.size(), v_sorted1.size());
8✔
1359
    CHECK_EQUAL(ObjKey(2), v_sorted1.get_key(0));
8✔
1360
    CHECK_EQUAL(ObjKey(0), v_sorted1.get_key(1));
8✔
1361
    CHECK_EQUAL(ObjKey(4), v_sorted1.get_key(2));
8✔
1362
    CHECK_EQUAL(ObjKey(1), v_sorted1.get_key(3));
8✔
1363
    CHECK_EQUAL(ObjKey(3), v_sorted1.get_key(4));
8✔
1364

1365
    std::vector<std::vector<ExtendedColumnKey>> col_ndx2 = {{col_1}, {col_0}};
8✔
1366

1367
    // (0, 10); (1, 10); (2, 10); (1, 14); (2, 14)
1368
    TableView v_sorted2 = table.get_sorted_view(SortDescriptor{col_ndx2, asc});
8✔
1369
    CHECK_EQUAL(table.size(), v_sorted2.size());
8✔
1370
    CHECK_EQUAL(ObjKey(2), v_sorted2.get_key(0));
8✔
1371
    CHECK_EQUAL(ObjKey(0), v_sorted2.get_key(1));
8✔
1372
    CHECK_EQUAL(ObjKey(1), v_sorted2.get_key(2));
8✔
1373
    CHECK_EQUAL(ObjKey(4), v_sorted2.get_key(3));
8✔
1374
    CHECK_EQUAL(ObjKey(3), v_sorted2.get_key(4));
8✔
1375
}
8✔
1376

1377
TEST(Table_IndexString)
1378
{
2✔
1379
    Table table;
2✔
1380
    auto col_int = table.add_column(type_Int, "first");
2✔
1381
    auto col_str = table.add_column(type_String, "second");
2✔
1382

1383
    table.add_search_index(col_str);
2✔
1384
    CHECK(table.has_search_index(col_str));
2✔
1385

1386
    ObjKey k0 = table.create_object(ObjKey{}, {{col_int, int(Mon)}, {col_str, "jeff"}}).get_key();
2✔
1387
    ObjKey k1 = table.create_object(ObjKey{}, {{col_str, "jim"}, {col_int, int(Tue)}}).get_key();
2✔
1388
    table.create_object().set_all(int(Wed), "jennifer");
2✔
1389
    table.create_object().set_all(int(Thu), "john");
2✔
1390
    table.create_object().set_all(int(Fri), "jimmy");
2✔
1391
    ObjKey k5 = table.create_object().set_all(int(Sat), "jimbo").get_key();
2✔
1392
    // Use a key where the first has the the second most significant bit set.
1393
    // When this is shifted up and down again, the most significant bit must
1394
    // still be 0.
1395
    ObjKey k6 = table.create_object(ObjKey(1LL << 62)).set_all(int(Sun), "johnny").get_key();
2✔
1396
    table.create_object().set_all(int(Mon), "jennifer"); // duplicate
2✔
1397

1398
    ObjKey r1 = table.find_first_string(col_str, "jimmi");
2✔
1399
    CHECK_EQUAL(null_key, r1);
2✔
1400

1401
    ObjKey r2 = table.find_first_string(col_str, "jeff");
2✔
1402
    ObjKey r3 = table.find_first_string(col_str, "jim");
2✔
1403
    ObjKey r4 = table.find_first_string(col_str, "jimbo");
2✔
1404
    ObjKey r5 = table.find_first_string(col_str, "johnny");
2✔
1405
    CHECK_EQUAL(k0, r2);
2✔
1406
    CHECK_EQUAL(k1, r3);
2✔
1407
    CHECK_EQUAL(k5, r4);
2✔
1408
    CHECK_EQUAL(k6, r5);
2✔
1409

1410
    const size_t c1 = table.count_string(col_str, "jennifer");
2✔
1411
    CHECK_EQUAL(2, c1);
2✔
1412
}
2✔
1413

1414

1415
TEST(Table_IndexStringTwice)
1416
{
2✔
1417
    Table table;
2✔
1418
    table.add_column(type_Int, "first");
2✔
1419
    auto col_str = table.add_column(type_String, "second");
2✔
1420

1421
    table.create_object().set_all(int(Mon), "jeff");
2✔
1422
    table.create_object().set_all(int(Tue), "jim");
2✔
1423
    table.create_object().set_all(int(Wed), "jennifer");
2✔
1424
    table.create_object().set_all(int(Thu), "john");
2✔
1425
    table.create_object().set_all(int(Fri), "jimmy");
2✔
1426
    table.create_object().set_all(int(Sat), "jimbo");
2✔
1427
    table.create_object().set_all(int(Sun), "johnny");
2✔
1428
    table.create_object().set_all(int(Mon), "jennifer"); // duplicate
2✔
1429

1430
    table.add_search_index(col_str);
2✔
1431
    CHECK_EQUAL(true, table.has_search_index(col_str));
2✔
1432
    table.add_search_index(col_str);
2✔
1433
    CHECK_EQUAL(true, table.has_search_index(col_str));
2✔
1434
}
2✔
1435

1436

1437
// Tests Table part of index on Int, OldDateTime and Bool columns. For a more exhaustive
1438
// test of the integer index (bypassing Table), see test_index_string.cpp)
1439
TEST(Table_IndexInteger)
1440
{
2✔
1441
    Table table;
2✔
1442
    ObjKey k;
2✔
1443

1444
    auto col_int = table.add_column(type_Int, "ints");
2✔
1445
    auto col_date = table.add_column(type_Timestamp, "date");
2✔
1446
    auto col_bool = table.add_column(type_Bool, "booleans");
2✔
1447

1448
    std::vector<ObjKey> keys;
2✔
1449
    table.create_objects(13, keys);
2✔
1450

1451
    table.get_object(keys[0]).set(col_int, 3);  // 0
2✔
1452
    table.get_object(keys[1]).set(col_int, 1);  // 1
2✔
1453
    table.get_object(keys[2]).set(col_int, 2);  // 2
2✔
1454
    table.get_object(keys[3]).set(col_int, 2);  // 3
2✔
1455
    table.get_object(keys[4]).set(col_int, 2);  // 4
2✔
1456
    table.get_object(keys[5]).set(col_int, 3);  // 5
2✔
1457
    table.get_object(keys[6]).set(col_int, 3);  // 6
2✔
1458
    table.get_object(keys[7]).set(col_int, 2);  // 7
2✔
1459
    table.get_object(keys[8]).set(col_int, 4);  // 8
2✔
1460
    table.get_object(keys[9]).set(col_int, 2);  // 9
2✔
1461
    table.get_object(keys[10]).set(col_int, 6); // 10
2✔
1462
    table.get_object(keys[11]).set(col_int, 2); // 11
2✔
1463
    table.get_object(keys[12]).set(col_int, 3); // 12
2✔
1464

1465
    table.add_search_index(col_int);
2✔
1466
    CHECK(table.has_search_index(col_int));
2✔
1467
    table.add_search_index(col_date);
2✔
1468
    CHECK(table.has_search_index(col_date));
2✔
1469
    table.add_search_index(col_bool);
2✔
1470
    CHECK(table.has_search_index(col_bool));
2✔
1471

1472
    table.get_object(keys[10]).set(col_date, Timestamp(43, 0));
2✔
1473
    k = table.find_first_timestamp(col_date, Timestamp(43, 0));
2✔
1474
    CHECK_EQUAL(keys[10], k);
2✔
1475

1476
    table.get_object(keys[11]).set(col_bool, true);
2✔
1477
    k = table.find_first_bool(col_bool, true);
2✔
1478
    CHECK_EQUAL(keys[11], k);
2✔
1479

1480
    k = table.find_first_int(col_int, 11);
2✔
1481
    CHECK_EQUAL(null_key, k);
2✔
1482

1483
    k = table.find_first_int(col_int, 3);
2✔
1484
    CHECK_EQUAL(keys[0], k);
2✔
1485

1486
    k = table.find_first_int(col_int, 4);
2✔
1487
    CHECK_EQUAL(keys[8], k);
2✔
1488

1489
    TableView tv = table.find_all_int(col_int, 2);
2✔
1490
    CHECK_EQUAL(6, tv.size());
2✔
1491

1492
    CHECK_EQUAL(keys[2], tv[0].get_key());
2✔
1493
    CHECK_EQUAL(keys[3], tv[1].get_key());
2✔
1494
    CHECK_EQUAL(keys[4], tv[2].get_key());
2✔
1495
    CHECK_EQUAL(keys[7], tv[3].get_key());
2✔
1496
    CHECK_EQUAL(keys[9], tv[4].get_key());
2✔
1497
    CHECK_EQUAL(keys[11], tv[5].get_key());
2✔
1498
}
2✔
1499

1500

1501
TEST(Table_AddInt)
1502
{
2✔
1503
    Table t;
2✔
1504
    auto col_int = t.add_column(type_Int, "i");
2✔
1505
    auto col_int_null = t.add_column(type_Int, "ni", /*nullable*/ true);
2✔
1506
    auto col_mixed = t.add_column(type_Mixed, "m");
2✔
1507
    Obj obj = t.create_object();
2✔
1508

1509
    obj.set(col_mixed, Mixed(5));
2✔
1510

1511
    obj.add_int(col_int, 1);
2✔
1512
    CHECK_EQUAL(obj.get<Int>(col_int), 1);
2✔
1513

1514
    // Check that signed integers wrap around. This invariant is necessary for
1515
    // full commutativity.
1516
    obj.add_int(col_int, Table::max_integer);
2✔
1517
    CHECK_EQUAL(obj.get<Int>(col_int), Table::min_integer);
2✔
1518
    obj.add_int(col_int, -1);
2✔
1519
    CHECK_EQUAL(obj.get<Int>(col_int), Table::max_integer);
2✔
1520

1521
    // add_int() has no effect on a NULL
1522
    CHECK(obj.is_null(col_int_null));
2✔
1523
    CHECK_LOGIC_ERROR(obj.add_int(col_int_null, 123), ErrorCodes::IllegalOperation);
2✔
1524

1525
    obj.add_int(col_mixed, 1);
2✔
1526
    CHECK_EQUAL(obj.get_any(col_mixed).get_int(), 6);
2✔
1527
    obj.set(col_mixed, Mixed("Foo"));
2✔
1528
    CHECK_LOGIC_ERROR(obj.add_int(col_mixed, 123), ErrorCodes::IllegalOperation);
2✔
1529
}
2✔
1530

1531
TEST(Table_AddIntIndexed)
1532
{
2✔
1533
    Table table;
2✔
1534
    auto col = table.add_column(DataType(0), "int_1", false);
2✔
1535
    Obj obj = table.create_object();
2✔
1536
    table.add_search_index(col);
2✔
1537
    obj.add_int(col, 8463800223514590069);
2✔
1538
    obj.remove();
2✔
1539
}
2✔
1540

1541
TEST(Table_IndexInt)
1542
{
2✔
1543
    Table table;
2✔
1544
    auto col = table.add_column(type_Int, "first");
2✔
1545

1546
    ObjKey k0 = table.create_object().set(col, 1).get_key();
2✔
1547
    ObjKey k1 = table.create_object().set(col, 15).get_key();
2✔
1548
    ObjKey k2 = table.create_object().set(col, 10).get_key();
2✔
1549
    ObjKey k3 = table.create_object().set(col, 20).get_key();
2✔
1550
    ObjKey k4 = table.create_object().set(col, 11).get_key();
2✔
1551
    ObjKey k5 = table.create_object().set(col, 45).get_key();
2✔
1552
    ObjKey k6 = table.create_object().set(col, 10).get_key();
2✔
1553
    ObjKey k7 = table.create_object().set(col, 0).get_key();
2✔
1554
    ObjKey k8 = table.create_object().set(col, 30).get_key();
2✔
1555
    ObjKey k9 = table.create_object().set(col, 9).get_key();
2✔
1556

1557
    // Create index for column two
1558
    table.add_search_index(col);
2✔
1559

1560
    // Search for a value that does not exits
1561
    ObjKey k = table.find_first_int(col, 2);
2✔
1562
    CHECK_EQUAL(null_key, k);
2✔
1563

1564
    // Find existing values
1565
    CHECK_EQUAL(k0, table.find_first_int(col, 1));
2✔
1566
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1567
    CHECK_EQUAL(k2, table.find_first_int(col, 10));
2✔
1568
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1569
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1570
    CHECK_EQUAL(k5, table.find_first_int(col, 45));
2✔
1571
    // CHECK_EQUAL(6, table.find_first_int(col, 10)); // only finds first match
1572
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1573
    CHECK_EQUAL(k8, table.find_first_int(col, 30));
2✔
1574
    CHECK_EQUAL(k9, table.find_first_int(col, 9));
2✔
1575

1576
    // Change some values
1577
    table.get_object(k2).set(col, 13);
2✔
1578
    table.get_object(k9).set(col, 100);
2✔
1579

1580
    CHECK_EQUAL(k0, table.find_first_int(col, 1));
2✔
1581
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1582
    CHECK_EQUAL(k2, table.find_first_int(col, 13));
2✔
1583
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1584
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1585
    CHECK_EQUAL(k5, table.find_first_int(col, 45));
2✔
1586
    CHECK_EQUAL(k6, table.find_first_int(col, 10));
2✔
1587
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1588
    CHECK_EQUAL(k8, table.find_first_int(col, 30));
2✔
1589
    CHECK_EQUAL(k9, table.find_first_int(col, 100));
2✔
1590

1591
    // Insert values
1592
    ObjKey k10 = table.create_object().set(col, 29).get_key();
2✔
1593
    // TODO: More than add
1594

1595
    CHECK_EQUAL(k0, table.find_first_int(col, 1));
2✔
1596
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1597
    CHECK_EQUAL(k2, table.find_first_int(col, 13));
2✔
1598
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1599
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1600
    CHECK_EQUAL(k5, table.find_first_int(col, 45));
2✔
1601
    CHECK_EQUAL(k6, table.find_first_int(col, 10));
2✔
1602
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1603
    CHECK_EQUAL(k8, table.find_first_int(col, 30));
2✔
1604
    CHECK_EQUAL(k9, table.find_first_int(col, 100));
2✔
1605
    CHECK_EQUAL(k10, table.find_first_int(col, 29));
2✔
1606

1607
    // Delete some values
1608
    table.remove_object(k0);
2✔
1609
    table.remove_object(k5);
2✔
1610
    table.remove_object(k8);
2✔
1611

1612
    CHECK_EQUAL(null_key, table.find_first_int(col, 1));
2✔
1613
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1614
    CHECK_EQUAL(k2, table.find_first_int(col, 13));
2✔
1615
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1616
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1617
    CHECK_EQUAL(null_key, table.find_first_int(col, 45));
2✔
1618
    CHECK_EQUAL(k6, table.find_first_int(col, 10));
2✔
1619
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1620
    CHECK_EQUAL(null_key, table.find_first_int(col, 30));
2✔
1621
    CHECK_EQUAL(k9, table.find_first_int(col, 100));
2✔
1622
    CHECK_EQUAL(k10, table.find_first_int(col, 29));
2✔
1623

1624
#ifdef REALM_DEBUG
2✔
1625
    table.verify();
2✔
1626
#endif
2✔
1627
}
2✔
1628

1629
TEST(Table_AutoEnumeration)
1630
{
2✔
1631
    Table table;
2✔
1632

1633
    auto col_int = table.add_column(type_Int, "first");
2✔
1634
    auto col_str = table.add_column(type_String, "second");
2✔
1635

1636
    for (size_t i = 0; i < 5; ++i) {
12✔
1637
        table.create_object().set_all(1, "abd");
10✔
1638
        table.create_object().set_all(2, "eftg");
10✔
1639
        table.create_object().set_all(5, "hijkl");
10✔
1640
        table.create_object().set_all(8, "mnopqr");
10✔
1641
        table.create_object().set_all(9, "stuvxyz");
10✔
1642
    }
10✔
1643

1644
    table.enumerate_string_column(col_str);
2✔
1645

1646
    for (size_t i = 0; i < 5; ++i) {
12✔
1647
        const size_t n = i * 5;
10✔
1648
        CHECK_EQUAL(1, table.get_object(ObjKey(0 + n)).get<Int>(col_int));
10✔
1649
        CHECK_EQUAL(2, table.get_object(ObjKey(1 + n)).get<Int>(col_int));
10✔
1650
        CHECK_EQUAL(5, table.get_object(ObjKey(2 + n)).get<Int>(col_int));
10✔
1651
        CHECK_EQUAL(8, table.get_object(ObjKey(3 + n)).get<Int>(col_int));
10✔
1652
        CHECK_EQUAL(9, table.get_object(ObjKey(4 + n)).get<Int>(col_int));
10✔
1653

1654
        CHECK_EQUAL("abd", table.get_object(ObjKey(0 + n)).get<String>(col_str));
10✔
1655
        CHECK_EQUAL("eftg", table.get_object(ObjKey(1 + n)).get<String>(col_str));
10✔
1656
        CHECK_EQUAL("hijkl", table.get_object(ObjKey(2 + n)).get<String>(col_str));
10✔
1657
        CHECK_EQUAL("mnopqr", table.get_object(ObjKey(3 + n)).get<String>(col_str));
10✔
1658
        CHECK_EQUAL("stuvxyz", table.get_object(ObjKey(4 + n)).get<String>(col_str));
10✔
1659
    }
10✔
1660

1661
    // Verify counts
1662
    const size_t count1 = table.count_string(col_str, "abd");
2✔
1663
    const size_t count2 = table.count_string(col_str, "eftg");
2✔
1664
    const size_t count3 = table.count_string(col_str, "hijkl");
2✔
1665
    const size_t count4 = table.count_string(col_str, "mnopqr");
2✔
1666
    const size_t count5 = table.count_string(col_str, "stuvxyz");
2✔
1667
    CHECK_EQUAL(5, count1);
2✔
1668
    CHECK_EQUAL(5, count2);
2✔
1669
    CHECK_EQUAL(5, count3);
2✔
1670
    CHECK_EQUAL(5, count4);
2✔
1671
    CHECK_EQUAL(5, count5);
2✔
1672

1673
    ObjKey t = table.find_first_string(col_str, "eftg");
2✔
1674
    CHECK_EQUAL(ObjKey(1), t);
2✔
1675

1676
    auto tv = table.find_all_string(col_str, "eftg");
2✔
1677
    CHECK_EQUAL(5, tv.size());
2✔
1678
    CHECK_EQUAL("eftg", tv.get_object(0).get<String>(col_str));
2✔
1679
    CHECK_EQUAL("eftg", tv.get_object(1).get<String>(col_str));
2✔
1680
    CHECK_EQUAL("eftg", tv.get_object(2).get<String>(col_str));
2✔
1681
    CHECK_EQUAL("eftg", tv.get_object(3).get<String>(col_str));
2✔
1682
    CHECK_EQUAL("eftg", tv.get_object(4).get<String>(col_str));
2✔
1683

1684
    Obj obj = table.create_object();
2✔
1685
    CHECK_EQUAL(0, obj.get<Int>(col_int));
2✔
1686
    CHECK_EQUAL("", obj.get<String>(col_str));
2✔
1687
}
2✔
1688

1689

1690
TEST(Table_AutoEnumerationOptimize)
1691
{
2✔
1692
    Table t;
2✔
1693
    auto col0 = t.add_column(type_String, "col1");
2✔
1694
    auto col1 = t.add_column(type_String, "col2");
2✔
1695
    auto col2 = t.add_column(type_String, "col3");
2✔
1696
    auto col3 = t.add_column(type_String, "col4");
2✔
1697

1698
    // Insert non-optimizable strings
1699
    std::string s;
2✔
1700
    std::vector<ObjKey> keys;
2✔
1701
    t.create_objects(10, keys);
2✔
1702
    for (Obj o : t) {
20✔
1703
        o.set_all(s.c_str(), s.c_str(), s.c_str(), s.c_str());
20✔
1704
        s += "x";
20✔
1705
    }
20✔
1706

1707
    // AutoEnumerate in reverse order
1708
    for (Obj o : t) {
20✔
1709
        o.set(col3, "test");
20✔
1710
    }
20✔
1711
    t.enumerate_string_column(col3);
2✔
1712
    for (Obj o : t) {
20✔
1713
        o.set(col2, "test");
20✔
1714
    }
20✔
1715
    t.enumerate_string_column(col2);
2✔
1716
    for (Obj o : t) {
20✔
1717
        o.set(col1, "test");
20✔
1718
    }
20✔
1719
    t.enumerate_string_column(col1);
2✔
1720
    for (Obj o : t) {
20✔
1721
        o.set(col0, "test");
20✔
1722
    }
20✔
1723
    t.enumerate_string_column(col0);
2✔
1724

1725
    for (Obj o : t) {
20✔
1726
        CHECK_EQUAL("test", o.get<String>(col0));
20✔
1727
        CHECK_EQUAL("test", o.get<String>(col1));
20✔
1728
        CHECK_EQUAL("test", o.get<String>(col2));
20✔
1729
        CHECK_EQUAL("test", o.get<String>(col3));
20✔
1730
    }
20✔
1731

1732
#ifdef REALM_DEBUG
2✔
1733
    t.verify();
2✔
1734
#endif
2✔
1735
}
2✔
1736

1737
TEST(Table_OptimizeCompare)
1738
{
2✔
1739
    Table t1, t2;
2✔
1740
    auto col_t1 = t1.add_column(type_String, "str");
2✔
1741
    auto col_t2 = t2.add_column(type_String, "str");
2✔
1742

1743
    std::vector<ObjKey> keys_t1;
2✔
1744
    std::vector<ObjKey> keys_t2;
2✔
1745
    t1.create_objects(100, keys_t1);
2✔
1746
    for (Obj o : t1) {
200✔
1747
        o.set(col_t1, "foo");
200✔
1748
    }
200✔
1749
    t2.create_objects(100, keys_t2);
2✔
1750
    for (Obj o : t2) {
200✔
1751
        o.set(col_t2, "foo");
200✔
1752
    }
200✔
1753
    t1.enumerate_string_column(col_t1);
2✔
1754
    CHECK(t1 == t2);
2✔
1755
    Obj obj1 = t1.get_object(keys_t1[50]);
2✔
1756
    Obj obj2 = t2.get_object(keys_t2[50]);
2✔
1757
    obj1.set(col_t1, "bar");
2✔
1758
    CHECK(t1 != t2);
2✔
1759
    obj1.set(col_t1, "foo");
2✔
1760
    CHECK(t1 == t2);
2✔
1761
    obj2.set(col_t2, "bar");
2✔
1762
    CHECK(t1 != t2);
2✔
1763
    obj2.set(col_t2, "foo");
2✔
1764
    CHECK(t1 == t2);
2✔
1765
}
2✔
1766

1767

1768
TEST(Table_SlabAlloc)
1769
{
2✔
1770
    SlabAlloc alloc;
2✔
1771
    alloc.attach_empty();
2✔
1772
    Table table(alloc);
2✔
1773

1774
    auto col_int0 = table.add_column(type_Int, "int0");
2✔
1775
    auto col_int1 = table.add_column(type_Int, "int1");
2✔
1776
    auto col_bool = table.add_column(type_Bool, "bool");
2✔
1777
    auto col_int2 = table.add_column(type_Int, "int2");
2✔
1778

1779
    Obj obj = table.create_object().set_all(0, 10, true, int(Wed));
2✔
1780
    CHECK_EQUAL(0, obj.get<Int>(col_int0));
2✔
1781
    CHECK_EQUAL(10, obj.get<Int>(col_int1));
2✔
1782
    CHECK_EQUAL(true, obj.get<Bool>(col_bool));
2✔
1783
    CHECK_EQUAL(Wed, obj.get<Int>(col_int2));
2✔
1784

1785
    // Add some more rows
1786
    table.create_object().set_all(1, 10, true, int(Wed));
2✔
1787
    ObjKey k0 = table.create_object().set_all(2, 20, true, int(Wed)).get_key();
2✔
1788
    table.create_object().set_all(3, 10, true, int(Wed));
2✔
1789
    ObjKey k1 = table.create_object().set_all(4, 20, true, int(Wed)).get_key();
2✔
1790
    table.create_object().set_all(5, 10, true, int(Wed));
2✔
1791

1792
    // Delete some rows
1793
    table.remove_object(k0);
2✔
1794
    table.remove_object(k1);
2✔
1795

1796
#ifdef REALM_DEBUG
2✔
1797
    table.verify();
2✔
1798
#endif
2✔
1799
}
2✔
1800

1801
TEST(Table_NullInEnum)
1802
{
2✔
1803
    Group group;
2✔
1804
    TableRef table = group.add_table("test");
2✔
1805
    auto col = table->add_column(type_String, "second", true);
2✔
1806

1807
    for (size_t c = 0; c < 100; c++) {
202✔
1808
        table->create_object().set(col, "hello");
200✔
1809
    }
200✔
1810

1811
    size_t r;
2✔
1812

1813
    r = table->where().equal(col, "hello").count();
2✔
1814
    CHECK_EQUAL(100, r);
2✔
1815

1816
    Obj obj50 = table->get_object(ObjKey(50));
2✔
1817
    obj50.set<String>(col, realm::null());
2✔
1818
    r = table->where().equal(col, "hello").count();
2✔
1819
    CHECK_EQUAL(99, r);
2✔
1820

1821
    table->enumerate_string_column(col);
2✔
1822

1823
    obj50.set<String>(col, realm::null());
2✔
1824
    r = table->where().equal(col, "hello").count();
2✔
1825
    CHECK_EQUAL(99, r);
2✔
1826

1827
    obj50.set<String>(col, "hello");
2✔
1828
    r = table->where().equal(col, "hello").count();
2✔
1829
    CHECK_EQUAL(100, r);
2✔
1830

1831
    obj50.set<String>(col, realm::null());
2✔
1832
    r = table->where().equal(col, "hello").count();
2✔
1833
    CHECK_EQUAL(99, r);
2✔
1834

1835
    r = table->where().equal(col, realm::null()).count();
2✔
1836
    CHECK_EQUAL(1, r);
2✔
1837

1838
    table->get_object(ObjKey(55)).set(col, realm::null());
2✔
1839
    r = table->where().equal(col, realm::null()).count();
2✔
1840
    CHECK_EQUAL(2, r);
2✔
1841

1842
    r = table->where().equal(col, "hello").count();
2✔
1843
    CHECK_EQUAL(98, r);
2✔
1844

1845
    table->remove_object(ObjKey(55));
2✔
1846
    r = table->where().equal(col, realm::null()).count();
2✔
1847
    CHECK_EQUAL(1, r);
2✔
1848
}
2✔
1849

1850

1851
TEST(Table_DateAndBinary)
1852
{
2✔
1853
    Table t;
2✔
1854
    auto col_date = t.add_column(type_Timestamp, "date");
2✔
1855
    auto col_bin = t.add_column(type_Binary, "bin");
2✔
1856

1857
    const size_t size = 10;
2✔
1858
    char data[size];
2✔
1859
    for (size_t i = 0; i < size; ++i)
22✔
1860
        data[i] = static_cast<char>(i);
20✔
1861
    t.create_object().set_all(Timestamp(8, 0), BinaryData(data, size));
2✔
1862
    Obj obj = *t.begin();
2✔
1863
    CHECK_EQUAL(obj.get<Timestamp>(col_date), Timestamp(8, 0));
2✔
1864
    BinaryData bin = obj.get<Binary>(col_bin);
2✔
1865
    CHECK_EQUAL(bin.size(), size);
2✔
1866
    CHECK(std::equal(bin.data(), bin.data() + size, data));
2✔
1867

1868
    // Test that 64-bit dates are preserved
1869
    Timestamp date(std::numeric_limits<int64_t>::max() - 400, 0);
2✔
1870
    obj.set(col_date, date);
2✔
1871
    CHECK_EQUAL(obj.get<Timestamp>(col_date), date);
2✔
1872
}
2✔
1873

1874
#if TEST_DURATION > 0
1875
#define TBL_SIZE REALM_MAX_BPNODE_SIZE * 10
1876
#else
1877
#define TBL_SIZE 10
24✔
1878
#endif // TEST_DURATION
1879

1880
TEST(Table_Aggregates)
1881
{
2✔
1882
    Table table;
2✔
1883
    auto int_col = table.add_column(type_Int, "c_int");
2✔
1884
    auto float_col = table.add_column(type_Float, "c_float");
2✔
1885
    auto double_col = table.add_column(type_Double, "c_double");
2✔
1886
    auto str_col = table.add_column(type_String, "c_string");
2✔
1887
    auto decimal_col = table.add_column(type_Decimal, "c_decimal");
2✔
1888
    int64_t i_sum = 0;
2✔
1889
    double f_sum = 0;
2✔
1890
    double d_sum = 0;
2✔
1891
    Decimal128 decimal_sum(0);
2✔
1892

1893
    for (int i = 0; i < TBL_SIZE; i++) {
22✔
1894
        table.create_object().set_all(5987654, 4.0f, 3.0, "Hello", Decimal128(7.7));
20✔
1895
        i_sum += 5987654;
20✔
1896
        f_sum += 4.0f;
20✔
1897
        d_sum += 3.0;
20✔
1898
        decimal_sum += Decimal128(7.7);
20✔
1899
    }
20✔
1900
    table.create_object().set_all(1, 1.1f, 1.2, "Hi", Decimal128(8.9));
2✔
1901
    table.create_object().set_all(987654321, 11.0f, 12.0, "Goodbye", Decimal128(10.1));
2✔
1902
    table.create_object().set_all(5, 4.0f, 3.0, "Hey", Decimal128("1.12e23"));
2✔
1903
    i_sum += 1 + 987654321 + 5;
2✔
1904
    f_sum += double(1.1f) + double(11.0f) + double(4.0f);
2✔
1905
    d_sum += 1.2 + 12.0 + 3.0;
2✔
1906
    decimal_sum += Decimal128(8.9) + Decimal128(10.1) + Decimal128("1.12e23");
2✔
1907
    double size = TBL_SIZE + 3;
2✔
1908

1909
    double epsilon = std::numeric_limits<double>::epsilon();
2✔
1910

1911
    // count
1912
    CHECK_EQUAL(1, table.count_int(int_col, 987654321));
2✔
1913
    CHECK_EQUAL(1, table.count_float(float_col, 11.0f));
2✔
1914
    CHECK_EQUAL(1, table.count_double(double_col, 12.0));
2✔
1915
    CHECK_EQUAL(1, table.count_string(str_col, "Goodbye"));
2✔
1916
    CHECK_EQUAL(1, table.count_decimal(decimal_col, Decimal128("1.12e23")));
2✔
1917
    ObjKey ret;
2✔
1918
    // minimum
1919
    CHECK_EQUAL(1, table.min(int_col, &ret)->get_int());
2✔
1920
    CHECK(ret && table.get_object(ret).get<Int>(int_col) == 1);
2✔
1921
    ret = ObjKey();
2✔
1922
    CHECK_EQUAL(1.1f, table.min(float_col, &ret)->get_float());
2✔
1923
    CHECK(ret);
2✔
1924
    CHECK_EQUAL(table.get_object(ret).get<Float>(float_col), 1.1f);
2✔
1925
    ret = ObjKey();
2✔
1926
    CHECK_EQUAL(1.2, table.min(double_col, &ret)->get_double());
2✔
1927
    CHECK(ret);
2✔
1928
    CHECK_EQUAL(table.get_object(ret).get<Double>(double_col), 1.2);
2✔
1929
    ret = ObjKey();
2✔
1930
    CHECK_EQUAL(Decimal128(7.7), table.min(decimal_col, &ret)->get_decimal());
2✔
1931
    CHECK(ret);
2✔
1932
    CHECK_EQUAL(table.get_object(ret).get<Decimal128>(decimal_col), Decimal128(7.7));
2✔
1933

1934
    // maximum
1935
    ret = ObjKey();
2✔
1936
    CHECK_EQUAL(987654321, table.max(int_col, &ret)->get_int());
2✔
1937
    CHECK(ret);
2✔
1938
    CHECK_EQUAL(table.get_object(ret).get<Int>(int_col), 987654321);
2✔
1939
    ret = ObjKey();
2✔
1940
    CHECK_EQUAL(11.0f, table.max(float_col, &ret)->get_float());
2✔
1941
    CHECK(ret);
2✔
1942
    CHECK_EQUAL(11.0f, table.get_object(ret).get<Float>(float_col));
2✔
1943
    ret = ObjKey();
2✔
1944
    CHECK_EQUAL(12.0, table.max(double_col, &ret)->get_double());
2✔
1945
    CHECK(ret);
2✔
1946
    CHECK_EQUAL(12.0, table.get_object(ret).get<Double>(double_col));
2✔
1947
    ret = ObjKey();
2✔
1948
    CHECK_EQUAL(Decimal128("1.12e23"), table.max(decimal_col, &ret)->get_decimal());
2✔
1949
    CHECK(ret);
2✔
1950
    CHECK_EQUAL(Decimal128("1.12e23"), table.get_object(ret).get<Decimal128>(decimal_col));
2✔
1951
    // sum
1952
    CHECK_APPROXIMATELY_EQUAL(double(i_sum), double(table.sum(int_col)->get_int()), 10 * epsilon);
2✔
1953
    CHECK_APPROXIMATELY_EQUAL(f_sum, table.sum(float_col)->get_double(), 10 * epsilon);
2✔
1954
    CHECK_APPROXIMATELY_EQUAL(d_sum, table.sum(double_col)->get_double(), 10 * epsilon);
2✔
1955
    CHECK_EQUAL(decimal_sum, table.sum(decimal_col)->get_decimal());
2✔
1956
    // average
1957
    size_t count = realm::npos;
2✔
1958
    CHECK_APPROXIMATELY_EQUAL(i_sum / size, table.avg(int_col, &count)->get_double(), 10 * epsilon);
2✔
1959
    CHECK_EQUAL(count, size);
2✔
1960
    count = realm::npos;
2✔
1961
    CHECK_APPROXIMATELY_EQUAL(f_sum / size, table.avg(float_col, &count)->get_double(), 10 * epsilon);
2✔
1962
    CHECK_EQUAL(count, size);
2✔
1963
    count = realm::npos;
2✔
1964
    CHECK_APPROXIMATELY_EQUAL(d_sum / size, table.avg(double_col, &count)->get_double(), 10 * epsilon);
2✔
1965
    CHECK_EQUAL(count, size);
2✔
1966
    count = realm::npos;
2✔
1967
    CHECK_EQUAL(decimal_sum / Decimal128(size), table.avg(decimal_col, &count)->get_decimal());
2✔
1968
    CHECK_EQUAL(count, size);
2✔
1969
}
2✔
1970

1971
TEST(Table_Aggregates2)
1972
{
2✔
1973
    Table table;
2✔
1974
    auto int_col = table.add_column(type_Int, "c_count");
2✔
1975
    int c = -420;
2✔
1976
    int s = 0;
2✔
1977
    while (c < -20) {
802✔
1978
        table.create_object().set(int_col, c);
800✔
1979
        s += c;
800✔
1980
        c++;
800✔
1981
    }
800✔
1982

1983
    CHECK_EQUAL(-420, table.min(int_col)->get_int());
2✔
1984
    CHECK_EQUAL(-21, table.max(int_col)->get_int());
2✔
1985
    CHECK_EQUAL(s, table.sum(int_col)->get_int());
2✔
1986
}
2✔
1987

1988
// Test Table methods max, min, avg, sum, on both nullable and non-nullable columns
1989
TEST(Table_Aggregates3)
1990
{
2✔
1991
    bool nullable = false;
2✔
1992

1993
    for (int i = 0; i < 2; i++) {
6✔
1994
        // First we test everything with columns being nullable and with each column having at least 1 null
1995
        // Then we test everything with non-nullable columns where the null entries will instead be just
1996
        // 0, 0.0, etc.
1997
        nullable = (i == 1);
4✔
1998

1999
        Group g;
4✔
2000
        TableRef table = g.add_table("Inventory");
4✔
2001

2002
        auto col_price = table->add_column(type_Int, "Price", nullable);
4✔
2003
        auto col_shipping = table->add_column(type_Float, "Shipping", nullable);
4✔
2004
        auto col_rating = table->add_column(type_Double, "Rating", nullable);
4✔
2005
        auto col_date = table->add_column(type_Timestamp, "Delivery date", nullable);
4✔
2006

2007
        Obj obj0 = table->create_object(ObjKey(0));
4✔
2008
        Obj obj1 = table->create_object(ObjKey(1));
4✔
2009
        Obj obj2 = table->create_object(ObjKey(2));
4✔
2010

2011
        obj0.set(col_price, 1);
4✔
2012
        // table->set_null(0, 1);
2013
        obj2.set(col_price, 3);
4✔
2014

2015
        // table->set_null(1, 0);
2016
        // table->set_null(1, 1);
2017
        obj2.set(col_shipping, 30.f);
4✔
2018

2019
        obj0.set(col_rating, 1.1);
4✔
2020
        obj1.set(col_rating, 2.2);
4✔
2021
        // table->set_null(2, 2);
2022

2023
        obj0.set(col_date, Timestamp(2, 2));
4✔
2024
        // table->set_null(4, 1);
2025
        obj2.set(col_date, Timestamp(6, 6));
4✔
2026

2027
        size_t count;
4✔
2028
        ObjKey pos;
4✔
2029
        if (nullable) {
4✔
2030
            // max
2031
            pos = ObjKey(123);
2✔
2032
            CHECK_EQUAL(table->max(col_price)->get_int(), 3);
2✔
2033
            CHECK_EQUAL(table->max(col_price, &pos)->get_int(), 3);
2✔
2034
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2035

2036
            pos = ObjKey(123);
2✔
2037
            CHECK_EQUAL(table->max(col_shipping)->get_float(), 30.f);
2✔
2038
            CHECK_EQUAL(table->max(col_shipping, &pos)->get_float(), 30.f);
2✔
2039
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2040

2041
            pos = ObjKey(123);
2✔
2042
            CHECK_EQUAL(table->max(col_rating)->get_double(), 2.2);
2✔
2043
            CHECK_EQUAL(table->max(col_rating, &pos)->get_double(), 2.2);
2✔
2044
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2045

2046
            pos = ObjKey(123);
2✔
2047
            CHECK_EQUAL(table->max(col_date)->get_timestamp(), Timestamp(6, 6));
2✔
2048
            CHECK_EQUAL(table->max(col_date, &pos)->get_timestamp(), Timestamp(6, 6));
2✔
2049
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2050

2051
            // min
2052
            pos = ObjKey(123);
2✔
2053
            CHECK_EQUAL(table->min(col_price)->get_int(), 1);
2✔
2054
            CHECK_EQUAL(table->min(col_price, &pos)->get_int(), 1);
2✔
2055
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2056

2057
            pos = ObjKey(123);
2✔
2058
            CHECK_EQUAL(table->min(col_shipping)->get_float(), 30.f);
2✔
2059
            CHECK_EQUAL(table->min(col_shipping, &pos)->get_float(), 30.f);
2✔
2060
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2061

2062
            pos = ObjKey(123);
2✔
2063
            CHECK_EQUAL(table->min(col_rating)->get_double(), 1.1);
2✔
2064
            CHECK_EQUAL(table->min(col_rating, &pos)->get_double(), 1.1);
2✔
2065
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2066

2067
            pos = ObjKey(123);
2✔
2068
            CHECK_EQUAL(table->min(col_date)->get_timestamp(), Timestamp(2, 2));
2✔
2069
            CHECK_EQUAL(table->min(col_date, &pos)->get_timestamp(), Timestamp(2, 2));
2✔
2070
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2071

2072
            // average
2073
            count = 123;
2✔
2074
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_price)->get_double(), (1 + 3) / 2., 0.01);
2✔
2075
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_price, &count)->get_double(), (1 + 3) / 2., 0.01);
2✔
2076
            CHECK_EQUAL(count, 2);
2✔
2077

2078
            count = 123;
2✔
2079
            CHECK_EQUAL(table->avg(col_shipping)->get_double(), 30.f);
2✔
2080
            CHECK_EQUAL(table->avg(col_shipping, &count)->get_double(), 30.f);
2✔
2081
            CHECK_EQUAL(count, 1);
2✔
2082

2083
            count = 123;
2✔
2084
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_rating)->get_double(), (1.1 + 2.2) / 2., 0.01);
2✔
2085
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_rating, &count)->get_double(), (1.1 + 2.2) / 2., 0.01);
2✔
2086
            CHECK_EQUAL(count, 2);
2✔
2087

2088
            // sum
2089
            CHECK_EQUAL(table->sum(col_price)->get_int(), 4);
2✔
2090
            CHECK_EQUAL(table->sum(col_shipping)->get_double(), 30.f);
2✔
2091
            CHECK_APPROXIMATELY_EQUAL(table->sum(col_rating)->get_double(), 1.1 + 2.2, 0.01);
2✔
2092
        }
2✔
2093
        else { // not nullable
2✔
2094
            // max
2095
            pos = ObjKey(123);
2✔
2096
            CHECK_EQUAL(table->max(col_price, &pos)->get_int(), 3);
2✔
2097
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2098

2099
            pos = ObjKey(123);
2✔
2100
            CHECK_EQUAL(table->max(col_shipping, &pos)->get_float(), 30.f);
2✔
2101
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2102

2103
            pos = ObjKey(123);
2✔
2104
            CHECK_EQUAL(table->max(col_rating, &pos)->get_double(), 2.2);
2✔
2105
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2106

2107
            pos = ObjKey(123);
2✔
2108
            CHECK_EQUAL(table->max(col_date, &pos)->get_timestamp(), Timestamp(6, 6));
2✔
2109
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2110

2111
            // min
2112
            pos = ObjKey(123);
2✔
2113
            CHECK_EQUAL(table->min(col_price, &pos)->get_int(), 0);
2✔
2114
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2115

2116
            pos = ObjKey(123);
2✔
2117
            CHECK_EQUAL(table->min(col_shipping, &pos)->get_float(), 0.f);
2✔
2118
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2119

2120
            pos = ObjKey(123);
2✔
2121
            CHECK_EQUAL(table->min(col_rating, &pos)->get_double(), 0.);
2✔
2122
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2123

2124
            pos = ObjKey(123);
2✔
2125
            // Timestamp(0, 0) is default value for non-nullable column
2126
            CHECK_EQUAL(table->min(col_date, &pos)->get_timestamp(), Timestamp(0, 0));
2✔
2127
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2128

2129
            // average
2130
            count = 123;
2✔
2131
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_price, &count)->get_double(), (1 + 3 + 0) / 3., 0.01);
2✔
2132
            CHECK_EQUAL(count, 3);
2✔
2133

2134
            count = 123;
2✔
2135
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_shipping, &count)->get_double(), 30.f / 3., 0.01);
2✔
2136
            CHECK_EQUAL(count, 3);
2✔
2137

2138
            count = 123;
2✔
2139
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_rating, &count)->get_double(), (1.1 + 2.2 + 0.) / 3., 0.01);
2✔
2140
            CHECK_EQUAL(count, 3);
2✔
2141

2142
            // sum
2143
            CHECK_EQUAL(table->sum(col_price)->get_int(), 4);
2✔
2144
            CHECK_EQUAL(table->sum(col_shipping)->get_double(), 30.f);
2✔
2145
            CHECK_APPROXIMATELY_EQUAL(table->sum(col_rating)->get_double(), 1.1 + 2.2, 0.01);
2✔
2146
        }
2✔
2147
    }
4✔
2148
}
2✔
2149

2150
TEST(Table_EmptyMinmax)
2151
{
2✔
2152
    Group g;
2✔
2153
    TableRef table = g.add_table("");
2✔
2154
    auto col = table->add_column(type_Timestamp, "date");
2✔
2155

2156
    ObjKey min_key;
2✔
2157
    bool is_null = table->min(col, &min_key)->is_null();
2✔
2158
    CHECK_EQUAL(min_key, null_key);
2✔
2159
    CHECK(is_null);
2✔
2160

2161
    ObjKey max_key;
2✔
2162
    is_null = table->max(col, &max_key)->is_null();
2✔
2163
    CHECK_EQUAL(max_key, null_key);
2✔
2164
    CHECK(is_null);
2✔
2165
}
2✔
2166

2167
TEST(Table_EnumStringInsertEmptyRow)
2168
{
2✔
2169
    Table table;
2✔
2170
    auto col_str = table.add_column(type_String, "strings");
2✔
2171
    for (int i = 0; i < 128; ++i)
258✔
2172
        table.create_object().set(col_str, "foo");
256✔
2173

2174
    CHECK_EQUAL(0, table.get_num_unique_values(col_str));
2✔
2175
    table.enumerate_string_column(col_str);
2✔
2176
    // Make sure we now have an enumerated strings column
2177
    CHECK_EQUAL(1, table.get_num_unique_values(col_str));
2✔
2178
    Obj obj = table.create_object();
2✔
2179
    CHECK_EQUAL("", obj.get<String>(col_str));
2✔
2180
    CHECK_EQUAL(2, table.get_num_unique_values(col_str));
2✔
2181
}
2✔
2182

2183
TEST(Table_AddColumnWithThreeLevelBptree)
2184
{
2✔
2185
    Table table;
2✔
2186
    std::vector<ObjKey> keys;
2✔
2187
    table.add_column(type_Int, "int0");
2✔
2188
    table.create_objects(REALM_MAX_BPNODE_SIZE * REALM_MAX_BPNODE_SIZE + 1, keys);
2✔
2189
    table.add_column(type_Int, "int1");
2✔
2190
    table.verify();
2✔
2191
}
2✔
2192

2193
TEST(Table_DeleteObjectsInFirstCluster)
2194
{
2✔
2195
    // Designed to exercise logic if cluster size is 4
2196
    Table table;
2✔
2197
    table.add_column(type_Int, "int0");
2✔
2198

2199
    ObjKeys keys;
2✔
2200
    table.create_objects(32, keys);
2✔
2201

2202
    // delete objects in first cluster
2203
    table.remove_object(keys[2]);
2✔
2204
    table.remove_object(keys[1]);
2✔
2205
    table.remove_object(keys[3]);
2✔
2206
    table.remove_object(keys[0]);
2✔
2207

2208
    table.create_object(ObjKey(1)); // Must not throw
2✔
2209

2210
    // Replace root node
2211
    while (table.size() > 16)
28✔
2212
        table.begin()->remove();
26✔
2213

2214
    // table.dump_objects();
2215
    table.create_object(ObjKey(1)); // Must not throw
2✔
2216
}
2✔
2217

2218
TEST(Table_ClearWithTwoLevelBptree)
2219
{
2✔
2220
    Table table;
2✔
2221
    std::vector<ObjKey> keys;
2✔
2222
    table.add_column(type_String, "strings");
2✔
2223
    table.create_objects(REALM_MAX_BPNODE_SIZE + 1, keys);
2✔
2224
    table.clear();
2✔
2225
    table.verify();
2✔
2226
}
2✔
2227

2228
TEST(Table_IndexStringDelete)
2229
{
2✔
2230
    Table t;
2✔
2231
    auto col = t.add_column(type_String, "str");
2✔
2232
    t.add_search_index(col);
2✔
2233

2234
    for (size_t i = 0; i < 1000; ++i) {
2,002✔
2235
        std::string out(util::to_string(i));
2,000✔
2236
        t.create_object().set<String>(col, out);
2,000✔
2237
    }
2,000✔
2238

2239
    t.clear();
2✔
2240

2241
    for (size_t i = 0; i < 1000; ++i) {
2,002✔
2242
        std::string out(util::to_string(i));
2,000✔
2243
        t.create_object().set<String>(col, out);
2,000✔
2244
    }
2,000✔
2245
}
2✔
2246

2247

2248
TEST(Table_NullableChecks)
2249
{
2✔
2250
    Table t;
2✔
2251
    TableView tv;
2✔
2252
    constexpr bool nullable = true;
2✔
2253
    auto str_col = t.add_column(type_String, "str", nullable);
2✔
2254
    auto int_col = t.add_column(type_Int, "int", nullable);
2✔
2255
    auto bool_col = t.add_column(type_Bool, "bool", nullable);
2✔
2256
    auto ts_col = t.add_column(type_Timestamp, "timestamp", nullable);
2✔
2257
    auto float_col = t.add_column(type_Float, "float", nullable);
2✔
2258
    auto double_col = t.add_column(type_Double, "double", nullable);
2✔
2259
    auto binary_col = t.add_column(type_Binary, "binary", nullable);
2✔
2260

2261
    Obj obj = t.create_object();
2✔
2262
    StringData sd; // construct a null reference
2✔
2263
    Timestamp ts;  // null
2✔
2264
    BinaryData bd; // null
2✔
2265
    obj.set(str_col, sd);
2✔
2266
    obj.set(int_col, realm::null());
2✔
2267
    obj.set(bool_col, realm::null());
2✔
2268
    obj.set(ts_col, ts);
2✔
2269
    obj.set(float_col, realm::null());
2✔
2270
    obj.set(double_col, realm::null());
2✔
2271
    obj.set(binary_col, bd);
2✔
2272

2273
    // is_null is always reliable regardless of type
2274
    CHECK(obj.is_null(str_col));
2✔
2275
    CHECK(obj.is_null(int_col));
2✔
2276
    CHECK(obj.is_null(bool_col));
2✔
2277
    CHECK(obj.is_null(ts_col));
2✔
2278
    CHECK(obj.is_null(float_col));
2✔
2279
    CHECK(obj.is_null(double_col));
2✔
2280
    CHECK(obj.is_null(binary_col));
2✔
2281

2282
    StringData str0 = obj.get<String>(str_col);
2✔
2283
    CHECK(str0.is_null());
2✔
2284
    util::Optional<int64_t> int0 = obj.get<util::Optional<int64_t>>(int_col);
2✔
2285
    CHECK(!int0);
2✔
2286
    util::Optional<bool> bool0 = obj.get<util::Optional<bool>>(bool_col);
2✔
2287
    CHECK(!bool0);
2✔
2288
    Timestamp ts0 = obj.get<Timestamp>(ts_col);
2✔
2289
    CHECK(ts0.is_null());
2✔
2290
    util::Optional<float> float0 = obj.get<util::Optional<float>>(float_col);
2✔
2291
    CHECK(!float0);
2✔
2292
    util::Optional<double> double0 = obj.get<util::Optional<double>>(double_col);
2✔
2293
    CHECK(!double0);
2✔
2294
    BinaryData binary0 = obj.get<Binary>(binary_col);
2✔
2295
    CHECK(binary0.is_null());
2✔
2296
}
2✔
2297

2298

2299
TEST(Table_Nulls)
2300
{
2✔
2301
    // 'round' lets us run this entire test both with and without index and with/without optimize/enum
2302
    for (size_t round = 0; round < 5; round++) {
12✔
2303
        Table t;
10✔
2304
        TableView tv;
10✔
2305
        auto col_str = t.add_column(type_String, "str", true /*nullable*/);
10✔
2306

2307
        if (round == 1)
10✔
2308
            t.add_search_index(col_str);
2✔
2309
        else if (round == 2)
8✔
2310
            t.enumerate_string_column(col_str);
2✔
2311
        else if (round == 3) {
6✔
2312
            t.add_search_index(col_str);
2✔
2313
            t.enumerate_string_column(col_str);
2✔
2314
        }
2✔
2315
        else if (round == 4) {
4✔
2316
            t.enumerate_string_column(col_str);
2✔
2317
            t.add_search_index(col_str);
2✔
2318
        }
2✔
2319

2320
        std::vector<ObjKey> keys;
10✔
2321
        t.create_objects(3, keys);
10✔
2322
        t.get_object(keys[0]).set(col_str, "foo"); // short strings
10✔
2323
        t.get_object(keys[1]).set(col_str, "");
10✔
2324
        t.get_object(keys[2]).set(col_str, StringData()); // null
10✔
2325

2326
        CHECK_EQUAL(1, t.count_string(col_str, "foo"));
10✔
2327
        CHECK_EQUAL(1, t.count_string(col_str, ""));
10✔
2328
        CHECK_EQUAL(1, t.count_string(col_str, realm::null()));
10✔
2329

2330
        CHECK_EQUAL(keys[0], t.find_first_string(col_str, "foo"));
10✔
2331
        CHECK_EQUAL(keys[1], t.find_first_string(col_str, ""));
10✔
2332
        CHECK_EQUAL(keys[2], t.find_first_string(col_str, realm::null()));
10✔
2333

2334
        tv = t.find_all_string(col_str, "foo");
10✔
2335
        CHECK_EQUAL(1, tv.size());
10✔
2336
        CHECK_EQUAL(keys[0], tv.get_key(0));
10✔
2337
        tv = t.find_all_string(col_str, "");
10✔
2338
        CHECK_EQUAL(1, tv.size());
10✔
2339
        CHECK_EQUAL(keys[1], tv.get_key(0));
10✔
2340
        tv = t.find_all_string(col_str, realm::null());
10✔
2341
        CHECK_EQUAL(1, tv.size());
10✔
2342
        CHECK_EQUAL(keys[2], tv.get_key(0));
10✔
2343

2344
        const char* string_medium = "xxxxxxxxxxYYYYYYYYYY";
10✔
2345
        t.get_object(keys[0]).set(col_str, string_medium); // medium strings (< 64)
10✔
2346

2347
        CHECK_EQUAL(1, t.count_string(col_str, string_medium));
10✔
2348
        CHECK_EQUAL(1, t.count_string(col_str, ""));
10✔
2349
        CHECK_EQUAL(1, t.count_string(col_str, realm::null()));
10✔
2350

2351
        CHECK_EQUAL(keys[0], t.find_first_string(col_str, string_medium));
10✔
2352
        CHECK_EQUAL(keys[1], t.find_first_string(col_str, ""));
10✔
2353
        CHECK_EQUAL(keys[2], t.find_first_string(col_str, realm::null()));
10✔
2354

2355
        tv = t.find_all_string(col_str, string_medium);
10✔
2356
        CHECK_EQUAL(1, tv.size());
10✔
2357
        CHECK_EQUAL(keys[0], tv.get_key(0));
10✔
2358
        tv = t.find_all_string(col_str, "");
10✔
2359
        CHECK_EQUAL(1, tv.size());
10✔
2360
        CHECK_EQUAL(keys[1], tv.get_key(0));
10✔
2361
        tv = t.find_all_string(col_str, realm::null());
10✔
2362
        CHECK_EQUAL(1, tv.size());
10✔
2363
        CHECK_EQUAL(keys[2], tv.get_key(0));
10✔
2364

2365

2366
        // long strings (>= 64)
2367
        const char* string_long = "xxxxxxxxxxYYYYYYYYYYxxxxxxxxxxYYYYYYYYYYxxxxxxxxxxYYYYYYYYYYxxxxxxxxxx";
10✔
2368
        t.get_object(keys[0]).set(col_str, string_long);
10✔
2369

2370
        CHECK_EQUAL(1, t.count_string(col_str, string_long));
10✔
2371
        CHECK_EQUAL(1, t.count_string(col_str, ""));
10✔
2372
        CHECK_EQUAL(1, t.count_string(col_str, realm::null()));
10✔
2373

2374
        CHECK_EQUAL(keys[0], t.find_first_string(col_str, string_long));
10✔
2375
        CHECK_EQUAL(keys[1], t.find_first_string(col_str, ""));
10✔
2376
        CHECK_EQUAL(keys[2], t.find_first_string(col_str, realm::null()));
10✔
2377

2378
        tv = t.find_all_string(col_str, string_long);
10✔
2379
        CHECK_EQUAL(1, tv.size());
10✔
2380
        CHECK_EQUAL(keys[0], tv.get_key(0));
10✔
2381
        tv = t.find_all_string(col_str, "");
10✔
2382
        CHECK_EQUAL(1, tv.size());
10✔
2383
        CHECK_EQUAL(keys[1], tv.get_key(0));
10✔
2384
        tv = t.find_all_string(col_str, realm::null());
10✔
2385
        CHECK_EQUAL(1, tv.size());
10✔
2386
        CHECK_EQUAL(keys[2], tv.get_key(0));
10✔
2387
    }
10✔
2388

2389
    {
2✔
2390
        Table t;
2✔
2391
        auto col_int = t.add_column(type_Int, "int", true);         // nullable = true
2✔
2392
        auto col_bool = t.add_column(type_Bool, "bool", true);      // nullable = true
2✔
2393
        auto col_date = t.add_column(type_Timestamp, "date", true); // nullable = true
2✔
2394

2395
        Obj obj0 = t.create_object();
2✔
2396
        Obj obj1 = t.create_object();
2✔
2397
        ObjKey k0 = obj0.get_key();
2✔
2398
        ObjKey k1 = obj1.get_key();
2✔
2399

2400
        obj0.set(col_int, 65);
2✔
2401
        obj0.set(col_bool, false);
2✔
2402
        obj0.set(col_date, Timestamp(3, 0));
2✔
2403

2404
        CHECK_EQUAL(65, obj0.get<Int>(col_int));
2✔
2405
        CHECK_EQUAL(false, obj0.get<Bool>(col_bool));
2✔
2406
        CHECK_EQUAL(Timestamp(3, 0), obj0.get<Timestamp>(col_date));
2✔
2407

2408
        CHECK_EQUAL(65, t.max(col_int)->get_int());
2✔
2409
        CHECK_EQUAL(65, t.min(col_int)->get_int());
2✔
2410
        CHECK_EQUAL(Timestamp(3, 0), t.max(col_date)->get_timestamp());
2✔
2411
        CHECK_EQUAL(Timestamp(3, 0), t.min(col_date)->get_timestamp());
2✔
2412

2413
        CHECK_NOT(obj0.is_null(col_int));
2✔
2414
        CHECK_NOT(obj0.is_null(col_bool));
2✔
2415
        CHECK_NOT(obj0.is_null(col_date));
2✔
2416

2417
        CHECK_THROW_ANY(obj1.get<Int>(col_int));
2✔
2418
        CHECK(obj1.is_null(col_int));
2✔
2419
        CHECK(obj1.is_null(col_bool));
2✔
2420
        CHECK(obj1.is_null(col_date));
2✔
2421

2422
        CHECK_EQUAL(k1, t.find_first_null(col_int));
2✔
2423
        CHECK_EQUAL(k1, t.find_first_null(col_bool));
2✔
2424
        CHECK_EQUAL(k1, t.find_first_null(col_date));
2✔
2425

2426
        CHECK_EQUAL(null_key, t.find_first_int(col_int, -1));
2✔
2427
        CHECK_EQUAL(null_key, t.find_first_bool(col_bool, true));
2✔
2428
        CHECK_EQUAL(null_key, t.find_first_timestamp(col_date, Timestamp(5, 0)));
2✔
2429

2430
        CHECK_EQUAL(k0, t.find_first_int(col_int, 65));
2✔
2431
        CHECK_EQUAL(k0, t.find_first_bool(col_bool, false));
2✔
2432
        CHECK_EQUAL(k0, t.find_first_timestamp(col_date, Timestamp(3, 0)));
2✔
2433

2434
        obj0.set_null(col_int);
2✔
2435
        obj0.set_null(col_bool);
2✔
2436
        obj0.set_null(col_date);
2✔
2437

2438
        CHECK(obj0.is_null(col_int));
2✔
2439
        CHECK(obj0.is_null(col_bool));
2✔
2440
        CHECK(obj0.is_null(col_date));
2✔
2441
    }
2✔
2442
    {
2✔
2443
        Table t;
2✔
2444
        auto col_float = t.add_column(type_Float, "float", true);    // nullable = true
2✔
2445
        auto col_double = t.add_column(type_Double, "double", true); // nullable = true
2✔
2446

2447
        Obj obj0 = t.create_object();
2✔
2448
        Obj obj1 = t.create_object();
2✔
2449
        ObjKey k0 = obj0.get_key();
2✔
2450
        ObjKey k1 = obj1.get_key();
2✔
2451

2452
        obj0.set_all(1.23f, 12.3);
2✔
2453

2454
        CHECK_EQUAL(1.23f, obj0.get<float>(col_float));
2✔
2455
        CHECK_EQUAL(12.3, obj0.get<double>(col_double));
2✔
2456

2457
        CHECK_EQUAL(1.23f, t.max(col_float)->get_float());
2✔
2458
        CHECK_EQUAL(1.23f, t.min(col_float)->get_float());
2✔
2459
        CHECK_EQUAL(12.3, t.max(col_double)->get_double());
2✔
2460
        CHECK_EQUAL(12.3, t.min(col_double)->get_double());
2✔
2461

2462
        CHECK_NOT(obj0.is_null(col_float));
2✔
2463
        CHECK_NOT(obj0.is_null(col_double));
2✔
2464

2465
        CHECK(obj1.is_null(col_float));
2✔
2466
        CHECK(obj1.is_null(col_double));
2✔
2467

2468
        CHECK_EQUAL(k1, t.find_first_null(col_float));
2✔
2469
        CHECK_EQUAL(k1, t.find_first_null(col_double));
2✔
2470

2471
        CHECK_EQUAL(null_key, t.find_first_float(col_float, 2.22f));
2✔
2472
        CHECK_EQUAL(null_key, t.find_first_double(col_double, 2.22));
2✔
2473

2474
        CHECK_EQUAL(k0, t.find_first_float(col_float, 1.23f));
2✔
2475
        CHECK_EQUAL(k0, t.find_first_double(col_double, 12.3));
2✔
2476

2477
        util::Optional<Float> f_val = 5.f;
2✔
2478
        obj0.set(col_float, f_val);
2✔
2479
        CHECK_NOT(obj0.is_null(col_float));
2✔
2480
        CHECK_EQUAL(obj0.get<Optional<float>>(col_float), 5.f);
2✔
2481

2482
        util::Optional<Double> d_val = 5.;
2✔
2483
        obj0.set(col_double, d_val);
2✔
2484
        CHECK_NOT(obj0.is_null(col_double));
2✔
2485
        CHECK_EQUAL(obj0.get<Optional<double>>(col_double), 5.);
2✔
2486

2487
        obj0.set_null(col_float);
2✔
2488
        obj0.set_null(col_double);
2✔
2489

2490
        CHECK(obj0.is_null(col_float));
2✔
2491
        CHECK(obj0.is_null(col_double));
2✔
2492
    }
2✔
2493
}
2✔
2494

2495
// This triggers a severe bug in the Array::alloc() allocator in which its capacity-doubling
2496
// scheme forgets to test of the doubling has overflowed the maximum allowed size of an
2497
// array which is 2^24 - 1 bytes
2498
// NONCONCURRENT because if run in parallel with other tests which request large amounts of
2499
// memory, there may be a std::bad_alloc on low memory machines
2500
NONCONCURRENT_TEST(Table_AllocatorCapacityBug)
2501
{
2✔
2502
    std::unique_ptr<char[]> buf(new char[20000000]);
2✔
2503

2504
    // First a simple trigger of `Assertion failed: value <= 0xFFFFFFL [26000016, 16777215]`
2505
    {
2✔
2506
        BPlusTree<BinaryData> c(Allocator::get_default());
2✔
2507
        c.create();
2✔
2508

2509
        c.add(BinaryData(buf.get(), 13000000));
2✔
2510
        c.set(0, BinaryData(buf.get(), 14000000));
2✔
2511

2512
        c.destroy();
2✔
2513
    }
2✔
2514

2515
    // Now a small fuzzy test to catch other such bugs
2516
    {
2✔
2517
        Table t;
2✔
2518
        std::vector<ObjKey> keys;
2✔
2519
        auto col_bin = t.add_column(type_Binary, "Binaries", true);
2✔
2520

2521
        for (size_t j = 0; j < 100; j++) {
202✔
2522
            size_t r = (j * 123456789 + 123456789) % 100;
200✔
2523
            if (r < 20) {
200✔
2524
                keys.push_back(t.create_object().get_key());
40✔
2525
            }
40✔
2526
            else if (t.size() > 0 && t.size() < 5) {
160✔
2527
                // Set only if there are no more than 4 rows, else it takes up too much space on devices (4 * 16 MB
2528
                // worst case now)
2529
                size_t row = (j * 123456789 + 123456789) % t.size();
102✔
2530
                size_t len = (j * 123456789 + 123456789) % 16000000;
102✔
2531
                BinaryData bd;
102✔
2532
                bd = BinaryData(buf.get(), len);
102✔
2533
                t.get_object(keys[row]).set(col_bin, bd);
102✔
2534
            }
102✔
2535
            else if (t.size() >= 4) {
58✔
2536
                t.clear();
6✔
2537
                keys.clear();
6✔
2538
            }
6✔
2539
        }
200✔
2540
    }
2✔
2541
}
2✔
2542

2543

2544
TEST(Table_DetachedAccessor)
2545
{
2✔
2546
    Group group;
2✔
2547
    TableRef table = group.add_table("table");
2✔
2548
    auto col_int = table->add_column(type_Int, "i");
2✔
2549
    auto col_str = table->add_column(type_String, "s");
2✔
2550
    table->add_column(type_Binary, "b");
2✔
2551
    table->add_column(*table, "l");
2✔
2552
    ObjKey key0 = table->create_object().get_key();
2✔
2553
    Obj obj1 = table->create_object();
2✔
2554
    group.remove_table("table");
2✔
2555

2556
    CHECK_THROW(table->clear(), InvalidTableRef);
2✔
2557
    CHECK_THROW(table->add_search_index(col_int), InvalidTableRef);
2✔
2558
    CHECK_THROW(table->remove_search_index(col_int), InvalidTableRef);
2✔
2559
    CHECK_THROW(table->get_object(key0), InvalidTableRef);
2✔
2560
    CHECK_THROW_ANY(obj1.set(col_str, "hello"));
2✔
2561
}
2✔
2562

2563
TEST(Table_addRowsToTableWithNoColumns)
2564
{
2✔
2565
    Group g; // type_Link must be part of a group
2✔
2566
    TableRef t = g.add_table("t");
2✔
2567

2568
    t->create_object();
2✔
2569
    CHECK_EQUAL(t->size(), 1);
2✔
2570
    auto col = t->add_column(type_String, "str_col");
2✔
2571
    t->create_object();
2✔
2572
    CHECK_EQUAL(t->size(), 2);
2✔
2573
    t->add_search_index(col);
2✔
2574
    t->create_object();
2✔
2575
    CHECK_EQUAL(t->size(), 3);
2✔
2576
    t->remove_column(col);
2✔
2577
    CHECK_EQUAL(t->size(), 3);
2✔
2578

2579
    // Check that links are nulled when connected table is cleared
2580
    TableRef u = g.add_table("u");
2✔
2581
    auto col_link = u->add_column(*t, "link from u to t");
2✔
2582
    Obj obj = u->create_object();
2✔
2583
    CHECK_EQUAL(u->size(), 1);
2✔
2584
    CHECK_EQUAL(t->size(), 3);
2✔
2585
    CHECK_LOGIC_ERROR(obj.set(col_link, ObjKey(45)), ErrorCodes::KeyNotFound);
2✔
2586
    CHECK(obj.is_null(col_link));
2✔
2587
    CHECK_EQUAL(t->size(), 3);
2✔
2588
    ObjKey k = t->create_object().get_key();
2✔
2589
    obj.set(col_link, k);
2✔
2590
    CHECK_EQUAL(obj.get<ObjKey>(col_link), k);
2✔
2591
    CHECK(!obj.is_null(col_link));
2✔
2592
    CHECK_EQUAL(t->size(), 4);
2✔
2593
    t->clear();
2✔
2594
    CHECK_EQUAL(t->size(), 0);
2✔
2595
    CHECK_EQUAL(u->size(), 1);
2✔
2596
    CHECK(obj.is_null(col_link));
2✔
2597
    u->remove_column(col_link);
2✔
2598
}
2✔
2599

2600

2601
TEST(Table_getVersionCounterAfterRowAccessor)
2602
{
2✔
2603
    Table t;
2✔
2604
    auto col_bool = t.add_column(type_Bool, "bool", true);
2✔
2605
    auto col_int = t.add_column(type_Int, "int", true);
2✔
2606
    auto col_string = t.add_column(type_String, "string", true);
2✔
2607
    auto col_float = t.add_column(type_Float, "float", true);
2✔
2608
    auto col_double = t.add_column(type_Double, "double", true);
2✔
2609
    auto col_binary = t.add_column(type_Binary, "binary", true);
2✔
2610
    auto col_date = t.add_column(type_Timestamp, "timestamp", true);
2✔
2611

2612
    Obj obj = t.create_object();
2✔
2613

2614
    int_fast64_t ver = t.get_content_version();
2✔
2615
    int_fast64_t newVer;
2✔
2616

2617
    auto check_ver_bump = [&]() {
16✔
2618
        newVer = t.get_content_version();
16✔
2619
        CHECK_GREATER(newVer, ver);
16✔
2620
        ver = newVer;
16✔
2621
    };
16✔
2622

2623
    obj.set<Bool>(col_bool, true);
2✔
2624
    check_ver_bump();
2✔
2625

2626
    obj.set<Int>(col_int, 42);
2✔
2627
    check_ver_bump();
2✔
2628

2629
    obj.set<String>(col_string, "foo");
2✔
2630
    check_ver_bump();
2✔
2631

2632
    obj.set<Float>(col_float, 0.42f);
2✔
2633
    check_ver_bump();
2✔
2634

2635
    obj.set<Double>(col_double, 0.42);
2✔
2636
    check_ver_bump();
2✔
2637

2638
    obj.set<Binary>(col_binary, BinaryData("binary", 7));
2✔
2639
    check_ver_bump();
2✔
2640

2641
    obj.set<Timestamp>(col_date, Timestamp(777, 888));
2✔
2642
    check_ver_bump();
2✔
2643

2644
    obj.set_null(col_string);
2✔
2645
    check_ver_bump();
2✔
2646
}
2✔
2647

2648

2649
TEST(Table_object_basic)
2650
{
2✔
2651
    Table table;
2✔
2652
    auto int_col = table.add_column(type_Int, "int");
2✔
2653
    auto intnull_col = table.add_column(type_Int, "intnull", true);
2✔
2654

2655
    char data[10];
2✔
2656
    memset(data, 0x5a, 10);
2✔
2657
    BinaryData bin_data(data, 10);
2✔
2658
    BinaryData bin_zero(data, 0);
2✔
2659

2660
    table.create_object(ObjKey(5)).set_all(100, 7);
2✔
2661
    CHECK_EQUAL(table.size(), 1);
2✔
2662
    CHECK_THROW(table.create_object(ObjKey(5)), KeyAlreadyUsed);
2✔
2663
    CHECK_EQUAL(table.size(), 1);
2✔
2664
    table.create_object(ObjKey(2));
2✔
2665
    Obj x = table.create_object(ObjKey(7));
2✔
2666
    table.create_object(ObjKey(8));
2✔
2667
    table.create_object(ObjKey(10));
2✔
2668
    table.create_object(ObjKey(6));
2✔
2669

2670
    Obj y = table.get_object(ObjKey(5));
2✔
2671

2672
    // Int
2673
    CHECK(!x.is_null(int_col));
2✔
2674
    CHECK_EQUAL(0, x.get<int64_t>(int_col));
2✔
2675
    CHECK(x.is_null(intnull_col));
2✔
2676

2677
    CHECK_EQUAL(100, y.get<int64_t>(int_col));
2✔
2678
    CHECK(!y.is_null(intnull_col));
2✔
2679
    CHECK_EQUAL(7, y.get<util::Optional<int64_t>>(intnull_col));
2✔
2680
    y.set_null(intnull_col);
2✔
2681
    CHECK(y.is_null(intnull_col));
2✔
2682

2683
    // Boolean
2684
    auto bool_col = table.add_column(type_Bool, "bool");
2✔
2685
    auto boolnull_col = table.add_column(type_Bool, "boolnull", true);
2✔
2686
    y.set(bool_col, true);
2✔
2687
    y.set(boolnull_col, false);
2✔
2688

2689
    CHECK(!x.is_null(bool_col));
2✔
2690
    CHECK_EQUAL(false, x.get<Bool>(bool_col));
2✔
2691
    CHECK(x.is_null(boolnull_col));
2✔
2692

2693
    CHECK_EQUAL(true, y.get<Bool>(bool_col));
2✔
2694
    CHECK(!y.is_null(boolnull_col));
2✔
2695
    auto bool_val = y.get<util::Optional<Bool>>(boolnull_col);
2✔
2696
    CHECK_EQUAL(true, bool(bool_val));
2✔
2697
    CHECK_EQUAL(false, *bool_val);
2✔
2698
    y.set_null(boolnull_col);
2✔
2699
    CHECK(y.is_null(boolnull_col));
2✔
2700

2701
    // Float
2702
    auto float_col = table.add_column(type_Float, "float");
2✔
2703
    auto floatnull_col = table.add_column(type_Float, "floatnull", true);
2✔
2704
    y.set(float_col, 2.7182818f);
2✔
2705
    y.set(floatnull_col, 3.1415927f);
2✔
2706

2707
    CHECK(!x.is_null(float_col));
2✔
2708
    CHECK_EQUAL(0.0f, x.get<Float>(float_col));
2✔
2709
    CHECK(x.is_null(floatnull_col));
2✔
2710

2711
    CHECK_EQUAL(2.7182818f, y.get<Float>(float_col));
2✔
2712
    CHECK(!y.is_null(floatnull_col));
2✔
2713
    CHECK_EQUAL(3.1415927f, y.get<util::Optional<Float>>(floatnull_col));
2✔
2714
    y.set_null(floatnull_col);
2✔
2715
    CHECK(y.is_null(floatnull_col));
2✔
2716

2717
    // Double
2718
    auto double_col = table.add_column(type_Double, "double");
2✔
2719
    auto doublenull_col = table.add_column(type_Double, "doublenull", true);
2✔
2720
    y.set(double_col, 2.718281828459045);
2✔
2721
    y.set(doublenull_col, 3.141592653589793);
2✔
2722

2723
    CHECK(!x.is_null(double_col));
2✔
2724
    CHECK_EQUAL(0.0f, x.get<Double>(double_col));
2✔
2725
    CHECK(x.is_null(doublenull_col));
2✔
2726

2727
    CHECK_EQUAL(2.718281828459045, y.get<Double>(double_col));
2✔
2728
    CHECK(!y.is_null(doublenull_col));
2✔
2729
    CHECK_EQUAL(3.141592653589793, y.get<util::Optional<Double>>(doublenull_col));
2✔
2730
    y.set_null(doublenull_col);
2✔
2731
    CHECK(y.is_null(doublenull_col));
2✔
2732

2733
    // String
2734
    auto str_col = table.add_column(type_String, "str");
2✔
2735
    auto strnull_col = table.add_column(type_String, "strnull", true);
2✔
2736
    y.set(str_col, "Hello");
2✔
2737
    y.set(strnull_col, "World");
2✔
2738

2739
    CHECK(!x.is_null(str_col));
2✔
2740
    CHECK_EQUAL("", x.get<String>(str_col));
2✔
2741
    CHECK(x.is_null(strnull_col));
2✔
2742

2743
    CHECK_EQUAL("Hello", y.get<String>(str_col));
2✔
2744
    CHECK(!y.is_null(strnull_col));
2✔
2745
    CHECK_EQUAL("World", y.get<String>(strnull_col));
2✔
2746
    y.set_null(strnull_col);
2✔
2747
    CHECK(y.is_null(strnull_col));
2✔
2748

2749
    // Upgrade to medium leaf
2750
    y.set(str_col, "This is a fine day");
2✔
2751
    CHECK_EQUAL("This is a fine day", y.get<String>(str_col));
2✔
2752
    CHECK(!y.is_null(str_col));
2✔
2753

2754
    // Binary
2755
    auto bin_col = table.add_column(type_Binary, "bin");
2✔
2756
    auto binnull_col = table.add_column(type_Binary, "binnull", true);
2✔
2757
    y.set(bin_col, bin_data);
2✔
2758
    y.set(binnull_col, bin_data);
2✔
2759

2760
    CHECK(!x.is_null(bin_col));
2✔
2761
    CHECK_EQUAL(bin_zero, x.get<Binary>(bin_col));
2✔
2762
    CHECK(x.is_null(binnull_col));
2✔
2763

2764
    CHECK_EQUAL(bin_data, y.get<Binary>(bin_col));
2✔
2765
    CHECK(!y.is_null(binnull_col));
2✔
2766
    CHECK_EQUAL(bin_data, y.get<Binary>(binnull_col));
2✔
2767
    y.set_null(binnull_col);
2✔
2768
    CHECK(y.is_null(binnull_col));
2✔
2769

2770
    // Upgrade from small to big
2771
    char big_data[100];
2✔
2772
    memset(big_data, 0xa5, 100);
2✔
2773
    BinaryData bin_data_big(big_data, 100);
2✔
2774
    x.set(bin_col, bin_data);
2✔
2775
    y.set(bin_col, bin_data_big);
2✔
2776
    CHECK_EQUAL(bin_data, x.get<Binary>(bin_col));
2✔
2777
    CHECK_EQUAL(bin_data_big, y.get<Binary>(bin_col));
2✔
2778
    CHECK(!y.is_null(bin_col));
2✔
2779

2780
    // Timestamp
2781
    auto ts_col = table.add_column(type_Timestamp, "ts");
2✔
2782
    auto tsnull_col = table.add_column(type_Timestamp, "tsnull", true);
2✔
2783
    y.set(ts_col, Timestamp(123, 456));
2✔
2784
    y.set(tsnull_col, Timestamp(789, 10));
2✔
2785

2786
    CHECK(!x.is_null(ts_col));
2✔
2787
    CHECK_EQUAL(Timestamp(0, 0), x.get<Timestamp>(ts_col));
2✔
2788
    CHECK(x.is_null(tsnull_col));
2✔
2789

2790
    CHECK_EQUAL(Timestamp(123, 456), y.get<Timestamp>(ts_col));
2✔
2791
    CHECK(!y.is_null(tsnull_col));
2✔
2792
    CHECK_EQUAL(Timestamp(789, 10), y.get<Timestamp>(tsnull_col));
2✔
2793
    y.set_null(binnull_col);
2✔
2794
    CHECK(y.is_null(binnull_col));
2✔
2795

2796
    // Check that accessing a removed object will throw
2797
    table.remove_object(ObjKey(5));
2✔
2798
    CHECK_THROW(y.get<int64_t>(intnull_col), KeyNotFound);
2✔
2799

2800
    CHECK(table.get_object(ObjKey(8)).is_null(intnull_col));
2✔
2801
}
2✔
2802

2803

2804
TEST(Table_ObjectsWithNoColumns)
2805
{
2✔
2806
    Table table;
2✔
2807
    std::vector<ObjKey> keys;
2✔
2808
    table.create_objects(REALM_MAX_BPNODE_SIZE * 2, keys);
2✔
2809
    CHECK_NOT(table.is_empty());
2✔
2810
    CHECK_EQUAL(table.size(), REALM_MAX_BPNODE_SIZE * 2);
2✔
2811
    for (ObjKey k : keys) {
4,000✔
2812
        Obj obj = table.get_object(k);
4,000✔
2813
        CHECK(obj.is_valid());
4,000✔
2814
        obj.remove();
4,000✔
2815
        CHECK(!obj.is_valid());
4,000✔
2816
    }
4,000✔
2817
    CHECK(table.is_empty());
2✔
2818
    CHECK_EQUAL(table.size(), 0);
2✔
2819
}
2✔
2820

2821
TEST(Table_remove_column)
2822
{
2✔
2823
    Table table;
2✔
2824
    table.add_column(type_Int, "int1");
2✔
2825
    auto int2_col = table.add_column(type_Int, "int2");
2✔
2826
    table.add_column(type_Int, "int3");
2✔
2827

2828
    Obj obj = table.create_object(ObjKey(5)).set_all(100, 7, 25);
2✔
2829

2830
    CHECK_EQUAL(obj.get<int64_t>("int1"), 100);
2✔
2831
    CHECK_EQUAL(obj.get<int64_t>("int2"), 7);
2✔
2832
    CHECK_EQUAL(obj.get<int64_t>("int3"), 25);
2✔
2833

2834
    table.remove_column(int2_col);
2✔
2835

2836
    CHECK_EQUAL(obj.get<int64_t>("int1"), 100);
2✔
2837
    CHECK_THROW(obj.get<int64_t>("int2"), LogicError);
2✔
2838
    CHECK_EQUAL(obj.get<int64_t>("int3"), 25);
2✔
2839
    table.add_column(type_Int, "int4");
2✔
2840
    CHECK_EQUAL(obj.get<int64_t>("int4"), 0);
2✔
2841
}
2✔
2842

2843
TEST(Table_object_merge_nodes)
2844
{
2✔
2845
    // This test works best for REALM_MAX_BPNODE_SIZE == 8.
2846
    // To be used mostly as a help when debugging new implementation
2847

2848
    int nb_rows = REALM_MAX_BPNODE_SIZE * 8;
2✔
2849
    Table table;
2✔
2850
    std::vector<int64_t> key_set;
2✔
2851
    auto c0 = table.add_column(type_Int, "int1");
2✔
2852
    auto c1 = table.add_column(type_Int, "int2", true);
2✔
2853

2854
    for (int i = 0; i < nb_rows; i++) {
16,002✔
2855
        table.create_object(ObjKey(i)).set_all(i << 1, i << 2);
16,000✔
2856
        key_set.push_back(i);
16,000✔
2857
    }
16,000✔
2858

2859
    for (int i = 0; i < nb_rows; i++) {
16,002✔
2860
        auto key_index = test_util::random_int<int64_t>(0, key_set.size() - 1);
16,000✔
2861
        auto it = key_set.begin() + int(key_index);
16,000✔
2862

2863
        // table.dump_objects();
2864
        // std::cout << "Key to remove: " << std::hex << *it << std::dec << std::endl;
2865

2866
        table.remove_object(ObjKey(*it));
16,000✔
2867
        key_set.erase(it);
16,000✔
2868
        for (unsigned j = 0; j < key_set.size(); j += 23) {
2,805,916✔
2869
            int64_t key_val = key_set[j];
2,789,916✔
2870
            Obj o = table.get_object(ObjKey(key_val));
2,789,916✔
2871
            CHECK_EQUAL(key_val << 1, o.get<int64_t>(c0));
2,789,916✔
2872
            CHECK_EQUAL(key_val << 2, o.get<util::Optional<int64_t>>(c1));
2,789,916✔
2873
        }
2,789,916✔
2874
    }
16,000✔
2875
}
2✔
2876

2877
TEST(Table_object_forward_iterator)
2878
{
2✔
2879
    int nb_rows = 1024;
2✔
2880
    Table table;
2✔
2881
    auto c0 = table.add_column(type_Int, "int1");
2✔
2882
    auto c1 = table.add_column(type_Int, "int2", true);
2✔
2883

2884
    for (int i = 0; i < nb_rows; i++) {
2,050✔
2885
        table.create_object(ObjKey(i));
2,048✔
2886
    }
2,048✔
2887

2888
    size_t tree_size = 0;
2✔
2889
    auto f = [&tree_size](const Cluster* cluster) {
8✔
2890
        tree_size += cluster->node_size();
8✔
2891
        return IteratorControl::AdvanceToNext;
8✔
2892
    };
8✔
2893
    table.traverse_clusters(f);
2✔
2894
    CHECK_EQUAL(tree_size, size_t(nb_rows));
2✔
2895

2896
    for (Obj o : table) {
2,048✔
2897
        int64_t key_value = o.get_key().value;
2,048✔
2898
        o.set_all(key_value << 1, key_value << 2);
2,048✔
2899
    }
2,048✔
2900

2901
    // table.dump_objects();
2902

2903
    size_t ndx = 0;
2✔
2904
    for (Obj o : table) {
2,048✔
2905
        int64_t key_value = o.get_key().value;
2,048✔
2906
        // std::cout << "Key value: " << std::hex << key_value << std::dec << std::endl;
2907
        CHECK_EQUAL(key_value << 1, o.get<int64_t>(c0));
2,048✔
2908
        CHECK_EQUAL(key_value << 2, o.get<util::Optional<int64_t>>(c1));
2,048✔
2909

2910
        Obj x = table.get_object(ndx);
2,048✔
2911
        CHECK_EQUAL(o.get_key(), x.get_key());
2,048✔
2912
        CHECK_EQUAL(o.get<int64_t>(c0), x.get<int64_t>(c0));
2,048✔
2913
        ndx++;
2,048✔
2914
    }
2,048✔
2915

2916
    auto it = table.begin();
2✔
2917
    while (it != table.end()) {
2,050✔
2918
        int64_t val = it->get_key().value;
2,048✔
2919
        // Delete every 7th object
2920
        if ((val % 7) == 0) {
2,048✔
2921
            table.remove_object(it);
294✔
2922
        }
294✔
2923
        ++it;
2,048✔
2924
    }
2,048✔
2925
    CHECK_EQUAL(table.size(), nb_rows * 6 / 7);
2✔
2926

2927
    auto it1 = table.begin();
2✔
2928
    ObjKey key = it1->get_key();
2✔
2929
    ++it1;
2✔
2930
    int64_t val = it1->get<int64_t>(c0);
2✔
2931
    table.remove_object(key);
2✔
2932
    CHECK_EQUAL(val, it1->get<int64_t>(c0));
2✔
2933

2934
    val = (it1 + 2)->get<int64_t>(c0);
2✔
2935
    table.remove_object(it1);
2✔
2936
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
2937
    // Still invalid
2938
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
2939
    it1 += 0;
2✔
2940
    // Still invalid
2941
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
2942
    it1 += 2;
2✔
2943
    CHECK_EQUAL(val, it1->get<int64_t>(c0));
2✔
2944
}
2✔
2945

2946
TEST(Table_object_by_index)
2947
{
2✔
2948
    Table table;
2✔
2949

2950
    ObjKeys keys({17, 4, 345, 65, 1, 46, 93, 43, 76, 123, 33, 42, 99, 53, 52, 256, 2}); // 17 elements
2✔
2951
    std::map<ObjKey, size_t> positions;
2✔
2952
    table.create_objects(keys);
2✔
2953
    size_t sz = table.size();
2✔
2954
    CHECK_EQUAL(sz, keys.size());
2✔
2955
    for (size_t i = 0; i < sz; i++) {
36✔
2956
        Obj o = table.get_object(i);
34✔
2957
        auto it = std::find(keys.begin(), keys.end(), o.get_key());
34✔
2958
        CHECK(it != keys.end());
34✔
2959
        positions.emplace(o.get_key(), i);
34✔
2960
    }
34✔
2961
    for (auto k : keys) {
34✔
2962
        size_t ndx = table.get_object_ndx(k);
34✔
2963
        CHECK_EQUAL(ndx, positions[k]);
34✔
2964
    }
34✔
2965
}
2✔
2966

2967
// String query benchmark
2968
NONCONCURRENT_TEST(Table_QuickSort2)
2969
{
2✔
2970
    Table ttt;
2✔
2971
    auto strings = ttt.add_column(type_String, "2");
2✔
2972

2973
    for (size_t t = 0; t < 1000; t++) {
2,002✔
2974
        Obj o = ttt.create_object();
2,000✔
2975
        std::string s = util::to_string(t % 100);
2,000✔
2976
        o.set<StringData>(strings, s);
2,000✔
2977
    }
2,000✔
2978

2979
    Query q = ttt.where().equal(strings, "10");
2✔
2980

2981
    auto t1 = steady_clock::now();
2✔
2982

2983
    CALLGRIND_START_INSTRUMENTATION;
2✔
2984

2985
    size_t nb_reps = 1000;
2✔
2986
    for (size_t t = 0; t < nb_reps; t++) {
2,002✔
2987
        TableView tv = q.find_all();
2,000✔
2988
        CHECK_EQUAL(tv.size(), 10);
2,000✔
2989
    }
2,000✔
2990

2991
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
2992

2993
    auto t2 = steady_clock::now();
2✔
2994

2995
    std::cout << nb_reps << " repetitions of find_all" << std::endl;
2✔
2996
    std::cout << "    time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_reps << " ns/rep" << std::endl;
2✔
2997
}
2✔
2998

2999
NONCONCURRENT_TEST(Table_object_sequential)
3000
{
2✔
3001
#ifdef PERFORMACE_TESTING
3002
    int nb_rows = 10'000'000;
3003
    int num_runs = 1;
3004
#else
3005
    int nb_rows = 100'000;
2✔
3006
    int num_runs = 1;
2✔
3007
#endif
2✔
3008
    SHARED_GROUP_TEST_PATH(path);
2✔
3009
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3010
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3011
    ColKey c0;
2✔
3012
    ColKey c1;
2✔
3013

3014
    CALLGRIND_START_INSTRUMENTATION;
2✔
3015

3016
    std::cout << nb_rows << " rows - sequential" << std::endl;
2✔
3017

3018
    {
2✔
3019
        WriteTransaction wt(sg);
2✔
3020
        auto table = wt.add_table("test");
2✔
3021

3022
        c0 = table->add_column(type_Int, "int1");
2✔
3023
        c1 = table->add_column(type_Int, "int2", true);
2✔
3024

3025

3026
        auto t1 = steady_clock::now();
2✔
3027

3028
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3029
            table->create_object(ObjKey(i)).set_all(i << 1, i << 2);
200,000✔
3030
        }
200,000✔
3031

3032
        auto t2 = steady_clock::now();
2✔
3033
        std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3034
                  << std::endl;
2✔
3035

3036
        CHECK_EQUAL(table->size(), nb_rows);
2✔
3037
        wt.commit();
2✔
3038
    }
2✔
3039
    {
2✔
3040
        auto t1 = steady_clock::now();
2✔
3041
        sg->compact();
2✔
3042
        auto t2 = steady_clock::now();
2✔
3043
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3044
    }
2✔
3045
    {
2✔
3046
        ReadTransaction rt(sg);
2✔
3047
        auto table = rt.get_table("test");
2✔
3048

3049
        auto t1 = steady_clock::now();
2✔
3050

3051
        for (int j = 0; j < num_runs; ++j) {
4✔
3052
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3053
                table->get_object(ObjKey(i));
200,000✔
3054
            }
200,000✔
3055
        }
2✔
3056

3057
        auto t2 = steady_clock::now();
2✔
3058

3059
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3060
                  << " ns/key" << std::endl;
2✔
3061
    }
2✔
3062

3063
    {
2✔
3064
        ReadTransaction rt(sg);
2✔
3065
        auto table = rt.get_table("test");
2✔
3066

3067
        auto t1 = steady_clock::now();
2✔
3068

3069
        for (int j = 0; j < num_runs; ++j) {
4✔
3070
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3071
                const Obj o = table->get_object(ObjKey(i));
200,000✔
3072
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3073
            }
200,000✔
3074
        }
2✔
3075

3076
        auto t2 = steady_clock::now();
2✔
3077

3078
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3079
                  << " ns/key" << std::endl;
2✔
3080
    }
2✔
3081

3082
    {
2✔
3083
        ReadTransaction rt(sg);
2✔
3084
        auto table = rt.get_table("test");
2✔
3085

3086
        auto t1 = steady_clock::now();
2✔
3087

3088
        for (int j = 0; j < num_runs; ++j) {
4✔
3089
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3090
                const Obj o = table->get_object(ObjKey(i));
200,000✔
3091
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3092
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3093
            }
200,000✔
3094
        }
2✔
3095

3096
        auto t2 = steady_clock::now();
2✔
3097

3098
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3099
                  << " ns/key" << std::endl;
2✔
3100
    }
2✔
3101

3102
    {
2✔
3103
        WriteTransaction wt(sg);
2✔
3104
        auto table = wt.get_table("test");
2✔
3105

3106
        auto t1 = steady_clock::now();
2✔
3107

3108
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3109
            Obj o = table->get_object(ObjKey(i));
200,000✔
3110
            o.set(c0, i << 2).set(c1, i << 1);
200,000✔
3111
        }
200,000✔
3112

3113
        auto t2 = steady_clock::now();
2✔
3114

3115
        std::cout << "   update time   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3116
                  << std::endl;
2✔
3117
        wt.commit();
2✔
3118
    }
2✔
3119

3120
    {
2✔
3121
        WriteTransaction wt(sg);
2✔
3122
        auto table = wt.get_table("test");
2✔
3123

3124
        auto t1 = steady_clock::now();
2✔
3125

3126
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3127
            table->remove_object(ObjKey(i));
200,000✔
3128
#ifdef REALM_DEBUG
200,000✔
3129
            CHECK_EQUAL(table->size(), nb_rows - i - 1);
200,000✔
3130

3131
            for (int j = i + 1; j < nb_rows; j += nb_rows / 100) {
10,299,800✔
3132
                Obj o = table->get_object(ObjKey(j));
10,099,800✔
3133
                CHECK_EQUAL(j << 2, o.get<int64_t>(c0));
10,099,800✔
3134
                CHECK_EQUAL(j << 1, o.get<util::Optional<int64_t>>(c1));
10,099,800✔
3135
            }
10,099,800✔
3136

3137
#endif
200,000✔
3138
        }
200,000✔
3139
        auto t2 = steady_clock::now();
2✔
3140
        std::cout << "   erase time    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3141
                  << std::endl;
2✔
3142

3143
        wt.commit();
2✔
3144
    }
2✔
3145

3146
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3147
}
2✔
3148

3149
NONCONCURRENT_TEST(Table_object_seq_rnd)
3150
{
2✔
3151
#ifdef PERFORMACE_TESTING
3152
    size_t rows = 1'000'000;
3153
    int runs = 100; // runs for building scenario
3154
#else
3155
    size_t rows = 100'000;
2✔
3156
    int runs = 100;
2✔
3157
#endif
2✔
3158
    int64_t next_key = 0;
2✔
3159
    std::vector<int64_t> key_values;
2✔
3160
    std::set<int64_t> key_set;
2✔
3161
    SHARED_GROUP_TEST_PATH(path);
2✔
3162
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3163
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3164
    ColKey c0;
2✔
3165
    {
2✔
3166
        std::cout << "Establishing scenario seq ins/rnd erase " << std::endl;
2✔
3167
        WriteTransaction wt(sg);
2✔
3168
        auto table = wt.add_table("test");
2✔
3169
        c0 = table->add_column(type_Int, "int1");
2✔
3170

3171
        for (int run = 0; run < runs; ++run) {
202✔
3172
            if (key_values.size() < rows) { // expanding by 2%!
200✔
3173
                for (size_t n = 0; n < rows / 50; ++n) {
400,200✔
3174
                    auto key_val = next_key++;
400,000✔
3175
                    key_values.push_back(key_val);
400,000✔
3176
                    key_set.insert(key_val);
400,000✔
3177
                    table->create_object(ObjKey(key_val)).set_all(key_val << 1);
400,000✔
3178
                }
400,000✔
3179
            }
200✔
3180
            // do 1% random deletions
3181
            for (size_t n = 0; n < rows / 100; ++n) {
200,200✔
3182
                auto index = test_util::random_int<size_t>(0, key_values.size() - 1);
200,000✔
3183
                auto key_val = key_values[index];
200,000✔
3184
                if (index < key_values.size() - 1)
200,000✔
3185
                    key_values[index] = key_values.back();
199,996✔
3186
                key_values.pop_back();
200,000✔
3187
                table->remove_object(ObjKey(key_val));
200,000✔
3188
            }
200,000✔
3189
        }
200✔
3190
        wt.commit();
2✔
3191
    }
2✔
3192
    // scenario established!
3193
    int nb_rows = int(key_values.size());
2✔
3194
#ifdef PERFORMACE_TESTING
3195
    int num_runs = 10; // runs for timing access
3196
#else
3197
    int num_runs = 1; // runs for timing access
2✔
3198
#endif
2✔
3199
    {
2✔
3200
        auto t1 = steady_clock::now();
2✔
3201
        sg->compact();
2✔
3202
        auto t2 = steady_clock::now();
2✔
3203
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3204
    }
2✔
3205
    std::cout << "Scenario has " << nb_rows << " rows. Timing...." << std::endl;
2✔
3206
    {
2✔
3207
        ReadTransaction rt(sg);
2✔
3208
        auto table = rt.get_table("test");
2✔
3209

3210
        auto t1 = steady_clock::now();
2✔
3211

3212
        for (int j = 0; j < num_runs; ++j) {
4✔
3213
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3214
                table->get_object(ObjKey(key_values[i]));
200,000✔
3215
            }
200,000✔
3216
        }
2✔
3217

3218
        auto t2 = steady_clock::now();
2✔
3219

3220
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3221
                  << " ns/key" << std::endl;
2✔
3222
    }
2✔
3223

3224
    {
2✔
3225
        ReadTransaction rt(sg);
2✔
3226
        auto table = rt.get_table("test");
2✔
3227

3228
        auto t1 = steady_clock::now();
2✔
3229

3230
        for (int j = 0; j < num_runs; ++j) {
4✔
3231
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3232
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3233
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3234
            }
200,000✔
3235
        }
2✔
3236

3237
        auto t2 = steady_clock::now();
2✔
3238

3239
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3240
                  << " ns/key" << std::endl;
2✔
3241
    }
2✔
3242

3243
    {
2✔
3244
        ReadTransaction rt(sg);
2✔
3245
        auto table = rt.get_table("test");
2✔
3246

3247
        auto t1 = steady_clock::now();
2✔
3248

3249
        for (int j = 0; j < num_runs; ++j) {
4✔
3250
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3251
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3252
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3253
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3254
            }
200,000✔
3255
        }
2✔
3256

3257
        auto t2 = steady_clock::now();
2✔
3258

3259
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3260
                  << " ns/key" << std::endl;
2✔
3261
    }
2✔
3262
}
2✔
3263

3264
NONCONCURRENT_TEST(Table_object_random)
3265
{
2✔
3266
#ifdef PERFORMACE_TESTING
3267
    int nb_rows = 1'000'000;
3268
    int num_runs = 10;
3269
#else
3270
    int nb_rows = 100'000;
2✔
3271
    int num_runs = 1;
2✔
3272
#endif
2✔
3273
    SHARED_GROUP_TEST_PATH(path);
2✔
3274
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3275
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3276
    ColKey c0;
2✔
3277
    ColKey c1;
2✔
3278
    std::vector<int64_t> key_values;
2✔
3279

3280
    {
2✔
3281
        std::set<int64_t> key_set;
2✔
3282
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3283
            bool ok = false;
200,000✔
3284
            while (!ok) {
410,643✔
3285
                auto key_val = test_util::random_int<int64_t>(0, nb_rows * 10);
210,643✔
3286
                if (key_set.count(key_val) == 0) {
210,643✔
3287
                    key_values.push_back(key_val);
200,000✔
3288
                    key_set.insert(key_val);
200,000✔
3289
                    ok = true;
200,000✔
3290
                }
200,000✔
3291
            }
210,643✔
3292
        }
200,000✔
3293
    }
2✔
3294

3295
    CALLGRIND_START_INSTRUMENTATION;
2✔
3296

3297
    std::cout << nb_rows << " rows - random" << std::endl;
2✔
3298

3299
    {
2✔
3300
        WriteTransaction wt(sg);
2✔
3301
        auto table = wt.add_table("test");
2✔
3302

3303
        c0 = table->add_column(type_Int, "int1");
2✔
3304
        c1 = table->add_column(type_Int, "int2", true);
2✔
3305

3306

3307
        auto t1 = steady_clock::now();
2✔
3308

3309
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3310
            table->create_object(ObjKey(key_values[i])).set_all(i << 1, i << 2);
200,000✔
3311
        }
200,000✔
3312

3313

3314
        auto t2 = steady_clock::now();
2✔
3315
        std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3316
                  << std::endl;
2✔
3317

3318
        CHECK_EQUAL(table->size(), nb_rows);
2✔
3319
        wt.commit();
2✔
3320
    }
2✔
3321
    {
2✔
3322
        auto t1 = steady_clock::now();
2✔
3323
        sg->compact();
2✔
3324
        auto t2 = steady_clock::now();
2✔
3325
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3326
    }
2✔
3327

3328
    {
2✔
3329
        ReadTransaction rt(sg);
2✔
3330
        auto table = rt.get_table("test");
2✔
3331

3332
        auto t1 = steady_clock::now();
2✔
3333

3334
        for (int j = 0; j < num_runs; ++j) {
4✔
3335
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3336
                table->get_object(ObjKey(key_values[i]));
200,000✔
3337
            }
200,000✔
3338
        }
2✔
3339

3340
        auto t2 = steady_clock::now();
2✔
3341

3342
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3343
                  << " ns/key" << std::endl;
2✔
3344
    }
2✔
3345

3346
    {
2✔
3347
        ReadTransaction rt(sg);
2✔
3348
        auto table = rt.get_table("test");
2✔
3349

3350
        auto t1 = steady_clock::now();
2✔
3351

3352
        for (int j = 0; j < num_runs; ++j) {
4✔
3353
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3354
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3355
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3356
            }
200,000✔
3357
        }
2✔
3358

3359
        auto t2 = steady_clock::now();
2✔
3360

3361
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3362
                  << " ns/key" << std::endl;
2✔
3363
    }
2✔
3364

3365
    {
2✔
3366
        ReadTransaction rt(sg);
2✔
3367
        auto table = rt.get_table("test");
2✔
3368

3369
        auto t1 = steady_clock::now();
2✔
3370

3371
        for (int j = 0; j < num_runs; ++j) {
4✔
3372
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3373
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3374
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3375
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3376
            }
200,000✔
3377
        }
2✔
3378

3379
        auto t2 = steady_clock::now();
2✔
3380

3381
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3382
                  << " ns/key" << std::endl;
2✔
3383
    }
2✔
3384

3385
    {
2✔
3386
        WriteTransaction wt(sg);
2✔
3387
        auto table = wt.get_table("test");
2✔
3388

3389
        auto t1 = steady_clock::now();
2✔
3390

3391
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3392
            Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3393
            o.set(c0, i << 2).set(c1, i << 1);
200,000✔
3394
        }
200,000✔
3395

3396
        auto t2 = steady_clock::now();
2✔
3397

3398
        std::cout << "   update time   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3399
                  << std::endl;
2✔
3400
        wt.commit();
2✔
3401
    }
2✔
3402

3403
    {
2✔
3404
        WriteTransaction wt(sg);
2✔
3405
        auto table = wt.get_table("test");
2✔
3406

3407
        auto t1 = steady_clock::now();
2✔
3408

3409
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3410
            table->remove_object(ObjKey(key_values[i]));
200,000✔
3411
#ifdef REALM_DEBUG
200,000✔
3412
            CHECK_EQUAL(table->size(), nb_rows - i - 1);
200,000✔
3413
            for (int j = i + 1; j < nb_rows; j += nb_rows / 100) {
10,299,800✔
3414
                Obj o = table->get_object(ObjKey(key_values[j]));
10,099,800✔
3415
                CHECK_EQUAL(j << 2, o.get<int64_t>(c0));
10,099,800✔
3416
                CHECK_EQUAL(j << 1, o.get<util::Optional<int64_t>>(c1));
10,099,800✔
3417
            }
10,099,800✔
3418
#endif
200,000✔
3419
        }
200,000✔
3420
        auto t2 = steady_clock::now();
2✔
3421
        std::cout << "   erase time    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3422
                  << std::endl;
2✔
3423

3424
        wt.commit();
2✔
3425
    }
2✔
3426

3427
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3428
}
2✔
3429

3430
TEST(Table_CollisionMapping)
3431
{
2✔
3432

3433
#if REALM_EXERCISE_OBJECT_ID_COLLISION
3434
    bool expect_collisions = true;
3435
#else
3436
    bool expect_collisions = false;
2✔
3437
#endif
2✔
3438

3439
    // This number corresponds to the mask used to calculate "optimistic"
3440
    // object IDs. See `ObjectIDProvider::get_optimistic_local_id_hashed`.
3441
    const size_t num_objects_with_guaranteed_collision = 0xff;
2✔
3442

3443
    SHARED_GROUP_TEST_PATH(path);
2✔
3444

3445
    {
2✔
3446
        DBRef sg = DB::create(path);
2✔
3447
        {
2✔
3448
            auto wt = sg->start_write();
2✔
3449
            TableRef t0 = wt->add_table_with_primary_key("class_t0", type_String, "pk");
2✔
3450

3451
            char buffer[12];
2✔
3452
            for (size_t i = 0; i < num_objects_with_guaranteed_collision; ++i) {
512✔
3453
                const char* in = reinterpret_cast<char*>(&i);
510✔
3454
                size_t len = base64_encode({in, sizeof(i)}, buffer);
510✔
3455

3456
                t0->create_object_with_primary_key(StringData{buffer, len});
510✔
3457
            }
510✔
3458
            wt->commit();
2✔
3459
        }
2✔
3460

3461
        {
2✔
3462
            ReadTransaction rt{sg};
2✔
3463
            ConstTableRef t0 = rt.get_table("class_t0");
2✔
3464
            // Check that at least one object exists where the 63rd bit is set.
3465
            size_t num_object_keys_with_63rd_bit_set = 0;
2✔
3466
            uint64_t bit63 = 0x4000000000000000;
2✔
3467
            for (Obj obj : *t0) {
510✔
3468
                if (obj.get_key().value & bit63)
510✔
3469
                    ++num_object_keys_with_63rd_bit_set;
×
3470
            }
510✔
3471
            CHECK(!expect_collisions || num_object_keys_with_63rd_bit_set > 0);
2!
3472
        }
2✔
3473
    }
2✔
3474

3475
    // Check that locally allocated IDs are properly persisted
3476
    {
2✔
3477
        DBRef sg_2 = DB::create(path);
2✔
3478
        {
2✔
3479
            WriteTransaction wt{sg_2};
2✔
3480
            TableRef t0 = wt.get_table("class_t0");
2✔
3481

3482
            // Make objects with primary keys that do not already exist but are guaranteed
3483
            // to cause further collisions.
3484
            char buffer[12];
2✔
3485
            for (size_t i = 0; i < num_objects_with_guaranteed_collision; ++i) {
512✔
3486
                size_t foo = num_objects_with_guaranteed_collision + i;
510✔
3487
                const char* in = reinterpret_cast<char*>(&foo);
510✔
3488
                size_t len = base64_encode({in, sizeof(foo)}, buffer);
510✔
3489

3490
                t0->create_object_with_primary_key(StringData{buffer, len});
510✔
3491
            }
510✔
3492
            wt.commit();
2✔
3493
        }
2✔
3494
        {
2✔
3495
            WriteTransaction wt{sg_2};
2✔
3496
            TableRef t0 = wt.get_table("class_t0");
2✔
3497

3498
            // Find an object with collision key
3499
            std::string pk;
2✔
3500
            ObjKey key;
2✔
3501
            uint64_t bit63 = 0x4000000000000000;
2✔
3502
            for (Obj obj : *t0) {
1,020✔
3503
                if (obj.get_key().value & bit63) {
1,020✔
3504
                    key = obj.get_key();
×
3505
                    pk = obj.get<String>("pk");
×
3506
                    obj.remove();
×
3507
                    break;
×
3508
                }
×
3509
            }
1,020✔
3510

3511
            if (key) {
2✔
3512
                // Insert object again - should get a different key
3513
                auto obj = t0->create_object_with_primary_key(pk);
×
3514
                CHECK_NOT_EQUAL(obj.get_key(), key);
×
3515
            }
×
3516

3517
            wt.commit();
2✔
3518
        }
2✔
3519
    }
2✔
3520
}
2✔
3521

3522
TEST(Table_CreateObjectWithPrimaryKeyDidCreate)
3523
{
2✔
3524
    SHARED_GROUP_TEST_PATH(path);
2✔
3525
    DBRef sg = DB::create(path);
2✔
3526

3527
    auto wt = sg->start_write();
2✔
3528
    TableRef string_table = wt->add_table_with_primary_key("string pk", type_String, "pk", true);
2✔
3529

3530
    bool did_create = false;
2✔
3531
    string_table->create_object_with_primary_key(StringData("1"), &did_create);
2✔
3532
    CHECK(did_create);
2✔
3533
    string_table->create_object_with_primary_key(StringData("1"), &did_create);
2✔
3534
    CHECK_NOT(did_create);
2✔
3535
    string_table->create_object_with_primary_key(StringData("2"), &did_create);
2✔
3536
    CHECK(did_create);
2✔
3537
    string_table->create_object_with_primary_key(StringData(), &did_create);
2✔
3538
    CHECK(did_create);
2✔
3539
    string_table->create_object_with_primary_key(StringData(), &did_create);
2✔
3540
    CHECK_NOT(did_create);
2✔
3541

3542
    TableRef int_table = wt->add_table_with_primary_key("int pk", type_Int, "pk", true);
2✔
3543

3544
    did_create = false;
2✔
3545
    int_table->create_object_with_primary_key(1, &did_create);
2✔
3546
    CHECK(did_create);
2✔
3547
    int_table->create_object_with_primary_key(1, &did_create);
2✔
3548
    CHECK_NOT(did_create);
2✔
3549
    int_table->create_object_with_primary_key(2, &did_create);
2✔
3550
    CHECK(did_create);
2✔
3551
    int_table->create_object_with_primary_key(util::Optional<int64_t>(), &did_create);
2✔
3552
    CHECK(did_create);
2✔
3553
    int_table->create_object_with_primary_key(util::Optional<int64_t>(), &did_create);
2✔
3554
    CHECK_NOT(did_create);
2✔
3555
}
2✔
3556

3557
TEST(Table_PrimaryKeyIndexBug)
3558
{
2✔
3559
    Group g;
2✔
3560
    TableRef table = g.add_table("table");
2✔
3561
    TableRef origin = g.add_table("origin");
2✔
3562
    auto col_id = table->add_column(type_String, "id");
2✔
3563
    auto col_link = origin->add_column(*table, "link");
2✔
3564
    table->add_search_index(col_id);
2✔
3565
    // Create an object where bit 62 is set in the ObkKey
3566
    auto obj = table->create_object(ObjKey(0x40083f0f9b0fb598)).set(col_id, "hallo");
2✔
3567
    origin->create_object().set(col_link, obj.get_key());
2✔
3568

3569
    auto q = origin->link(col_link).column<String>(col_id) == "hallo";
2✔
3570
    auto cnt = q.count();
2✔
3571
    CHECK_EQUAL(cnt, 1);
2✔
3572
}
2✔
3573

3574
NONCONCURRENT_TEST(Table_PrimaryKeyString)
3575
{
2✔
3576
#ifdef REALM_DEBUG
2✔
3577
    int nb_rows = 1000;
2✔
3578
#else
3579
    int nb_rows = 100000;
3580
#endif
3581
    SHARED_GROUP_TEST_PATH(path);
2✔
3582

3583
    DBRef sg = DB::create(path);
2✔
3584
    auto wt = sg->start_write();
2✔
3585
    TableRef t0 = wt->add_table_with_primary_key("class_t0", type_String, "pk");
2✔
3586
    auto pk_col = t0->get_primary_key_column();
2✔
3587

3588
    auto t1 = steady_clock::now();
2✔
3589
    CALLGRIND_START_INSTRUMENTATION;
2✔
3590

3591
    for (int i = 0; i < nb_rows; ++i) {
2,002✔
3592
        std::string pk = "KEY_" + util::to_string(i);
2,000✔
3593
        t0->create_object_with_primary_key(pk);
2,000✔
3594
    }
2,000✔
3595

3596
    auto t2 = steady_clock::now();
2✔
3597

3598
    for (int i = 0; i < nb_rows; ++i) {
2,002✔
3599
        std::string pk = "KEY_" + util::to_string(i);
2,000✔
3600
        ObjKey k = t0->find_first(pk_col, StringData(pk));
2,000✔
3601
#ifdef REALM_DEBUG
2,000✔
3602
        CHECK(t0->is_valid(k));
2,000✔
3603
#else
3604
        CHECK(k);
3605
#endif
3606
    }
2,000✔
3607

3608
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3609
    auto t3 = steady_clock::now();
2✔
3610
    std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3611
              << std::endl;
2✔
3612
    std::cout << "   lookup time: " << duration_cast<nanoseconds>(t3 - t2).count() / nb_rows << " ns/key"
2✔
3613
              << std::endl;
2✔
3614
    wt->commit();
2✔
3615
}
2✔
3616

3617
TEST(Table_3)
3618
{
2✔
3619
    Table table;
2✔
3620

3621
    auto col_int0 = table.add_column(type_Int, "first");
2✔
3622
    auto col_int1 = table.add_column(type_Int, "second");
2✔
3623
    auto col_bool2 = table.add_column(type_Bool, "third");
2✔
3624
    auto col_int3 = table.add_column(type_Int, "fourth");
2✔
3625

3626
    for (int64_t i = 0; i < 100; ++i) {
202✔
3627
        table.create_object(ObjKey(i)).set_all(i, 10, true, int(Wed));
200✔
3628
    }
200✔
3629

3630
    // Test column searching
3631
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int0, 0));
2✔
3632
    CHECK_EQUAL(ObjKey(50), table.find_first_int(col_int0, 50));
2✔
3633
    CHECK_EQUAL(null_key, table.find_first_int(col_int0, 500));
2✔
3634
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int1, 10));
2✔
3635
    CHECK_EQUAL(null_key, table.find_first_int(col_int1, 100));
2✔
3636
    CHECK_EQUAL(ObjKey(0), table.find_first_bool(col_bool2, true));
2✔
3637
    CHECK_EQUAL(null_key, table.find_first_bool(col_bool2, false));
2✔
3638
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int3, Wed));
2✔
3639
    CHECK_EQUAL(null_key, table.find_first_int(col_int3, Mon));
2✔
3640

3641
#ifdef REALM_DEBUG
2✔
3642
    table.verify();
2✔
3643
#endif
2✔
3644
}
2✔
3645

3646

3647
TEST(Table_4)
3648
{
2✔
3649
    Table table;
2✔
3650
    auto c0 = table.add_column(type_String, "strings");
2✔
3651
    const char* hello_hello = "HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHello";
2✔
3652

3653
    table.create_object(ObjKey(5)).set(c0, "Hello");
2✔
3654
    table.create_object(ObjKey(7)).set(c0, hello_hello);
2✔
3655

3656
    CHECK_EQUAL(hello_hello, table.get_object(ObjKey(7)).get<String>(c0));
2✔
3657

3658
    // Test string column searching
3659
    CHECK_EQUAL(ObjKey(7), table.find_first_string(c0, hello_hello));
2✔
3660
    CHECK_EQUAL(null_key, table.find_first_string(c0, "Foo"));
2✔
3661

3662
#ifdef REALM_DEBUG
2✔
3663
    table.verify();
2✔
3664
#endif
2✔
3665
}
2✔
3666

3667
// Very basic sanity check of search index when you add, remove and set objects
3668
TEST(Table_SearchIndexFindFirst)
3669
{
2✔
3670
    Table table;
2✔
3671

3672
    auto c1 = table.add_column(type_Int, "a");
2✔
3673
    auto c2 = table.add_column(type_Int, "b", true);
2✔
3674
    auto c3 = table.add_column(type_String, "c");
2✔
3675
    auto c4 = table.add_column(type_String, "d", true);
2✔
3676
    auto c5 = table.add_column(type_Bool, "e");
2✔
3677
    auto c6 = table.add_column(type_Bool, "f", true);
2✔
3678
    auto c7 = table.add_column(type_Timestamp, "g");
2✔
3679
    auto c8 = table.add_column(type_Timestamp, "h", true);
2✔
3680

3681
    Obj o0 = table.create_object();
2✔
3682
    Obj o1 = table.create_object();
2✔
3683
    Obj o2 = table.create_object();
2✔
3684
    Obj o3 = table.create_object();
2✔
3685

3686
    o0.set_all(100, 100, "100", "100", false, false, Timestamp(100, 100), Timestamp(100, 100));
2✔
3687
    o1.set_all(200, 200, "200", "200", true, true, Timestamp(200, 200), Timestamp(200, 200));
2✔
3688
    o2.set_all(200, 200, "200", "200", true, true, Timestamp(200, 200), Timestamp(200, 200));
2✔
3689
    CHECK(o3.is_null(c2));
2✔
3690
    CHECK(o3.is_null(c4));
2✔
3691
    CHECK(o3.is_null(c6));
2✔
3692
    CHECK(o3.is_null(c8));
2✔
3693

3694
    table.add_search_index(c1);
2✔
3695
    table.add_search_index(c2);
2✔
3696
    table.add_search_index(c3);
2✔
3697
    table.add_search_index(c4);
2✔
3698
    table.add_search_index(c5);
2✔
3699
    table.add_search_index(c6);
2✔
3700
    table.add_search_index(c7);
2✔
3701
    table.add_search_index(c8);
2✔
3702

3703
    // Non-nullable integers
3704
    CHECK_EQUAL(table.find_first_int(c1, 100), o0.get_key());
2✔
3705
    CHECK_EQUAL(table.find_first_int(c1, 200), o1.get_key());
2✔
3706
    // Uninitialized non-nullable integers equal 0
3707
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3708

3709
    // Nullable integers
3710
    CHECK_EQUAL(table.find_first_int(c2, 100), o0.get_key());
2✔
3711
    CHECK_EQUAL(table.find_first_int(c2, 200), o1.get_key());
2✔
3712
    // FIXME: Waiting for fix outside scope of search index PR
3713
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
3714

3715
    // Non-nullable strings
3716
    CHECK_EQUAL(table.find_first_string(c3, "100"), o0.get_key());
2✔
3717
    CHECK_EQUAL(table.find_first_string(c3, "200"), o1.get_key());
2✔
3718
    // Uninitialized non-nullable strings equal ""
3719
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3720

3721
    // Nullable strings
3722
    CHECK_EQUAL(table.find_first_string(c4, "100"), o0.get_key());
2✔
3723
    CHECK_EQUAL(table.find_first_string(c4, "200"), o1.get_key());
2✔
3724
    // FIXME: Waiting for fix outside scope of search index PR
3725
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
3726

3727
    // Non-nullable bools
3728
    CHECK_EQUAL(table.find_first_bool(c5, false), o0.get_key());
2✔
3729
    CHECK_EQUAL(table.find_first_bool(c5, true), o1.get_key());
2✔
3730

3731
    // Nullable bools
3732
    CHECK_EQUAL(table.find_first_bool(c6, false), o0.get_key());
2✔
3733
    CHECK_EQUAL(table.find_first_bool(c6, true), o1.get_key());
2✔
3734
    // FIXME: Waiting for fix outside scope of search index PR
3735
    // CHECK_EQUAL(table.find_first_null(5), o3.get_key());
3736

3737
    // Non-nullable Timestamp
3738
    CHECK_EQUAL(table.find_first_timestamp(c7, Timestamp(100, 100)), o0.get_key());
2✔
3739
    CHECK_EQUAL(table.find_first_timestamp(c7, Timestamp(200, 200)), o1.get_key());
2✔
3740

3741
    // Nullable Timestamp
3742
    CHECK_EQUAL(table.find_first_timestamp(c8, Timestamp(100, 100)), o0.get_key());
2✔
3743
    CHECK_EQUAL(table.find_first_timestamp(c8, Timestamp(200, 200)), o1.get_key());
2✔
3744
    // FIXME: Waiting for fix outside scope of search index PR
3745
    // CHECK_EQUAL(table.find_first_null(7), o3.get_key());
3746

3747
    // Remove object and see if things still work
3748
    // *******************************************************************************
3749
    table.remove_object(o0.get_key());
2✔
3750

3751
    // Integers
3752
    CHECK_EQUAL(table.find_first_int(c1, 100), null_key);
2✔
3753
    CHECK_EQUAL(table.find_first_int(c1, 200), o1.get_key());
2✔
3754
    // Uninitialized non-nullable integers equal 0
3755
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3756

3757
    CHECK_EQUAL(table.find_first_int(c2, 200), o1.get_key());
2✔
3758
    // FIXME: Waiting for fix outside scope of search index PR
3759
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
3760

3761
    // Non-nullable strings
3762
    CHECK_EQUAL(table.find_first_string(c3, "100"), null_key);
2✔
3763
    CHECK_EQUAL(table.find_first_string(c3, "200"), o1.get_key());
2✔
3764
    // Uninitialized non-nullable strings equal ""
3765
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3766

3767
    // Nullable strings
3768
    CHECK_EQUAL(table.find_first_string(c4, "100"), null_key);
2✔
3769
    CHECK_EQUAL(table.find_first_string(c4, "200"), o1.get_key());
2✔
3770
    // FIXME: Waiting for fix outside scope of search index PR
3771
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
3772

3773
    // Non-nullable bools
3774
    // default value for non-nullable bool is false, so o3 is a match
3775
    CHECK_EQUAL(table.find_first_bool(c5, false), o3.get_key());
2✔
3776
    CHECK_EQUAL(table.find_first_bool(c5, true), o1.get_key());
2✔
3777

3778
    // Nullable bools
3779
    CHECK_EQUAL(table.find_first_bool(c6, false), null_key);
2✔
3780
    CHECK_EQUAL(table.find_first_bool(c6, true), o1.get_key());
2✔
3781

3782
    // Call "set" and see if things still work
3783
    // *******************************************************************************
3784
    o1.set_all(500, 500, "500", "500");
2✔
3785
    o2.set_all(600, 600, "600", "600");
2✔
3786

3787
    CHECK_EQUAL(table.find_first_int(c1, 500), o1.get_key());
2✔
3788
    CHECK_EQUAL(table.find_first_int(c1, 600), o2.get_key());
2✔
3789
    // Uninitialized non-nullable integers equal 0
3790
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3791
    CHECK_EQUAL(table.find_first_int(c2, 500), o1.get_key());
2✔
3792
    // FIXME: Waiting for fix outside scope of search index PR
3793
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
3794

3795
    // Non-nullable strings
3796
    CHECK_EQUAL(table.find_first_string(c3, "500"), o1.get_key());
2✔
3797
    CHECK_EQUAL(table.find_first_string(c3, "600"), o2.get_key());
2✔
3798
    // Uninitialized non-nullable strings equal ""
3799
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3800

3801
    // Nullable strings
3802
    CHECK_EQUAL(table.find_first_string(c4, "500"), o1.get_key());
2✔
3803
    CHECK_EQUAL(table.find_first_string(c4, "600"), o2.get_key());
2✔
3804
    // FIXME: Waiting for fix outside scope of search index PR
3805
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
3806

3807
    // Remove four of the indexes through remove_search_index() call. Let other four remain to see
3808
    // if they leak memory when Table goes out of scope (needs leak detector)
3809
    table.remove_search_index(c1);
2✔
3810
    table.remove_search_index(c2);
2✔
3811
    table.remove_search_index(c3);
2✔
3812
    table.remove_search_index(c4);
2✔
3813
}
2✔
3814

3815
TEST(Table_SearchIndexFindAll)
3816
{
2✔
3817
    Table table;
2✔
3818
    auto col_int = table.add_column(type_Int, "integers");
2✔
3819
    auto col_str = table.add_column(type_String, "strings");
2✔
3820
    // Add index before creating objects
3821
    table.add_search_index(col_int);
2✔
3822
    table.add_search_index(col_str);
2✔
3823

3824
    ObjKeys keys;
2✔
3825
    table.create_objects(100, keys);
2✔
3826
    for (auto o : table) {
200✔
3827
        int64_t key_value = o.get_key().value;
200✔
3828
        o.set(col_int, key_value);
200✔
3829
        // When node size is 4 the objects with "Hello" will be in 2 clusters
3830
        if (key_value > 21 && key_value < 28) {
200✔
3831
            o.set(col_str, "Hello");
12✔
3832
        }
12✔
3833
    }
200✔
3834

3835
    auto tv = table.find_all_string(col_str, "Hello");
2✔
3836
    CHECK_EQUAL(tv.size(), 6);
2✔
3837
}
2✔
3838

3839
TEST(Table_QueryNullOnNonNullSearchIndex)
3840
{
2✔
3841
    Group g;
2✔
3842
    TableRef t = g.add_table("table");
2✔
3843
    ColKey col0 = t->add_column(type_Int, "value", false);
2✔
3844
    ColKey col_link = t->add_column(*t, "link");
2✔
3845
    t->add_search_index(col0);
2✔
3846

3847
    std::vector<Int> values = {0, 9, 4, 2, 7};
2✔
3848

3849
    for (Int v : values) {
10✔
3850
        auto obj = t->create_object();
10✔
3851
        obj.set(col0, v);
10✔
3852
        obj.set(col_link, obj.get_key());
10✔
3853
    }
10✔
3854

3855
    for (Int v : values) {
10✔
3856
        Query q0 = t->column<Int>(col0) == v;
10✔
3857
        CHECK_EQUAL(q0.count(), 1);
10✔
3858
        Query q1 = t->link(col_link).column<Int>(col0) == v;
10✔
3859
        CHECK_EQUAL(q1.count(), 1);
10✔
3860
        Query q2 = t->link(col_link).link(col_link).column<Int>(col0) == v;
10✔
3861
        CHECK_EQUAL(q2.count(), 1);
10✔
3862
        Query q3 = t->where().equal(col0, v);
10✔
3863
        CHECK_EQUAL(q3.count(), 1);
10✔
3864
    }
10✔
3865

3866
    {
2✔
3867
        Query q0 = t->column<Int>(col0) == realm::null();
2✔
3868
        CHECK_EQUAL(q0.count(), 0);
2✔
3869
        Query q1 = t->link(col_link).column<Int>(col0) == realm::null();
2✔
3870
        CHECK_EQUAL(q1.count(), 0);
2✔
3871
        Query q2 = t->link(col_link).link(col_link).column<Int>(col0) == realm::null();
2✔
3872
        CHECK_EQUAL(q2.count(), 0);
2✔
3873
    }
2✔
3874
}
2✔
3875

3876
TEST_TYPES(Table_QuerySearchEqualsNull, Prop<Int>, Prop<double>, Prop<float>, Prop<ObjectId>, Prop<Timestamp>,
3877
           Prop<StringData>, Prop<BinaryData>, Prop<Decimal128>, Prop<UUID>, Nullable<Int>, Nullable<double>,
3878
           Nullable<float>, Nullable<ObjectId>, Nullable<Timestamp>, Nullable<StringData>, Nullable<BinaryData>,
3879
           Nullable<Decimal128>, Nullable<UUID>, Indexed<Int>, Indexed<ObjectId>, Indexed<Timestamp>,
3880
           Indexed<StringData>, Indexed<UUID>, NullableIndexed<Int>, NullableIndexed<ObjectId>,
3881
           NullableIndexed<Timestamp>, NullableIndexed<StringData>, NullableIndexed<UUID>)
3882
{
56✔
3883
    using type = typename TEST_TYPE::type;
56✔
3884
    using underlying_type = typename TEST_TYPE::underlying_type;
56✔
3885
    TestValueGenerator gen;
56✔
3886
    Group g;
56✔
3887
    TableRef t = g.add_table("table");
56✔
3888
    ColKey col0 = t->add_column(TEST_TYPE::data_type, "value", TEST_TYPE::is_nullable);
56✔
3889
    ColKey col1 = t->add_column_list(TEST_TYPE::data_type, "values", TEST_TYPE::is_nullable);
56✔
3890
    ColKey col_link = t->add_column(*t, "link");
56✔
3891

3892
    if constexpr (TEST_TYPE::is_indexed) {
56✔
3893
        t->add_search_index(col0);
20✔
3894
    }
20✔
3895

3896
    std::vector<underlying_type> values = gen.values_from_int<underlying_type>({9, 4, 2, 7});
56✔
3897
    underlying_type default_non_null_value = TEST_TYPE::default_non_nullable_value();
56✔
3898
    values.push_back(default_non_null_value);
56✔
3899
    std::vector<size_t> indices;
56✔
3900

3901
    for (underlying_type v : values) {
280✔
3902
        auto obj = t->create_object();
280✔
3903
        obj.set(col0, v);
280✔
3904
        obj.set(col_link, obj.get_key());
280✔
3905
    }
280✔
3906

3907
    constexpr size_t num_defaults_added = 3;
56✔
3908
    for (size_t i = 0; i < num_defaults_added; ++i) {
224✔
3909
        auto obj = t->create_object();
168✔
3910
        obj.set(col_link, obj.get_key());
168✔
3911
    }
168✔
3912
    auto list = t->begin()->get_list<type>(col1);
56✔
3913
    for (underlying_type v : values) {
280✔
3914
        list.add(v);
280✔
3915
    }
280✔
3916

3917
    constexpr size_t num_nulls = TEST_TYPE::is_nullable ? num_defaults_added : 0;
56✔
3918
    constexpr size_t num_default_non_nullables = TEST_TYPE::is_nullable ? 1 : num_defaults_added + 1;
56✔
3919

3920
    for (underlying_type v : values) {
280✔
3921
        const size_t num_expected = (v == default_non_null_value ? num_default_non_nullables : 1);
280✔
3922
        Query q0 = t->column<underlying_type>(col0) == v;
280✔
3923
        CHECK_EQUAL(q0.count(), num_expected);
280✔
3924
        Query q1 = t->link(col_link).column<underlying_type>(col0) == v;
280✔
3925
        CHECK_EQUAL(q1.count(), num_expected);
280✔
3926
        Query q2 = t->link(col_link).link(col_link).column<underlying_type>(col0) == v;
280✔
3927
        CHECK_EQUAL(q2.count(), num_expected);
280✔
3928
        Query q3 = t->where().equal(col0, v);
280✔
3929
        CHECK_EQUAL(q3.count(), num_expected);
280✔
3930
        Query q4 = t->column<Lst<underlying_type>>(col1) == v;
280✔
3931
        CHECK_EQUAL(q4.count(), 1);
280✔
3932
    }
280✔
3933

3934
    {
56✔
3935
        Query q0 = t->column<underlying_type>(col0) == realm::null();
56✔
3936
        CHECK_EQUAL(q0.count(), num_nulls);
56✔
3937
        Query q1 = t->link(col_link).column<underlying_type>(col0) == realm::null();
56✔
3938
        CHECK_EQUAL(q1.count(), num_nulls);
56✔
3939
        Query q2 = t->link(col_link).link(col_link).column<underlying_type>(col0) == realm::null();
56✔
3940
        CHECK_EQUAL(q2.count(), num_nulls);
56✔
3941
        Query q3 = t->column<underlying_type>(col0) == default_non_null_value;
56✔
3942
        CHECK_EQUAL(q3.count(), num_default_non_nullables);
56✔
3943
        Query q4 = t->link(col_link).column<underlying_type>(col0) == default_non_null_value;
56✔
3944
        CHECK_EQUAL(q4.count(), num_default_non_nullables);
56✔
3945
        Query q5 = t->link(col_link).link(col_link).column<underlying_type>(col0) == default_non_null_value;
56✔
3946
        CHECK_EQUAL(q5.count(), num_default_non_nullables);
56✔
3947
    }
56✔
3948
}
56✔
3949

3950
namespace {
3951

3952
template <class T, bool nullable>
3953
struct Tester {
3954
    using T2 = typename util::RemoveOptional<T>::type;
3955

3956
    static ColKey col;
3957

3958
    static std::vector<ObjKey> find_all_reference(TableRef table, T v)
3959
    {
14,544✔
3960
        std::vector<ObjKey> res;
14,544✔
3961
        Table::Iterator it = table->begin();
14,544✔
3962
        while (it != table->end()) {
1,223,401✔
3963
            if (!it->is_null(col)) {
1,208,857✔
3964
                T v2 = it->get<T>(col);
1,095,392✔
3965
                if (v == v2) {
1,095,392✔
3966
                    res.push_back(it->get_key());
255,348✔
3967
                }
255,348✔
3968
            }
1,095,392✔
3969
            ++it;
1,208,857✔
3970
        };
1,208,857✔
3971
        // res is returned with nrvo optimization
3972
        return res;
14,544✔
3973
    }
14,544✔
3974

3975
    static void validate(TableRef table)
3976
    {
16,000✔
3977
        Table::Iterator it = table->begin();
16,000✔
3978

3979
        if (it != table->end()) {
16,000✔
3980
            auto v = it->get<T>(col);
15,867✔
3981

3982
            if (!it->is_null(col)) {
15,867✔
3983
                std::vector<ObjKey> res;
14,544✔
3984
                table->get_search_index(col)->find_all(res, v, false);
14,544✔
3985
                std::vector<ObjKey> ref = find_all_reference(table, v);
14,544✔
3986

3987
                size_t a = ref.size();
14,544✔
3988
                size_t b = res.size();
14,544✔
3989

3990
                REALM_ASSERT(a == b);
14,544✔
3991
            }
14,544✔
3992
        }
15,867✔
3993
    }
16,000✔
3994

3995
    static void run(DBRef db, realm::DataType type)
3996
    {
16✔
3997
        auto trans = db->start_write();
16✔
3998
        auto table = trans->add_table("my_table");
16✔
3999
        col = table->add_column(type, "name", nullable);
16✔
4000
        table->add_search_index(col);
16✔
4001
        const size_t iters = 1000;
16✔
4002

4003
        bool add_trend = true;
16✔
4004

4005
        for (size_t iter = 0; iter < iters; iter++) {
16,016✔
4006

4007
            if (iter == iters / 2) {
16,000✔
4008
                add_trend = false;
16✔
4009
            }
16✔
4010

4011
            // Add object (with 60% probability, so we grow the object count over time)
4012
            if (fastrand(100) < (add_trend ? 80 : 20)) {
16,000✔
4013
                Obj o = table->create_object();
7,976✔
4014
                bool set_to_null = fastrand(100) < 20;
7,976✔
4015

4016
                if (!set_to_null) {
7,976✔
4017
                    auto t = create();
6,412✔
4018
                    o.set<T2>(col, t);
6,412✔
4019
                }
6,412✔
4020
            }
7,976✔
4021

4022
            // Remove random object
4023
            if (fastrand(100) < 50 && table->size() > 0) {
16,000✔
4024
                Table::Iterator it = table->begin();
7,827✔
4025
                auto r = fastrand(table->size() - 1);
7,827✔
4026
                // FIXME: Is there a faster way to pick a random object?
4027
                for (unsigned t = 0; t < r; t++) {
329,975✔
4028
                    ++it;
322,148✔
4029
                }
322,148✔
4030
                Obj o = *it;
7,827✔
4031
                table->remove_object(o.get_key());
7,827✔
4032
            }
7,827✔
4033

4034
            // Edit random object
4035
            if (table->size() > 0) {
16,000✔
4036
                Table::Iterator it = table->begin();
15,867✔
4037
                auto r = fastrand(table->size() - 1);
15,867✔
4038
                // FIXME: Is there a faster way to pick a random object?
4039
                for (unsigned t = 0; t < r; t++) {
659,284✔
4040
                    ++it;
643,417✔
4041
                }
643,417✔
4042
                Obj o = *it;
15,867✔
4043
                bool set_to_null = fastrand(100) < 20;
15,867✔
4044
                if (set_to_null && table->is_nullable(col)) {
15,867✔
4045
                    o.set_null(col);
1,582✔
4046
                }
1,582✔
4047
                else {
14,285✔
4048
                    auto t = create();
14,285✔
4049
                    o.set<T2>(col, t);
14,285✔
4050
                }
14,285✔
4051
            }
15,867✔
4052

4053
            if (iter % (iters / 1000) == 0) {
16,000✔
4054
                validate(table);
16,000✔
4055
            }
16,000✔
4056
        }
16,000✔
4057
        trans->rollback();
16✔
4058
    }
16✔
4059

4060

4061
    // Create random data element of any type supported by the search index
4062
    template <typename Type = T2>
4063
    typename std::enable_if<std::is_same<Type, StringData>::value, std::string>::type static create()
4064
    {
5,140✔
4065
        std::string s = realm::util::to_string(fastrand(5));
5,140✔
4066
        return s;
5,140✔
4067
    }
5,140✔
4068
    template <typename Type = T2>
4069
    typename std::enable_if<std::is_same<Type, Timestamp>::value, T2>::type static create()
4070
    {
5,156✔
4071
        return Timestamp(fastrand(3), int32_t(fastrand(3)));
5,156✔
4072
    }
5,156✔
4073
    template <typename Type = T2>
4074
    typename std::enable_if<std::is_same<Type, int64_t>::value, T2>::type static create()
4075
    {
5,199✔
4076
        return fastrand(5);
5,199✔
4077
    }
5,199✔
4078

4079
    template <typename Type = T2>
4080
    typename std::enable_if<std::is_same<Type, bool>::value, T2>::type static create()
4081
    {
5,202✔
4082
        return fastrand(100) > 50;
5,202✔
4083
    }
5,202✔
4084
};
4085

4086
template <class T, bool nullable>
4087
ColKey Tester<T, nullable>::col;
4088
} // namespace
4089

4090
// The run() method will first add lots of objects, and then remove them. This will test
4091
// both node splits and empty leaf destruction and get good search index code coverage
4092
TEST(Table_search_index_fuzzer)
4093
{
2✔
4094
    // Syntax for Tester<T, nullable>:
4095
    // T:         Type that must be used in calls too Obj::get<T>
4096
    // nullable:  If the columns must be is nullable or not
4097
    // Obj::set() will be automatically be called with set<RemoveOptional<T>>()
4098

4099
    SHARED_GROUP_TEST_PATH(path);
2✔
4100
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4101
    auto db = DB::create(*hist, path);
2✔
4102
    Tester<bool, false>::run(db, type_Bool);
2✔
4103
    Tester<Optional<bool>, true>::run(db, type_Bool);
2✔
4104

4105
    Tester<int64_t, false>::run(db, type_Int);
2✔
4106
    Tester<Optional<int64_t>, true>::run(db, type_Int);
2✔
4107

4108
    // Self-contained null state
4109
    Tester<Timestamp, false>::run(db, type_Timestamp);
2✔
4110
    Tester<Timestamp, true>::run(db, type_Timestamp);
2✔
4111

4112
    // Self-contained null state
4113
    Tester<StringData, true>::run(db, type_String);
2✔
4114
    Tester<StringData, false>::run(db, type_String);
2✔
4115
}
2✔
4116

4117
TEST(Table_StaleColumnKey)
4118
{
2✔
4119
    Table table;
2✔
4120

4121
    auto col = table.add_column(type_Int, "age");
2✔
4122

4123
    Obj obj = table.create_object();
2✔
4124
    obj.set(col, 5);
2✔
4125

4126
    table.remove_column(col);
2✔
4127
    // col is now obsolete
4128
    table.add_column(type_Int, "score");
2✔
4129
    CHECK_THROW_ANY(obj.get<Int>(col));
2✔
4130
}
2✔
4131

4132
TEST(Table_KeysRow)
4133
{
2✔
4134
    Table table;
2✔
4135
    auto col_int = table.add_column(type_Int, "int");
2✔
4136
    auto col_string = table.add_column(type_String, "string", true);
2✔
4137
    table.add_search_index(col_int);
2✔
4138
    table.add_search_index(col_string);
2✔
4139

4140
    table.create_object(ObjKey(7), {{col_int, 123}, {col_string, "Hello, "}});
2✔
4141
    table.create_object(ObjKey(9), {{col_int, 456}, {col_string, StringData()}});
2✔
4142

4143
    auto i = table.find_first_int(col_int, 123);
2✔
4144
    CHECK_EQUAL(i, ObjKey(7));
2✔
4145
    i = table.find_first_int(col_int, 456);
2✔
4146
    CHECK_EQUAL(i, ObjKey(9));
2✔
4147

4148
    i = table.find_first_string(col_string, "Hello, ");
2✔
4149
    CHECK_EQUAL(i, ObjKey(7));
2✔
4150
    i = table.find_first_string(col_string, StringData());
2✔
4151
    CHECK_EQUAL(i, ObjKey(9));
2✔
4152
}
2✔
4153

4154
template <typename T>
4155
T generate_value()
4156
{
31,418✔
4157
    return test_util::random_int<T>();
31,418✔
4158
}
31,418✔
4159

4160
template <>
4161
std::string generate_value()
4162
{
62,841✔
4163
    std::string str;
62,841✔
4164
    str.resize(31);
62,841✔
4165
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), &test_util::random_int<char>);
62,841✔
4166
    return str;
62,841✔
4167
}
62,841✔
4168

4169
template <>
4170
bool generate_value()
4171
{
31,403✔
4172
    return test_util::random_int<int>() & 0x1;
31,403✔
4173
}
31,403✔
4174
template <>
4175
float generate_value()
4176
{
31,460✔
4177
    return float(1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000)));
31,460✔
4178
}
31,460✔
4179
template <>
4180
double generate_value()
4181
{
31,411✔
4182
    return 1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000));
31,411✔
4183
}
31,411✔
4184
template <>
4185
Timestamp generate_value()
4186
{
31,455✔
4187
    return Timestamp(test_util::random_int<int>(0, 1000000), test_util::random_int<int>(0, 1000000000));
31,455✔
4188
}
31,455✔
4189
template <>
4190
Decimal128 generate_value()
4191
{
31,436✔
4192
    return Decimal128(test_util::random_int<int>(-100000, 100000));
31,436✔
4193
}
31,436✔
4194
template <>
4195
ObjectId generate_value()
4196
{
31,441✔
4197
    return ObjectId::gen();
31,441✔
4198
}
31,441✔
4199
template <>
4200
UUID generate_value()
4201
{
31,501✔
4202
    std::string str;
31,501✔
4203
    str.resize(36);
31,501✔
4204
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), []() -> char {
1,134,036✔
4205
        char c = test_util::random_int<char>(0, 15);
1,134,036✔
4206
        return c >= 10 ? (c - 10 + 'a') : (c + '0');
1,134,036✔
4207
    });
1,134,036✔
4208
    str.at(8) = '-';
31,501✔
4209
    str.at(13) = '-';
31,501✔
4210
    str.at(18) = '-';
31,501✔
4211
    str.at(23) = '-';
31,501✔
4212
    return UUID(str.c_str());
31,501✔
4213
}
31,501✔
4214

4215
// helper object taking care of destroying memory underlying StringData and BinaryData
4216
// just a passthrough for other types
4217
template <typename T>
4218
struct managed {
4219
    T value;
4220
};
4221

4222
template <typename T>
4223
struct ManagedStorage {
4224
    std::string storage;
4225
    T value;
4226

4227
    ManagedStorage() {}
40,160✔
4228
    ManagedStorage(null) {}
3,317✔
4229
    ManagedStorage(std::string&& v)
4230
        : storage(std::move(v))
31,466✔
4231
        , value(storage)
31,466✔
4232
    {
62,843✔
4233
    }
62,843✔
4234
    ManagedStorage(const ManagedStorage& other)
4235
    {
137,308✔
4236
        *this = other;
137,308✔
4237
    }
137,308✔
4238
    ManagedStorage(ManagedStorage&& other)
4239
    {
16,792✔
4240
        *this = std::move(other);
16,792✔
4241
    }
16,792✔
4242

4243
    ManagedStorage(T v)
4244
    {
16,160✔
4245
        if (v) {
16,160✔
4246
            if (v.size()) {
15,761✔
4247
                storage.assign(v.data(), v.data() + v.size());
15,356✔
4248
            }
15,356✔
4249
            value = T(storage);
15,761✔
4250
        }
15,761✔
4251
    }
16,160✔
4252
    ManagedStorage& operator=(const ManagedStorage& other)
4253
    {
138,103✔
4254
        storage = other.storage;
138,103✔
4255
        value = other.value ? T(storage) : T();
138,103✔
4256
        return *this;
138,103✔
4257
    }
138,103✔
4258
    ManagedStorage& operator=(ManagedStorage&& other)
4259
    {
2,763,843✔
4260
        storage = std::move(other.storage);
2,763,843✔
4261
        value = other.value ? T(storage) : T();
2,763,843✔
4262
        return *this;
2,763,843✔
4263
    }
2,763,843✔
4264
};
4265

4266
template <>
4267
struct managed<StringData> : ManagedStorage<StringData> {
4268
    using ManagedStorage::ManagedStorage;
4269
};
4270
template <>
4271
struct managed<BinaryData> : ManagedStorage<BinaryData> {
4272
    using ManagedStorage::ManagedStorage;
4273
};
4274

4275

4276
template <typename T>
4277
void check_values(TestContext& test_context, Lst<T>& lst, std::vector<managed<T>>& reference)
4278
{
400✔
4279
    CHECK_EQUAL(lst.size(), reference.size());
400✔
4280
    for (unsigned j = 0; j < reference.size(); ++j)
341,440✔
4281
        CHECK_EQUAL(lst.get(j), reference[j].value);
341,040✔
4282
}
400✔
4283

4284
template <typename T>
4285
struct generator {
4286
    static managed<T> get(bool optional)
4287
    {
163,000✔
4288
        if (optional && (test_util::random_int<int>() % 10) == 0) {
163,000!
4289
            return managed<T>{T()};
4,613✔
4290
        }
4,613✔
4291
        else {
158,387✔
4292
            return managed<T>{generate_value<T>()};
158,387✔
4293
        }
158,387✔
4294
    }
163,000✔
4295
};
4296

4297
template <>
4298
struct generator<StringData> {
4299
    static managed<StringData> get(bool optional)
4300
    {
33,080✔
4301
        if (optional && (test_util::random_int<int>() % 10) == 0) {
33,080✔
4302
            return managed<StringData>(null());
1,699✔
4303
        }
1,699✔
4304
        else {
31,381✔
4305
            return generate_value<std::string>();
31,381✔
4306
        }
31,381✔
4307
    }
33,080✔
4308
};
4309

4310
template <>
4311
struct generator<BinaryData> {
4312
    static managed<BinaryData> get(bool optional)
4313
    {
33,077✔
4314
        if (optional && (test_util::random_int<int>() % 10) == 0) {
33,077✔
4315
            return managed<BinaryData>(null());
1,618✔
4316
        }
1,618✔
4317
        else {
31,459✔
4318
            return generate_value<std::string>();
31,459✔
4319
        }
31,459✔
4320
    }
33,077✔
4321
};
4322

4323
template <>
4324
struct generator<ObjectId> {
4325
    static managed<ObjectId> get(bool)
4326
    {
16,540✔
4327
        return managed<ObjectId>{generate_value<ObjectId>()};
16,540✔
4328
    }
16,540✔
4329
};
4330

4331
template <typename T>
4332
struct generator<Optional<T>> {
4333
    static managed<Optional<T>> get(bool)
4334
    {
85,100✔
4335
        if ((test_util::random_int<int>() % 10) == 0)
85,100✔
4336
            return managed<Optional<T>>{Optional<T>()};
8,502✔
4337
        else
76,598✔
4338
            return managed<Optional<T>>{generate_value<T>()};
76,598✔
4339
    }
85,100✔
4340
};
4341

4342
// specialize for Optional<StringData> and Optional<BinaryData> just to trigger errors if ever used
4343
template <>
4344
struct generator<Optional<StringData>> {};
4345
template <>
4346
struct generator<Optional<BinaryData>> {};
4347

4348
template <typename T>
4349
void test_lists(TestContext& test_context, DBRef sg, const realm::DataType type_id, bool optional = false)
4350
{
40✔
4351
    auto t = sg->start_write();
40✔
4352
    auto table = t->add_table("the_table");
40✔
4353
    auto col = table->add_column_list(type_id, "the column", optional);
40✔
4354
    Obj o = table->create_object();
40✔
4355
    Lst<T> lst = o.get_list<T>(col);
40✔
4356
    std::vector<managed<T>> reference;
40✔
4357
    for (int j = 0; j < 1000; ++j) {
40,040✔
4358
        managed<T> value = generator<T>::get(optional);
40,000✔
4359
        lst.add(value.value);
40,000✔
4360
        reference.push_back(value);
40,000✔
4361
    }
40,000✔
4362
    check_values(test_context, lst, reference);
40✔
4363
    for (int j = 0; j < 100; ++j) {
4,040✔
4364
        managed<T> value = generator<T>::get(optional);
4,000✔
4365
        lst.insert(493, value.value);
4,000✔
4366
        value = generator<T>::get(optional);
4,000✔
4367
        lst.set(493, value.value);
4,000✔
4368
        reference.insert(reference.begin() + 493, value);
4,000✔
4369
    }
4,000✔
4370
    check_values(test_context, lst, reference);
40✔
4371
    for (int j = 0; j < 100; ++j) {
4,040✔
4372
        lst.remove(142);
4,000✔
4373
        reference.erase(reference.begin() + 142);
4,000✔
4374
    }
4,000✔
4375
    check_values(test_context, lst, reference);
40✔
4376
    for (int disp = 0; disp < 4; ++disp) {
200✔
4377
        for (int j = 250 + disp; j > 50; j -= 3) {
10,960✔
4378
            lst.remove(j);
10,800✔
4379
            reference.erase(reference.begin() + j);
10,800✔
4380
        }
10,800✔
4381
        check_values(test_context, lst, reference);
160✔
4382
    }
160✔
4383
    auto it = reference.begin();
40✔
4384
    for (auto value : lst) {
29,200✔
4385
        CHECK(value == it->value);
29,200✔
4386
        ++it;
29,200✔
4387
    }
29,200✔
4388
    for (size_t j = lst.size(); j >= 100; --j) {
25,280✔
4389
        lst.remove(j - 1);
25,240✔
4390
        reference.pop_back();
25,240✔
4391
    }
25,240✔
4392
    check_values(test_context, lst, reference);
40✔
4393
    while (size_t sz = lst.size()) {
4,000✔
4394
        lst.remove(sz - 1);
3,960✔
4395
        reference.pop_back();
3,960✔
4396
    }
3,960✔
4397
    CHECK_EQUAL(0, reference.size());
40✔
4398
    t->rollback();
40✔
4399
}
40✔
4400

4401
TEST(Table_List_Ops)
4402
{
2✔
4403
    SHARED_GROUP_TEST_PATH(path);
2✔
4404

4405
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4406
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4407

4408
    test_lists<int64_t>(test_context, sg, type_Int);
2✔
4409
    test_lists<StringData>(test_context, sg, type_String);
2✔
4410
    test_lists<BinaryData>(test_context, sg, type_Binary);
2✔
4411
    test_lists<bool>(test_context, sg, type_Bool);
2✔
4412
    test_lists<float>(test_context, sg, type_Float);
2✔
4413
    test_lists<double>(test_context, sg, type_Double);
2✔
4414
    test_lists<Timestamp>(test_context, sg, type_Timestamp);
2✔
4415
    test_lists<Decimal128>(test_context, sg, type_Decimal);
2✔
4416
    test_lists<ObjectId>(test_context, sg, type_ObjectId);
2✔
4417
    test_lists<UUID>(test_context, sg, type_UUID);
2✔
4418

4419
    test_lists<Optional<int64_t>>(test_context, sg, type_Int, true);
2✔
4420
    test_lists<StringData>(test_context, sg, type_String, true); // always Optional?
2✔
4421
    test_lists<BinaryData>(test_context, sg, type_Binary, true); // always Optional?
2✔
4422
    test_lists<Optional<bool>>(test_context, sg, type_Bool, true);
2✔
4423
    test_lists<Optional<float>>(test_context, sg, type_Float, true);
2✔
4424
    test_lists<Optional<double>>(test_context, sg, type_Double, true);
2✔
4425
    test_lists<Timestamp>(test_context, sg, type_Timestamp, true); // always Optional?
2✔
4426
    test_lists<Decimal128>(test_context, sg, type_Decimal, true);
2✔
4427
    test_lists<Optional<ObjectId>>(test_context, sg, type_ObjectId, true);
2✔
4428
    test_lists<Optional<UUID>>(test_context, sg, type_UUID, true);
2✔
4429
}
2✔
4430

4431
template <typename T>
4432
void check_table_values(TestContext& test_context, TableRef t, ColKey col, std::map<int, managed<T>>& reference)
4433
{
200✔
4434
    if (t->size() != reference.size()) {
200✔
4435
        std::cout << "gah" << std::endl;
×
4436
    }
×
4437
    CHECK_EQUAL(t->size(), reference.size());
200✔
4438
    for (auto it : reference) {
480,800✔
4439
        T value = it.second.value;
480,800✔
4440
        Obj o = t->get_object(ObjKey(it.first));
480,800✔
4441
        CHECK_EQUAL(o.get<T>(col), value);
480,800✔
4442
    }
480,800✔
4443
}
200✔
4444

4445
template <typename T>
4446
void test_tables(TestContext& test_context, DBRef sg, const realm::DataType type_id, bool optional = false)
4447
{
40✔
4448
    auto t = sg->start_write();
40✔
4449
    auto table = t->add_table("the_table");
40✔
4450
    auto col = table->add_column(type_id, "the column", optional);
40✔
4451
    std::map<int, managed<T>> reference;
40✔
4452

4453
    // insert elements 0 - 999
4454
    for (int j = 0; j < 1000; ++j) {
40,040✔
4455
        managed<T> value = generator<T>::get(optional);
40,000✔
4456
        table->create_object(ObjKey(j)).set_all(value.value);
40,000✔
4457
        reference[j] = std::move(value);
40,000✔
4458
    }
40,000✔
4459
    // insert elements 10000 - 10999
4460
    for (int j = 10000; j < 11000; ++j) {
40,040✔
4461
        managed<T> value = generator<T>::get(optional);
40,000✔
4462
        table->create_object(ObjKey(j)).set_all(value.value);
40,000✔
4463
        reference[j] = std::move(value);
40,000✔
4464
    }
40,000✔
4465
    // insert in between previous groups
4466
    for (int j = 4000; j < 7000; ++j) {
120,040✔
4467
        managed<T> value = generator<T>::get(optional);
120,000✔
4468
        table->create_object(ObjKey(j)).set_all(value.value);
120,000✔
4469
        reference[j] = std::move(value);
120,000✔
4470
    }
120,000✔
4471
    check_table_values(test_context, table, col, reference);
40✔
4472

4473
    // modify values
4474
    for (int j = 0; j < 11000; j += 100) {
4,440✔
4475
        auto it = reference.find(j);
4,400✔
4476
        if (it == reference.end()) // skip over holes in the key range
4,400✔
4477
            continue;
2,400✔
4478
        managed<T> value = generator<T>::get(optional);
2,000✔
4479
        table->get_object(ObjKey(j)).set<T>(col, value.value);
2,000✔
4480
        it->second = value;
2,000✔
4481
    }
2,000✔
4482
    check_table_values(test_context, table, col, reference);
40✔
4483

4484
    // remove chunk in the middle
4485
    for (int j = 1000; j < 10000; ++j) {
360,040✔
4486
        auto it = reference.find(j);
360,000✔
4487
        if (it == reference.end()) // skip over holes in the key range
360,000✔
4488
            continue;
240,000✔
4489
        table->remove_object(ObjKey(j));
120,000✔
4490
        reference.erase(it);
120,000✔
4491
    }
120,000✔
4492
    check_table_values(test_context, table, col, reference);
40✔
4493
    t->rollback();
40✔
4494
}
40✔
4495

4496
TEST(Table_Ops)
4497
{
2✔
4498
    SHARED_GROUP_TEST_PATH(path);
2✔
4499

4500
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4501
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4502

4503
    test_tables<int64_t>(test_context, sg, type_Int);
2✔
4504
    test_tables<StringData>(test_context, sg, type_String);
2✔
4505
    test_tables<BinaryData>(test_context, sg, type_Binary);
2✔
4506
    test_tables<bool>(test_context, sg, type_Bool);
2✔
4507
    test_tables<float>(test_context, sg, type_Float);
2✔
4508
    test_tables<double>(test_context, sg, type_Double);
2✔
4509
    test_tables<Timestamp>(test_context, sg, type_Timestamp);
2✔
4510
    test_tables<Decimal128>(test_context, sg, type_Decimal);
2✔
4511
    test_tables<ObjectId>(test_context, sg, type_ObjectId);
2✔
4512
    test_tables<UUID>(test_context, sg, type_UUID);
2✔
4513

4514
    test_tables<Optional<int64_t>>(test_context, sg, type_Int, true);
2✔
4515
    test_tables<StringData>(test_context, sg, type_String, true); // always Optional?
2✔
4516
    test_tables<BinaryData>(test_context, sg, type_Binary, true); // always Optional?
2✔
4517
    test_tables<Optional<bool>>(test_context, sg, type_Bool, true);
2✔
4518
    test_tables<Optional<float>>(test_context, sg, type_Float, true);
2✔
4519
    test_tables<Optional<double>>(test_context, sg, type_Double, true);
2✔
4520
    test_tables<Timestamp>(test_context, sg, type_Timestamp, true); // always Optional?
2✔
4521
    test_tables<Decimal128>(test_context, sg, type_Decimal, true);
2✔
4522
    test_tables<Optional<ObjectId>>(test_context, sg, type_ObjectId, true);
2✔
4523
    test_tables<UUID>(test_context, sg, type_UUID, true);
2✔
4524
}
2✔
4525

4526
template <typename TFrom, typename TTo>
4527
void test_dynamic_conversion(TestContext& test_context, DBRef sg, realm::DataType type_id, bool from_nullable,
4528
                             bool to_nullable)
4529
{
80✔
4530
    // Create values of type TFrom and ask for dynamic conversion to TTo
4531
    auto t = sg->start_write();
80✔
4532
    auto table = t->add_table("the_table");
80✔
4533
    auto col_from = table->add_column(type_id, "the column", from_nullable);
80✔
4534
    if (type_id == type_String) {
80✔
4535
        table->add_search_index(col_from);
8✔
4536
    }
8✔
4537
    std::map<int, managed<TTo>> reference;
80✔
4538
    value_copier<TFrom, TTo> copier(false);
80✔
4539
    for (int j = 0; j < 10; ++j) {
880✔
4540
        managed<TFrom> value = generator<TFrom>::get(from_nullable);
800✔
4541
        table->create_object(ObjKey(j)).set_all(value.value);
800✔
4542
        TTo conv_value = copier(
800✔
4543
            value.value, to_nullable); // one may argue that using the same converter for ref and dut is.. mmmh...
800✔
4544
        reference[j] = managed<TTo>{conv_value};
800✔
4545
    }
800✔
4546
    auto col_to = table->set_nullability(col_from, to_nullable, false);
80✔
4547
    if (type_id == type_String) {
80✔
4548
        CHECK(table->has_search_index(col_to));
8✔
4549
    }
8✔
4550
    check_table_values(test_context, table, col_to, reference);
80✔
4551
    t->rollback();
80✔
4552
}
80✔
4553

4554
template <typename TFrom, typename TTo>
4555
void test_dynamic_conversion_list(TestContext& test_context, DBRef sg, realm::DataType type_id, bool from_nullable,
4556
                                  bool to_nullable)
4557
{
80✔
4558
    // Create values of type TFrom and ask for dynamic conversion to TTo
4559
    auto t = sg->start_write();
80✔
4560
    auto table = t->add_table("the_table");
80✔
4561
    auto col_from = table->add_column_list(type_id, "the column", from_nullable);
80✔
4562
    Obj o = table->create_object();
80✔
4563
    table->create_object(); // This object will have an empty list
80✔
4564
    Lst<TFrom> from_lst = o.get_list<TFrom>(col_from);
80✔
4565
    std::vector<managed<TTo>> reference;
80✔
4566
    value_copier<TFrom, TTo> copier(false);
80✔
4567
    for (int j = 0; j < 1000; ++j) {
80,080✔
4568
        managed<TFrom> value = generator<TFrom>::get(from_nullable);
80,000✔
4569
        from_lst.add(value.value);
80,000✔
4570
        TTo conv_value = copier(value.value, to_nullable);
80,000✔
4571
        reference.push_back(managed<TTo>{conv_value});
80,000✔
4572
    }
80,000✔
4573
    auto col_to = table->set_nullability(col_from, to_nullable, false);
80✔
4574
    Lst<TTo> to_lst = o.get_list<TTo>(col_to);
80✔
4575
    check_values(test_context, to_lst, reference);
80✔
4576
    t->rollback();
80✔
4577
}
80✔
4578

4579
template <typename T>
4580
void test_dynamic_conversion_combi(TestContext& test_context, DBRef sg, realm::DataType type_id)
4581
{
10✔
4582
    test_dynamic_conversion<T, Optional<T>>(test_context, sg, type_id, false, true);
10✔
4583
    test_dynamic_conversion<Optional<T>, T>(test_context, sg, type_id, true, false);
10✔
4584
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, false);
10✔
4585
    test_dynamic_conversion<Optional<T>, Optional<T>>(test_context, sg, type_id, true, true);
10✔
4586
}
10✔
4587

4588
template <typename T>
4589
void test_dynamic_conversion_combi_sametype(TestContext& test_context, DBRef sg, realm::DataType type_id)
4590
{
10✔
4591
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, true);
10✔
4592
    test_dynamic_conversion<T, T>(test_context, sg, type_id, true, false);
10✔
4593
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, false);
10✔
4594
    test_dynamic_conversion<T, T>(test_context, sg, type_id, true, true);
10✔
4595
}
10✔
4596

4597
template <typename T>
4598
void test_dynamic_conversion_list_combi(TestContext& test_context, DBRef sg, realm::DataType type_id)
4599
{
10✔
4600
    test_dynamic_conversion_list<T, Optional<T>>(test_context, sg, type_id, false, true);
10✔
4601
    test_dynamic_conversion_list<Optional<T>, T>(test_context, sg, type_id, true, false);
10✔
4602
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, false);
10✔
4603
    test_dynamic_conversion_list<Optional<T>, Optional<T>>(test_context, sg, type_id, true, true);
10✔
4604
}
10✔
4605

4606
template <typename T>
4607
void test_dynamic_conversion_list_combi_sametype(TestContext& test_context, DBRef sg, realm::DataType type_id)
4608
{
10✔
4609
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, true);
10✔
4610
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, true, false);
10✔
4611
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, false);
10✔
4612
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, true, true);
10✔
4613
}
10✔
4614

4615
TEST(Table_Column_DynamicConversions)
4616
{
2✔
4617
    SHARED_GROUP_TEST_PATH(path);
2✔
4618

4619
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4620
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4621

4622
    test_dynamic_conversion_combi<int64_t>(test_context, sg, type_Int);
2✔
4623
    test_dynamic_conversion_combi<float>(test_context, sg, type_Float);
2✔
4624
    test_dynamic_conversion_combi<double>(test_context, sg, type_Double);
2✔
4625
    test_dynamic_conversion_combi<bool>(test_context, sg, type_Bool);
2✔
4626
    test_dynamic_conversion_combi<ObjectId>(test_context, sg, type_ObjectId);
2✔
4627

4628
    test_dynamic_conversion_combi_sametype<StringData>(test_context, sg, type_String);
2✔
4629
    test_dynamic_conversion_combi_sametype<BinaryData>(test_context, sg, type_Binary);
2✔
4630
    test_dynamic_conversion_combi_sametype<Timestamp>(test_context, sg, type_Timestamp);
2✔
4631
    test_dynamic_conversion_combi_sametype<Decimal128>(test_context, sg, type_Decimal);
2✔
4632
    test_dynamic_conversion_combi_sametype<UUID>(test_context, sg, type_UUID);
2✔
4633
    // lists...:
4634
    test_dynamic_conversion_list_combi<int64_t>(test_context, sg, type_Int);
2✔
4635
    test_dynamic_conversion_list_combi<float>(test_context, sg, type_Float);
2✔
4636
    test_dynamic_conversion_list_combi<double>(test_context, sg, type_Double);
2✔
4637
    test_dynamic_conversion_list_combi<bool>(test_context, sg, type_Bool);
2✔
4638
    test_dynamic_conversion_list_combi<ObjectId>(test_context, sg, type_ObjectId);
2✔
4639

4640
    test_dynamic_conversion_list_combi_sametype<StringData>(test_context, sg, type_String);
2✔
4641
    test_dynamic_conversion_list_combi_sametype<BinaryData>(test_context, sg, type_Binary);
2✔
4642
    test_dynamic_conversion_list_combi_sametype<Timestamp>(test_context, sg, type_Timestamp);
2✔
4643
    test_dynamic_conversion_list_combi_sametype<Decimal128>(test_context, sg, type_Decimal);
2✔
4644
    test_dynamic_conversion_list_combi_sametype<UUID>(test_context, sg, type_UUID);
2✔
4645
}
2✔
4646

4647
/*
4648
TEST(Table_Column_Conversions)
4649
{
4650
    SHARED_GROUP_TEST_PATH(path);
4651

4652
    std::unique_ptr<Replication> hist(make_in_realm_history());
4653
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4654

4655
    test_column_conversion<int64_t, Optional<int64_t>>(test_context, sg, type_Int);
4656
    test_column_conversion<float, Optional<float>>(test_context, sg, type_Float);
4657
    test_column_conversion<double, Optional<double>>(test_context, sg, type_Double);
4658
    test_column_conversion<bool, Optional<bool>>(test_context, sg, type_Bool);
4659
    test_column_conversion<StringData, StringData>(test_context, sg, type_String);
4660
    test_column_conversion<BinaryData, BinaryData>(test_context, sg, type_Binary);
4661
    test_column_conversion<Timestamp, Timestamp>(test_context, sg, type_Timestamp);
4662

4663
    test_column_conversion_optional<int64_t>(test_context, sg, type_Int);
4664
    test_column_conversion_optional<float>(test_context, sg, type_Float);
4665
    test_column_conversion_optional<double>(test_context, sg, type_Double);
4666
    test_column_conversion_optional<bool>(test_context, sg, type_Bool);
4667

4668
    test_column_conversion_sametype<StringData>(test_context, sg, type_String);
4669
    test_column_conversion_sametype<BinaryData>(test_context, sg, type_Binary);
4670
    test_column_conversion_sametype<Timestamp>(test_context, sg, type_Timestamp);
4671

4672
}
4673
*/
4674

4675
TEST(Table_ChangePKNullability)
4676
{
2✔
4677
    SHARED_GROUP_TEST_PATH(path);
2✔
4678

4679
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4680
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4681

4682
    auto wt = sg->start_write();
2✔
4683
    auto table = wt->add_table_with_primary_key("foo", type_String, "id", false);
2✔
4684

4685
    table->create_object_with_primary_key("Paul");
2✔
4686
    table->create_object_with_primary_key("John");
2✔
4687
    table->create_object_with_primary_key("George");
2✔
4688
    table->create_object_with_primary_key("Ringo");
2✔
4689

4690
    auto pk_col = table->get_primary_key_column();
2✔
4691
    pk_col = table->set_nullability(pk_col, true, true);
2✔
4692
    CHECK(pk_col.is_nullable());
2✔
4693

4694
    table->create_object_with_primary_key("");
2✔
4695
    table->create_object_with_primary_key({});
2✔
4696

4697
    std::string message;
2✔
4698
    CHECK_THROW_ANY_GET_MESSAGE(table->set_nullability(pk_col, false, true), message);
2✔
4699
    CHECK_EQUAL(message, "Objects in 'foo' has null value(s) in property 'id'");
2✔
4700

4701
    table->get_object_with_primary_key({}).remove();
2✔
4702
    table->set_nullability(pk_col, false, true);
2✔
4703
}
2✔
4704

4705
TEST(Table_MultipleObjs)
4706
{
2✔
4707
    SHARED_GROUP_TEST_PATH(path);
2✔
4708

4709
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4710
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4711

4712
    auto tr = sg->start_write();
2✔
4713
    auto table = tr->add_table("my_table");
2✔
4714
    auto col = table->add_column_list(*table, "the links");
2✔
4715
    auto col_int = table->add_column_list(type_String, "the integers");
2✔
4716
    auto obj_key = table->create_object().get_key();
2✔
4717
    tr->commit();
2✔
4718
    tr = sg->start_write();
2✔
4719
    table = tr->get_table("my_table");
2✔
4720
    auto obj = table->get_object(obj_key);
2✔
4721
    auto list_1 = obj.get_linklist(col);
2✔
4722
    auto list_2 = obj.get_linklist(col);
2✔
4723

4724
    auto list_3 = obj.get_list<StringData>(col_int);
2✔
4725
    auto list_4 = obj.get_list<StringData>(col_int);
2✔
4726
    std::string s = "42";
2✔
4727
    StringData ss(s.data(), s.size());
2✔
4728
    list_3.add(ss);
2✔
4729
    CHECK_EQUAL(list_4.get(0), ss);
2✔
4730

4731
    list_1.add(obj_key);
2✔
4732
    CHECK_EQUAL(list_1.get(0), obj_key);
2✔
4733
    CHECK_EQUAL(list_2.get(0), obj_key);
2✔
4734
}
2✔
4735

4736
TEST(Table_IteratorRandomAccess)
4737
{
2✔
4738
    Table t;
2✔
4739

4740
    ObjKeys keys;
2✔
4741
    t.create_objects(1000, keys);
2✔
4742

4743
    auto key = keys.begin();
2✔
4744
    auto iter = t.begin();
2✔
4745
    for (size_t pos = 0; (pos + 3) < 1000; pos += 3) {
668✔
4746
        CHECK_EQUAL(iter->get_key(), *key);
666✔
4747
        iter += 3;
666✔
4748
        key += 3;
666✔
4749
    }
666✔
4750

4751
    // random access
4752
    for (int j = 0; j < 5; j++) {
12✔
4753
        std::vector<size_t> random_idx(keys.size());
10✔
4754
        std::iota(random_idx.begin(), random_idx.end(), 0);
10✔
4755
        // unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
4756
        // std::cout << "Seed " << seed << std::endl;
4757
        std::shuffle(random_idx.begin(), random_idx.end(), std::mt19937(unit_test_random_seed));
10✔
4758
        iter = t.begin();
10✔
4759
        int i = 0;
10✔
4760
        for (auto index : random_idx) {
6,459✔
4761
            if (index < keys.size()) {
6,459✔
4762
                auto k = keys[index];
5,723✔
4763
                if (i == 4) {
5,723✔
4764
                    t.remove_object(k);
1,424✔
4765
                    keys.erase(keys.begin() + index);
1,424✔
4766
                    if (index == 0)
1,424✔
UNCOV
4767
                        iter = t.begin();
×
4768
                    i = 0;
1,424✔
4769
                }
1,424✔
4770
                else {
4,299✔
4771
                    iter.go(index);
4,299✔
4772
                    CHECK_EQUAL(k, iter->get_key());
4,299✔
4773
                }
4,299✔
4774
                i++;
5,723✔
4775
            }
5,723✔
4776
        }
6,459✔
4777
    }
10✔
4778

4779
    iter.go(0);
2✔
4780
    auto iter200 = iter + 200;
2✔
4781
    CHECK_EQUAL(keys[200], iter200->get_key());
2✔
4782
}
2✔
4783

4784
TEST(Table_EmbeddedObjects)
4785
{
2✔
4786
    SHARED_GROUP_TEST_PATH(path);
2✔
4787

4788
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4789
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4790

4791
    auto tr = sg->start_write();
2✔
4792
    auto table = tr->add_table("mytable", Table::Type::Embedded);
2✔
4793
    tr->commit_and_continue_as_read();
2✔
4794
    tr->promote_to_write();
2✔
4795
    CHECK(table->is_embedded());
2✔
4796
    CHECK_THROW(table->create_object(), LogicError);
2✔
4797
    tr->rollback();
2✔
4798

4799
    tr = sg->start_read();
2✔
4800
    table = tr->get_table("mytable");
2✔
4801
    CHECK(table->is_embedded());
2✔
4802
}
2✔
4803

4804
TEST(Table_EmbeddedObjectCreateAndDestroy)
4805
{
2✔
4806
    SHARED_GROUP_TEST_PATH(path);
2✔
4807

4808
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4809
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4810

4811
    {
2✔
4812
        auto tr = sg->start_write();
2✔
4813
        auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4814
        auto col_recurse = table->add_column(*table, "theRecursiveBit");
2✔
4815
        CHECK_THROW(table->create_object(), LogicError);
2✔
4816
        auto parent = tr->add_table("myParentStuff");
2✔
4817
        auto ck = parent->add_column(*table, "theGreatColumn");
2✔
4818
        Obj o = parent->create_object();
2✔
4819
        Obj o2 = o.create_and_set_linked_object(ck);
2✔
4820
        Obj o3 = o2.create_and_set_linked_object(col_recurse);
2✔
4821
        auto parent_obj = o2.get_parent_object();
2✔
4822
        CHECK_EQUAL(o.get_key(), parent_obj.get_key());
2✔
4823
        parent_obj = o3.get_parent_object();
2✔
4824
        CHECK_EQUAL(o2.get_key(), parent_obj.get_key());
2✔
4825
        CHECK(table->size() == 2);
2✔
4826
        tr->commit();
2✔
4827
    }
2✔
4828
    {
2✔
4829
        auto tr = sg->start_write();
2✔
4830
        auto table = tr->get_table("myEmbeddedStuff");
2✔
4831
        auto parent = tr->get_table("myParentStuff");
2✔
4832
        CHECK(table->size() == 2);
2✔
4833
        auto first = parent->begin();
2✔
4834
        first->set("theGreatColumn", ObjKey());
2✔
4835
        CHECK(table->size() == 0);
2✔
4836
        // do not commit
4837
    }
2✔
4838
    {
2✔
4839
        auto tr = sg->start_write();
2✔
4840
        auto table = tr->get_table("myEmbeddedStuff");
2✔
4841
        auto parent = tr->get_table("myParentStuff");
2✔
4842
        CHECK(table->size() == 2);
2✔
4843
        auto first = parent->begin();
2✔
4844
        first->remove();
2✔
4845
        CHECK(table->size() == 0);
2✔
4846
        // do not commit
4847
    }
2✔
4848
    {
2✔
4849
        // Sync operations
4850
        auto tr = sg->start_write();
2✔
4851
        auto table = tr->get_table("myEmbeddedStuff");
2✔
4852
        auto parent = tr->get_table("myParentStuff");
2✔
4853
        CHECK(table->size() == 2);
2✔
4854
        auto first = parent->begin();
2✔
4855
        first->invalidate();
2✔
4856
        CHECK(table->size() == 0);
2✔
4857
        // do not commit
4858
    }
2✔
4859
}
2✔
4860

4861
TEST(Table_EmbeddedObjectCreateAndDestroyList)
4862
{
2✔
4863
    SHARED_GROUP_TEST_PATH(path);
2✔
4864

4865
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4866
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4867

4868
    auto tr = sg->start_write();
2✔
4869
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4870
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
4871
    CHECK_THROW(table->create_object(), LogicError);
2✔
4872
    auto parent = tr->add_table("myParentStuff");
2✔
4873
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
4874
    Obj o = parent->create_object();
2✔
4875
    auto parent_ll = o.get_linklist(ck);
2✔
4876
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
4877
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
4878
    parent_ll.create_and_insert_linked_object(0);
2✔
4879
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
4880
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
4881
    o2_ll.create_and_insert_linked_object(0);
2✔
4882
    o2_ll.create_and_insert_linked_object(0);
2✔
4883
    o3_ll.create_and_insert_linked_object(0);
2✔
4884

4885
    tr->commit_and_continue_as_read();
2✔
4886
    tr->verify();
2✔
4887

4888
    tr->promote_to_write();
2✔
4889
    CHECK(table->size() == 6);
2✔
4890
    parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
4891
    CHECK(!o2.is_valid());
2✔
4892
    CHECK(table->size() == 4);
2✔
4893
    parent_ll.clear();
2✔
4894
    CHECK(table->size() == 0);
2✔
4895
    parent_ll.create_and_insert_linked_object(0);
2✔
4896
    parent_ll.create_and_insert_linked_object(1);
2✔
4897
    CHECK(table->size() == 2);
2✔
4898
    o.remove();
2✔
4899
    CHECK(table->size() == 0);
2✔
4900
    tr->commit();
2✔
4901
}
2✔
4902

4903
TEST(Table_EmbeddedObjectCreateAndDestroyDictionary)
4904
{
2✔
4905
    SHARED_GROUP_TEST_PATH(path);
2✔
4906

4907
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4908
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4909

4910
    auto tr = sg->start_write();
2✔
4911
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4912
    auto col_recurse = table->add_column_dictionary(*table, "theRecursiveBit");
2✔
4913
    CHECK_THROW(table->create_object(), LogicError);
2✔
4914
    auto parent = tr->add_table("myParentStuff");
2✔
4915
    auto ck = parent->add_column_dictionary(*table, "theGreatColumn");
2✔
4916
    auto ck1 = parent->add_column(*table, "theLesserColumn");
2✔
4917
    Obj o = parent->create_object();
2✔
4918
    auto parent_dict = o.get_dictionary(ck);
2✔
4919
    Obj o2 = parent_dict.create_and_insert_linked_object("one");
2✔
4920
    Obj o4 = o.create_and_set_linked_object(ck1);
2✔
4921

4922
    auto obj_path = o2.get_path();
2✔
4923
    CHECK_EQUAL(obj_path.path_from_top.size(), 2);
2✔
4924
    CHECK_EQUAL(obj_path.path_from_top[0], ck);
2✔
4925
    CHECK_EQUAL(obj_path.path_from_top[1], "one");
2✔
4926

4927
    Obj o3 = parent_dict.create_and_insert_linked_object("two");
2✔
4928
    parent_dict.create_and_insert_linked_object("three");
2✔
4929

4930
    CHECK_EQUAL(parent_dict.get_object("one").get_key(), o2.get_key());
2✔
4931

4932
    auto o2_dict = o2.get_dictionary(col_recurse);
2✔
4933
    auto o3_dict = o3.get_dictionary(col_recurse);
2✔
4934
    auto o4_dict = o4.get_dictionary(col_recurse);
2✔
4935
    o2_dict.create_and_insert_linked_object("foo1");
2✔
4936
    o2_dict.create_and_insert_linked_object("foo2");
2✔
4937
    o3_dict.create_and_insert_linked_object("foo3");
2✔
4938
    o4_dict.create_and_insert_linked_object("foo4");
2✔
4939

4940
    obj_path = o2_dict.get_object("foo1").get_path();
2✔
4941
    CHECK_EQUAL(obj_path.path_from_top.size(), 4);
2✔
4942
    CHECK_EQUAL(obj_path.path_from_top[0], ck);
2✔
4943
    CHECK_EQUAL(obj_path.path_from_top[1], "one");
2✔
4944
    CHECK_EQUAL(obj_path.path_from_top[2], "theRecursiveBit");
2✔
4945
    CHECK_EQUAL(obj_path.path_from_top[3], "foo1");
2✔
4946

4947
    obj_path = o4_dict.get_object("foo4").get_path();
2✔
4948
    CHECK_EQUAL(obj_path.path_from_top.size(), 3);
2✔
4949
    CHECK_EQUAL(obj_path.path_from_top[0], ck1);
2✔
4950
    CHECK_EQUAL(obj_path.path_from_top[1], "theRecursiveBit");
2✔
4951
    CHECK_EQUAL(obj_path.path_from_top[2], "foo4");
2✔
4952

4953
    tr->commit_and_continue_as_read();
2✔
4954
    tr->verify();
2✔
4955

4956
    tr->promote_to_write();
2✔
4957
    CHECK_EQUAL(table->size(), 8);
2✔
4958
    parent_dict.create_and_insert_linked_object("one"); // implicitly remove entry for 02
2✔
4959
    CHECK(!o2.is_valid());
2✔
4960
    CHECK_EQUAL(table->size(), 6);
2✔
4961
    parent_dict.clear();
2✔
4962
    CHECK_EQUAL(table->size(), 2);
2✔
4963
    parent_dict.create_and_insert_linked_object("four");
2✔
4964
    parent_dict.create_and_insert_linked_object("five");
2✔
4965
    CHECK_EQUAL(table->size(), 4);
2✔
4966
    o.remove();
2✔
4967
    CHECK_EQUAL(table->size(), 0);
2✔
4968
    tr->commit();
2✔
4969
}
2✔
4970

4971
TEST(Table_EmbeddedObjectNotifications)
4972
{
2✔
4973
    SHARED_GROUP_TEST_PATH(path);
2✔
4974

4975
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4976
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4977

4978
    auto tr = sg->start_write();
2✔
4979
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4980
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
4981
    CHECK_THROW(table->create_object(), LogicError);
2✔
4982
    auto parent = tr->add_table("myParentStuff");
2✔
4983
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
4984
    Obj o = parent->create_object();
2✔
4985
    auto parent_ll = o.get_linklist(ck);
2✔
4986
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
4987
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
4988
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
4989
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
4990
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
4991
    o2_ll.create_and_insert_linked_object(0);
2✔
4992
    o2_ll.create_and_insert_linked_object(0);
2✔
4993
    o3_ll.create_and_insert_linked_object(0);
2✔
4994
    CHECK(table->size() == 6);
2✔
4995
    Obj o5 = parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
4996
    CHECK(!o2.is_valid());
2✔
4997
    CHECK(table->size() == 4);
2✔
4998
    // now the notifications...
4999
    int calls = 0;
2✔
5000
    tr->set_cascade_notification_handler([&](const Group::CascadeNotification& notification) {
6✔
5001
        CHECK_EQUAL(0, notification.links.size());
6✔
5002
        if (calls == 0) {
6✔
5003
            CHECK_EQUAL(1, notification.rows.size());
2✔
5004
            CHECK_EQUAL(parent->get_key(), notification.rows[0].table_key);
2✔
5005
            CHECK_EQUAL(o.get_key(), notification.rows[0].key);
2✔
5006
        }
2✔
5007
        else if (calls == 1) {
4✔
5008
            CHECK_EQUAL(3, notification.rows.size());
2✔
5009
            for (auto& row : notification.rows)
2✔
5010
                CHECK_EQUAL(table->get_key(), row.table_key);
6✔
5011
            CHECK_EQUAL(o4.get_key(), notification.rows[0].key);
2✔
5012
            CHECK_EQUAL(o5.get_key(), notification.rows[1].key);
2✔
5013
            CHECK_EQUAL(o3.get_key(), notification.rows[2].key);
2✔
5014
        }
2✔
5015
        else if (calls == 2) {
2✔
5016
            CHECK_EQUAL(1, notification.rows.size()); // from o3
2✔
5017
            for (auto& row : notification.rows)
2✔
5018
                CHECK_EQUAL(table->get_key(), row.table_key);
2✔
5019
            // don't bother checking the keys...
5020
        }
2✔
5021
        ++calls;
6✔
5022
    });
6✔
5023

5024
    o.remove();
2✔
5025
    CHECK(calls == 3);
2✔
5026
    tr->commit();
2✔
5027
}
2✔
5028
TEST(Table_EmbeddedObjectTableClearNotifications)
5029
{
2✔
5030
    SHARED_GROUP_TEST_PATH(path);
2✔
5031

5032
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5033
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5034

5035
    auto tr = sg->start_write();
2✔
5036
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5037
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
5038
    CHECK_THROW(table->create_object(), LogicError);
2✔
5039
    auto parent = tr->add_table("myParentStuff");
2✔
5040
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
5041
    Obj o = parent->create_object();
2✔
5042
    auto parent_ll = o.get_linklist(ck);
2✔
5043
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
5044
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
5045
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
5046
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
5047
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
5048
    o2_ll.create_and_insert_linked_object(0);
2✔
5049
    o2_ll.create_and_insert_linked_object(0);
2✔
5050
    o3_ll.create_and_insert_linked_object(0);
2✔
5051
    CHECK(table->size() == 6);
2✔
5052
    Obj o5 = parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
5053
    CHECK(!o2.is_valid());
2✔
5054
    CHECK(table->size() == 4);
2✔
5055
    // now the notifications...
5056
    int calls = 0;
2✔
5057
    tr->set_cascade_notification_handler([&](const Group::CascadeNotification& notification) {
4✔
5058
        if (calls == 0) {
4✔
5059
            CHECK_EQUAL(3, notification.rows.size());
2✔
5060
            for (auto& row : notification.rows)
2✔
5061
                CHECK_EQUAL(table->get_key(), row.table_key);
6✔
5062
            CHECK_EQUAL(o4.get_key(), notification.rows[0].key);
2✔
5063
            CHECK_EQUAL(o5.get_key(), notification.rows[1].key);
2✔
5064
            CHECK_EQUAL(o3.get_key(), notification.rows[2].key);
2✔
5065
        }
2✔
5066
        else if (calls == 1) {
2✔
5067
            CHECK_EQUAL(1, notification.rows.size()); // from o3
2✔
5068
            for (auto& row : notification.rows)
2✔
5069
                CHECK_EQUAL(table->get_key(), row.table_key);
2✔
5070
            // don't bother checking the keys...
5071
        }
2✔
5072
        ++calls;
4✔
5073
    });
4✔
5074

5075
    parent->clear();
2✔
5076
    CHECK(calls == 2);
2✔
5077
    CHECK_EQUAL(parent->size(), 0);
2✔
5078
    tr->commit();
2✔
5079
}
2✔
5080

5081
TEST(Table_EmbeddedObjectPath)
5082
{
2✔
5083
    auto collect_path = [](const Obj& o) {
10✔
5084
        return o.get_fat_path();
10✔
5085
    };
10✔
5086

5087
    SHARED_GROUP_TEST_PATH(path);
2✔
5088

5089
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5090
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5091

5092
    auto tr = sg->start_write();
2✔
5093
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5094
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
5095
    CHECK_THROW(table->create_object(), LogicError);
2✔
5096
    auto parent = tr->add_table("myParentStuff");
2✔
5097
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
5098
    Obj o = parent->create_object();
2✔
5099
    auto gch = collect_path(o);
2✔
5100
    CHECK(gch.size() == 0);
2✔
5101
    auto parent_ll = o.get_linklist(ck);
2✔
5102
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
5103
    auto gbh = collect_path(o2);
2✔
5104
    CHECK(gbh.size() == 1);
2✔
5105
    CHECK(gbh[0].obj.get_key() == o.get_key());
2✔
5106
    CHECK(gbh[0].col_key == ck);
2✔
5107
    CHECK(gbh[0].index == 0);
2✔
5108
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
5109
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
5110
    auto gah = collect_path(o4);
2✔
5111
    CHECK(gah.size() == 1);
2✔
5112
    CHECK(gah[0].obj.get_key() == o.get_key());
2✔
5113
    CHECK(gah[0].col_key == ck);
2✔
5114
    CHECK(gah[0].index == 0);
2✔
5115
    auto gzh = collect_path(o3);
2✔
5116
    CHECK(gzh.size() == 1);
2✔
5117
    CHECK(gzh[0].obj.get_key() == o.get_key());
2✔
5118
    CHECK(gzh[0].col_key == ck);
2✔
5119
    CHECK(gzh[0].index == 2);
2✔
5120
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
5121
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
5122
    o2_ll.create_and_insert_linked_object(0);
2✔
5123
    o2_ll.create_and_insert_linked_object(0);
2✔
5124
    o3_ll.create_and_insert_linked_object(0);
2✔
5125
    CHECK(table->size() == 6);
2✔
5126
    auto gyh = collect_path(o3_ll.get_object(0));
2✔
5127
    CHECK(gyh.size() == 2);
2✔
5128
    CHECK(gyh[0].obj.get_key() == o.get_key());
2✔
5129
    CHECK(gyh[0].col_key == ck);
2✔
5130
    CHECK(gyh[0].index == 2);
2✔
5131
    CHECK(gyh[1].obj.get_key() == o3.get_key());
2✔
5132
    CHECK(gyh[1].col_key = col_recurse);
2✔
5133
    CHECK(gyh[1].index == 0);
2✔
5134
}
2✔
5135

5136
TEST(Table_IndexOnMixed)
5137
{
2✔
5138
    Timestamp now{std::chrono::system_clock::now()};
2✔
5139
    Group g;
2✔
5140

5141
    auto bars = g.add_table("bar");
2✔
5142
    auto foos = g.add_table("foo");
2✔
5143
    auto col = foos->add_column(type_Mixed, "any");
2✔
5144
    foos->add_search_index(col);
2✔
5145

5146
    auto bar = bars->create_object();
2✔
5147

5148
    auto k0 = foos->create_object().set(col, Mixed()).get_key();
2✔
5149
    auto k1 = foos->create_object().set(col, Mixed(25)).get_key();
2✔
5150
    auto k2 = foos->create_object().set(col, Mixed(123.456f)).get_key();
2✔
5151
    auto k3 = foos->create_object().set(col, Mixed(987.654)).get_key();
2✔
5152
    auto k4 = foos->create_object().set(col, Mixed("Hello")).get_key();
2✔
5153
    auto k5 = foos->create_object().set(col, Mixed(now)).get_key();
2✔
5154
    auto k6 = foos->create_object().set(col, Mixed(Decimal128("2.25"))).get_key();
2✔
5155
    auto k7 = foos->create_object().set(col, Mixed(1)).get_key();
2✔
5156
    auto k8 = foos->create_object().set(col, Mixed(true)).get_key();
2✔
5157
    auto k9 = foos->create_object().set(col, Mixed(bar.get_link())).get_key();
2✔
5158
    auto k10 = foos->create_object().set(col, Mixed(UUID("3b241101-e2bb-4255-8caf-4136c566a962"))).get_key();
2✔
5159

5160
    CHECK_EQUAL(foos->find_first<Mixed>(col, {}), k0);
2✔
5161
    CHECK_EQUAL(foos->find_first<Mixed>(col, 25), k1);
2✔
5162
    CHECK_EQUAL(foos->find_first<Mixed>(col, 123.456f), k2);
2✔
5163
    CHECK_EQUAL(foos->find_first<Mixed>(col, 987.654), k3);
2✔
5164
    CHECK_EQUAL(foos->find_first<Mixed>(col, "Hello"), k4);
2✔
5165
    CHECK_EQUAL(foos->find_first<Mixed>(col, now), k5);
2✔
5166
    CHECK_EQUAL(foos->find_first<Mixed>(col, Decimal128("2.25")), k6);
2✔
5167
    CHECK_EQUAL(foos->find_first<Mixed>(col, 1), k7);
2✔
5168
    CHECK_EQUAL(foos->find_first<Mixed>(col, true), k8);
2✔
5169
    CHECK_EQUAL(foos->find_first<Mixed>(col, bar.get_link()), k9);
2✔
5170
    CHECK_EQUAL(foos->find_first<Mixed>(col, UUID("3b241101-e2bb-4255-8caf-4136c566a962")), k10);
2✔
5171

5172
    foos->remove_search_index(col);
2✔
5173

5174
    CHECK_EQUAL(foos->find_first<Mixed>(col, {}), k0);
2✔
5175
    CHECK_EQUAL(foos->find_first<Mixed>(col, 25), k1);
2✔
5176
    CHECK_EQUAL(foos->find_first<Mixed>(col, 123.456f), k2);
2✔
5177
    CHECK_EQUAL(foos->find_first<Mixed>(col, 987.654), k3);
2✔
5178
    CHECK_EQUAL(foos->find_first<Mixed>(col, "Hello"), k4);
2✔
5179
    CHECK_EQUAL(foos->find_first<Mixed>(col, now), k5);
2✔
5180
    CHECK_EQUAL(foos->find_first<Mixed>(col, Decimal128("2.25")), k6);
2✔
5181
    CHECK_EQUAL(foos->find_first<Mixed>(col, 1), k7);
2✔
5182
    CHECK_EQUAL(foos->find_first<Mixed>(col, true), k8);
2✔
5183
    CHECK_EQUAL(foos->find_first<Mixed>(col, bar.get_link()), k9);
2✔
5184
    CHECK_EQUAL(foos->find_first<Mixed>(col, UUID("3b241101-e2bb-4255-8caf-4136c566a962")), k10);
2✔
5185
}
2✔
5186

5187
TEST(Table_MixedNull)
5188
{
2✔
5189
    Group g;
2✔
5190
    auto foos = g.add_table("foo");
2✔
5191
    auto col = foos->add_column_list(type_Mixed, "any", true);
2✔
5192
    auto obj = foos->create_object();
2✔
5193
    auto list = obj.get_list<Mixed>(col);
2✔
5194
    list.add(Mixed());
2✔
5195
    list.set(0, Mixed(1));
2✔
5196
    list.set(0, Mixed());
2✔
5197
    list.remove(0);
2✔
5198
}
2✔
5199

5200
TEST(Table_InsertWithMixedLink)
5201
{
2✔
5202
    Group g;
2✔
5203
    TableRef dest = g.add_table_with_primary_key("dest", type_Int, "value");
2✔
5204
    TableRef source = g.add_table_with_primary_key("source", type_Int, "value");
2✔
5205
    ColKey mixed_col = source->add_column(type_Mixed, "mixed");
2✔
5206

5207
    Obj dest_obj = dest->create_object_with_primary_key(0);
2✔
5208

5209
    Mixed mixed_link = ObjLink{dest->get_key(), dest_obj.get_key()};
2✔
5210
    FieldValues values = {
2✔
5211
        {mixed_col, mixed_link},
2✔
5212
    };
2✔
5213
    source->create_object_with_primary_key(0, std::move(values));
2✔
5214

5215
    source->clear();
2✔
5216
    dest->clear();
2✔
5217
}
2✔
5218

5219
TEST(Table_SortEncrypted)
5220
{
2✔
5221
    SHARED_GROUP_TEST_PATH(path);
2✔
5222
    Random random(random_int<unsigned long>());
2✔
5223

5224
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5225
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key(true)));
2✔
5226

5227
    auto wt = sg->start_write();
2✔
5228
    auto foos = wt->add_table("foo");
2✔
5229
    auto col_id = foos->add_column(type_String, "id");
2✔
5230
    auto col_b = foos->add_column(type_Bool, "b");
2✔
5231

5232
    for (int i = 0; i < 10000; i++) {
20,002✔
5233
        auto n = random.draw_int_max(10000);
20,000✔
5234
        foos->create_object().set(col_id, util::to_string(n));
20,000✔
5235
    }
20,000✔
5236
    wt->commit_and_continue_as_read();
2✔
5237
    auto q = foos->where();
2✔
5238
    DescriptorOrdering ordering;
2✔
5239
    ordering.append_sort(SortDescriptor({{col_b}, {col_id}}));
2✔
5240

5241
    // auto t1 = steady_clock::now();
5242

5243
    CALLGRIND_START_INSTRUMENTATION;
2✔
5244
    auto tv = q.find_all(ordering);
2✔
5245
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
5246

5247
    // auto t2 = steady_clock::now();
5248

5249
    // std::cout << "time: " << duration_cast<microseconds>(t2 - t1).count() << " us" << std::endl;
5250
}
2✔
5251

5252
TEST(Table_RebuildTable)
5253
{
2✔
5254
    Group g;
2✔
5255
    auto t = g.add_table("foo");
2✔
5256
    auto id = t->add_column(type_Int, "id");
2✔
5257
    for (int64_t i = 1; i < 8; i++) {
16✔
5258
        t->create_object().set(id, i);
14✔
5259
    }
14✔
5260
    t->set_primary_key_column(id);
2✔
5261
}
2✔
5262

5263
TEST(Table_ListOfPrimitivesTransaction)
5264
{
2✔
5265
    SHARED_GROUP_TEST_PATH(path);
2✔
5266
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5267
    DBRef db = DB::create(*hist, path);
2✔
5268

5269
    auto tr = db->start_write();
2✔
5270
    TableRef t = tr->add_table("table");
2✔
5271
    ColKey int_col = t->add_column_list(type_Int, "integers");
2✔
5272
    ObjKeys keys;
2✔
5273
    t->create_objects(32, keys);
2✔
5274
    auto list = t->get_object(keys[7]).get_list<Int>(int_col);
2✔
5275
    list.add(7);
2✔
5276
    list.add(25);
2✔
5277
    list.add(42);
2✔
5278
    tr->commit_and_continue_as_read();
2✔
5279

5280
    tr->promote_to_write();
2✔
5281
    list.set(0, 5);
2✔
5282
    tr->commit_and_continue_as_read();
2✔
5283
    CHECK_EQUAL(list.get(0), 5);
2✔
5284
    tr->promote_to_write();
2✔
5285
    list.swap(0, 1);
2✔
5286
    tr->commit_and_continue_as_read();
2✔
5287
    CHECK_EQUAL(list.get(0), 25);
2✔
5288
    tr->promote_to_write();
2✔
5289
    list.move(1, 0);
2✔
5290
    tr->commit_and_continue_as_read();
2✔
5291
    CHECK_EQUAL(list.get(0), 5);
2✔
5292
    tr->promote_to_write();
2✔
5293
    list.remove(1);
2✔
5294
    tr->commit_and_continue_as_read();
2✔
5295
    CHECK_EQUAL(list.get(1), 42);
2✔
5296
    tr->promote_to_write();
2✔
5297
    list.clear();
2✔
5298
    tr->commit_and_continue_as_read();
2✔
5299
    CHECK_EQUAL(list.size(), 0);
2✔
5300
}
2✔
5301

5302
TEST(Table_AsymmetricObjects)
5303
{
2✔
5304
    SHARED_GROUP_TEST_PATH(path);
2✔
5305

5306
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5307
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5308

5309
    auto tr = sg->start_write();
2✔
5310
    auto table = tr->add_table("mytable", Table::Type::TopLevelAsymmetric);
2✔
5311
    tr->commit_and_continue_as_read();
2✔
5312
    tr->promote_to_write();
2✔
5313
    CHECK(table->is_asymmetric());
2✔
5314
    table->create_object();
2✔
5315
    tr->commit();
2✔
5316

5317
    tr = sg->start_read();
2✔
5318
    table = tr->get_table("mytable");
2✔
5319
    CHECK(table->is_asymmetric());
2✔
5320

5321
    tr = sg->start_write();
2✔
5322
    auto table2 = tr->add_table("target table");
2✔
5323
    table = tr->get_table("mytable");
2✔
5324
    // Outgoing link from asymmetric object is allowed.
5325
    CHECK_NOTHROW(table->add_column(*table2, "link"));
2✔
5326
    // Incoming link to asymmetric object is not allowed.
5327
    CHECK_THROW(table2->add_column(*table, "link"), LogicError);
2✔
5328
    tr->commit();
2✔
5329
}
2✔
5330

5331
TEST(Table_FullTextIndex)
5332
{
2✔
5333
    SHARED_GROUP_TEST_PATH(path);
2✔
5334
    auto db = DB::create(path);
2✔
5335
    ColKey col;
2✔
5336

5337
    {
2✔
5338
        auto wt = db->start_write();
2✔
5339

5340
        auto t = wt->add_table("foo");
2✔
5341
        col = t->add_column(type_String, "str");
2✔
5342
        t->add_fulltext_index(col);
2✔
5343
        auto index = t->get_string_index(col);
2✔
5344
        CHECK(index->is_fulltext_index());
2✔
5345

5346
        t->create_object().set(col, "This is a test, with  spaces!");
2✔
5347
        t->create_object().set(col, "More testing, with normal spaces");
2✔
5348
        t->create_object().set(col, "ål, ø og æbler");
2✔
5349

5350
        wt->commit();
2✔
5351
    }
2✔
5352

5353
    auto rt = db->start_read();
2✔
5354
    auto t = rt->get_table("foo");
2✔
5355
    auto index = t->get_string_index(col);
2✔
5356
    CHECK(index->is_fulltext_index());
2✔
5357
    TableView res = t->find_all_fulltext(col, "spaces with");
2✔
5358
    CHECK_EQUAL(2, res.size());
2✔
5359
}
2✔
5360

5361
TEST(Table_LoggingMutations)
5362
{
2✔
5363
    std::stringstream buffer;
2✔
5364
    SHARED_GROUP_TEST_PATH(path);
2✔
5365
    DBOptions options;
2✔
5366
    options.logger = std::make_shared<StreamLogger>(buffer);
2✔
5367
    options.logger->set_level_threshold("Realm", util::Logger::Level::all);
2✔
5368
    auto db = DB::create(make_in_realm_history(), path, options);
2✔
5369
    ColKey col;
2✔
5370
    ColKey col_int;
2✔
5371

5372
    {
2✔
5373
        auto wt = db->start_write();
2✔
5374

5375
        auto t = wt->add_table_with_primary_key("foo", type_Int, "id");
2✔
5376
        col = t->add_column(type_Mixed, "any");
2✔
5377
        col_int = t->add_column(type_Int, "int");
2✔
5378

5379
        auto dict =
2✔
5380
            t->create_object_with_primary_key(1).set_collection(col, CollectionType::Dictionary).get_dictionary(col);
2✔
5381
        dict.insert("hello", "world");
2✔
5382

5383
        auto list =
2✔
5384
            t->create_object_with_primary_key(2).set_collection(col, CollectionType::List).get_list<Mixed>(col);
2✔
5385
        list.add(47.50);
2✔
5386

5387
        std::vector<char> str_data(90);
2✔
5388
        std::iota(str_data.begin(), str_data.end(), ' ');
2✔
5389
        t->create_object_with_primary_key(5).set_any(col, StringData(str_data.data(), str_data.size()));
2✔
5390

5391
        std::vector<char> bin_data(50);
2✔
5392
        std::iota(bin_data.begin(), bin_data.end(), 0);
2✔
5393
        t->create_object_with_primary_key(6).set_any(col, BinaryData(bin_data.data(), bin_data.size()));
2✔
5394

5395
        t->create_object_with_primary_key(7).set_any(col, Timestamp(1695207215, 0));
2✔
5396

5397
        wt->commit();
2✔
5398
    }
2✔
5399
    {
2✔
5400
        // Try to serialize a query with a constraining view
5401
        auto rt = db->start_read();
2✔
5402
        auto table = rt->get_table("foo");
2✔
5403
        TableView tv = table->find_all_int(col_int, 0);
2✔
5404
        table->where(&tv).equal(col_int, 0).count();
2✔
5405
    }
2✔
5406

5407
    auto str = buffer.str();
2✔
5408
    // std::cout << str << std::endl;
5409
    CHECK(str.find("abcdefghijklmno ...") != std::string::npos);
2✔
5410
    CHECK(str.find("14 15 16 17 18 19 ...") != std::string::npos);
2✔
5411
    CHECK(str.find("2023-09-20 10:53:35") != std::string::npos);
2✔
5412
    CHECK(str.find("VIEW { 5 element(s) }") != std::string::npos);
2✔
5413
    CHECK(str.find("Set 'any' to dictionary") != std::string::npos);
2✔
5414
    CHECK(str.find("Set 'any' to list") != std::string::npos);
2✔
5415
}
2✔
5416

5417
#endif // TEST_TABLE
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