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

realm / realm-core / nicola.cabiddu_1040

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

Pull #6766

Evergreen

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

97128 of 178458 branches covered (0.0%)

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

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

99.62
/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)
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)
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,138✔
134
        else {
962✔
135
            if (m_throw_on_null)
962✔
136
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null found");
×
137
            else
962✔
138
                return T2(); // default value for type
962✔
139
        }
962✔
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)
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)
427✔
168
                return StringData();
220✔
169

107✔
170
            if (m_throw_on_null) {
207✔
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
207✔
175
                return StringData("", 0);
207✔
176
        }
7,653✔
177
        const char* p = from_value.data();
7,653✔
178
        const char* limit = p + from_value.size();
7,653✔
179
        data.clear();
7,653✔
180
        data.reserve(from_value.size());
7,653✔
181
        while (p != limit)
244,896✔
182
            data.push_back(*p++);
237,243✔
183
        return StringData(&data[0], from_value.size());
7,653✔
184
    }
7,653✔
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)
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)
390✔
199
                return BinaryData();
186✔
200

108✔
201
            if (m_throw_on_null) {
204✔
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
204✔
206
                return BinaryData("", 0);
204✔
207
        }
7,690✔
208
        const char* p = from_value.data();
7,690✔
209
        const char* limit = p + from_value.size();
7,690✔
210
        data.clear();
7,690✔
211
        data.reserve(from_value.size());
7,690✔
212
        while (p != limit)
246,080✔
213
            data.push_back(*p++);
238,390✔
214
        return BinaryData(&data[0], from_value.size());
7,690✔
215
    }
7,690✔
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)
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)
374✔
229
                return Timestamp();
198✔
230

90✔
231
            if (m_throw_on_null)
176✔
232
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null found");
×
233
            else
176✔
234
                return Timestamp(0, 0);
176✔
235
        }
7,706✔
236
        return from_value;
7,706✔
237
    }
7,706✔
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
1✔
287
        Group group;
2✔
288
        TableRef table = group.add_table("test");
2✔
289

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

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

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

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

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

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

1✔
311
    {
2✔
312
        // Check that add_empty_row() adds null integer as default
1✔
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

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

1✔
321
        // Check that you can obtain a non null value through get<Int>
1✔
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

1✔
327
    {
2✔
328
        // Check that add_empty_row() adds 0 integer as default.
1✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

411
TEST(Table_OptimizeCrash)
412
{
2✔
413
    // This will crash at the .add() method
1✔
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

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

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

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

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

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

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

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

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

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

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

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

1✔
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
1✔
478
    Group g;
2✔
479
    TableRef table = g.add_table("test_table");
2✔
480

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

1✔
486
    ObjKey key;
2✔
487

1✔
488
    // Maximum
1✔
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

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

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

1✔
511
        table->create_object();
2✔
512

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

1✔
520
    // Minimum
1✔
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

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

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

1✔
543
        table->create_object();
2✔
544

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

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

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

1✔
571
        // average == sum / rows, where rows does *not* include values with null.
1✔
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

1✔
576
        // There are currently 3 ways of doing average: on tableview, table and query:
1✔
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

1✔
582
        // Core has an optimization where it executes average directly on the column if there
1✔
583
        // are no query conditions. Bypass that here.
1✔
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

1✔
588
        // Now doubles
1✔
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

1✔
593
        // average == sum / rows, where rows does *not* include values with null.
1✔
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

1✔
597
        // There are currently 3 ways of doing average: on tableview, table and query:
1✔
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

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

1✔
606
        // Core has an optimization where it executes average directly on the column if there
1✔
607
        // are no query conditions. Bypass that here.
1✔
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

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

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

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

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

1✔
631
        // There are currently 3 ways of doing average: on tableview, table and query:
1✔
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

1✔
635
        // Core has an optimization where it executes average directly on the column if there
1✔
636
        // are no query conditions. Bypass that here.
1✔
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
1✔
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

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

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

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

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

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

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

50✔
694
        // Test methods on Table
50✔
695
        {
100✔
696
            // Table::max
50✔
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));
93✔
702

50✔
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));
93✔
708

50✔
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));
93✔
714

50✔
715
            // Table::min
50✔
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));
93✔
721

50✔
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));
93✔
727

50✔
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));
93✔
733

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

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

50✔
740
            // Table::avg
50✔
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);
93✔
745

50✔
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);
93✔
751

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

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

50✔
760
        // Test methods on TableView
50✔
761
        {
100✔
762
            // TableView::max
50✔
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));
93✔
768

50✔
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));
93✔
774

50✔
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));
93✔
780

50✔
781
            // TableView::min
50✔
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));
93✔
787

50✔
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));
93✔
793

50✔
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));
93✔
799

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

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

50✔
806
            // TableView::avg
50✔
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);
93✔
811

50✔
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);
93✔
817

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

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

50✔
826
        // Test methods on Query
50✔
827
        {
100✔
828
            // TableView::max
50✔
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));
93✔
834

50✔
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));
93✔
840

50✔
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));
93✔
846

50✔
847
            // TableView::min
50✔
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));
93✔
853

50✔
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));
93✔
859

50✔
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));
93✔
865

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

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

50✔
872
            // TableView::avg
50✔
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);
93✔
877

50✔
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);
93✔
883

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

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

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

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

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

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

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

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

1✔
949
    // Test adding multiple rows
1✔
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

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

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

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

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

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

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

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

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

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

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

1✔
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
1✔
1014
    {
2✔
1015
        Table table;
2✔
1016
        CHECK_EQUAL("", table.get_name());
2✔
1017
    }
2✔
1018

1✔
1019
    // Direct members of groups do have names
1✔
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
2✔
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

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

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

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

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

30✔
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
30✔
1084
        obj.set(bool_col, (i % 2 ? true : false));
46✔
1085
        // float
30✔
1086
        obj.set(float_col, 123.456f * sign);
60✔
1087
        // double
30✔
1088
        obj.set(double_col, 9876.54321 * sign);
60✔
1089
        // strings
30✔
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
30✔
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

2✔
1121
    // We also want a StringEnumColumn
2✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
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);
900✔
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

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

3✔
1328
    // nulls should appear first,
3✔
1329
    // followed by nans, folllowed by the rest of the values in ascending order
3✔
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

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

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

4✔
1356
    // (0, 10); (1, 10); (1, 14); (2, 10); (2; 14)
4✔
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

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

4✔
1367
    // (0, 10); (1, 10); (2, 10); (1, 14); (2, 14)
4✔
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

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

1✔
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.
1✔
1393
    // When this is shifted up and down again, the most significant bit must
1✔
1394
    // still be 0.
1✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1514
    // Check that signed integers wrap around. This invariant is necessary for
1✔
1515
    // full commutativity.
1✔
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

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

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

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

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

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

1✔
1564
    // Find existing values
1✔
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
1✔
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

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

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

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

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

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

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

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

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

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

1✔
1644
    table.enumerate_string_column(col_str);
2✔
1645

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

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

1✔
1661
    // Verify counts
1✔
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

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

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

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

1✔
1698
    // Insert non-optimizable strings
1✔
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

1✔
1707
    // AutoEnumerate in reverse order
1✔
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

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

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

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

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

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

1✔
1785
    // Add some more rows
1✔
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

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

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

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

1✔
1811
    size_t r;
2✔
1812

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

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

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

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

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

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

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

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

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

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

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

1✔
1868
    // Test that 64-bit dates are preserved
1✔
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

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

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

1✔
1911
    // count
1✔
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
1✔
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

1✔
1934
    // maximum
1✔
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
1✔
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
1✔
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

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

1✔
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
2✔
1995
        // Then we test everything with non-nullable columns where the null entries will instead be just
2✔
1996
        // 0, 0.0, etc.
2✔
1997
        nullable = (i == 1);
4✔
1998

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

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

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

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

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

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

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

2✔
2027
        size_t count;
4✔
2028
        ObjKey pos;
4✔
2029
        if (nullable) {
4✔
2030
            // max
1✔
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

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

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

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

1✔
2051
            // min
1✔
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

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

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

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

1✔
2072
            // average
1✔
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

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

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

1✔
2088
            // sum
1✔
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
1✔
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

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

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

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

1✔
2111
            // min
1✔
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

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

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

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

1✔
2129
            // average
1✔
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

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

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

1✔
2142
            // sum
1✔
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

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

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

1✔
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
1✔
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
1✔
2196
    Table table;
2✔
2197
    table.add_column(type_Int, "int0");
2✔
2198

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

1✔
2202
    // delete objects in first cluster
1✔
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

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

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

1✔
2214
    // table.dump_objects();
1✔
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

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

1✔
2239
    t.clear();
2✔
2240

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

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

1✔
2273
    // is_null is always reliable regardless of type
1✔
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

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

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

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

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

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

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

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

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

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

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

5✔
2365

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
2515
    // Now a small fuzzy test to catch other such bugs
1✔
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

1✔
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
51✔
2528
                // worst case now)
51✔
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

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

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

1✔
2579
    // Check that links are nulled when connected table is cleared
1✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
2672
    // Int
1✔
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

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

1✔
2683
    // Boolean
1✔
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

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

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

1✔
2701
    // Float
1✔
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

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

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

1✔
2717
    // Double
1✔
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

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

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

1✔
2733
    // String
1✔
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

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

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

1✔
2749
    // Upgrade to medium leaf
1✔
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

1✔
2754
    // Binary
1✔
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

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

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

1✔
2770
    // Upgrade from small to big
1✔
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

1✔
2780
    // Timestamp
1✔
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

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

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

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

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

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

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

1✔
2834
    table.remove_column(int2_col);
2✔
2835

1✔
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.
1✔
2846
    // To be used mostly as a help when debugging new implementation
1✔
2847

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

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

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

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

8,000✔
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

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

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

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

1✔
2901
    // table.dump_objects();
1✔
2902

1✔
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;
1,024✔
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

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

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

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

1✔
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
1✔
2938
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
2939
    it1 += 0;
2✔
2940
    // Still invalid
1✔
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

1✔
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
TEST(Table_QuickSort2)
2969
{
2✔
2970
    Table ttt;
2✔
2971
    auto strings = ttt.add_column(type_String, "2");
2✔
2972

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

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

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

1✔
2983
    CALLGRIND_START_INSTRUMENTATION;
2✔
2984

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

1✔
2991
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
2992

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

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

1✔
3014
    CALLGRIND_START_INSTRUMENTATION;
2✔
3015

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

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

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

1✔
3025

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

100,000✔
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

100,000✔
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

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

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

1✔
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
100✔
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,991✔
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!
1✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
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,702✔
3285
                auto key_val = test_util::random_int<int64_t>(0, nb_rows * 10);
210,702✔
3286
                if (key_set.count(key_val) == 0) {
210,702✔
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,702✔
3292
        }
200,000✔
3293
    }
2✔
3294

1✔
3295
    CALLGRIND_START_INSTRUMENTATION;
2✔
3296

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

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

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

1✔
3306

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

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

1✔
3313

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
3427
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3428
}
2✔
3429

3430
TEST(Table_CollisionMapping)
3431
{
2✔
3432

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

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

1✔
3443
    SHARED_GROUP_TEST_PATH(path);
2✔
3444

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

1✔
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, sizeof(buffer));
510✔
3455

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

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

1✔
3475
    // Check that locally allocated IDs are properly persisted
1✔
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

1✔
3482
            // Make objects with primary keys that do not already exist but are guaranteed
1✔
3483
            // to cause further collisions.
1✔
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, sizeof(buffer));
510✔
3489

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

1✔
3498
            // Find an object with collision key
1✔
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✔
UNCOV
3504
                    key = obj.get_key();
×
UNCOV
3505
                    pk = obj.get<String>("pk");
×
UNCOV
3506
                    obj.remove();
×
UNCOV
3507
                    break;
×
UNCOV
3508
                }
×
3509
            }
1,020✔
3510

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
3630
    // Test column searching
1✔
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

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

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

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

1✔
3658
    // Test string column searching
1✔
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

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

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

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

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

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

1✔
3703
    // Non-nullable integers
1✔
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
1✔
3707
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3708

1✔
3709
    // Nullable integers
1✔
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
1✔
3713
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
1✔
3714

1✔
3715
    // Non-nullable strings
1✔
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 ""
1✔
3719
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3720

1✔
3721
    // Nullable strings
1✔
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
1✔
3725
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
1✔
3726

1✔
3727
    // Non-nullable bools
1✔
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

1✔
3731
    // Nullable bools
1✔
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
1✔
3735
    // CHECK_EQUAL(table.find_first_null(5), o3.get_key());
1✔
3736

1✔
3737
    // Non-nullable Timestamp
1✔
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

1✔
3741
    // Nullable Timestamp
1✔
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
1✔
3745
    // CHECK_EQUAL(table.find_first_null(7), o3.get_key());
1✔
3746

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

1✔
3751
    // Integers
1✔
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
1✔
3755
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3756

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

1✔
3761
    // Non-nullable strings
1✔
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 ""
1✔
3765
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3766

1✔
3767
    // Nullable strings
1✔
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
1✔
3771
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
1✔
3772

1✔
3773
    // Non-nullable bools
1✔
3774
    // default value for non-nullable bool is false, so o3 is a match
1✔
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

1✔
3778
    // Nullable bools
1✔
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

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

1✔
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
1✔
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
1✔
3793
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
1✔
3794

1✔
3795
    // Non-nullable strings
1✔
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 ""
1✔
3799
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3800

1✔
3801
    // Nullable strings
1✔
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
1✔
3805
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
1✔
3806

1✔
3807
    // Remove four of the indexes through remove_search_index() call. Let other four remain to see
1✔
3808
    // if they leak memory when Table goes out of scope (needs leak detector)
1✔
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
1✔
3821
    table.add_search_index(col_int);
2✔
3822
    table.add_search_index(col_str);
2✔
3823

1✔
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
100✔
3830
        if (key_value > 21 && key_value < 28) {
200✔
3831
            o.set(col_str, "Hello");
12✔
3832
        }
12✔
3833
    }
200✔
3834

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

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

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

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

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

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

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

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

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

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

28✔
3920
    for (underlying_type v : values) {
280✔
3921
        const size_t num_expected = (v == default_non_null_value ? num_default_non_nullables : 1);
252✔
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

28✔
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
    {
13,606✔
3960
        std::vector<ObjKey> res;
13,606✔
3961
        Table::Iterator it = table->begin();
13,606✔
3962
        while (it != table->end()) {
997,677✔
3963
            if (!it->is_null(col)) {
984,071✔
3964
                T v2 = it->get<T>(col);
904,018✔
3965
                if (v == v2) {
904,018✔
3966
                    res.push_back(it->get_key());
214,203✔
3967
                }
214,203✔
3968
            }
904,018✔
3969
            ++it;
984,071✔
3970
        };
984,071✔
3971
        // res is returned with nrvo optimization
6,542✔
3972
        return res;
13,606✔
3973
    }
13,606✔
3974

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

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

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

6,542✔
3987
                size_t a = ref.size();
13,606✔
3988
                size_t b = res.size();
13,606✔
3989

6,542✔
3990
                REALM_ASSERT(a == b);
13,606✔
3991
            }
13,606✔
3992
        }
15,539✔
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

8✔
4003
        bool add_trend = true;
16✔
4004

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

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

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

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

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

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

8,000✔
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,000✔
4065
        std::string s = realm::util::to_string(fastrand(5));
5,000✔
4066
        return s;
5,000✔
4067
    }
5,000✔
4068
    template <typename Type = T2>
4069
    typename std::enable_if<std::is_same<Type, Timestamp>::value, T2>::type static create()
4070
    {
5,207✔
4071
        return Timestamp(fastrand(3), int32_t(fastrand(3)));
5,207✔
4072
    }
5,207✔
4073
    template <typename Type = T2>
4074
    typename std::enable_if<std::is_same<Type, int64_t>::value, T2>::type static create()
4075
    {
5,019✔
4076
        return fastrand(5);
5,019✔
4077
    }
5,019✔
4078

4079
    template <typename Type = T2>
4080
    typename std::enable_if<std::is_same<Type, bool>::value, T2>::type static create()
4081
    {
5,097✔
4082
        return fastrand(100) > 50;
5,097✔
4083
    }
5,097✔
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>:
1✔
4095
    // T:         Type that must be used in calls too Obj::get<T>
1✔
4096
    // nullable:  If the columns must be is nullable or not
1✔
4097
    // Obj::set() will be automatically be called with set<RemoveOptional<T>>()
1✔
4098

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

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

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

1✔
4112
    // Self-contained null state
1✔
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

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

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

1✔
4126
    table.remove_column(col);
2✔
4127
    // col is now obsolete
1✔
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

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

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

1✔
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,436✔
4157
    return test_util::random_int<T>();
31,436✔
4158
}
31,436✔
4159

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

4169
template <>
4170
bool generate_value()
4171
{
31,438✔
4172
    return test_util::random_int<int>() & 0x1;
31,438✔
4173
}
31,438✔
4174
template <>
4175
float generate_value()
4176
{
31,383✔
4177
    return float(1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000)));
31,383✔
4178
}
31,383✔
4179
template <>
4180
double generate_value()
4181
{
31,480✔
4182
    return 1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000));
31,480✔
4183
}
31,480✔
4184
template <>
4185
Timestamp generate_value()
4186
{
31,479✔
4187
    return Timestamp(test_util::random_int<int>(0, 1000000), test_util::random_int<int>(0, 1000000000));
31,479✔
4188
}
31,479✔
4189
template <>
4190
Decimal128 generate_value()
4191
{
31,386✔
4192
    return Decimal128(test_util::random_int<int>(-100000, 100000));
31,386✔
4193
}
31,386✔
4194
template <>
4195
ObjectId generate_value()
4196
{
31,460✔
4197
    return ObjectId::gen();
31,460✔
4198
}
31,460✔
4199
template <>
4200
UUID generate_value()
4201
{
31,466✔
4202
    std::string str;
31,466✔
4203
    str.resize(36);
31,466✔
4204
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), []() -> char {
1,132,776✔
4205
        char c = test_util::random_int<char>(0, 15);
1,132,776✔
4206
        return c >= 10 ? (c - 10 + 'a') : (c + '0');
920,408✔
4207
    });
1,132,776✔
4208
    str.at(8) = '-';
31,466✔
4209
    str.at(13) = '-';
31,466✔
4210
    str.at(18) = '-';
31,466✔
4211
    str.at(23) = '-';
31,466✔
4212
    return UUID(str.c_str());
31,466✔
4213
}
31,466✔
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,261✔
4229
    ManagedStorage(std::string&& v)
4230
        : storage(std::move(v))
4231
        , value(storage)
4232
    {
62,899✔
4233
    }
62,899✔
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,754✔
4247
                storage.assign(v.data(), v.data() + v.size());
15,343✔
4248
            }
15,343✔
4249
            value = T(storage);
15,754✔
4250
        }
15,754✔
4251
    }
16,160✔
4252
    ManagedStorage& operator=(const ManagedStorage& other)
4253
    {
138,104✔
4254
        storage = other.storage;
138,104✔
4255
        value = other.value ? T(storage) : T();
134,783✔
4256
        return *this;
138,104✔
4257
    }
138,104✔
4258
    ManagedStorage& operator=(ManagedStorage&& other)
4259
    {
2,763,844✔
4260
        storage = std::move(other.storage);
2,763,844✔
4261
        value = other.value ? T(storage) : T();
2,697,022✔
4262
        return *this;
2,763,844✔
4263
    }
2,763,844✔
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,681✔
4290
        }
4,681✔
4291
        else {
158,319✔
4292
            return managed<T>{generate_value<T>()};
158,319✔
4293
        }
158,319✔
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,684✔
4303
        }
1,684✔
4304
        else {
31,396✔
4305
            return generate_value<std::string>();
31,396✔
4306
        }
31,396✔
4307
    }
33,080✔
4308
};
4309

4310
template <>
4311
struct generator<BinaryData> {
4312
    static managed<BinaryData> get(bool optional)
4313
    {
33,080✔
4314
        if (optional && (test_util::random_int<int>() % 10) == 0) {
33,080✔
4315
            return managed<BinaryData>(null());
1,577✔
4316
        }
1,577✔
4317
        else {
31,503✔
4318
            return generate_value<std::string>();
31,503✔
4319
        }
31,503✔
4320
    }
33,080✔
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,431✔
4337
        else
76,669✔
4338
            return managed<Optional<T>>{generate_value<T>()};
76,669✔
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
};
4346
template <>
4347
struct generator<Optional<BinaryData>> {
4348
};
4349

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

4403
TEST(Table_List_Ops)
4404
{
2✔
4405
    SHARED_GROUP_TEST_PATH(path);
2✔
4406

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

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

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

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

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

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

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

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

4498
TEST(Table_Ops)
4499
{
2✔
4500
    SHARED_GROUP_TEST_PATH(path);
2✔
4501

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

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

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

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

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

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

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

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

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

4617
TEST(Table_Column_DynamicConversions)
4618
{
2✔
4619
    SHARED_GROUP_TEST_PATH(path);
2✔
4620

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

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

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

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

4649
/*
4650
TEST(Table_Column_Conversions)
4651
{
4652
    SHARED_GROUP_TEST_PATH(path);
4653

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

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

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

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

4674
}
4675
*/
4676

4677
TEST(Table_ChangePKNullability)
4678
{
2✔
4679
    SHARED_GROUP_TEST_PATH(path);
2✔
4680

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

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

1✔
4687
    table->create_object_with_primary_key("Paul");
2✔
4688
    table->create_object_with_primary_key("John");
2✔
4689
    table->create_object_with_primary_key("George");
2✔
4690
    table->create_object_with_primary_key("Ringo");
2✔
4691

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

1✔
4696
    table->create_object_with_primary_key("");
2✔
4697
    table->create_object_with_primary_key({});
2✔
4698

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

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

4707
TEST(Table_MultipleObjs)
4708
{
2✔
4709
    SHARED_GROUP_TEST_PATH(path);
2✔
4710

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

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

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

1✔
4733
    list_1.add(obj_key);
2✔
4734
    CHECK_EQUAL(list_1.get(0), obj_key);
2✔
4735
    CHECK_EQUAL(list_2.get(0), obj_key);
2✔
4736
}
2✔
4737

4738
TEST(Table_IteratorRandomAccess)
4739
{
2✔
4740
    Table t;
2✔
4741

1✔
4742
    ObjKeys keys;
2✔
4743
    t.create_objects(1000, keys);
2✔
4744

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

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

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

4786
TEST(Table_EmbeddedObjects)
4787
{
2✔
4788
    SHARED_GROUP_TEST_PATH(path);
2✔
4789

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

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

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

4806
TEST(Table_EmbeddedObjectCreateAndDestroy)
4807
{
2✔
4808
    SHARED_GROUP_TEST_PATH(path);
2✔
4809

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

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

4863
TEST(Table_EmbeddedObjectCreateAndDestroyList)
4864
{
2✔
4865
    SHARED_GROUP_TEST_PATH(path);
2✔
4866

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

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

1✔
4887
    tr->commit_and_continue_as_read();
2✔
4888
    tr->verify();
2✔
4889

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

4905
TEST(Table_EmbeddedObjectCreateAndDestroyDictionary)
4906
{
2✔
4907
    SHARED_GROUP_TEST_PATH(path);
2✔
4908

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

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

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

1✔
4929
    Obj o3 = parent_dict.create_and_insert_linked_object("two");
2✔
4930
    parent_dict.create_and_insert_linked_object("three");
2✔
4931

1✔
4932
    CHECK_EQUAL(parent_dict.get_object("one").get_key(), o2.get_key());
2✔
4933

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

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

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

1✔
4955
    tr->commit_and_continue_as_read();
2✔
4956
    tr->verify();
2✔
4957

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

4973
TEST(Table_EmbeddedObjectNotifications)
4974
{
2✔
4975
    SHARED_GROUP_TEST_PATH(path);
2✔
4976

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

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

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

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

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

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

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

1✔
5089
    SHARED_GROUP_TEST_PATH(path);
2✔
5090

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

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

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

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

1✔
5148
    auto bar = bars->create_object();
2✔
5149

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

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

1✔
5174
    foos->remove_search_index(col);
2✔
5175

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

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

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

1✔
5209
    Obj dest_obj = dest->create_object_with_primary_key(0);
2✔
5210

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

1✔
5217
    source->clear();
2✔
5218
    dest->clear();
2✔
5219
}
2✔
5220

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

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

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

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

1✔
5243
    // auto t1 = steady_clock::now();
1✔
5244

1✔
5245
    CALLGRIND_START_INSTRUMENTATION;
2✔
5246
    auto tv = q.find_all(ordering);
2✔
5247
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
5248

1✔
5249
    // auto t2 = steady_clock::now();
1✔
5250

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

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

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

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

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

5304
TEST(Table_AsymmetricObjects)
5305
{
2✔
5306
    SHARED_GROUP_TEST_PATH(path);
2✔
5307

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

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

1✔
5319
    tr = sg->start_read();
2✔
5320
    table = tr->get_table("mytable");
2✔
5321
    CHECK(table->is_asymmetric());
2✔
5322

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

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

1✔
5339
    {
2✔
5340
        auto wt = db->start_write();
2✔
5341

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

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

1✔
5352
        wt->commit();
2✔
5353
    }
2✔
5354

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

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

1✔
5374
    {
2✔
5375
        auto wt = db->start_write();
2✔
5376

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

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

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

1✔
5389
        auto set = t->create_object_with_primary_key(3).set_collection(col, CollectionType::Set).get_set<Mixed>(col);
2✔
5390
        set.insert(false);
2✔
5391

1✔
5392
        std::vector<char> str_data(90);
2✔
5393
        std::iota(str_data.begin(), str_data.end(), ' ');
2✔
5394
        t->create_object_with_primary_key(5).set_any(col, StringData(str_data.data(), str_data.size()));
2✔
5395

1✔
5396
        std::vector<char> bin_data(50);
2✔
5397
        std::iota(bin_data.begin(), bin_data.end(), 0);
2✔
5398
        t->create_object_with_primary_key(6).set_any(col, BinaryData(bin_data.data(), bin_data.size()));
2✔
5399

1✔
5400
        t->create_object_with_primary_key(7).set_any(col, Timestamp(1695207215, 0));
2✔
5401

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

1✔
5412
    auto str = buffer.str();
2✔
5413
    // std::cout << str << std::endl;
1✔
5414
    CHECK(str.find("abcdefghijklmno ...") != std::string::npos);
2✔
5415
    CHECK(str.find("14 15 16 17 18 19 ...") != std::string::npos);
2✔
5416
    CHECK(str.find("2023-09-20 10:53:35") != std::string::npos);
2✔
5417
    CHECK(str.find("Query::get_description() failed:") != std::string::npos);
2✔
5418
    CHECK(str.find("Set 'any' to dictionary") != std::string::npos);
2✔
5419
    CHECK(str.find("Set 'any' to list") != std::string::npos);
2✔
5420
    CHECK(str.find("Set 'any' to set") != std::string::npos);
2✔
5421
}
2✔
5422

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