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

realm / realm-core / github_pull_request_285284

21 Nov 2023 01:56PM UTC coverage: 91.664% (-0.03%) from 91.689%
github_pull_request_285284

Pull #7123

Evergreen

jedelbo
Merge branch 'master' into jf/mql
Pull Request #7123: PoC: Add MQL translation skeleton

92364 of 169228 branches covered (0.0%)

264 of 308 new or added lines in 5 files covered. (85.71%)

102 existing lines in 21 files now uncovered.

231504 of 252558 relevant lines covered (91.66%)

5988933.97 hits per line

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

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

112✔
170
            if (m_throw_on_null) {
211✔
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
211✔
175
                return StringData("", 0);
211✔
176
        }
7,679✔
177
        const char* p = from_value.data();
7,679✔
178
        const char* limit = p + from_value.size();
7,679✔
179
        data.clear();
7,679✔
180
        data.reserve(from_value.size());
7,679✔
181
        while (p != limit)
245,728✔
182
            data.push_back(*p++);
238,049✔
183
        return StringData(&data[0], from_value.size());
7,679✔
184
    }
7,679✔
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)
422✔
199
                return BinaryData();
220✔
200

100✔
201
            if (m_throw_on_null) {
202✔
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
202✔
206
                return BinaryData("", 0);
202✔
207
        }
7,658✔
208
        const char* p = from_value.data();
7,658✔
209
        const char* limit = p + from_value.size();
7,658✔
210
        data.clear();
7,658✔
211
        data.reserve(from_value.size());
7,658✔
212
        while (p != limit)
245,056✔
213
            data.push_back(*p++);
237,398✔
214
        return BinaryData(&data[0], from_value.size());
7,658✔
215
    }
7,658✔
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)
373✔
229
                return Timestamp();
178✔
230

99✔
231
            if (m_throw_on_null)
195✔
232
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null found");
×
233
            else
195✔
234
                return Timestamp(0, 0);
195✔
235
        }
7,707✔
236
        return from_value;
7,707✔
237
    }
7,707✔
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++) {
589✔
668
            bool null = (fastrand(1) == 0);
489✔
669
            if (!null) {
489✔
670
                int64_t value = fastrand(10);
253✔
671
                sum += value;
253✔
672
                if (largest_pos == null_key || value > largest) {
253✔
673
                    largest = value;
129✔
674
                    largest_pos = keys[t];
129✔
675
                }
129✔
676
                if (smallest_pos == null_key || value < smallest) {
253✔
677
                    smallest = value;
140✔
678
                    smallest_pos = keys[t];
140✔
679
                }
140✔
680
                table->get_object(keys[t]).set_all(Timestamp(value, 0), value, float(value));
253✔
681
            }
253✔
682
            else {
236✔
683
                nulls++;
236✔
684
            }
236✔
685
        }
489✔
686

50✔
687
        avg = double(sum) / (rows - nulls == 0 ? 1 : rows - nulls);
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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);
90✔
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);
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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);
90✔
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);
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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));
90✔
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);
90✔
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);
90✔
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_list_basic)
2844
{
2✔
2845
    Table table;
2✔
2846
    auto list_col = table.add_column_list(type_Int, "int_list");
2✔
2847
    int sum = 0;
2✔
2848

1✔
2849
    {
2✔
2850
        Obj obj = table.create_object(ObjKey(5));
2✔
2851
        CHECK_NOT(obj.is_null(list_col));
2✔
2852
        auto list = obj.get_list<int64_t>(list_col);
2✔
2853
        CHECK_NOT(obj.is_null(list_col));
2✔
2854
        CHECK(list.is_empty());
2✔
2855

1✔
2856
        size_t return_cnt = 0, return_ndx = 0;
2✔
2857
        list.sum(&return_cnt);
2✔
2858
        CHECK_EQUAL(return_cnt, 0);
2✔
2859
        list.max(&return_ndx);
2✔
2860
        CHECK_EQUAL(return_ndx, not_found);
2✔
2861
        return_ndx = 0;
2✔
2862
        list.min(&return_ndx);
2✔
2863
        CHECK_EQUAL(return_ndx, not_found);
2✔
2864
        list.avg(&return_cnt);
2✔
2865
        CHECK_EQUAL(return_cnt, 0);
2✔
2866

1✔
2867
        for (int i = 0; i < 100; i++) {
202✔
2868
            list.add(i + 1000);
200✔
2869
            sum += (i + 1000);
200✔
2870
        }
200✔
2871
    }
2✔
2872
    {
2✔
2873
        Obj obj = table.get_object(ObjKey(5));
2✔
2874
        auto list1 = obj.get_list<int64_t>(list_col);
2✔
2875
        CHECK_EQUAL(list1.size(), 100);
2✔
2876
        CHECK_EQUAL(list1.get(0), 1000);
2✔
2877
        CHECK_EQUAL(list1.get(99), 1099);
2✔
2878
        auto list_base = obj.get_listbase_ptr(list_col);
2✔
2879
        CHECK_EQUAL(list_base->size(), 100);
2✔
2880
        CHECK(dynamic_cast<Lst<Int>*>(list_base.get()));
2✔
2881

1✔
2882
        CHECK_EQUAL(list1.sum(), sum);
2✔
2883
        CHECK_EQUAL(list1.max(), 1099);
2✔
2884
        CHECK_EQUAL(list1.min(), 1000);
2✔
2885
        CHECK_EQUAL(list1.avg(), double(sum) / 100);
2✔
2886

1✔
2887
        auto list2 = obj.get_list<int64_t>(list_col);
2✔
2888
        list2.set(50, 747);
2✔
2889
        CHECK_EQUAL(list1.get(50), 747);
2✔
2890
        list1.resize(101);
2✔
2891
        CHECK_EQUAL(list1.get(100), 0);
2✔
2892
        list1.resize(50);
2✔
2893
        CHECK_EQUAL(list1.size(), 50);
2✔
2894
    }
2✔
2895
    {
2✔
2896
        Obj obj = table.create_object(ObjKey(7));
2✔
2897
        auto list = obj.get_list<int64_t>(list_col);
2✔
2898
        list.resize(10);
2✔
2899
        CHECK_EQUAL(list.size(), 10);
2✔
2900
        for (int i = 0; i < 10; i++) {
22✔
2901
            CHECK_EQUAL(list.get(i), 0);
20✔
2902
        }
20✔
2903
    }
2✔
2904
    table.remove_object(ObjKey(5));
2✔
2905
}
2✔
2906

2907
template <typename T>
2908
struct NullableTypeConverter {
2909
    using NullableType = util::Optional<T>;
2910
    static bool is_null(NullableType t)
2911
    {
72✔
2912
        return !bool(t);
72✔
2913
    }
72✔
2914
};
2915

2916
template <>
2917
struct NullableTypeConverter<Decimal128> {
2918
    using NullableType = Decimal128;
2919
    static bool is_null(Decimal128 val)
2920
    {
24✔
2921
        return val.is_null();
24✔
2922
    }
24✔
2923
};
2924

2925
TEST_TYPES(Table_list_nullable, int64_t, float, double, Decimal128)
2926
{
8✔
2927
    Table table;
8✔
2928
    auto list_col = table.add_column_list(ColumnTypeTraits<TEST_TYPE>::id, "int_list", true);
8✔
2929
    ColumnSumType<TEST_TYPE> sum = TEST_TYPE(0);
8✔
2930

4✔
2931
    {
8✔
2932
        Obj obj = table.create_object(ObjKey(5));
8✔
2933
        CHECK_NOT(obj.is_null(list_col));
8✔
2934
        auto list = obj.get_list<typename NullableTypeConverter<TEST_TYPE>::NullableType>(list_col);
8✔
2935
        CHECK_NOT(obj.is_null(list_col));
8✔
2936
        CHECK(list.is_empty());
8✔
2937
        for (int i = 0; i < 100; i++) {
808✔
2938
            TEST_TYPE val = TEST_TYPE(i + 1000);
800✔
2939
            list.add(val);
800✔
2940
            sum += (val);
800✔
2941
        }
800✔
2942
    }
8✔
2943
    {
8✔
2944
        Obj obj = table.get_object(ObjKey(5));
8✔
2945
        auto list1 = obj.get_list<typename NullableTypeConverter<TEST_TYPE>::NullableType>(list_col);
8✔
2946
        CHECK_EQUAL(list1.size(), 100);
8✔
2947
        CHECK_EQUAL(list1.get(0), TEST_TYPE(1000));
8✔
2948
        CHECK_EQUAL(list1.get(99), TEST_TYPE(1099));
8✔
2949
        CHECK_NOT(list1.is_null(0));
8✔
2950
        auto list_base = obj.get_listbase_ptr(list_col);
8✔
2951
        CHECK_EQUAL(list_base->size(), 100);
8✔
2952
        CHECK_NOT(list_base->is_null(0));
8✔
2953
        CHECK(dynamic_cast<Lst<typename NullableTypeConverter<TEST_TYPE>::NullableType>*>(list_base.get()));
8✔
2954

4✔
2955
        CHECK_EQUAL(list1.sum(), sum);
8✔
2956
        CHECK_EQUAL(list1.max(), TEST_TYPE(1099));
8✔
2957
        CHECK_EQUAL(list1.min(), TEST_TYPE(1000));
8✔
2958
        CHECK_EQUAL(list1.avg(), typename ColumnTypeTraits<TEST_TYPE>::average_type(sum) / 100);
8✔
2959

4✔
2960
        auto list2 = obj.get_list<typename NullableTypeConverter<TEST_TYPE>::NullableType>(list_col);
8✔
2961
        list2.set(50, TEST_TYPE(747));
8✔
2962
        CHECK_EQUAL(list1.get(50), TEST_TYPE(747));
8✔
2963
        list1.set_null(50);
8✔
2964
        CHECK(NullableTypeConverter<TEST_TYPE>::is_null(list1.get(50)));
8✔
2965
        list1.resize(101);
8✔
2966
        CHECK(NullableTypeConverter<TEST_TYPE>::is_null(list1.get(100)));
8✔
2967
    }
8✔
2968
    {
8✔
2969
        Obj obj = table.create_object(ObjKey(7));
8✔
2970
        auto list = obj.get_list<typename NullableTypeConverter<TEST_TYPE>::NullableType>(list_col);
8✔
2971
        list.resize(10);
8✔
2972
        CHECK_EQUAL(list.size(), 10);
8✔
2973
        for (int i = 0; i < 10; i++) {
88✔
2974
            CHECK(NullableTypeConverter<TEST_TYPE>::is_null(list.get(i)));
80✔
2975
        }
80✔
2976
    }
8✔
2977
    table.remove_object(ObjKey(5));
8✔
2978
}
8✔
2979

2980

2981
TEST_TYPES(Table_ListOps, Prop<Int>, Prop<Float>, Prop<Double>, Prop<Decimal>, Prop<ObjectId>, Prop<UUID>,
2982
           Prop<Timestamp>, Prop<String>, Prop<Binary>, Prop<Bool>, Nullable<Int>, Nullable<Float>, Nullable<Double>,
2983
           Nullable<Decimal>, Nullable<ObjectId>, Nullable<UUID>, Nullable<Timestamp>, Nullable<String>,
2984
           Nullable<Binary>, Nullable<Bool>)
2985
{
40✔
2986
    using underlying_type = typename TEST_TYPE::underlying_type;
40✔
2987
    using type = typename TEST_TYPE::type;
40✔
2988
    TestValueGenerator gen;
40✔
2989
    Table table;
40✔
2990
    ColKey col = table.add_column_list(TEST_TYPE::data_type, "values", TEST_TYPE::is_nullable);
40✔
2991

20✔
2992
    Obj obj = table.create_object();
40✔
2993
    Lst<type> list = obj.get_list<type>(col);
40✔
2994
    list.add(gen.convert_for_test<underlying_type>(1));
40✔
2995
    list.add(gen.convert_for_test<underlying_type>(2));
40✔
2996
    list.swap(0, 1);
40✔
2997
    CHECK_EQUAL(list.get(0), gen.convert_for_test<underlying_type>(2));
40✔
2998
    CHECK_EQUAL(list.get(1), gen.convert_for_test<underlying_type>(1));
40✔
2999
    CHECK_EQUAL(list.find_first(gen.convert_for_test<underlying_type>(2)), 0);
40✔
3000
    CHECK_EQUAL(list.find_first(gen.convert_for_test<underlying_type>(1)), 1);
40✔
3001
    CHECK(!list.is_null(0));
40✔
3002
    CHECK(!list.is_null(1));
40✔
3003

20✔
3004
    Lst<type> list1;
40✔
3005
    CHECK_EQUAL(list1.size(), 0);
40✔
3006
    list1 = list;
40✔
3007
    CHECK_EQUAL(list1.size(), 2);
40✔
3008
    list.add(gen.convert_for_test<underlying_type>(3));
40✔
3009
    CHECK_EQUAL(list.size(), 3);
40✔
3010
    CHECK_EQUAL(list1.size(), 3);
40✔
3011

20✔
3012
    Query q = table.where().size_equal(col, 3); // SizeListNode
40✔
3013
    CHECK_EQUAL(q.count(), 1);
40✔
3014
    q = table.column<Lst<type>>(col).size() == 3; // SizeOperator expresison
40✔
3015
    CHECK_EQUAL(q.count(), 1);
40✔
3016

20✔
3017
    Lst<type> list2 = list;
40✔
3018
    CHECK_EQUAL(list2.size(), 3);
40✔
3019
    list2.clear();
40✔
3020
    CHECK_EQUAL(list2.size(), 0);
40✔
3021

20✔
3022
    if constexpr (TEST_TYPE::is_nullable) {
40✔
3023
        list2.insert_null(0);
20✔
3024
        CHECK_EQUAL(list.size(), 1);
20✔
3025
        type item0 = list2.get(0);
20✔
3026
        CHECK(value_is_null(item0));
20✔
3027
        CHECK(list.is_null(0));
20✔
3028
        CHECK(list.get_any(0).is_null());
20✔
3029
    }
20✔
3030
}
40✔
3031

3032
TEST(Table_ListOfPrimitives)
3033
{
2✔
3034
    Group g;
2✔
3035
    std::vector<CollectionBase*> lists;
2✔
3036
    TableRef t = g.add_table("table");
2✔
3037
    ColKey int_col = t->add_column_list(type_Int, "integers");
2✔
3038
    ColKey bool_col = t->add_column_list(type_Bool, "booleans");
2✔
3039
    ColKey string_col = t->add_column_list(type_String, "strings");
2✔
3040
    ColKey double_col = t->add_column_list(type_Double, "doubles");
2✔
3041
    ColKey timestamp_col = t->add_column_list(type_Timestamp, "timestamps");
2✔
3042
    Obj obj = t->create_object(ObjKey(7));
2✔
3043

1✔
3044
    std::vector<int64_t> integer_vector = {1, 2, 3, 4};
2✔
3045
    obj.set_list_values(int_col, integer_vector);
2✔
3046

1✔
3047
    std::vector<bool> bool_vector = {false, false, true, false, true};
2✔
3048
    obj.set_list_values(bool_col, bool_vector);
2✔
3049

1✔
3050
    std::vector<StringData> string_vector = {"monday", "tuesday", "thursday", "friday", "saturday", "sunday"};
2✔
3051
    obj.set_list_values(string_col, string_vector);
2✔
3052

1✔
3053
    std::vector<double> double_vector = {898742.09382, 3.14159265358979, 2.71828182845904};
2✔
3054
    obj.set_list_values(double_col, double_vector);
2✔
3055

1✔
3056
    time_t seconds_since_epoc = time(nullptr);
2✔
3057
    std::vector<Timestamp> timestamp_vector = {Timestamp(seconds_since_epoc, 0),
2✔
3058
                                               Timestamp(seconds_since_epoc + 60, 0)};
2✔
3059
    obj.set_list_values(timestamp_col, timestamp_vector);
2✔
3060

1✔
3061
    auto int_list = obj.get_list<int64_t>(int_col);
2✔
3062
    lists.push_back(&int_list);
2✔
3063
    std::vector<int64_t> vec(int_list.size());
2✔
3064
    CHECK_EQUAL(integer_vector.size(), int_list.size());
2✔
3065
    // {1, 2, 3, 4}
1✔
3066
    auto it = int_list.begin();
2✔
3067
    CHECK_EQUAL(*it, 1);
2✔
3068
    std::copy(int_list.begin(), int_list.end(), vec.begin());
2✔
3069
    unsigned j = 0;
2✔
3070
    for (auto i : int_list) {
8✔
3071
        CHECK_EQUAL(vec[j], i);
8✔
3072
        CHECK_EQUAL(integer_vector[j++], i);
8✔
3073
    }
8✔
3074
    auto f = std::find(int_list.begin(), int_list.end(), 3);
2✔
3075
    CHECK_EQUAL(3, *f++);
2✔
3076
    CHECK_EQUAL(4, *f);
2✔
3077

1✔
3078
    for (unsigned i = 0; i < int_list.size(); i++) {
10✔
3079
        CHECK_EQUAL(integer_vector[i], int_list[i]);
8✔
3080
    }
8✔
3081

1✔
3082
    CHECK_EQUAL(3, int_list.remove(2));
2✔
3083
    // {1, 2, 4}
1✔
3084
    CHECK_EQUAL(integer_vector.size() - 1, int_list.size());
2✔
3085
    CHECK_EQUAL(4, int_list[2]);
2✔
3086
    int_list.resize(6);
2✔
3087
    // {1, 2, 4, 0, 0, 0}
1✔
3088
    CHECK_EQUAL(int_list[5], 0);
2✔
3089
    int_list.swap(0, 1);
2✔
3090
    // {2, 1, 4, 0, 0, 0}
1✔
3091
    CHECK_EQUAL(2, int_list[0]);
2✔
3092
    CHECK_EQUAL(1, int_list[1]);
2✔
3093
    int_list.move(1, 4);
2✔
3094
    // {2, 4, 0, 0, 1, 0}
1✔
3095
    CHECK_EQUAL(4, int_list[1]);
2✔
3096
    CHECK_EQUAL(1, int_list[4]);
2✔
3097
    int_list.remove(1, 3);
2✔
3098
    // {2, 0, 1, 0}
1✔
3099
    CHECK_EQUAL(1, int_list[2]);
2✔
3100
    int_list.resize(2);
2✔
3101
    // {2, 0}
1✔
3102
    CHECK_EQUAL(2, int_list.size());
2✔
3103
    CHECK_EQUAL(2, int_list[0]);
2✔
3104
    CHECK_EQUAL(0, int_list[1]);
2✔
3105
    CHECK_EQUAL(lists[0]->size(), 2);
2✔
3106
    CHECK_EQUAL(lists[0]->get_col_key(), int_col);
2✔
3107

1✔
3108
    int_list.clear();
2✔
3109
    auto int_list2 = obj.get_list<int64_t>(int_col);
2✔
3110
    CHECK_EQUAL(0, int_list2.size());
2✔
3111

1✔
3112
    CHECK_THROW_ANY(obj.get_list<util::Optional<int64_t>>(int_col));
2✔
3113

1✔
3114
    auto bool_list = obj.get_list<bool>(bool_col);
2✔
3115
    lists.push_back(&bool_list);
2✔
3116
    CHECK_EQUAL(bool_vector.size(), bool_list.size());
2✔
3117
    for (unsigned i = 0; i < bool_list.size(); i++) {
12✔
3118
        CHECK_EQUAL(bool_vector[i], bool_list[i]);
10✔
3119
    }
10✔
3120

1✔
3121
    auto bool_list_nullable = obj.get_list<util::Optional<bool>>(bool_col);
2✔
3122
    CHECK_THROW_ANY(bool_list_nullable.set(0, util::none));
2✔
3123

1✔
3124
    auto string_list = obj.get_list<StringData>(string_col);
2✔
3125
    auto str_min = string_list.min();
2✔
3126
    CHECK(!str_min);
2✔
3127
    CHECK_EQUAL(string_list.begin()->size(), string_vector.begin()->size());
2✔
3128
    CHECK_EQUAL(string_vector.size(), string_list.size());
2✔
3129
    for (unsigned i = 0; i < string_list.size(); i++) {
14✔
3130
        CHECK_EQUAL(string_vector[i], string_list[i]);
12✔
3131
    }
12✔
3132

1✔
3133
    string_list.insert(2, "Wednesday");
2✔
3134
    CHECK_EQUAL(string_vector.size() + 1, string_list.size());
2✔
3135
    CHECK_EQUAL(StringData("Wednesday"), string_list.get(2));
2✔
3136
    CHECK_THROW_ANY(string_list.set(2, StringData{}));
2✔
3137
    CHECK_THROW_ANY(string_list.add(StringData{}));
2✔
3138
    CHECK_THROW_ANY(string_list.insert(2, StringData{}));
2✔
3139

1✔
3140
    auto double_list = obj.get_list<double>(double_col);
2✔
3141
    CHECK_EQUAL(double_vector.size(), double_list.size());
2✔
3142
    for (unsigned i = 0; i < double_list.size(); i++) {
8✔
3143
        CHECK_EQUAL(double_vector[i], double_list.get(i));
6✔
3144
    }
6✔
3145

1✔
3146
    auto timestamp_list = obj.get_list<Timestamp>(timestamp_col);
2✔
3147
    CHECK_EQUAL(timestamp_vector.size(), timestamp_list.size());
2✔
3148
    for (unsigned i = 0; i < timestamp_list.size(); i++) {
6✔
3149
        CHECK_EQUAL(timestamp_vector[i], timestamp_list.get(i));
4✔
3150
    }
4✔
3151
    size_t return_ndx = 7;
2✔
3152
    timestamp_list.min(&return_ndx);
2✔
3153
    CHECK_EQUAL(return_ndx, 0);
2✔
3154
    timestamp_list.max(&return_ndx);
2✔
3155
    CHECK_EQUAL(return_ndx, 1);
2✔
3156

1✔
3157
    t->remove_object(ObjKey(7));
2✔
3158
    CHECK_NOT(timestamp_list.is_attached());
2✔
3159
}
2✔
3160

3161
TEST_TYPES(Table_ListOfPrimitivesSort, Prop<int64_t>, Prop<float>, Prop<double>, Prop<Decimal128>, Prop<ObjectId>,
3162
           Prop<Timestamp>, Prop<String>, Prop<BinaryData>, Prop<UUID>, Nullable<int64_t>, Nullable<float>,
3163
           Nullable<double>, Nullable<Decimal128>, Nullable<ObjectId>, Nullable<Timestamp>, Nullable<String>,
3164
           Nullable<BinaryData>, Nullable<UUID>)
3165
{
36✔
3166
    using type = typename TEST_TYPE::type;
36✔
3167
    using underlying_type = typename TEST_TYPE::underlying_type;
36✔
3168

18✔
3169
    TestValueGenerator gen;
36✔
3170
    Group g;
36✔
3171
    TableRef t = g.add_table("table");
36✔
3172
    ColKey col = t->add_column_list(TEST_TYPE::data_type, "values", TEST_TYPE::is_nullable);
36✔
3173

18✔
3174
    auto obj = t->create_object();
36✔
3175
    auto list = obj.get_list<type>(col);
36✔
3176

18✔
3177
    std::vector<type> values = gen.values_from_int<type>({9, 4, 2, 7, 4, 1, 8, 11, 3, 4, 5, 22});
36✔
3178
    std::vector<size_t> indices;
36✔
3179
    type default_or_null = TEST_TYPE::default_value();
36✔
3180
    values.push_back(default_or_null);
36✔
3181
    obj.set_list_values(col, values);
36✔
3182

18✔
3183
    CHECK(list.has_changed());
36✔
3184
    CHECK_NOT(list.has_changed());
36✔
3185

18✔
3186
    auto cmp = [&]() {
144✔
3187
        CHECK_EQUAL(values.size(), indices.size());
144✔
3188
        for (size_t i = 0; i < values.size(); i++) {
1,836✔
3189
            CHECK_EQUAL(values[i], list.get(indices[i]));
1,692✔
3190
        }
1,692✔
3191
    };
144✔
3192
    std::sort(values.begin(), values.end(), ::less());
36✔
3193
    list.sort(indices);
36✔
3194
    cmp();
36✔
3195
    std::sort(values.begin(), values.end(), ::greater());
36✔
3196
    list.sort(indices, false);
36✔
3197
    cmp();
36✔
3198
    CHECK_NOT(list.has_changed());
36✔
3199

18✔
3200
    underlying_type new_value = gen.convert_for_test<underlying_type>(6);
36✔
3201
    values.push_back(new_value);
36✔
3202
    list.add(type(new_value));
36✔
3203
    CHECK(list.has_changed());
36✔
3204
    std::sort(values.begin(), values.end(), ::less());
36✔
3205
    list.sort(indices);
36✔
3206
    cmp();
36✔
3207

18✔
3208
    values.resize(7);
36✔
3209
    obj.set_list_values(col, values);
36✔
3210
    std::sort(values.begin(), values.end(), ::greater());
36✔
3211
    list.sort(indices, false);
36✔
3212
    cmp();
36✔
3213
}
36✔
3214

3215
TEST_TYPES(Table_ListOfPrimitivesDistinct, Prop<int64_t>, Prop<float>, Prop<double>, Prop<Decimal128>, Prop<ObjectId>,
3216
           Prop<Timestamp>, Prop<String>, Prop<BinaryData>, Prop<UUID>, Nullable<int64_t>, Nullable<float>,
3217
           Nullable<double>, Nullable<Decimal128>, Nullable<ObjectId>, Nullable<Timestamp>, Nullable<String>,
3218
           Nullable<BinaryData>, Nullable<UUID>)
3219
{
36✔
3220
    using type = typename TEST_TYPE::type;
36✔
3221
    TestValueGenerator gen;
36✔
3222
    Group g;
36✔
3223
    TableRef t = g.add_table("table");
36✔
3224
    ColKey col = t->add_column_list(TEST_TYPE::data_type, "values", TEST_TYPE::is_nullable);
36✔
3225

18✔
3226
    auto obj = t->create_object();
36✔
3227
    auto list = obj.get_list<type>(col);
36✔
3228

18✔
3229
    std::vector<type> values = gen.values_from_int<type>({9, 4, 2, 7, 4, 9, 8, 11, 2, 4, 5});
36✔
3230
    std::vector<type> distinct_values = gen.values_from_int<type>({9, 4, 2, 7, 8, 11, 5});
36✔
3231
    type default_or_null = TEST_TYPE::default_value();
36✔
3232
    values.push_back(default_or_null);
36✔
3233
    distinct_values.push_back(default_or_null);
36✔
3234
    std::vector<size_t> indices;
36✔
3235
    obj.set_list_values(col, values);
36✔
3236

18✔
3237
    auto cmp = [&]() {
108✔
3238
        CHECK_EQUAL(distinct_values.size(), indices.size());
108✔
3239
        for (size_t i = 0; i < distinct_values.size(); i++) {
972✔
3240
            CHECK_EQUAL(distinct_values[i], list.get(indices[i]));
864✔
3241
        }
864✔
3242
    };
108✔
3243

18✔
3244
    list.distinct(indices);
36✔
3245
    cmp();
36✔
3246
    list.distinct(indices, true);
36✔
3247
    std::sort(distinct_values.begin(), distinct_values.end(), std::less<type>());
36✔
3248
    cmp();
36✔
3249
    list.distinct(indices, false);
36✔
3250
    std::sort(distinct_values.begin(), distinct_values.end(), std::greater<type>());
36✔
3251
    cmp();
36✔
3252
}
36✔
3253

3254
TEST(Table_ListOfMixedSwap)
3255
{
2✔
3256
    Group g;
2✔
3257
    TableRef t = g.add_table("table");
2✔
3258
    ColKey col = t->add_column_list(type_Mixed, "values");
2✔
3259
    BinaryData bin("foo", 3);
2✔
3260

1✔
3261
    auto obj = t->create_object();
2✔
3262
    auto list = obj.get_list<Mixed>(col);
2✔
3263
    list.add("a");
2✔
3264
    list.add("b");
2✔
3265
    list.add("c");
2✔
3266
    list.add(bin);
2✔
3267
    list.move(2, 0);
2✔
3268
    CHECK_EQUAL(list.get(0).get_string(), "c");
2✔
3269
    CHECK_EQUAL(list.get(1).get_string(), "a");
2✔
3270
    CHECK_EQUAL(list.get(2).get_string(), "b");
2✔
3271
    CHECK_EQUAL(list.get(3).get_binary(), bin);
2✔
3272
    list.swap(3, 2);
2✔
3273
    CHECK_EQUAL(list.get(0).get_string(), "c");
2✔
3274
    CHECK_EQUAL(list.get(1).get_string(), "a");
2✔
3275
    CHECK_EQUAL(list.get(2).get_binary(), bin);
2✔
3276
    CHECK_EQUAL(list.get(3).get_string(), "b");
2✔
3277
}
2✔
3278

3279
TEST(Table_object_merge_nodes)
3280
{
2✔
3281
    // This test works best for REALM_MAX_BPNODE_SIZE == 8.
1✔
3282
    // To be used mostly as a help when debugging new implementation
1✔
3283

1✔
3284
    int nb_rows = REALM_MAX_BPNODE_SIZE * 8;
2✔
3285
    Table table;
2✔
3286
    std::vector<int64_t> key_set;
2✔
3287
    auto c0 = table.add_column(type_Int, "int1");
2✔
3288
    auto c1 = table.add_column(type_Int, "int2", true);
2✔
3289

1✔
3290
    for (int i = 0; i < nb_rows; i++) {
16,002✔
3291
        table.create_object(ObjKey(i)).set_all(i << 1, i << 2);
16,000✔
3292
        key_set.push_back(i);
16,000✔
3293
    }
16,000✔
3294

1✔
3295
    for (int i = 0; i < nb_rows; i++) {
16,002✔
3296
        auto key_index = test_util::random_int<int64_t>(0, key_set.size() - 1);
16,000✔
3297
        auto it = key_set.begin() + int(key_index);
16,000✔
3298

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

8,000✔
3302
        table.remove_object(ObjKey(*it));
16,000✔
3303
        key_set.erase(it);
16,000✔
3304
        for (unsigned j = 0; j < key_set.size(); j += 23) {
2,805,916✔
3305
            int64_t key_val = key_set[j];
2,789,916✔
3306
            Obj o = table.get_object(ObjKey(key_val));
2,789,916✔
3307
            CHECK_EQUAL(key_val << 1, o.get<int64_t>(c0));
2,789,916✔
3308
            CHECK_EQUAL(key_val << 2, o.get<util::Optional<int64_t>>(c1));
2,789,916✔
3309
        }
2,789,916✔
3310
    }
16,000✔
3311
}
2✔
3312

3313
TEST(Table_object_forward_iterator)
3314
{
2✔
3315
    int nb_rows = 1024;
2✔
3316
    Table table;
2✔
3317
    auto c0 = table.add_column(type_Int, "int1");
2✔
3318
    auto c1 = table.add_column(type_Int, "int2", true);
2✔
3319

1✔
3320
    for (int i = 0; i < nb_rows; i++) {
2,050✔
3321
        table.create_object(ObjKey(i));
2,048✔
3322
    }
2,048✔
3323

1✔
3324
    size_t tree_size = 0;
2✔
3325
    auto f = [&tree_size](const Cluster* cluster) {
8✔
3326
        tree_size += cluster->node_size();
8✔
3327
        return IteratorControl::AdvanceToNext;
8✔
3328
    };
8✔
3329
    table.traverse_clusters(f);
2✔
3330
    CHECK_EQUAL(tree_size, size_t(nb_rows));
2✔
3331

1✔
3332
    for (Obj o : table) {
2,048✔
3333
        int64_t key_value = o.get_key().value;
2,048✔
3334
        o.set_all(key_value << 1, key_value << 2);
2,048✔
3335
    }
2,048✔
3336

1✔
3337
    // table.dump_objects();
1✔
3338

1✔
3339
    size_t ndx = 0;
2✔
3340
    for (Obj o : table) {
2,048✔
3341
        int64_t key_value = o.get_key().value;
2,048✔
3342
        // std::cout << "Key value: " << std::hex << key_value << std::dec << std::endl;
1,024✔
3343
        CHECK_EQUAL(key_value << 1, o.get<int64_t>(c0));
2,048✔
3344
        CHECK_EQUAL(key_value << 2, o.get<util::Optional<int64_t>>(c1));
2,048✔
3345

1,024✔
3346
        Obj x = table.get_object(ndx);
2,048✔
3347
        CHECK_EQUAL(o.get_key(), x.get_key());
2,048✔
3348
        CHECK_EQUAL(o.get<int64_t>(c0), x.get<int64_t>(c0));
2,048✔
3349
        ndx++;
2,048✔
3350
    }
2,048✔
3351

1✔
3352
    auto it = table.begin();
2✔
3353
    while (it != table.end()) {
2,050✔
3354
        int64_t val = it->get_key().value;
2,048✔
3355
        // Delete every 7th object
1,024✔
3356
        if ((val % 7) == 0) {
2,048✔
3357
            table.remove_object(it);
294✔
3358
        }
294✔
3359
        ++it;
2,048✔
3360
    }
2,048✔
3361
    CHECK_EQUAL(table.size(), nb_rows * 6 / 7);
2✔
3362

1✔
3363
    auto it1 = table.begin();
2✔
3364
    ObjKey key = it1->get_key();
2✔
3365
    ++it1;
2✔
3366
    int64_t val = it1->get<int64_t>(c0);
2✔
3367
    table.remove_object(key);
2✔
3368
    CHECK_EQUAL(val, it1->get<int64_t>(c0));
2✔
3369

1✔
3370
    val = (it1 + 2)->get<int64_t>(c0);
2✔
3371
    table.remove_object(it1);
2✔
3372
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
3373
    // Still invalid
1✔
3374
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
3375
    it1 += 0;
2✔
3376
    // Still invalid
1✔
3377
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
3378
    it1 += 2;
2✔
3379
    CHECK_EQUAL(val, it1->get<int64_t>(c0));
2✔
3380
}
2✔
3381

3382
TEST(Table_object_by_index)
3383
{
2✔
3384
    Table table;
2✔
3385

1✔
3386
    ObjKeys keys({17, 4, 345, 65, 1, 46, 93, 43, 76, 123, 33, 42, 99, 53, 52, 256, 2}); // 17 elements
2✔
3387
    std::map<ObjKey, size_t> positions;
2✔
3388
    table.create_objects(keys);
2✔
3389
    size_t sz = table.size();
2✔
3390
    CHECK_EQUAL(sz, keys.size());
2✔
3391
    for (size_t i = 0; i < sz; i++) {
36✔
3392
        Obj o = table.get_object(i);
34✔
3393
        auto it = std::find(keys.begin(), keys.end(), o.get_key());
34✔
3394
        CHECK(it != keys.end());
34✔
3395
        positions.emplace(o.get_key(), i);
34✔
3396
    }
34✔
3397
    for (auto k : keys) {
34✔
3398
        size_t ndx = table.get_object_ndx(k);
34✔
3399
        CHECK_EQUAL(ndx, positions[k]);
34✔
3400
    }
34✔
3401
}
2✔
3402

3403
// String query benchmark
3404
NONCONCURRENT_TEST(Table_QuickSort2)
3405
{
2✔
3406
    Table ttt;
2✔
3407
    auto strings = ttt.add_column(type_String, "2");
2✔
3408

1✔
3409
    for (size_t t = 0; t < 1000; t++) {
2,002✔
3410
        Obj o = ttt.create_object();
2,000✔
3411
        std::string s = util::to_string(t % 100);
2,000✔
3412
        o.set<StringData>(strings, s);
2,000✔
3413
    }
2,000✔
3414

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

1✔
3417
    auto t1 = steady_clock::now();
2✔
3418

1✔
3419
    CALLGRIND_START_INSTRUMENTATION;
2✔
3420

1✔
3421
    size_t nb_reps = 1000;
2✔
3422
    for (size_t t = 0; t < nb_reps; t++) {
2,002✔
3423
        TableView tv = q.find_all();
2,000✔
3424
        CHECK_EQUAL(tv.size(), 10);
2,000✔
3425
    }
2,000✔
3426

1✔
3427
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3428

1✔
3429
    auto t2 = steady_clock::now();
2✔
3430

1✔
3431
    std::cout << nb_reps << " repetitions of find_all" << std::endl;
2✔
3432
    std::cout << "    time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_reps << " ns/rep" << std::endl;
2✔
3433
}
2✔
3434

3435
NONCONCURRENT_TEST(Table_object_sequential)
3436
{
2✔
3437
#ifdef PERFORMACE_TESTING
3438
    int nb_rows = 10'000'000;
3439
    int num_runs = 1;
3440
#else
3441
    int nb_rows = 100'000;
2✔
3442
    int num_runs = 1;
2✔
3443
#endif
2✔
3444
    SHARED_GROUP_TEST_PATH(path);
2✔
3445
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3446
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3447
    ColKey c0;
2✔
3448
    ColKey c1;
2✔
3449

1✔
3450
    CALLGRIND_START_INSTRUMENTATION;
2✔
3451

1✔
3452
    std::cout << nb_rows << " rows - sequential" << std::endl;
2✔
3453

1✔
3454
    {
2✔
3455
        WriteTransaction wt(sg);
2✔
3456
        auto table = wt.add_table("test");
2✔
3457

1✔
3458
        c0 = table->add_column(type_Int, "int1");
2✔
3459
        c1 = table->add_column(type_Int, "int2", true);
2✔
3460

1✔
3461

1✔
3462
        auto t1 = steady_clock::now();
2✔
3463

1✔
3464
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3465
            table->create_object(ObjKey(i)).set_all(i << 1, i << 2);
200,000✔
3466
        }
200,000✔
3467

1✔
3468
        auto t2 = steady_clock::now();
2✔
3469
        std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3470
                  << std::endl;
2✔
3471

1✔
3472
        CHECK_EQUAL(table->size(), nb_rows);
2✔
3473
        wt.commit();
2✔
3474
    }
2✔
3475
    {
2✔
3476
        auto t1 = steady_clock::now();
2✔
3477
        sg->compact();
2✔
3478
        auto t2 = steady_clock::now();
2✔
3479
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3480
    }
2✔
3481
    {
2✔
3482
        ReadTransaction rt(sg);
2✔
3483
        auto table = rt.get_table("test");
2✔
3484

1✔
3485
        auto t1 = steady_clock::now();
2✔
3486

1✔
3487
        for (int j = 0; j < num_runs; ++j) {
4✔
3488
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3489
                table->get_object(ObjKey(i));
200,000✔
3490
            }
200,000✔
3491
        }
2✔
3492

1✔
3493
        auto t2 = steady_clock::now();
2✔
3494

1✔
3495
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3496
                  << " ns/key" << std::endl;
2✔
3497
    }
2✔
3498

1✔
3499
    {
2✔
3500
        ReadTransaction rt(sg);
2✔
3501
        auto table = rt.get_table("test");
2✔
3502

1✔
3503
        auto t1 = steady_clock::now();
2✔
3504

1✔
3505
        for (int j = 0; j < num_runs; ++j) {
4✔
3506
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3507
                const Obj o = table->get_object(ObjKey(i));
200,000✔
3508
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3509
            }
200,000✔
3510
        }
2✔
3511

1✔
3512
        auto t2 = steady_clock::now();
2✔
3513

1✔
3514
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3515
                  << " ns/key" << std::endl;
2✔
3516
    }
2✔
3517

1✔
3518
    {
2✔
3519
        ReadTransaction rt(sg);
2✔
3520
        auto table = rt.get_table("test");
2✔
3521

1✔
3522
        auto t1 = steady_clock::now();
2✔
3523

1✔
3524
        for (int j = 0; j < num_runs; ++j) {
4✔
3525
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3526
                const Obj o = table->get_object(ObjKey(i));
200,000✔
3527
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3528
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3529
            }
200,000✔
3530
        }
2✔
3531

1✔
3532
        auto t2 = steady_clock::now();
2✔
3533

1✔
3534
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3535
                  << " ns/key" << std::endl;
2✔
3536
    }
2✔
3537

1✔
3538
    {
2✔
3539
        WriteTransaction wt(sg);
2✔
3540
        auto table = wt.get_table("test");
2✔
3541

1✔
3542
        auto t1 = steady_clock::now();
2✔
3543

1✔
3544
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3545
            Obj o = table->get_object(ObjKey(i));
200,000✔
3546
            o.set(c0, i << 2).set(c1, i << 1);
200,000✔
3547
        }
200,000✔
3548

1✔
3549
        auto t2 = steady_clock::now();
2✔
3550

1✔
3551
        std::cout << "   update time   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3552
                  << std::endl;
2✔
3553
        wt.commit();
2✔
3554
    }
2✔
3555

1✔
3556
    {
2✔
3557
        WriteTransaction wt(sg);
2✔
3558
        auto table = wt.get_table("test");
2✔
3559

1✔
3560
        auto t1 = steady_clock::now();
2✔
3561

1✔
3562
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3563
            table->remove_object(ObjKey(i));
200,000✔
3564
#ifdef REALM_DEBUG
200,000✔
3565
            CHECK_EQUAL(table->size(), nb_rows - i - 1);
200,000✔
3566

100,000✔
3567
            for (int j = i + 1; j < nb_rows; j += nb_rows / 100) {
10,299,800✔
3568
                Obj o = table->get_object(ObjKey(j));
10,099,800✔
3569
                CHECK_EQUAL(j << 2, o.get<int64_t>(c0));
10,099,800✔
3570
                CHECK_EQUAL(j << 1, o.get<util::Optional<int64_t>>(c1));
10,099,800✔
3571
            }
10,099,800✔
3572

100,000✔
3573
#endif
200,000✔
3574
        }
200,000✔
3575
        auto t2 = steady_clock::now();
2✔
3576
        std::cout << "   erase time    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3577
                  << std::endl;
2✔
3578

1✔
3579
        wt.commit();
2✔
3580
    }
2✔
3581

1✔
3582
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3583
}
2✔
3584

3585
NONCONCURRENT_TEST(Table_object_seq_rnd)
3586
{
2✔
3587
#ifdef PERFORMACE_TESTING
3588
    size_t rows = 1'000'000;
3589
    int runs = 100; // runs for building scenario
3590
#else
3591
    size_t rows = 100'000;
2✔
3592
    int runs = 100;
2✔
3593
#endif
2✔
3594
    int64_t next_key = 0;
2✔
3595
    std::vector<int64_t> key_values;
2✔
3596
    std::set<int64_t> key_set;
2✔
3597
    SHARED_GROUP_TEST_PATH(path);
2✔
3598
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3599
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3600
    ColKey c0;
2✔
3601
    {
2✔
3602
        std::cout << "Establishing scenario seq ins/rnd erase " << std::endl;
2✔
3603
        WriteTransaction wt(sg);
2✔
3604
        auto table = wt.add_table("test");
2✔
3605
        c0 = table->add_column(type_Int, "int1");
2✔
3606

1✔
3607
        for (int run = 0; run < runs; ++run) {
202✔
3608
            if (key_values.size() < rows) { // expanding by 2%!
200✔
3609
                for (size_t n = 0; n < rows / 50; ++n) {
400,200✔
3610
                    auto key_val = next_key++;
400,000✔
3611
                    key_values.push_back(key_val);
400,000✔
3612
                    key_set.insert(key_val);
400,000✔
3613
                    table->create_object(ObjKey(key_val)).set_all(key_val << 1);
400,000✔
3614
                }
400,000✔
3615
            }
200✔
3616
            // do 1% random deletions
100✔
3617
            for (size_t n = 0; n < rows / 100; ++n) {
200,200✔
3618
                auto index = test_util::random_int<size_t>(0, key_values.size() - 1);
200,000✔
3619
                auto key_val = key_values[index];
200,000✔
3620
                if (index < key_values.size() - 1)
200,000✔
3621
                    key_values[index] = key_values.back();
199,993✔
3622
                key_values.pop_back();
200,000✔
3623
                table->remove_object(ObjKey(key_val));
200,000✔
3624
            }
200,000✔
3625
        }
200✔
3626
        wt.commit();
2✔
3627
    }
2✔
3628
    // scenario established!
1✔
3629
    int nb_rows = int(key_values.size());
2✔
3630
#ifdef PERFORMACE_TESTING
3631
    int num_runs = 10; // runs for timing access
3632
#else
3633
    int num_runs = 1; // runs for timing access
2✔
3634
#endif
2✔
3635
    {
2✔
3636
        auto t1 = steady_clock::now();
2✔
3637
        sg->compact();
2✔
3638
        auto t2 = steady_clock::now();
2✔
3639
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3640
    }
2✔
3641
    std::cout << "Scenario has " << nb_rows << " rows. Timing...." << std::endl;
2✔
3642
    {
2✔
3643
        ReadTransaction rt(sg);
2✔
3644
        auto table = rt.get_table("test");
2✔
3645

1✔
3646
        auto t1 = steady_clock::now();
2✔
3647

1✔
3648
        for (int j = 0; j < num_runs; ++j) {
4✔
3649
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3650
                table->get_object(ObjKey(key_values[i]));
200,000✔
3651
            }
200,000✔
3652
        }
2✔
3653

1✔
3654
        auto t2 = steady_clock::now();
2✔
3655

1✔
3656
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3657
                  << " ns/key" << std::endl;
2✔
3658
    }
2✔
3659

1✔
3660
    {
2✔
3661
        ReadTransaction rt(sg);
2✔
3662
        auto table = rt.get_table("test");
2✔
3663

1✔
3664
        auto t1 = steady_clock::now();
2✔
3665

1✔
3666
        for (int j = 0; j < num_runs; ++j) {
4✔
3667
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3668
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3669
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3670
            }
200,000✔
3671
        }
2✔
3672

1✔
3673
        auto t2 = steady_clock::now();
2✔
3674

1✔
3675
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3676
                  << " ns/key" << std::endl;
2✔
3677
    }
2✔
3678

1✔
3679
    {
2✔
3680
        ReadTransaction rt(sg);
2✔
3681
        auto table = rt.get_table("test");
2✔
3682

1✔
3683
        auto t1 = steady_clock::now();
2✔
3684

1✔
3685
        for (int j = 0; j < num_runs; ++j) {
4✔
3686
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3687
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3688
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3689
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3690
            }
200,000✔
3691
        }
2✔
3692

1✔
3693
        auto t2 = steady_clock::now();
2✔
3694

1✔
3695
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3696
                  << " ns/key" << std::endl;
2✔
3697
    }
2✔
3698
}
2✔
3699

3700
NONCONCURRENT_TEST(Table_object_random)
3701
{
2✔
3702
#ifdef PERFORMACE_TESTING
3703
    int nb_rows = 1'000'000;
3704
    int num_runs = 10;
3705
#else
3706
    int nb_rows = 100'000;
2✔
3707
    int num_runs = 1;
2✔
3708
#endif
2✔
3709
    SHARED_GROUP_TEST_PATH(path);
2✔
3710
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3711
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3712
    ColKey c0;
2✔
3713
    ColKey c1;
2✔
3714
    std::vector<int64_t> key_values;
2✔
3715

1✔
3716
    {
2✔
3717
        std::set<int64_t> key_set;
2✔
3718
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3719
            bool ok = false;
200,000✔
3720
            while (!ok) {
410,894✔
3721
                auto key_val = test_util::random_int<int64_t>(0, nb_rows * 10);
210,894✔
3722
                if (key_set.count(key_val) == 0) {
210,894✔
3723
                    key_values.push_back(key_val);
200,000✔
3724
                    key_set.insert(key_val);
200,000✔
3725
                    ok = true;
200,000✔
3726
                }
200,000✔
3727
            }
210,894✔
3728
        }
200,000✔
3729
    }
2✔
3730

1✔
3731
    CALLGRIND_START_INSTRUMENTATION;
2✔
3732

1✔
3733
    std::cout << nb_rows << " rows - random" << std::endl;
2✔
3734

1✔
3735
    {
2✔
3736
        WriteTransaction wt(sg);
2✔
3737
        auto table = wt.add_table("test");
2✔
3738

1✔
3739
        c0 = table->add_column(type_Int, "int1");
2✔
3740
        c1 = table->add_column(type_Int, "int2", true);
2✔
3741

1✔
3742

1✔
3743
        auto t1 = steady_clock::now();
2✔
3744

1✔
3745
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3746
            table->create_object(ObjKey(key_values[i])).set_all(i << 1, i << 2);
200,000✔
3747
        }
200,000✔
3748

1✔
3749

1✔
3750
        auto t2 = steady_clock::now();
2✔
3751
        std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3752
                  << std::endl;
2✔
3753

1✔
3754
        CHECK_EQUAL(table->size(), nb_rows);
2✔
3755
        wt.commit();
2✔
3756
    }
2✔
3757
    {
2✔
3758
        auto t1 = steady_clock::now();
2✔
3759
        sg->compact();
2✔
3760
        auto t2 = steady_clock::now();
2✔
3761
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3762
    }
2✔
3763

1✔
3764
    {
2✔
3765
        ReadTransaction rt(sg);
2✔
3766
        auto table = rt.get_table("test");
2✔
3767

1✔
3768
        auto t1 = steady_clock::now();
2✔
3769

1✔
3770
        for (int j = 0; j < num_runs; ++j) {
4✔
3771
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3772
                table->get_object(ObjKey(key_values[i]));
200,000✔
3773
            }
200,000✔
3774
        }
2✔
3775

1✔
3776
        auto t2 = steady_clock::now();
2✔
3777

1✔
3778
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3779
                  << " ns/key" << std::endl;
2✔
3780
    }
2✔
3781

1✔
3782
    {
2✔
3783
        ReadTransaction rt(sg);
2✔
3784
        auto table = rt.get_table("test");
2✔
3785

1✔
3786
        auto t1 = steady_clock::now();
2✔
3787

1✔
3788
        for (int j = 0; j < num_runs; ++j) {
4✔
3789
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3790
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3791
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3792
            }
200,000✔
3793
        }
2✔
3794

1✔
3795
        auto t2 = steady_clock::now();
2✔
3796

1✔
3797
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3798
                  << " ns/key" << std::endl;
2✔
3799
    }
2✔
3800

1✔
3801
    {
2✔
3802
        ReadTransaction rt(sg);
2✔
3803
        auto table = rt.get_table("test");
2✔
3804

1✔
3805
        auto t1 = steady_clock::now();
2✔
3806

1✔
3807
        for (int j = 0; j < num_runs; ++j) {
4✔
3808
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3809
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3810
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3811
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3812
            }
200,000✔
3813
        }
2✔
3814

1✔
3815
        auto t2 = steady_clock::now();
2✔
3816

1✔
3817
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3818
                  << " ns/key" << std::endl;
2✔
3819
    }
2✔
3820

1✔
3821
    {
2✔
3822
        WriteTransaction wt(sg);
2✔
3823
        auto table = wt.get_table("test");
2✔
3824

1✔
3825
        auto t1 = steady_clock::now();
2✔
3826

1✔
3827
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3828
            Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3829
            o.set(c0, i << 2).set(c1, i << 1);
200,000✔
3830
        }
200,000✔
3831

1✔
3832
        auto t2 = steady_clock::now();
2✔
3833

1✔
3834
        std::cout << "   update time   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3835
                  << std::endl;
2✔
3836
        wt.commit();
2✔
3837
    }
2✔
3838

1✔
3839
    {
2✔
3840
        WriteTransaction wt(sg);
2✔
3841
        auto table = wt.get_table("test");
2✔
3842

1✔
3843
        auto t1 = steady_clock::now();
2✔
3844

1✔
3845
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3846
            table->remove_object(ObjKey(key_values[i]));
200,000✔
3847
#ifdef REALM_DEBUG
200,000✔
3848
            CHECK_EQUAL(table->size(), nb_rows - i - 1);
200,000✔
3849
            for (int j = i + 1; j < nb_rows; j += nb_rows / 100) {
10,299,800✔
3850
                Obj o = table->get_object(ObjKey(key_values[j]));
10,099,800✔
3851
                CHECK_EQUAL(j << 2, o.get<int64_t>(c0));
10,099,800✔
3852
                CHECK_EQUAL(j << 1, o.get<util::Optional<int64_t>>(c1));
10,099,800✔
3853
            }
10,099,800✔
3854
#endif
200,000✔
3855
        }
200,000✔
3856
        auto t2 = steady_clock::now();
2✔
3857
        std::cout << "   erase time    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3858
                  << std::endl;
2✔
3859

1✔
3860
        wt.commit();
2✔
3861
    }
2✔
3862

1✔
3863
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3864
}
2✔
3865

3866
TEST(Table_CollisionMapping)
3867
{
2✔
3868

1✔
3869
#if REALM_EXERCISE_OBJECT_ID_COLLISION
3870
    bool expect_collisions = true;
3871
#else
3872
    bool expect_collisions = false;
2✔
3873
#endif
2✔
3874

1✔
3875
    // This number corresponds to the mask used to calculate "optimistic"
1✔
3876
    // object IDs. See `ObjectIDProvider::get_optimistic_local_id_hashed`.
1✔
3877
    const size_t num_objects_with_guaranteed_collision = 0xff;
2✔
3878

1✔
3879
    SHARED_GROUP_TEST_PATH(path);
2✔
3880

1✔
3881
    {
2✔
3882
        DBRef sg = DB::create(path);
2✔
3883
        {
2✔
3884
            auto wt = sg->start_write();
2✔
3885
            TableRef t0 = wt->add_table_with_primary_key("class_t0", type_String, "pk");
2✔
3886

1✔
3887
            char buffer[12];
2✔
3888
            for (size_t i = 0; i < num_objects_with_guaranteed_collision; ++i) {
512✔
3889
                const char* in = reinterpret_cast<char*>(&i);
510✔
3890
                size_t len = base64_encode(in, sizeof(i), buffer, sizeof(buffer));
510✔
3891

255✔
3892
                t0->create_object_with_primary_key(StringData{buffer, len});
510✔
3893
            }
510✔
3894
            wt->commit();
2✔
3895
        }
2✔
3896

1✔
3897
        {
2✔
3898
            ReadTransaction rt{sg};
2✔
3899
            ConstTableRef t0 = rt.get_table("class_t0");
2✔
3900
            // Check that at least one object exists where the 63rd bit is set.
1✔
3901
            size_t num_object_keys_with_63rd_bit_set = 0;
2✔
3902
            uint64_t bit63 = 0x4000000000000000;
2✔
3903
            for (Obj obj : *t0) {
510✔
3904
                if (obj.get_key().value & bit63)
510✔
3905
                    ++num_object_keys_with_63rd_bit_set;
×
3906
            }
510✔
3907
            CHECK(!expect_collisions || num_object_keys_with_63rd_bit_set > 0);
2!
3908
        }
2✔
3909
    }
2✔
3910

1✔
3911
    // Check that locally allocated IDs are properly persisted
1✔
3912
    {
2✔
3913
        DBRef sg_2 = DB::create(path);
2✔
3914
        {
2✔
3915
            WriteTransaction wt{sg_2};
2✔
3916
            TableRef t0 = wt.get_table("class_t0");
2✔
3917

1✔
3918
            // Make objects with primary keys that do not already exist but are guaranteed
1✔
3919
            // to cause further collisions.
1✔
3920
            char buffer[12];
2✔
3921
            for (size_t i = 0; i < num_objects_with_guaranteed_collision; ++i) {
512✔
3922
                size_t foo = num_objects_with_guaranteed_collision + i;
510✔
3923
                const char* in = reinterpret_cast<char*>(&foo);
510✔
3924
                size_t len = base64_encode(in, sizeof(foo), buffer, sizeof(buffer));
510✔
3925

255✔
3926
                t0->create_object_with_primary_key(StringData{buffer, len});
510✔
3927
            }
510✔
3928
            wt.commit();
2✔
3929
        }
2✔
3930
        {
2✔
3931
            WriteTransaction wt{sg_2};
2✔
3932
            TableRef t0 = wt.get_table("class_t0");
2✔
3933

1✔
3934
            // Find an object with collision key
1✔
3935
            std::string pk;
2✔
3936
            ObjKey key;
2✔
3937
            uint64_t bit63 = 0x4000000000000000;
2✔
3938
            for (Obj obj : *t0) {
1,020✔
3939
                if (obj.get_key().value & bit63) {
1,020✔
3940
                    key = obj.get_key();
×
3941
                    pk = obj.get<String>("pk");
×
3942
                    obj.remove();
×
3943
                    break;
×
3944
                }
×
3945
            }
1,020✔
3946

1✔
3947
            if (key) {
2✔
3948
                // Insert object again - should get a different key
3949
                auto obj = t0->create_object_with_primary_key(pk);
×
3950
                CHECK_NOT_EQUAL(obj.get_key(), key);
×
3951
            }
×
3952

1✔
3953
            wt.commit();
2✔
3954
        }
2✔
3955
    }
2✔
3956
}
2✔
3957

3958
TEST(Table_CreateObjectWithPrimaryKeyDidCreate)
3959
{
2✔
3960
    SHARED_GROUP_TEST_PATH(path);
2✔
3961
    DBRef sg = DB::create(path);
2✔
3962

1✔
3963
    auto wt = sg->start_write();
2✔
3964
    TableRef string_table = wt->add_table_with_primary_key("string pk", type_String, "pk", true);
2✔
3965

1✔
3966
    bool did_create = false;
2✔
3967
    string_table->create_object_with_primary_key(StringData("1"), &did_create);
2✔
3968
    CHECK(did_create);
2✔
3969
    string_table->create_object_with_primary_key(StringData("1"), &did_create);
2✔
3970
    CHECK_NOT(did_create);
2✔
3971
    string_table->create_object_with_primary_key(StringData("2"), &did_create);
2✔
3972
    CHECK(did_create);
2✔
3973
    string_table->create_object_with_primary_key(StringData(), &did_create);
2✔
3974
    CHECK(did_create);
2✔
3975
    string_table->create_object_with_primary_key(StringData(), &did_create);
2✔
3976
    CHECK_NOT(did_create);
2✔
3977

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

1✔
3980
    did_create = false;
2✔
3981
    int_table->create_object_with_primary_key(1, &did_create);
2✔
3982
    CHECK(did_create);
2✔
3983
    int_table->create_object_with_primary_key(1, &did_create);
2✔
3984
    CHECK_NOT(did_create);
2✔
3985
    int_table->create_object_with_primary_key(2, &did_create);
2✔
3986
    CHECK(did_create);
2✔
3987
    int_table->create_object_with_primary_key(util::Optional<int64_t>(), &did_create);
2✔
3988
    CHECK(did_create);
2✔
3989
    int_table->create_object_with_primary_key(util::Optional<int64_t>(), &did_create);
2✔
3990
    CHECK_NOT(did_create);
2✔
3991
}
2✔
3992

3993
TEST(Table_PrimaryKeyIndexBug)
3994
{
2✔
3995
    Group g;
2✔
3996
    TableRef table = g.add_table("table");
2✔
3997
    TableRef origin = g.add_table("origin");
2✔
3998
    auto col_id = table->add_column(type_String, "id");
2✔
3999
    auto col_link = origin->add_column(*table, "link");
2✔
4000
    table->add_search_index(col_id);
2✔
4001
    // Create an object where bit 62 is set in the ObkKey
1✔
4002
    auto obj = table->create_object(ObjKey(0x40083f0f9b0fb598)).set(col_id, "hallo");
2✔
4003
    origin->create_object().set(col_link, obj.get_key());
2✔
4004

1✔
4005
    auto q = origin->link(col_link).column<String>(col_id) == "hallo";
2✔
4006
    auto cnt = q.count();
2✔
4007
    CHECK_EQUAL(cnt, 1);
2✔
4008
}
2✔
4009

4010
NONCONCURRENT_TEST(Table_PrimaryKeyString)
4011
{
2✔
4012
#ifdef REALM_DEBUG
2✔
4013
    int nb_rows = 1000;
2✔
4014
#else
4015
    int nb_rows = 100000;
4016
#endif
4017
    SHARED_GROUP_TEST_PATH(path);
2✔
4018

1✔
4019
    DBRef sg = DB::create(path);
2✔
4020
    auto wt = sg->start_write();
2✔
4021
    TableRef t0 = wt->add_table_with_primary_key("class_t0", type_String, "pk");
2✔
4022
    auto pk_col = t0->get_primary_key_column();
2✔
4023

1✔
4024
    auto t1 = steady_clock::now();
2✔
4025
    CALLGRIND_START_INSTRUMENTATION;
2✔
4026

1✔
4027
    for (int i = 0; i < nb_rows; ++i) {
2,002✔
4028
        std::string pk = "KEY_" + util::to_string(i);
2,000✔
4029
        t0->create_object_with_primary_key(pk);
2,000✔
4030
    }
2,000✔
4031

1✔
4032
    auto t2 = steady_clock::now();
2✔
4033

1✔
4034
    for (int i = 0; i < nb_rows; ++i) {
2,002✔
4035
        std::string pk = "KEY_" + util::to_string(i);
2,000✔
4036
        ObjKey k = t0->find_first(pk_col, StringData(pk));
2,000✔
4037
#ifdef REALM_DEBUG
2,000✔
4038
        CHECK(t0->is_valid(k));
2,000✔
4039
#else
4040
        CHECK(k);
4041
#endif
4042
    }
2,000✔
4043

1✔
4044
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
4045
    auto t3 = steady_clock::now();
2✔
4046
    std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
4047
              << std::endl;
2✔
4048
    std::cout << "   lookup time: " << duration_cast<nanoseconds>(t3 - t2).count() / nb_rows << " ns/key"
2✔
4049
              << std::endl;
2✔
4050
    wt->commit();
2✔
4051
}
2✔
4052

4053
TEST(Table_3)
4054
{
2✔
4055
    Table table;
2✔
4056

1✔
4057
    auto col_int0 = table.add_column(type_Int, "first");
2✔
4058
    auto col_int1 = table.add_column(type_Int, "second");
2✔
4059
    auto col_bool2 = table.add_column(type_Bool, "third");
2✔
4060
    auto col_int3 = table.add_column(type_Int, "fourth");
2✔
4061

1✔
4062
    for (int64_t i = 0; i < 100; ++i) {
202✔
4063
        table.create_object(ObjKey(i)).set_all(i, 10, true, int(Wed));
200✔
4064
    }
200✔
4065

1✔
4066
    // Test column searching
1✔
4067
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int0, 0));
2✔
4068
    CHECK_EQUAL(ObjKey(50), table.find_first_int(col_int0, 50));
2✔
4069
    CHECK_EQUAL(null_key, table.find_first_int(col_int0, 500));
2✔
4070
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int1, 10));
2✔
4071
    CHECK_EQUAL(null_key, table.find_first_int(col_int1, 100));
2✔
4072
    CHECK_EQUAL(ObjKey(0), table.find_first_bool(col_bool2, true));
2✔
4073
    CHECK_EQUAL(null_key, table.find_first_bool(col_bool2, false));
2✔
4074
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int3, Wed));
2✔
4075
    CHECK_EQUAL(null_key, table.find_first_int(col_int3, Mon));
2✔
4076

1✔
4077
#ifdef REALM_DEBUG
2✔
4078
    table.verify();
2✔
4079
#endif
2✔
4080
}
2✔
4081

4082

4083
TEST(Table_4)
4084
{
2✔
4085
    Table table;
2✔
4086
    auto c0 = table.add_column(type_String, "strings");
2✔
4087
    const char* hello_hello = "HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHello";
2✔
4088

1✔
4089
    table.create_object(ObjKey(5)).set(c0, "Hello");
2✔
4090
    table.create_object(ObjKey(7)).set(c0, hello_hello);
2✔
4091

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

1✔
4094
    // Test string column searching
1✔
4095
    CHECK_EQUAL(ObjKey(7), table.find_first_string(c0, hello_hello));
2✔
4096
    CHECK_EQUAL(null_key, table.find_first_string(c0, "Foo"));
2✔
4097

1✔
4098
#ifdef REALM_DEBUG
2✔
4099
    table.verify();
2✔
4100
#endif
2✔
4101
}
2✔
4102

4103
// Very basic sanity check of search index when you add, remove and set objects
4104
TEST(Table_SearchIndexFindFirst)
4105
{
2✔
4106
    Table table;
2✔
4107

1✔
4108
    auto c1 = table.add_column(type_Int, "a");
2✔
4109
    auto c2 = table.add_column(type_Int, "b", true);
2✔
4110
    auto c3 = table.add_column(type_String, "c");
2✔
4111
    auto c4 = table.add_column(type_String, "d", true);
2✔
4112
    auto c5 = table.add_column(type_Bool, "e");
2✔
4113
    auto c6 = table.add_column(type_Bool, "f", true);
2✔
4114
    auto c7 = table.add_column(type_Timestamp, "g");
2✔
4115
    auto c8 = table.add_column(type_Timestamp, "h", true);
2✔
4116

1✔
4117
    Obj o0 = table.create_object();
2✔
4118
    Obj o1 = table.create_object();
2✔
4119
    Obj o2 = table.create_object();
2✔
4120
    Obj o3 = table.create_object();
2✔
4121

1✔
4122
    o0.set_all(100, 100, "100", "100", false, false, Timestamp(100, 100), Timestamp(100, 100));
2✔
4123
    o1.set_all(200, 200, "200", "200", true, true, Timestamp(200, 200), Timestamp(200, 200));
2✔
4124
    o2.set_all(200, 200, "200", "200", true, true, Timestamp(200, 200), Timestamp(200, 200));
2✔
4125
    CHECK(o3.is_null(c2));
2✔
4126
    CHECK(o3.is_null(c4));
2✔
4127
    CHECK(o3.is_null(c6));
2✔
4128
    CHECK(o3.is_null(c8));
2✔
4129

1✔
4130
    table.add_search_index(c1);
2✔
4131
    table.add_search_index(c2);
2✔
4132
    table.add_search_index(c3);
2✔
4133
    table.add_search_index(c4);
2✔
4134
    table.add_search_index(c5);
2✔
4135
    table.add_search_index(c6);
2✔
4136
    table.add_search_index(c7);
2✔
4137
    table.add_search_index(c8);
2✔
4138

1✔
4139
    // Non-nullable integers
1✔
4140
    CHECK_EQUAL(table.find_first_int(c1, 100), o0.get_key());
2✔
4141
    CHECK_EQUAL(table.find_first_int(c1, 200), o1.get_key());
2✔
4142
    // Uninitialized non-nullable integers equal 0
1✔
4143
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
4144

1✔
4145
    // Nullable integers
1✔
4146
    CHECK_EQUAL(table.find_first_int(c2, 100), o0.get_key());
2✔
4147
    CHECK_EQUAL(table.find_first_int(c2, 200), o1.get_key());
2✔
4148
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4149
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
1✔
4150

1✔
4151
    // Non-nullable strings
1✔
4152
    CHECK_EQUAL(table.find_first_string(c3, "100"), o0.get_key());
2✔
4153
    CHECK_EQUAL(table.find_first_string(c3, "200"), o1.get_key());
2✔
4154
    // Uninitialized non-nullable strings equal ""
1✔
4155
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
4156

1✔
4157
    // Nullable strings
1✔
4158
    CHECK_EQUAL(table.find_first_string(c4, "100"), o0.get_key());
2✔
4159
    CHECK_EQUAL(table.find_first_string(c4, "200"), o1.get_key());
2✔
4160
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4161
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
1✔
4162

1✔
4163
    // Non-nullable bools
1✔
4164
    CHECK_EQUAL(table.find_first_bool(c5, false), o0.get_key());
2✔
4165
    CHECK_EQUAL(table.find_first_bool(c5, true), o1.get_key());
2✔
4166

1✔
4167
    // Nullable bools
1✔
4168
    CHECK_EQUAL(table.find_first_bool(c6, false), o0.get_key());
2✔
4169
    CHECK_EQUAL(table.find_first_bool(c6, true), o1.get_key());
2✔
4170
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4171
    // CHECK_EQUAL(table.find_first_null(5), o3.get_key());
1✔
4172

1✔
4173
    // Non-nullable Timestamp
1✔
4174
    CHECK_EQUAL(table.find_first_timestamp(c7, Timestamp(100, 100)), o0.get_key());
2✔
4175
    CHECK_EQUAL(table.find_first_timestamp(c7, Timestamp(200, 200)), o1.get_key());
2✔
4176

1✔
4177
    // Nullable Timestamp
1✔
4178
    CHECK_EQUAL(table.find_first_timestamp(c8, Timestamp(100, 100)), o0.get_key());
2✔
4179
    CHECK_EQUAL(table.find_first_timestamp(c8, Timestamp(200, 200)), o1.get_key());
2✔
4180
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4181
    // CHECK_EQUAL(table.find_first_null(7), o3.get_key());
1✔
4182

1✔
4183
    // Remove object and see if things still work
1✔
4184
    // *******************************************************************************
1✔
4185
    table.remove_object(o0.get_key());
2✔
4186

1✔
4187
    // Integers
1✔
4188
    CHECK_EQUAL(table.find_first_int(c1, 100), null_key);
2✔
4189
    CHECK_EQUAL(table.find_first_int(c1, 200), o1.get_key());
2✔
4190
    // Uninitialized non-nullable integers equal 0
1✔
4191
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
4192

1✔
4193
    CHECK_EQUAL(table.find_first_int(c2, 200), o1.get_key());
2✔
4194
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4195
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
1✔
4196

1✔
4197
    // Non-nullable strings
1✔
4198
    CHECK_EQUAL(table.find_first_string(c3, "100"), null_key);
2✔
4199
    CHECK_EQUAL(table.find_first_string(c3, "200"), o1.get_key());
2✔
4200
    // Uninitialized non-nullable strings equal ""
1✔
4201
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
4202

1✔
4203
    // Nullable strings
1✔
4204
    CHECK_EQUAL(table.find_first_string(c4, "100"), null_key);
2✔
4205
    CHECK_EQUAL(table.find_first_string(c4, "200"), o1.get_key());
2✔
4206
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4207
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
1✔
4208

1✔
4209
    // Non-nullable bools
1✔
4210
    // default value for non-nullable bool is false, so o3 is a match
1✔
4211
    CHECK_EQUAL(table.find_first_bool(c5, false), o3.get_key());
2✔
4212
    CHECK_EQUAL(table.find_first_bool(c5, true), o1.get_key());
2✔
4213

1✔
4214
    // Nullable bools
1✔
4215
    CHECK_EQUAL(table.find_first_bool(c6, false), null_key);
2✔
4216
    CHECK_EQUAL(table.find_first_bool(c6, true), o1.get_key());
2✔
4217

1✔
4218
    // Call "set" and see if things still work
1✔
4219
    // *******************************************************************************
1✔
4220
    o1.set_all(500, 500, "500", "500");
2✔
4221
    o2.set_all(600, 600, "600", "600");
2✔
4222

1✔
4223
    CHECK_EQUAL(table.find_first_int(c1, 500), o1.get_key());
2✔
4224
    CHECK_EQUAL(table.find_first_int(c1, 600), o2.get_key());
2✔
4225
    // Uninitialized non-nullable integers equal 0
1✔
4226
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
4227
    CHECK_EQUAL(table.find_first_int(c2, 500), o1.get_key());
2✔
4228
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4229
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
1✔
4230

1✔
4231
    // Non-nullable strings
1✔
4232
    CHECK_EQUAL(table.find_first_string(c3, "500"), o1.get_key());
2✔
4233
    CHECK_EQUAL(table.find_first_string(c3, "600"), o2.get_key());
2✔
4234
    // Uninitialized non-nullable strings equal ""
1✔
4235
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
4236

1✔
4237
    // Nullable strings
1✔
4238
    CHECK_EQUAL(table.find_first_string(c4, "500"), o1.get_key());
2✔
4239
    CHECK_EQUAL(table.find_first_string(c4, "600"), o2.get_key());
2✔
4240
    // FIXME: Waiting for fix outside scope of search index PR
1✔
4241
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
1✔
4242

1✔
4243
    // Remove four of the indexes through remove_search_index() call. Let other four remain to see
1✔
4244
    // if they leak memory when Table goes out of scope (needs leak detector)
1✔
4245
    table.remove_search_index(c1);
2✔
4246
    table.remove_search_index(c2);
2✔
4247
    table.remove_search_index(c3);
2✔
4248
    table.remove_search_index(c4);
2✔
4249
}
2✔
4250

4251
TEST(Table_SearchIndexFindAll)
4252
{
2✔
4253
    Table table;
2✔
4254
    auto col_int = table.add_column(type_Int, "integers");
2✔
4255
    auto col_str = table.add_column(type_String, "strings");
2✔
4256
    // Add index before creating objects
1✔
4257
    table.add_search_index(col_int);
2✔
4258
    table.add_search_index(col_str);
2✔
4259

1✔
4260
    ObjKeys keys;
2✔
4261
    table.create_objects(100, keys);
2✔
4262
    for (auto o : table) {
200✔
4263
        int64_t key_value = o.get_key().value;
200✔
4264
        o.set(col_int, key_value);
200✔
4265
        // When node size is 4 the objects with "Hello" will be in 2 clusters
100✔
4266
        if (key_value > 21 && key_value < 28) {
200✔
4267
            o.set(col_str, "Hello");
12✔
4268
        }
12✔
4269
    }
200✔
4270

1✔
4271
    auto tv = table.find_all_string(col_str, "Hello");
2✔
4272
    CHECK_EQUAL(tv.size(), 6);
2✔
4273
}
2✔
4274

4275
TEST(Table_QueryNullOnNonNullSearchIndex)
4276
{
2✔
4277
    Group g;
2✔
4278
    TableRef t = g.add_table("table");
2✔
4279
    ColKey col0 = t->add_column(type_Int, "value", false);
2✔
4280
    ColKey col_link = t->add_column(*t, "link");
2✔
4281
    t->add_search_index(col0);
2✔
4282

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

1✔
4285
    for (Int v : values) {
10✔
4286
        auto obj = t->create_object();
10✔
4287
        obj.set(col0, v);
10✔
4288
        obj.set(col_link, obj.get_key());
10✔
4289
    }
10✔
4290

1✔
4291
    for (Int v : values) {
10✔
4292
        Query q0 = t->column<Int>(col0) == v;
10✔
4293
        CHECK_EQUAL(q0.count(), 1);
10✔
4294
        Query q1 = t->link(col_link).column<Int>(col0) == v;
10✔
4295
        CHECK_EQUAL(q1.count(), 1);
10✔
4296
        Query q2 = t->link(col_link).link(col_link).column<Int>(col0) == v;
10✔
4297
        CHECK_EQUAL(q2.count(), 1);
10✔
4298
        Query q3 = t->where().equal(col0, v);
10✔
4299
        CHECK_EQUAL(q3.count(), 1);
10✔
4300
    }
10✔
4301

1✔
4302
    {
2✔
4303
        Query q0 = t->column<Int>(col0) == realm::null();
2✔
4304
        CHECK_EQUAL(q0.count(), 0);
2✔
4305
        Query q1 = t->link(col_link).column<Int>(col0) == realm::null();
2✔
4306
        CHECK_EQUAL(q1.count(), 0);
2✔
4307
        Query q2 = t->link(col_link).link(col_link).column<Int>(col0) == realm::null();
2✔
4308
        CHECK_EQUAL(q2.count(), 0);
2✔
4309
    }
2✔
4310
}
2✔
4311

4312
TEST_TYPES(Table_QuerySearchEqualsNull, Prop<Int>, Prop<double>, Prop<float>, Prop<ObjectId>, Prop<Timestamp>,
4313
           Prop<StringData>, Prop<BinaryData>, Prop<Decimal128>, Prop<UUID>, Nullable<Int>, Nullable<double>,
4314
           Nullable<float>, Nullable<ObjectId>, Nullable<Timestamp>, Nullable<StringData>, Nullable<BinaryData>,
4315
           Nullable<Decimal128>, Nullable<UUID>, Indexed<Int>, Indexed<ObjectId>, Indexed<Timestamp>,
4316
           Indexed<StringData>, Indexed<UUID>, NullableIndexed<Int>, NullableIndexed<ObjectId>,
4317
           NullableIndexed<Timestamp>, NullableIndexed<StringData>, NullableIndexed<UUID>)
4318
{
56✔
4319
    using type = typename TEST_TYPE::type;
56✔
4320
    using underlying_type = typename TEST_TYPE::underlying_type;
56✔
4321
    TestValueGenerator gen;
56✔
4322
    Group g;
56✔
4323
    TableRef t = g.add_table("table");
56✔
4324
    ColKey col0 = t->add_column(TEST_TYPE::data_type, "value", TEST_TYPE::is_nullable);
56✔
4325
    ColKey col1 = t->add_column_list(TEST_TYPE::data_type, "values", TEST_TYPE::is_nullable);
56✔
4326
    ColKey col_link = t->add_column(*t, "link");
56✔
4327

28✔
4328
    if constexpr (TEST_TYPE::is_indexed) {
56✔
4329
        t->add_search_index(col0);
20✔
4330
    }
20✔
4331

28✔
4332
    std::vector<underlying_type> values = gen.values_from_int<underlying_type>({9, 4, 2, 7});
56✔
4333
    underlying_type default_non_null_value = TEST_TYPE::default_non_nullable_value();
56✔
4334
    values.push_back(default_non_null_value);
56✔
4335
    std::vector<size_t> indices;
56✔
4336

28✔
4337
    for (underlying_type v : values) {
280✔
4338
        auto obj = t->create_object();
280✔
4339
        obj.set(col0, v);
280✔
4340
        obj.set(col_link, obj.get_key());
280✔
4341
    }
280✔
4342

28✔
4343
    constexpr size_t num_defaults_added = 3;
56✔
4344
    for (size_t i = 0; i < num_defaults_added; ++i) {
224✔
4345
        auto obj = t->create_object();
168✔
4346
        obj.set(col_link, obj.get_key());
168✔
4347
    }
168✔
4348
    auto list = t->begin()->get_list<type>(col1);
56✔
4349
    for (underlying_type v : values) {
280✔
4350
        list.add(v);
280✔
4351
    }
280✔
4352

28✔
4353
    constexpr size_t num_nulls = TEST_TYPE::is_nullable ? num_defaults_added : 0;
56✔
4354
    constexpr size_t num_default_non_nullables = TEST_TYPE::is_nullable ? 1 : num_defaults_added + 1;
56✔
4355

28✔
4356
    for (underlying_type v : values) {
280✔
4357
        const size_t num_expected = (v == default_non_null_value ? num_default_non_nullables : 1);
252✔
4358
        Query q0 = t->column<underlying_type>(col0) == v;
280✔
4359
        CHECK_EQUAL(q0.count(), num_expected);
280✔
4360
        Query q1 = t->link(col_link).column<underlying_type>(col0) == v;
280✔
4361
        CHECK_EQUAL(q1.count(), num_expected);
280✔
4362
        Query q2 = t->link(col_link).link(col_link).column<underlying_type>(col0) == v;
280✔
4363
        CHECK_EQUAL(q2.count(), num_expected);
280✔
4364
        Query q3 = t->where().equal(col0, v);
280✔
4365
        CHECK_EQUAL(q3.count(), num_expected);
280✔
4366
        Query q4 = t->column<Lst<underlying_type>>(col1) == v;
280✔
4367
        CHECK_EQUAL(q4.count(), 1);
280✔
4368
    }
280✔
4369

28✔
4370
    {
56✔
4371
        Query q0 = t->column<underlying_type>(col0) == realm::null();
56✔
4372
        CHECK_EQUAL(q0.count(), num_nulls);
56✔
4373
        Query q1 = t->link(col_link).column<underlying_type>(col0) == realm::null();
56✔
4374
        CHECK_EQUAL(q1.count(), num_nulls);
56✔
4375
        Query q2 = t->link(col_link).link(col_link).column<underlying_type>(col0) == realm::null();
56✔
4376
        CHECK_EQUAL(q2.count(), num_nulls);
56✔
4377
        Query q3 = t->column<underlying_type>(col0) == default_non_null_value;
56✔
4378
        CHECK_EQUAL(q3.count(), num_default_non_nullables);
56✔
4379
        Query q4 = t->link(col_link).column<underlying_type>(col0) == default_non_null_value;
56✔
4380
        CHECK_EQUAL(q4.count(), num_default_non_nullables);
56✔
4381
        Query q5 = t->link(col_link).link(col_link).column<underlying_type>(col0) == default_non_null_value;
56✔
4382
        CHECK_EQUAL(q5.count(), num_default_non_nullables);
56✔
4383
    }
56✔
4384
}
56✔
4385

4386
namespace {
4387

4388
template <class T, bool nullable>
4389
struct Tester {
4390
    using T2 = typename util::RemoveOptional<T>::type;
4391

4392
    static ColKey col;
4393

4394
    static std::vector<ObjKey> find_all_reference(TableRef table, T v)
4395
    {
14,590✔
4396
        std::vector<ObjKey> res;
14,590✔
4397
        Table::Iterator it = table->begin();
14,590✔
4398
        while (it != table->end()) {
1,220,491✔
4399
            if (!it->is_null(col)) {
1,205,901✔
4400
                T v2 = it->get<T>(col);
1,103,720✔
4401
                if (v == v2) {
1,103,720✔
4402
                    res.push_back(it->get_key());
254,640✔
4403
                }
254,640✔
4404
            }
1,103,720✔
4405
            ++it;
1,205,901✔
4406
        };
1,205,901✔
4407
        // res is returned with nrvo optimization
7,363✔
4408
        return res;
14,590✔
4409
    }
14,590✔
4410

4411
    static void validate(TableRef table)
4412
    {
16,000✔
4413
        Table::Iterator it = table->begin();
16,000✔
4414

8,000✔
4415
        if (it != table->end()) {
16,000✔
4416
            auto v = it->get<T>(col);
15,756✔
4417

7,863✔
4418
            if (!it->is_null(col)) {
15,756✔
4419
                std::vector<ObjKey> res;
14,590✔
4420
                table->get_search_index(col)->find_all(res, v, false);
14,590✔
4421
                std::vector<ObjKey> ref = find_all_reference(table, v);
14,590✔
4422

7,363✔
4423
                size_t a = ref.size();
14,590✔
4424
                size_t b = res.size();
14,590✔
4425

7,363✔
4426
                REALM_ASSERT(a == b);
14,590✔
4427
            }
14,590✔
4428
        }
15,756✔
4429
    }
16,000✔
4430

4431
    static void run(DBRef db, realm::DataType type)
4432
    {
16✔
4433
        auto trans = db->start_write();
16✔
4434
        auto table = trans->add_table("my_table");
16✔
4435
        col = table->add_column(type, "name", nullable);
16✔
4436
        table->add_search_index(col);
16✔
4437
        const size_t iters = 1000;
16✔
4438

8✔
4439
        bool add_trend = true;
16✔
4440

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

8,000✔
4443
            if (iter == iters / 2) {
16,000✔
4444
                add_trend = false;
16✔
4445
            }
16✔
4446

8,000✔
4447
            // Add object (with 60% probability, so we grow the object count over time)
8,000✔
4448
            if (fastrand(100) < (add_trend ? 80 : 20)) {
16,000✔
4449
                Obj o = table->create_object();
7,944✔
4450
                bool set_to_null = fastrand(100) < 20;
7,944✔
4451

3,991✔
4452
                if (!set_to_null) {
7,944✔
4453
                    auto t = create();
6,337✔
4454
                    o.set<T2>(col, t);
6,337✔
4455
                }
6,337✔
4456
            }
7,944✔
4457

8,000✔
4458
            // Remove random object
8,000✔
4459
            if (fastrand(100) < 50 && table->size() > 0) {
16,000✔
4460
                Table::Iterator it = table->begin();
7,726✔
4461
                auto r = fastrand(table->size() - 1);
7,726✔
4462
                // FIXME: Is there a faster way to pick a random object?
3,888✔
4463
                for (unsigned t = 0; t < r; t++) {
327,404✔
4464
                    ++it;
319,678✔
4465
                }
319,678✔
4466
                Obj o = *it;
7,726✔
4467
                table->remove_object(o.get_key());
7,726✔
4468
            }
7,726✔
4469

8,000✔
4470
            // Edit random object
8,000✔
4471
            if (table->size() > 0) {
16,000✔
4472
                Table::Iterator it = table->begin();
15,756✔
4473
                auto r = fastrand(table->size() - 1);
15,756✔
4474
                // FIXME: Is there a faster way to pick a random object?
7,863✔
4475
                for (unsigned t = 0; t < r; t++) {
660,176✔
4476
                    ++it;
644,420✔
4477
                }
644,420✔
4478
                Obj o = *it;
15,756✔
4479
                bool set_to_null = fastrand(100) < 20;
15,756✔
4480
                if (set_to_null && table->is_nullable(col)) {
15,756✔
4481
                    o.set_null(col);
1,497✔
4482
                }
1,497✔
4483
                else {
14,259✔
4484
                    auto t = create();
14,259✔
4485
                    o.set<T2>(col, t);
14,259✔
4486
                }
14,259✔
4487
            }
15,756✔
4488

8,000✔
4489
            if (iter % (iters / 1000) == 0) {
16,000✔
4490
                validate(table);
16,000✔
4491
            }
16,000✔
4492
        }
16,000✔
4493
        trans->rollback();
16✔
4494
    }
16✔
4495

4496

4497
    // Create random data element of any type supported by the search index
4498
    template <typename Type = T2>
4499
    typename std::enable_if<std::is_same<Type, StringData>::value, std::string>::type static create()
4500
    {
5,163✔
4501
        std::string s = realm::util::to_string(fastrand(5));
5,163✔
4502
        return s;
5,163✔
4503
    }
5,163✔
4504
    template <typename Type = T2>
4505
    typename std::enable_if<std::is_same<Type, Timestamp>::value, T2>::type static create()
4506
    {
5,078✔
4507
        return Timestamp(fastrand(3), int32_t(fastrand(3)));
5,078✔
4508
    }
5,078✔
4509
    template <typename Type = T2>
4510
    typename std::enable_if<std::is_same<Type, int64_t>::value, T2>::type static create()
4511
    {
5,186✔
4512
        return fastrand(5);
5,186✔
4513
    }
5,186✔
4514

4515
    template <typename Type = T2>
4516
    typename std::enable_if<std::is_same<Type, bool>::value, T2>::type static create()
4517
    {
5,169✔
4518
        return fastrand(100) > 50;
5,169✔
4519
    }
5,169✔
4520
};
4521

4522
template <class T, bool nullable>
4523
ColKey Tester<T, nullable>::col;
4524
} // namespace
4525

4526
// The run() method will first add lots of objects, and then remove them. This will test
4527
// both node splits and empty leaf destruction and get good search index code coverage
4528
TEST(Table_search_index_fuzzer)
4529
{
2✔
4530
    // Syntax for Tester<T, nullable>:
1✔
4531
    // T:         Type that must be used in calls too Obj::get<T>
1✔
4532
    // nullable:  If the columns must be is nullable or not
1✔
4533
    // Obj::set() will be automatically be called with set<RemoveOptional<T>>()
1✔
4534

1✔
4535
    SHARED_GROUP_TEST_PATH(path);
2✔
4536
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4537
    auto db = DB::create(*hist, path);
2✔
4538
    Tester<bool, false>::run(db, type_Bool);
2✔
4539
    Tester<Optional<bool>, true>::run(db, type_Bool);
2✔
4540

1✔
4541
    Tester<int64_t, false>::run(db, type_Int);
2✔
4542
    Tester<Optional<int64_t>, true>::run(db, type_Int);
2✔
4543

1✔
4544
    // Self-contained null state
1✔
4545
    Tester<Timestamp, false>::run(db, type_Timestamp);
2✔
4546
    Tester<Timestamp, true>::run(db, type_Timestamp);
2✔
4547

1✔
4548
    // Self-contained null state
1✔
4549
    Tester<StringData, true>::run(db, type_String);
2✔
4550
    Tester<StringData, false>::run(db, type_String);
2✔
4551
}
2✔
4552

4553
TEST(Table_StaleColumnKey)
4554
{
2✔
4555
    Table table;
2✔
4556

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

1✔
4559
    Obj obj = table.create_object();
2✔
4560
    obj.set(col, 5);
2✔
4561

1✔
4562
    table.remove_column(col);
2✔
4563
    // col is now obsolete
1✔
4564
    table.add_column(type_Int, "score");
2✔
4565
    CHECK_THROW_ANY(obj.get<Int>(col));
2✔
4566
}
2✔
4567

4568
TEST(Table_KeysRow)
4569
{
2✔
4570
    Table table;
2✔
4571
    auto col_int = table.add_column(type_Int, "int");
2✔
4572
    auto col_string = table.add_column(type_String, "string", true);
2✔
4573
    table.add_search_index(col_int);
2✔
4574
    table.add_search_index(col_string);
2✔
4575

1✔
4576
    table.create_object(ObjKey(7), {{col_int, 123}, {col_string, "Hello, "}});
2✔
4577
    table.create_object(ObjKey(9), {{col_int, 456}, {col_string, StringData()}});
2✔
4578

1✔
4579
    auto i = table.find_first_int(col_int, 123);
2✔
4580
    CHECK_EQUAL(i, ObjKey(7));
2✔
4581
    i = table.find_first_int(col_int, 456);
2✔
4582
    CHECK_EQUAL(i, ObjKey(9));
2✔
4583

1✔
4584
    i = table.find_first_string(col_string, "Hello, ");
2✔
4585
    CHECK_EQUAL(i, ObjKey(7));
2✔
4586
    i = table.find_first_string(col_string, StringData());
2✔
4587
    CHECK_EQUAL(i, ObjKey(9));
2✔
4588
}
2✔
4589

4590
template <typename T>
4591
T generate_value()
4592
{
31,442✔
4593
    return test_util::random_int<T>();
31,442✔
4594
}
31,442✔
4595

4596
template <>
4597
std::string generate_value()
4598
{
62,862✔
4599
    std::string str;
62,862✔
4600
    str.resize(31);
62,862✔
4601
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), &test_util::random_int<char>);
62,862✔
4602
    return str;
62,862✔
4603
}
62,862✔
4604

4605
template <>
4606
bool generate_value()
4607
{
31,476✔
4608
    return test_util::random_int<int>() & 0x1;
31,476✔
4609
}
31,476✔
4610
template <>
4611
float generate_value()
4612
{
31,428✔
4613
    return float(1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000)));
31,428✔
4614
}
31,428✔
4615
template <>
4616
double generate_value()
4617
{
31,399✔
4618
    return 1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000));
31,399✔
4619
}
31,399✔
4620
template <>
4621
Timestamp generate_value()
4622
{
31,460✔
4623
    return Timestamp(test_util::random_int<int>(0, 1000000), test_util::random_int<int>(0, 1000000000));
31,460✔
4624
}
31,460✔
4625
template <>
4626
Decimal128 generate_value()
4627
{
31,431✔
4628
    return Decimal128(test_util::random_int<int>(-100000, 100000));
31,431✔
4629
}
31,431✔
4630
template <>
4631
ObjectId generate_value()
4632
{
31,450✔
4633
    return ObjectId::gen();
31,450✔
4634
}
31,450✔
4635
template <>
4636
UUID generate_value()
4637
{
31,371✔
4638
    std::string str;
31,371✔
4639
    str.resize(36);
31,371✔
4640
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), []() -> char {
1,129,356✔
4641
        char c = test_util::random_int<char>(0, 15);
1,129,356✔
4642
        return c >= 10 ? (c - 10 + 'a') : (c + '0');
917,712✔
4643
    });
1,129,356✔
4644
    str.at(8) = '-';
31,371✔
4645
    str.at(13) = '-';
31,371✔
4646
    str.at(18) = '-';
31,371✔
4647
    str.at(23) = '-';
31,371✔
4648
    return UUID(str.c_str());
31,371✔
4649
}
31,371✔
4650

4651
// helper object taking care of destroying memory underlying StringData and BinaryData
4652
// just a passthrough for other types
4653
template <typename T>
4654
struct managed {
4655
    T value;
4656
};
4657

4658
template <typename T>
4659
struct ManagedStorage {
4660
    std::string storage;
4661
    T value;
4662

4663
    ManagedStorage() {}
40,160✔
4664
    ManagedStorage(null) {}
3,298✔
4665
    ManagedStorage(std::string&& v)
4666
        : storage(std::move(v))
4667
        , value(storage)
4668
    {
62,862✔
4669
    }
62,862✔
4670
    ManagedStorage(const ManagedStorage& other)
4671
    {
137,308✔
4672
        *this = other;
137,308✔
4673
    }
137,308✔
4674
    ManagedStorage(ManagedStorage&& other)
4675
    {
16,792✔
4676
        *this = std::move(other);
16,792✔
4677
    }
16,792✔
4678

4679
    ManagedStorage(T v)
4680
    {
16,160✔
4681
        if (v) {
16,160✔
4682
            if (v.size()) {
15,750✔
4683
                storage.assign(v.data(), v.data() + v.size());
15,337✔
4684
            }
15,337✔
4685
            value = T(storage);
15,750✔
4686
        }
15,750✔
4687
    }
16,160✔
4688
    ManagedStorage& operator=(const ManagedStorage& other)
4689
    {
138,104✔
4690
        storage = other.storage;
138,104✔
4691
        value = other.value ? T(storage) : T();
134,993✔
4692
        return *this;
138,104✔
4693
    }
138,104✔
4694
    ManagedStorage& operator=(ManagedStorage&& other)
4695
    {
2,763,844✔
4696
        storage = std::move(other.storage);
2,763,844✔
4697
        value = other.value ? T(storage) : T();
2,696,724✔
4698
        return *this;
2,763,844✔
4699
    }
2,763,844✔
4700
};
4701

4702
template <>
4703
struct managed<StringData> : ManagedStorage<StringData> {
4704
    using ManagedStorage::ManagedStorage;
4705
};
4706
template <>
4707
struct managed<BinaryData> : ManagedStorage<BinaryData> {
4708
    using ManagedStorage::ManagedStorage;
4709
};
4710

4711

4712
template <typename T>
4713
void check_values(TestContext& test_context, Lst<T>& lst, std::vector<managed<T>>& reference)
4714
{
400✔
4715
    CHECK_EQUAL(lst.size(), reference.size());
400✔
4716
    for (unsigned j = 0; j < reference.size(); ++j)
341,440✔
4717
        CHECK_EQUAL(lst.get(j), reference[j].value);
341,040✔
4718
}
400✔
4719

4720
template <typename T>
4721
struct generator {
4722
    static managed<T> get(bool optional)
4723
    {
163,000✔
4724
        if (optional && (test_util::random_int<int>() % 10) == 0) {
163,000!
4725
            return managed<T>{T()};
4,723✔
4726
        }
4,723✔
4727
        else {
158,277✔
4728
            return managed<T>{generate_value<T>()};
158,277✔
4729
        }
158,277✔
4730
    }
163,000✔
4731
};
4732

4733
template <>
4734
struct generator<StringData> {
4735
    static managed<StringData> get(bool optional)
4736
    {
33,080✔
4737
        if (optional && (test_util::random_int<int>() % 10) == 0) {
33,080✔
4738
            return managed<StringData>(null());
1,627✔
4739
        }
1,627✔
4740
        else {
31,453✔
4741
            return generate_value<std::string>();
31,453✔
4742
        }
31,453✔
4743
    }
33,080✔
4744
};
4745

4746
template <>
4747
struct generator<BinaryData> {
4748
    static managed<BinaryData> get(bool optional)
4749
    {
33,080✔
4750
        if (optional && (test_util::random_int<int>() % 10) == 0) {
33,080✔
4751
            return managed<BinaryData>(null());
1,671✔
4752
        }
1,671✔
4753
        else {
31,409✔
4754
            return generate_value<std::string>();
31,409✔
4755
        }
31,409✔
4756
    }
33,080✔
4757
};
4758

4759
template <>
4760
struct generator<ObjectId> {
4761
    static managed<ObjectId> get(bool)
4762
    {
16,540✔
4763
        return managed<ObjectId>{generate_value<ObjectId>()};
16,540✔
4764
    }
16,540✔
4765
};
4766

4767
template <typename T>
4768
struct generator<Optional<T>> {
4769
    static managed<Optional<T>> get(bool)
4770
    {
85,100✔
4771
        if ((test_util::random_int<int>() % 10) == 0)
85,100✔
4772
            return managed<Optional<T>>{Optional<T>()};
8,460✔
4773
        else
76,640✔
4774
            return managed<Optional<T>>{generate_value<T>()};
76,640✔
4775
    }
85,100✔
4776
};
4777

4778
// specialize for Optional<StringData> and Optional<BinaryData> just to trigger errors if ever used
4779
template <>
4780
struct generator<Optional<StringData>> {
4781
};
4782
template <>
4783
struct generator<Optional<BinaryData>> {
4784
};
4785

4786
template <typename T>
4787
void test_lists(TestContext& test_context, DBRef sg, const realm::DataType type_id, bool optional = false)
4788
{
40✔
4789
    auto t = sg->start_write();
40✔
4790
    auto table = t->add_table("the_table");
40✔
4791
    auto col = table->add_column_list(type_id, "the column", optional);
40✔
4792
    Obj o = table->create_object();
40✔
4793
    Lst<T> lst = o.get_list<T>(col);
40✔
4794
    std::vector<managed<T>> reference;
40✔
4795
    for (int j = 0; j < 1000; ++j) {
40,040✔
4796
        managed<T> value = generator<T>::get(optional);
40,000✔
4797
        lst.add(value.value);
40,000✔
4798
        reference.push_back(value);
40,000✔
4799
    }
40,000✔
4800
    check_values(test_context, lst, reference);
40✔
4801
    for (int j = 0; j < 100; ++j) {
4,040✔
4802
        managed<T> value = generator<T>::get(optional);
4,000✔
4803
        lst.insert(493, value.value);
4,000✔
4804
        value = generator<T>::get(optional);
4,000✔
4805
        lst.set(493, value.value);
4,000✔
4806
        reference.insert(reference.begin() + 493, value);
4,000✔
4807
    }
4,000✔
4808
    check_values(test_context, lst, reference);
40✔
4809
    for (int j = 0; j < 100; ++j) {
4,040✔
4810
        lst.remove(142);
4,000✔
4811
        reference.erase(reference.begin() + 142);
4,000✔
4812
    }
4,000✔
4813
    check_values(test_context, lst, reference);
40✔
4814
    for (int disp = 0; disp < 4; ++disp) {
200✔
4815
        for (int j = 250 + disp; j > 50; j -= 3) {
10,960✔
4816
            lst.remove(j);
10,800✔
4817
            reference.erase(reference.begin() + j);
10,800✔
4818
        }
10,800✔
4819
        check_values(test_context, lst, reference);
160✔
4820
    }
160✔
4821
    auto it = reference.begin();
40✔
4822
    for (auto value : lst) {
29,200✔
4823
        CHECK(value == it->value);
29,200✔
4824
        ++it;
29,200✔
4825
    }
29,200✔
4826
    for (size_t j = lst.size(); j >= 100; --j) {
25,280✔
4827
        lst.remove(j - 1);
25,240✔
4828
        reference.pop_back();
25,240✔
4829
    }
25,240✔
4830
    check_values(test_context, lst, reference);
40✔
4831
    while (size_t sz = lst.size()) {
4,000✔
4832
        lst.remove(sz - 1);
3,960✔
4833
        reference.pop_back();
3,960✔
4834
    }
3,960✔
4835
    CHECK_EQUAL(0, reference.size());
40✔
4836
    t->rollback();
40✔
4837
}
40✔
4838

4839
TEST(List_Ops)
4840
{
2✔
4841
    SHARED_GROUP_TEST_PATH(path);
2✔
4842

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

1✔
4846
    test_lists<int64_t>(test_context, sg, type_Int);
2✔
4847
    test_lists<StringData>(test_context, sg, type_String);
2✔
4848
    test_lists<BinaryData>(test_context, sg, type_Binary);
2✔
4849
    test_lists<bool>(test_context, sg, type_Bool);
2✔
4850
    test_lists<float>(test_context, sg, type_Float);
2✔
4851
    test_lists<double>(test_context, sg, type_Double);
2✔
4852
    test_lists<Timestamp>(test_context, sg, type_Timestamp);
2✔
4853
    test_lists<Decimal128>(test_context, sg, type_Decimal);
2✔
4854
    test_lists<ObjectId>(test_context, sg, type_ObjectId);
2✔
4855
    test_lists<UUID>(test_context, sg, type_UUID);
2✔
4856

1✔
4857
    test_lists<Optional<int64_t>>(test_context, sg, type_Int, true);
2✔
4858
    test_lists<StringData>(test_context, sg, type_String, true); // always Optional?
2✔
4859
    test_lists<BinaryData>(test_context, sg, type_Binary, true); // always Optional?
2✔
4860
    test_lists<Optional<bool>>(test_context, sg, type_Bool, true);
2✔
4861
    test_lists<Optional<float>>(test_context, sg, type_Float, true);
2✔
4862
    test_lists<Optional<double>>(test_context, sg, type_Double, true);
2✔
4863
    test_lists<Timestamp>(test_context, sg, type_Timestamp, true); // always Optional?
2✔
4864
    test_lists<Decimal128>(test_context, sg, type_Decimal, true);
2✔
4865
    test_lists<Optional<ObjectId>>(test_context, sg, type_ObjectId, true);
2✔
4866
    test_lists<Optional<UUID>>(test_context, sg, type_UUID, true);
2✔
4867
}
2✔
4868

4869
template <typename T, typename U = T>
4870
void test_lists_numeric_agg(TestContext& test_context, DBRef sg, const realm::DataType type_id, U null_value = U{},
4871
                            bool optional = false)
4872
{
16✔
4873
    auto t = sg->start_write();
16✔
4874
    auto table = t->add_table("the_table");
16✔
4875
    auto col = table->add_column_list(type_id, "the column", optional);
16✔
4876
    Obj o = table->create_object();
16✔
4877
    Lst<T> lst = o.get_list<T>(col);
16✔
4878
    for (int j = -1000; j < 1000; ++j) {
32,016✔
4879
        T value = T(j);
32,000✔
4880
        lst.add(value);
32,000✔
4881
    }
32,000✔
4882
    if (optional) {
16✔
4883
        // given that sum/avg do not count nulls and min/max ignore nulls,
4✔
4884
        // adding any number of null values should not affect the results of any aggregates
4✔
4885
        for (size_t i = 0; i < 1000; ++i) {
8,008!
4886
            lst.add(null_value);
8,000✔
4887
        }
8,000✔
4888
    }
8✔
4889
    for (int j = -1000; j < 1000; ++j) {
32,016✔
4890
        CHECK_EQUAL(lst.get(j + 1000), T(j));
32,000✔
4891
    }
32,000✔
4892
    {
16✔
4893
        size_t ret_ndx = realm::npos;
16✔
4894
        auto min = lst.min(&ret_ndx);
16✔
4895
        CHECK(min);
16✔
4896
        CHECK(!min->is_null());
16✔
4897
        CHECK_EQUAL(ret_ndx, 0);
16✔
4898
        CHECK_EQUAL(min->template get<ColumnMinMaxType<T>>(), ColumnMinMaxType<T>(-1000));
16✔
4899
        auto max = lst.max(&ret_ndx);
16✔
4900
        CHECK(max);
16✔
4901
        CHECK(!max->is_null());
16✔
4902
        CHECK_EQUAL(ret_ndx, 1999);
16✔
4903
        CHECK_EQUAL(max->template get<ColumnMinMaxType<T>>(), ColumnMinMaxType<T>(999));
16✔
4904
        size_t ret_count = 0;
16✔
4905
        auto sum = lst.sum(&ret_count);
16✔
4906
        CHECK(sum);
16✔
4907
        CHECK(!sum->is_null());
16✔
4908
        CHECK_EQUAL(ret_count, 2000);
16✔
4909
        CHECK_EQUAL(sum->template get<ColumnSumType<T>>(), ColumnSumType<T>(-1000));
16✔
4910
        auto avg = lst.avg(&ret_count);
16✔
4911
        CHECK(avg);
16✔
4912
        CHECK(!avg->is_null());
16✔
4913
        CHECK_EQUAL(ret_count, 2000);
16✔
4914
        CHECK_EQUAL(avg->template get<ColumnAverageType<T>>(),
16✔
4915
                    (ColumnAverageType<T>(-1000) / ColumnAverageType<T>(2000)));
16✔
4916
    }
16✔
4917

8✔
4918
    lst.clear();
16✔
4919
    CHECK_EQUAL(lst.size(), 0);
16✔
4920
    {
16✔
4921
        size_t ret_ndx = realm::npos;
16✔
4922
        auto min = lst.min(&ret_ndx);
16✔
4923
        CHECK(min);
16✔
4924
        CHECK_EQUAL(ret_ndx, realm::npos);
16✔
4925
        ret_ndx = realm::npos;
16✔
4926
        auto max = lst.max(&ret_ndx);
16✔
4927
        CHECK(max);
16✔
4928
        CHECK_EQUAL(ret_ndx, realm::npos);
16✔
4929
        size_t ret_count = realm::npos;
16✔
4930
        auto sum = lst.sum(&ret_count);
16✔
4931
        CHECK(sum);
16✔
4932
        CHECK_EQUAL(ret_count, 0);
16✔
4933
        ret_count = realm::npos;
16✔
4934
        auto avg = lst.avg(&ret_count);
16✔
4935
        CHECK(avg);
16✔
4936
        CHECK_EQUAL(ret_count, 0);
16✔
4937
    }
16✔
4938

8✔
4939
    lst.add(T(1));
16✔
4940
    {
16✔
4941
        size_t ret_ndx = realm::npos;
16✔
4942
        auto min = lst.min(&ret_ndx);
16✔
4943
        CHECK(min);
16✔
4944
        CHECK(!min->is_null());
16✔
4945
        CHECK_EQUAL(ret_ndx, 0);
16✔
4946
        CHECK_EQUAL(min->template get<ColumnMinMaxType<T>>(), ColumnMinMaxType<T>(1));
16✔
4947
        auto max = lst.max(&ret_ndx);
16✔
4948
        CHECK(max);
16✔
4949
        CHECK(!max->is_null());
16✔
4950
        CHECK_EQUAL(ret_ndx, 0);
16✔
4951
        CHECK_EQUAL(max->template get<ColumnMinMaxType<T>>(), ColumnMinMaxType<T>(1));
16✔
4952
        size_t ret_count = 0;
16✔
4953
        auto sum = lst.sum(&ret_count);
16✔
4954
        CHECK(sum);
16✔
4955
        CHECK(!sum->is_null());
16✔
4956
        CHECK_EQUAL(ret_count, 1);
16✔
4957
        CHECK_EQUAL(sum->template get<ColumnSumType<T>>(), ColumnSumType<T>(1));
16✔
4958
        auto avg = lst.avg(&ret_count);
16✔
4959
        CHECK(avg);
16✔
4960
        CHECK(!avg->is_null());
16✔
4961
        CHECK_EQUAL(ret_count, 1);
16✔
4962
        CHECK_EQUAL(avg->template get<ColumnAverageType<T>>(), ColumnAverageType<T>(1));
16✔
4963
    }
16✔
4964

8✔
4965
    t->rollback();
16✔
4966
}
16✔
4967

4968
TEST(List_AggOps)
4969
{
2✔
4970
    SHARED_GROUP_TEST_PATH(path);
2✔
4971

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

1✔
4975
    test_lists_numeric_agg<int64_t>(test_context, sg, type_Int);
2✔
4976
    test_lists_numeric_agg<float>(test_context, sg, type_Float);
2✔
4977
    test_lists_numeric_agg<double>(test_context, sg, type_Double);
2✔
4978
    test_lists_numeric_agg<Decimal128>(test_context, sg, type_Decimal);
2✔
4979

1✔
4980
    test_lists_numeric_agg<Optional<int64_t>>(test_context, sg, type_Int, Optional<int64_t>{}, true);
2✔
4981
    test_lists_numeric_agg<float>(test_context, sg, type_Float, realm::null::get_null_float<float>(), true);
2✔
4982
    test_lists_numeric_agg<double>(test_context, sg, type_Double, realm::null::get_null_float<double>(), true);
2✔
4983
    test_lists_numeric_agg<Decimal128>(test_context, sg, type_Decimal, Decimal128(realm::null()), true);
2✔
4984
}
2✔
4985

4986
TEST(List_DecimalMinMax)
4987
{
2✔
4988
    SHARED_GROUP_TEST_PATH(path);
2✔
4989
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4990
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4991
    auto t = sg->start_write();
2✔
4992
    auto table = t->add_table("the_table");
2✔
4993
    auto col = table->add_column_list(type_Decimal, "the column");
2✔
4994
    Obj o = table->create_object();
2✔
4995
    Lst<Decimal128> lst = o.get_list<Decimal128>(col);
2✔
4996
    std::string larger_than_max_int64_t = "123.45e99";
2✔
4997
    lst.add(Decimal128(larger_than_max_int64_t));
2✔
4998
    CHECK_EQUAL(lst.size(), 1);
2✔
4999
    CHECK_EQUAL(lst.get(0), Decimal128(larger_than_max_int64_t));
2✔
5000
    size_t min_ndx = realm::npos;
2✔
5001
    auto min = lst.min(&min_ndx);
2✔
5002
    CHECK(min);
2✔
5003
    CHECK_EQUAL(min_ndx, 0);
2✔
5004
    CHECK_EQUAL(min->get<Decimal128>(), Decimal128(larger_than_max_int64_t));
2✔
5005
    lst.clear();
2✔
5006
    CHECK_EQUAL(lst.size(), 0);
2✔
5007
    std::string smaller_than_min_int64_t = "-123.45e99";
2✔
5008
    lst.add(Decimal128(smaller_than_min_int64_t));
2✔
5009
    CHECK_EQUAL(lst.size(), 1);
2✔
5010
    CHECK_EQUAL(lst.get(0), Decimal128(smaller_than_min_int64_t));
2✔
5011
    size_t max_ndx = realm::npos;
2✔
5012
    auto max = lst.max(&max_ndx);
2✔
5013
    CHECK(max);
2✔
5014
    CHECK_EQUAL(max_ndx, 0);
2✔
5015
    CHECK_EQUAL(max->get<Decimal128>(), Decimal128(smaller_than_min_int64_t));
2✔
5016
}
2✔
5017

5018
template <typename T>
5019
void check_table_values(TestContext& test_context, TableRef t, ColKey col, std::map<int, managed<T>>& reference)
5020
{
200✔
5021
    if (t->size() != reference.size()) {
200✔
5022
        std::cout << "gah" << std::endl;
×
5023
    }
×
5024
    CHECK_EQUAL(t->size(), reference.size());
200✔
5025
    for (auto it : reference) {
480,800✔
5026
        T value = it.second.value;
480,800✔
5027
        Obj o = t->get_object(ObjKey(it.first));
480,800✔
5028
        CHECK_EQUAL(o.get<T>(col), value);
480,800✔
5029
    }
480,800✔
5030
}
200✔
5031

5032
template <typename T>
5033
void test_tables(TestContext& test_context, DBRef sg, const realm::DataType type_id, bool optional = false)
5034
{
40✔
5035
    auto t = sg->start_write();
40✔
5036
    auto table = t->add_table("the_table");
40✔
5037
    auto col = table->add_column(type_id, "the column", optional);
40✔
5038
    std::map<int, managed<T>> reference;
40✔
5039

20✔
5040
    // insert elements 0 - 999
20✔
5041
    for (int j = 0; j < 1000; ++j) {
40,040✔
5042
        managed<T> value = generator<T>::get(optional);
40,000✔
5043
        table->create_object(ObjKey(j)).set_all(value.value);
40,000✔
5044
        reference[j] = std::move(value);
40,000✔
5045
    }
40,000✔
5046
    // insert elements 10000 - 10999
20✔
5047
    for (int j = 10000; j < 11000; ++j) {
40,040✔
5048
        managed<T> value = generator<T>::get(optional);
40,000✔
5049
        table->create_object(ObjKey(j)).set_all(value.value);
40,000✔
5050
        reference[j] = std::move(value);
40,000✔
5051
    }
40,000✔
5052
    // insert in between previous groups
20✔
5053
    for (int j = 4000; j < 7000; ++j) {
120,040✔
5054
        managed<T> value = generator<T>::get(optional);
120,000✔
5055
        table->create_object(ObjKey(j)).set_all(value.value);
120,000✔
5056
        reference[j] = std::move(value);
120,000✔
5057
    }
120,000✔
5058
    check_table_values(test_context, table, col, reference);
40✔
5059

20✔
5060
    // modify values
20✔
5061
    for (int j = 0; j < 11000; j += 100) {
4,440✔
5062
        auto it = reference.find(j);
4,400✔
5063
        if (it == reference.end()) // skip over holes in the key range
4,400✔
5064
            continue;
2,400✔
5065
        managed<T> value = generator<T>::get(optional);
2,000✔
5066
        table->get_object(ObjKey(j)).set<T>(col, value.value);
2,000✔
5067
        it->second = value;
2,000✔
5068
    }
2,000✔
5069
    check_table_values(test_context, table, col, reference);
40✔
5070

20✔
5071
    // remove chunk in the middle
20✔
5072
    for (int j = 1000; j < 10000; ++j) {
360,040✔
5073
        auto it = reference.find(j);
360,000✔
5074
        if (it == reference.end()) // skip over holes in the key range
360,000✔
5075
            continue;
240,000✔
5076
        table->remove_object(ObjKey(j));
120,000✔
5077
        reference.erase(it);
120,000✔
5078
    }
120,000✔
5079
    check_table_values(test_context, table, col, reference);
40✔
5080
    t->rollback();
40✔
5081
}
40✔
5082

5083
TEST(Table_Ops)
5084
{
2✔
5085
    SHARED_GROUP_TEST_PATH(path);
2✔
5086

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

1✔
5090
    test_tables<int64_t>(test_context, sg, type_Int);
2✔
5091
    test_tables<StringData>(test_context, sg, type_String);
2✔
5092
    test_tables<BinaryData>(test_context, sg, type_Binary);
2✔
5093
    test_tables<bool>(test_context, sg, type_Bool);
2✔
5094
    test_tables<float>(test_context, sg, type_Float);
2✔
5095
    test_tables<double>(test_context, sg, type_Double);
2✔
5096
    test_tables<Timestamp>(test_context, sg, type_Timestamp);
2✔
5097
    test_tables<Decimal128>(test_context, sg, type_Decimal);
2✔
5098
    test_tables<ObjectId>(test_context, sg, type_ObjectId);
2✔
5099
    test_tables<UUID>(test_context, sg, type_UUID);
2✔
5100

1✔
5101
    test_tables<Optional<int64_t>>(test_context, sg, type_Int, true);
2✔
5102
    test_tables<StringData>(test_context, sg, type_String, true); // always Optional?
2✔
5103
    test_tables<BinaryData>(test_context, sg, type_Binary, true); // always Optional?
2✔
5104
    test_tables<Optional<bool>>(test_context, sg, type_Bool, true);
2✔
5105
    test_tables<Optional<float>>(test_context, sg, type_Float, true);
2✔
5106
    test_tables<Optional<double>>(test_context, sg, type_Double, true);
2✔
5107
    test_tables<Timestamp>(test_context, sg, type_Timestamp, true); // always Optional?
2✔
5108
    test_tables<Decimal128>(test_context, sg, type_Decimal, true);
2✔
5109
    test_tables<Optional<ObjectId>>(test_context, sg, type_ObjectId, true);
2✔
5110
    test_tables<UUID>(test_context, sg, type_UUID, true);
2✔
5111
}
2✔
5112

5113
template <typename TFrom, typename TTo>
5114
void test_dynamic_conversion(TestContext& test_context, DBRef sg, realm::DataType type_id, bool from_nullable,
5115
                             bool to_nullable)
5116
{
80✔
5117
    // Create values of type TFrom and ask for dynamic conversion to TTo
40✔
5118
    auto t = sg->start_write();
80✔
5119
    auto table = t->add_table("the_table");
80✔
5120
    auto col_from = table->add_column(type_id, "the column", from_nullable);
80✔
5121
    if (type_id == type_String) {
80✔
5122
        table->add_search_index(col_from);
8✔
5123
    }
8✔
5124
    std::map<int, managed<TTo>> reference;
80✔
5125
    value_copier<TFrom, TTo> copier(false);
80✔
5126
    for (int j = 0; j < 10; ++j) {
880✔
5127
        managed<TFrom> value = generator<TFrom>::get(from_nullable);
800✔
5128
        table->create_object(ObjKey(j)).set_all(value.value);
800✔
5129
        TTo conv_value = copier(
800✔
5130
            value.value, to_nullable); // one may argue that using the same converter for ref and dut is.. mmmh...
800✔
5131
        reference[j] = managed<TTo>{conv_value};
800✔
5132
    }
800✔
5133
    auto col_to = table->set_nullability(col_from, to_nullable, false);
80✔
5134
    if (type_id == type_String) {
80✔
5135
        CHECK(table->has_search_index(col_to));
8✔
5136
    }
8✔
5137
    check_table_values(test_context, table, col_to, reference);
80✔
5138
    t->rollback();
80✔
5139
}
80✔
5140

5141
template <typename TFrom, typename TTo>
5142
void test_dynamic_conversion_list(TestContext& test_context, DBRef sg, realm::DataType type_id, bool from_nullable,
5143
                                  bool to_nullable)
5144
{
80✔
5145
    // Create values of type TFrom and ask for dynamic conversion to TTo
40✔
5146
    auto t = sg->start_write();
80✔
5147
    auto table = t->add_table("the_table");
80✔
5148
    auto col_from = table->add_column_list(type_id, "the column", from_nullable);
80✔
5149
    Obj o = table->create_object();
80✔
5150
    table->create_object(); // This object will have an empty list
80✔
5151
    Lst<TFrom> from_lst = o.get_list<TFrom>(col_from);
80✔
5152
    std::vector<managed<TTo>> reference;
80✔
5153
    value_copier<TFrom, TTo> copier(false);
80✔
5154
    for (int j = 0; j < 1000; ++j) {
80,080✔
5155
        managed<TFrom> value = generator<TFrom>::get(from_nullable);
80,000✔
5156
        from_lst.add(value.value);
80,000✔
5157
        TTo conv_value = copier(value.value, to_nullable);
80,000✔
5158
        reference.push_back(managed<TTo>{conv_value});
80,000✔
5159
    }
80,000✔
5160
    auto col_to = table->set_nullability(col_from, to_nullable, false);
80✔
5161
    Lst<TTo> to_lst = o.get_list<TTo>(col_to);
80✔
5162
    check_values(test_context, to_lst, reference);
80✔
5163
    t->rollback();
80✔
5164
}
80✔
5165

5166
template <typename T>
5167
void test_dynamic_conversion_combi(TestContext& test_context, DBRef sg, realm::DataType type_id)
5168
{
10✔
5169
    test_dynamic_conversion<T, Optional<T>>(test_context, sg, type_id, false, true);
10✔
5170
    test_dynamic_conversion<Optional<T>, T>(test_context, sg, type_id, true, false);
10✔
5171
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, false);
10✔
5172
    test_dynamic_conversion<Optional<T>, Optional<T>>(test_context, sg, type_id, true, true);
10✔
5173
}
10✔
5174

5175
template <typename T>
5176
void test_dynamic_conversion_combi_sametype(TestContext& test_context, DBRef sg, realm::DataType type_id)
5177
{
10✔
5178
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, true);
10✔
5179
    test_dynamic_conversion<T, T>(test_context, sg, type_id, true, false);
10✔
5180
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, false);
10✔
5181
    test_dynamic_conversion<T, T>(test_context, sg, type_id, true, true);
10✔
5182
}
10✔
5183

5184
template <typename T>
5185
void test_dynamic_conversion_list_combi(TestContext& test_context, DBRef sg, realm::DataType type_id)
5186
{
10✔
5187
    test_dynamic_conversion_list<T, Optional<T>>(test_context, sg, type_id, false, true);
10✔
5188
    test_dynamic_conversion_list<Optional<T>, T>(test_context, sg, type_id, true, false);
10✔
5189
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, false);
10✔
5190
    test_dynamic_conversion_list<Optional<T>, Optional<T>>(test_context, sg, type_id, true, true);
10✔
5191
}
10✔
5192

5193
template <typename T>
5194
void test_dynamic_conversion_list_combi_sametype(TestContext& test_context, DBRef sg, realm::DataType type_id)
5195
{
10✔
5196
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, true);
10✔
5197
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, true, false);
10✔
5198
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, false);
10✔
5199
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, true, true);
10✔
5200
}
10✔
5201

5202
TEST(Table_Column_DynamicConversions)
5203
{
2✔
5204
    SHARED_GROUP_TEST_PATH(path);
2✔
5205

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

1✔
5209
    test_dynamic_conversion_combi<int64_t>(test_context, sg, type_Int);
2✔
5210
    test_dynamic_conversion_combi<float>(test_context, sg, type_Float);
2✔
5211
    test_dynamic_conversion_combi<double>(test_context, sg, type_Double);
2✔
5212
    test_dynamic_conversion_combi<bool>(test_context, sg, type_Bool);
2✔
5213
    test_dynamic_conversion_combi<ObjectId>(test_context, sg, type_ObjectId);
2✔
5214

1✔
5215
    test_dynamic_conversion_combi_sametype<StringData>(test_context, sg, type_String);
2✔
5216
    test_dynamic_conversion_combi_sametype<BinaryData>(test_context, sg, type_Binary);
2✔
5217
    test_dynamic_conversion_combi_sametype<Timestamp>(test_context, sg, type_Timestamp);
2✔
5218
    test_dynamic_conversion_combi_sametype<Decimal128>(test_context, sg, type_Decimal);
2✔
5219
    test_dynamic_conversion_combi_sametype<UUID>(test_context, sg, type_UUID);
2✔
5220
    // lists...:
1✔
5221
    test_dynamic_conversion_list_combi<int64_t>(test_context, sg, type_Int);
2✔
5222
    test_dynamic_conversion_list_combi<float>(test_context, sg, type_Float);
2✔
5223
    test_dynamic_conversion_list_combi<double>(test_context, sg, type_Double);
2✔
5224
    test_dynamic_conversion_list_combi<bool>(test_context, sg, type_Bool);
2✔
5225
    test_dynamic_conversion_list_combi<ObjectId>(test_context, sg, type_ObjectId);
2✔
5226

1✔
5227
    test_dynamic_conversion_list_combi_sametype<StringData>(test_context, sg, type_String);
2✔
5228
    test_dynamic_conversion_list_combi_sametype<BinaryData>(test_context, sg, type_Binary);
2✔
5229
    test_dynamic_conversion_list_combi_sametype<Timestamp>(test_context, sg, type_Timestamp);
2✔
5230
    test_dynamic_conversion_list_combi_sametype<Decimal128>(test_context, sg, type_Decimal);
2✔
5231
    test_dynamic_conversion_list_combi_sametype<UUID>(test_context, sg, type_UUID);
2✔
5232
}
2✔
5233

5234
/*
5235
TEST(Table_Column_Conversions)
5236
{
5237
    SHARED_GROUP_TEST_PATH(path);
5238

5239
    std::unique_ptr<Replication> hist(make_in_realm_history());
5240
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
5241

5242
    test_column_conversion<int64_t, Optional<int64_t>>(test_context, sg, type_Int);
5243
    test_column_conversion<float, Optional<float>>(test_context, sg, type_Float);
5244
    test_column_conversion<double, Optional<double>>(test_context, sg, type_Double);
5245
    test_column_conversion<bool, Optional<bool>>(test_context, sg, type_Bool);
5246
    test_column_conversion<StringData, StringData>(test_context, sg, type_String);
5247
    test_column_conversion<BinaryData, BinaryData>(test_context, sg, type_Binary);
5248
    test_column_conversion<Timestamp, Timestamp>(test_context, sg, type_Timestamp);
5249

5250
    test_column_conversion_optional<int64_t>(test_context, sg, type_Int);
5251
    test_column_conversion_optional<float>(test_context, sg, type_Float);
5252
    test_column_conversion_optional<double>(test_context, sg, type_Double);
5253
    test_column_conversion_optional<bool>(test_context, sg, type_Bool);
5254

5255
    test_column_conversion_sametype<StringData>(test_context, sg, type_String);
5256
    test_column_conversion_sametype<BinaryData>(test_context, sg, type_Binary);
5257
    test_column_conversion_sametype<Timestamp>(test_context, sg, type_Timestamp);
5258

5259
}
5260
*/
5261

5262
TEST(Table_ChangePKNullability)
5263
{
2✔
5264
    SHARED_GROUP_TEST_PATH(path);
2✔
5265

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

1✔
5269
    auto wt = sg->start_write();
2✔
5270
    auto table = wt->add_table_with_primary_key("foo", type_String, "id", false);
2✔
5271

1✔
5272
    table->create_object_with_primary_key("Paul");
2✔
5273
    table->create_object_with_primary_key("John");
2✔
5274
    table->create_object_with_primary_key("George");
2✔
5275
    table->create_object_with_primary_key("Ringo");
2✔
5276

1✔
5277
    auto pk_col = table->get_primary_key_column();
2✔
5278
    pk_col = table->set_nullability(pk_col, true, true);
2✔
5279
    CHECK(pk_col.is_nullable());
2✔
5280

1✔
5281
    table->create_object_with_primary_key("");
2✔
5282
    table->create_object_with_primary_key({});
2✔
5283

1✔
5284
    std::string message;
2✔
5285
    CHECK_THROW_ANY_GET_MESSAGE(table->set_nullability(pk_col, false, true), message);
2✔
5286
    CHECK_EQUAL(message, "Objects in 'foo' has null value(s) in property 'id'");
2✔
5287

1✔
5288
    table->get_object_with_primary_key({}).remove();
2✔
5289
    table->set_nullability(pk_col, false, true);
2✔
5290
}
2✔
5291

5292
TEST(Table_MultipleObjs)
5293
{
2✔
5294
    SHARED_GROUP_TEST_PATH(path);
2✔
5295

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

1✔
5299
    auto tr = sg->start_write();
2✔
5300
    auto table = tr->add_table("my_table");
2✔
5301
    auto col = table->add_column_list(*table, "the links");
2✔
5302
    auto col_int = table->add_column_list(type_String, "the integers");
2✔
5303
    auto obj_key = table->create_object().get_key();
2✔
5304
    tr->commit();
2✔
5305
    tr = sg->start_write();
2✔
5306
    table = tr->get_table("my_table");
2✔
5307
    auto obj = table->get_object(obj_key);
2✔
5308
    auto list_1 = obj.get_linklist(col);
2✔
5309
    auto list_2 = obj.get_linklist(col);
2✔
5310

1✔
5311
    auto list_3 = obj.get_list<StringData>(col_int);
2✔
5312
    auto list_4 = obj.get_list<StringData>(col_int);
2✔
5313
    std::string s = "42";
2✔
5314
    StringData ss(s.data(), s.size());
2✔
5315
    list_3.add(ss);
2✔
5316
    CHECK_EQUAL(list_4.get(0), ss);
2✔
5317

1✔
5318
    list_1.add(obj_key);
2✔
5319
    CHECK_EQUAL(list_1.get(0), obj_key);
2✔
5320
    CHECK_EQUAL(list_2.get(0), obj_key);
2✔
5321
}
2✔
5322

5323
TEST(Table_IteratorRandomAccess)
5324
{
2✔
5325
    Table t;
2✔
5326

1✔
5327
    ObjKeys keys;
2✔
5328
    t.create_objects(1000, keys);
2✔
5329

1✔
5330
    auto key = keys.begin();
2✔
5331
    auto iter = t.begin();
2✔
5332
    for (size_t pos = 0; (pos + 3) < 1000; pos += 3) {
668✔
5333
        CHECK_EQUAL(iter->get_key(), *key);
666✔
5334
        iter += 3;
666✔
5335
        key += 3;
666✔
5336
    }
666✔
5337

1✔
5338
    // random access
1✔
5339
    for (int j = 0; j < 5; j++) {
12✔
5340
        std::vector<size_t> random_idx(keys.size());
10✔
5341
        std::iota(random_idx.begin(), random_idx.end(), 0);
10✔
5342
        // unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
5✔
5343
        // std::cout << "Seed " << seed << std::endl;
5✔
5344
        std::shuffle(random_idx.begin(), random_idx.end(), std::mt19937(unit_test_random_seed));
10✔
5345
        iter = t.begin();
10✔
5346
        int i = 0;
10✔
5347
        for (auto index : random_idx) {
6,455✔
5348
            if (index < keys.size()) {
6,455✔
5349
                auto k = keys[index];
5,724✔
5350
                if (i == 4) {
5,724✔
5351
                    t.remove_object(k);
1,426✔
5352
                    keys.erase(keys.begin() + index);
1,426✔
5353
                    if (index == 0)
1,426✔
UNCOV
5354
                        iter = t.begin();
×
5355
                    i = 0;
1,426✔
5356
                }
1,426✔
5357
                else {
4,298✔
5358
                    iter.go(index);
4,298✔
5359
                    CHECK_EQUAL(k, iter->get_key());
4,298✔
5360
                }
4,298✔
5361
                i++;
5,724✔
5362
            }
5,724✔
5363
        }
6,455✔
5364
    }
10✔
5365

1✔
5366
    iter.go(0);
2✔
5367
    auto iter200 = iter + 200;
2✔
5368
    CHECK_EQUAL(keys[200], iter200->get_key());
2✔
5369
}
2✔
5370

5371
TEST(Table_EmbeddedObjects)
5372
{
2✔
5373
    SHARED_GROUP_TEST_PATH(path);
2✔
5374

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

1✔
5378
    auto tr = sg->start_write();
2✔
5379
    auto table = tr->add_table("mytable", Table::Type::Embedded);
2✔
5380
    tr->commit_and_continue_as_read();
2✔
5381
    tr->promote_to_write();
2✔
5382
    CHECK(table->is_embedded());
2✔
5383
    CHECK_THROW(table->create_object(), LogicError);
2✔
5384
    tr->rollback();
2✔
5385

1✔
5386
    tr = sg->start_read();
2✔
5387
    table = tr->get_table("mytable");
2✔
5388
    CHECK(table->is_embedded());
2✔
5389
}
2✔
5390

5391
TEST(Table_EmbeddedObjectCreateAndDestroy)
5392
{
2✔
5393
    SHARED_GROUP_TEST_PATH(path);
2✔
5394

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

1✔
5398
    {
2✔
5399
        auto tr = sg->start_write();
2✔
5400
        auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5401
        auto col_recurse = table->add_column(*table, "theRecursiveBit");
2✔
5402
        CHECK_THROW(table->create_object(), LogicError);
2✔
5403
        auto parent = tr->add_table("myParentStuff");
2✔
5404
        auto ck = parent->add_column(*table, "theGreatColumn");
2✔
5405
        Obj o = parent->create_object();
2✔
5406
        Obj o2 = o.create_and_set_linked_object(ck);
2✔
5407
        Obj o3 = o2.create_and_set_linked_object(col_recurse);
2✔
5408
        auto parent_obj = o2.get_parent_object();
2✔
5409
        CHECK_EQUAL(o.get_key(), parent_obj.get_key());
2✔
5410
        parent_obj = o3.get_parent_object();
2✔
5411
        CHECK_EQUAL(o2.get_key(), parent_obj.get_key());
2✔
5412
        CHECK(table->size() == 2);
2✔
5413
        tr->commit();
2✔
5414
    }
2✔
5415
    {
2✔
5416
        auto tr = sg->start_write();
2✔
5417
        auto table = tr->get_table("myEmbeddedStuff");
2✔
5418
        auto parent = tr->get_table("myParentStuff");
2✔
5419
        CHECK(table->size() == 2);
2✔
5420
        auto first = parent->begin();
2✔
5421
        first->set("theGreatColumn", ObjKey());
2✔
5422
        CHECK(table->size() == 0);
2✔
5423
        // do not commit
1✔
5424
    }
2✔
5425
    {
2✔
5426
        auto tr = sg->start_write();
2✔
5427
        auto table = tr->get_table("myEmbeddedStuff");
2✔
5428
        auto parent = tr->get_table("myParentStuff");
2✔
5429
        CHECK(table->size() == 2);
2✔
5430
        auto first = parent->begin();
2✔
5431
        first->remove();
2✔
5432
        CHECK(table->size() == 0);
2✔
5433
        // do not commit
1✔
5434
    }
2✔
5435
    {
2✔
5436
        // Sync operations
1✔
5437
        auto tr = sg->start_write();
2✔
5438
        auto table = tr->get_table("myEmbeddedStuff");
2✔
5439
        auto parent = tr->get_table("myParentStuff");
2✔
5440
        CHECK(table->size() == 2);
2✔
5441
        auto first = parent->begin();
2✔
5442
        first->invalidate();
2✔
5443
        CHECK(table->size() == 0);
2✔
5444
        // do not commit
1✔
5445
    }
2✔
5446
}
2✔
5447

5448
TEST(Table_EmbeddedObjectCreateAndDestroyList)
5449
{
2✔
5450
    SHARED_GROUP_TEST_PATH(path);
2✔
5451

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

1✔
5455
    auto tr = sg->start_write();
2✔
5456
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5457
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
5458
    CHECK_THROW(table->create_object(), LogicError);
2✔
5459
    auto parent = tr->add_table("myParentStuff");
2✔
5460
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
5461
    Obj o = parent->create_object();
2✔
5462
    auto parent_ll = o.get_linklist(ck);
2✔
5463
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
5464
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
5465
    parent_ll.create_and_insert_linked_object(0);
2✔
5466
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
5467
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
5468
    o2_ll.create_and_insert_linked_object(0);
2✔
5469
    o2_ll.create_and_insert_linked_object(0);
2✔
5470
    o3_ll.create_and_insert_linked_object(0);
2✔
5471

1✔
5472
    tr->commit_and_continue_as_read();
2✔
5473
    tr->verify();
2✔
5474

1✔
5475
    tr->promote_to_write();
2✔
5476
    CHECK(table->size() == 6);
2✔
5477
    parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
5478
    CHECK(!o2.is_valid());
2✔
5479
    CHECK(table->size() == 4);
2✔
5480
    parent_ll.clear();
2✔
5481
    CHECK(table->size() == 0);
2✔
5482
    parent_ll.create_and_insert_linked_object(0);
2✔
5483
    parent_ll.create_and_insert_linked_object(1);
2✔
5484
    CHECK(table->size() == 2);
2✔
5485
    o.remove();
2✔
5486
    CHECK(table->size() == 0);
2✔
5487
    tr->commit();
2✔
5488
}
2✔
5489

5490
TEST(Table_EmbeddedObjectCreateAndDestroyDictionary)
5491
{
2✔
5492
    SHARED_GROUP_TEST_PATH(path);
2✔
5493

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

1✔
5497
    auto tr = sg->start_write();
2✔
5498
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5499
    auto col_recurse = table->add_column_dictionary(*table, "theRecursiveBit");
2✔
5500
    CHECK_THROW(table->create_object(), LogicError);
2✔
5501
    auto parent = tr->add_table("myParentStuff");
2✔
5502
    auto ck = parent->add_column_dictionary(*table, "theGreatColumn");
2✔
5503
    Obj o = parent->create_object();
2✔
5504
    auto parent_dict = o.get_dictionary(ck);
2✔
5505
    Obj o2 = parent_dict.create_and_insert_linked_object("one");
2✔
5506

1✔
5507
    auto obj_path = o2.get_path();
2✔
5508
    CHECK_EQUAL(obj_path.path_from_top.size(), 1);
2✔
5509
    CHECK_EQUAL(obj_path.path_from_top[0].col_key, ck);
2✔
5510
    CHECK_EQUAL(obj_path.path_from_top[0].index.get_string(), "one");
2✔
5511

1✔
5512
    Obj o3 = parent_dict.create_and_insert_linked_object("two");
2✔
5513
    parent_dict.create_and_insert_linked_object("three");
2✔
5514

1✔
5515
    CHECK_EQUAL(parent_dict.get_object("one").get_key(), o2.get_key());
2✔
5516

1✔
5517
    auto o2_dict = o2.get_dictionary(col_recurse);
2✔
5518
    auto o3_dict = o3.get_dictionary(col_recurse);
2✔
5519
    o2_dict.create_and_insert_linked_object("foo1");
2✔
5520
    o2_dict.create_and_insert_linked_object("foo2");
2✔
5521
    o3_dict.create_and_insert_linked_object("foo3");
2✔
5522

1✔
5523
    obj_path = o2_dict.get_object("foo1").get_path();
2✔
5524
    CHECK_EQUAL(obj_path.path_from_top.size(), 2);
2✔
5525
    CHECK_EQUAL(obj_path.path_from_top[0].index.get_string(), "one");
2✔
5526
    CHECK_EQUAL(obj_path.path_from_top[0].col_key, ck);
2✔
5527
    CHECK_EQUAL(obj_path.path_from_top[1].index.get_string(), "foo1");
2✔
5528
    CHECK_EQUAL(obj_path.path_from_top[1].col_key, col_recurse);
2✔
5529

1✔
5530
    tr->commit_and_continue_as_read();
2✔
5531
    tr->verify();
2✔
5532

1✔
5533
    tr->promote_to_write();
2✔
5534
    CHECK(table->size() == 6);
2✔
5535
    parent_dict.create_and_insert_linked_object("one"); // implicitly remove entry for 02
2✔
5536
    CHECK(!o2.is_valid());
2✔
5537
    CHECK_EQUAL(table->size(), 4);
2✔
5538
    parent_dict.clear();
2✔
5539
    CHECK(table->size() == 0);
2✔
5540
    parent_dict.create_and_insert_linked_object("four");
2✔
5541
    parent_dict.create_and_insert_linked_object("five");
2✔
5542
    CHECK(table->size() == 2);
2✔
5543
    o.remove();
2✔
5544
    CHECK(table->size() == 0);
2✔
5545
    tr->commit();
2✔
5546
}
2✔
5547

5548
TEST(Table_EmbeddedObjectNotifications)
5549
{
2✔
5550
    SHARED_GROUP_TEST_PATH(path);
2✔
5551

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

1✔
5555
    auto tr = sg->start_write();
2✔
5556
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5557
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
5558
    CHECK_THROW(table->create_object(), LogicError);
2✔
5559
    auto parent = tr->add_table("myParentStuff");
2✔
5560
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
5561
    Obj o = parent->create_object();
2✔
5562
    auto parent_ll = o.get_linklist(ck);
2✔
5563
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
5564
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
5565
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
5566
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
5567
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
5568
    o2_ll.create_and_insert_linked_object(0);
2✔
5569
    o2_ll.create_and_insert_linked_object(0);
2✔
5570
    o3_ll.create_and_insert_linked_object(0);
2✔
5571
    CHECK(table->size() == 6);
2✔
5572
    Obj o5 = parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
5573
    CHECK(!o2.is_valid());
2✔
5574
    CHECK(table->size() == 4);
2✔
5575
    // now the notifications...
1✔
5576
    int calls = 0;
2✔
5577
    tr->set_cascade_notification_handler([&](const Group::CascadeNotification& notification) {
6✔
5578
        CHECK_EQUAL(0, notification.links.size());
6✔
5579
        if (calls == 0) {
6✔
5580
            CHECK_EQUAL(1, notification.rows.size());
2✔
5581
            CHECK_EQUAL(parent->get_key(), notification.rows[0].table_key);
2✔
5582
            CHECK_EQUAL(o.get_key(), notification.rows[0].key);
2✔
5583
        }
2✔
5584
        else if (calls == 1) {
4✔
5585
            CHECK_EQUAL(3, notification.rows.size());
2✔
5586
            for (auto& row : notification.rows)
2✔
5587
                CHECK_EQUAL(table->get_key(), row.table_key);
6✔
5588
            CHECK_EQUAL(o4.get_key(), notification.rows[0].key);
2✔
5589
            CHECK_EQUAL(o5.get_key(), notification.rows[1].key);
2✔
5590
            CHECK_EQUAL(o3.get_key(), notification.rows[2].key);
2✔
5591
        }
2✔
5592
        else if (calls == 2) {
2✔
5593
            CHECK_EQUAL(1, notification.rows.size()); // from o3
2✔
5594
            for (auto& row : notification.rows)
2✔
5595
                CHECK_EQUAL(table->get_key(), row.table_key);
2✔
5596
            // don't bother checking the keys...
1✔
5597
        }
2✔
5598
        ++calls;
6✔
5599
    });
6✔
5600

1✔
5601
    o.remove();
2✔
5602
    CHECK(calls == 3);
2✔
5603
    tr->commit();
2✔
5604
}
2✔
5605
TEST(Table_EmbeddedObjectTableClearNotifications)
5606
{
2✔
5607
    SHARED_GROUP_TEST_PATH(path);
2✔
5608

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

1✔
5612
    auto tr = sg->start_write();
2✔
5613
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5614
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
5615
    CHECK_THROW(table->create_object(), LogicError);
2✔
5616
    auto parent = tr->add_table("myParentStuff");
2✔
5617
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
5618
    Obj o = parent->create_object();
2✔
5619
    auto parent_ll = o.get_linklist(ck);
2✔
5620
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
5621
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
5622
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
5623
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
5624
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
5625
    o2_ll.create_and_insert_linked_object(0);
2✔
5626
    o2_ll.create_and_insert_linked_object(0);
2✔
5627
    o3_ll.create_and_insert_linked_object(0);
2✔
5628
    CHECK(table->size() == 6);
2✔
5629
    Obj o5 = parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
5630
    CHECK(!o2.is_valid());
2✔
5631
    CHECK(table->size() == 4);
2✔
5632
    // now the notifications...
1✔
5633
    int calls = 0;
2✔
5634
    tr->set_cascade_notification_handler([&](const Group::CascadeNotification& notification) {
4✔
5635
        if (calls == 0) {
4✔
5636
            CHECK_EQUAL(3, notification.rows.size());
2✔
5637
            for (auto& row : notification.rows)
2✔
5638
                CHECK_EQUAL(table->get_key(), row.table_key);
6✔
5639
            CHECK_EQUAL(o4.get_key(), notification.rows[0].key);
2✔
5640
            CHECK_EQUAL(o5.get_key(), notification.rows[1].key);
2✔
5641
            CHECK_EQUAL(o3.get_key(), notification.rows[2].key);
2✔
5642
        }
2✔
5643
        else if (calls == 1) {
2✔
5644
            CHECK_EQUAL(1, notification.rows.size()); // from o3
2✔
5645
            for (auto& row : notification.rows)
2✔
5646
                CHECK_EQUAL(table->get_key(), row.table_key);
2✔
5647
            // don't bother checking the keys...
1✔
5648
        }
2✔
5649
        ++calls;
4✔
5650
    });
4✔
5651

1✔
5652
    parent->clear();
2✔
5653
    CHECK(calls == 2);
2✔
5654
    CHECK_EQUAL(parent->size(), 0);
2✔
5655
    tr->commit();
2✔
5656
}
2✔
5657

5658
TEST(Table_EmbeddedObjectPath)
5659
{
2✔
5660
    auto collect_path = [](const Obj& o) {
10✔
5661
        return o.get_fat_path();
10✔
5662
    };
10✔
5663

1✔
5664
    SHARED_GROUP_TEST_PATH(path);
2✔
5665

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

1✔
5669
    auto tr = sg->start_write();
2✔
5670
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5671
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
5672
    CHECK_THROW(table->create_object(), LogicError);
2✔
5673
    auto parent = tr->add_table("myParentStuff");
2✔
5674
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
5675
    Obj o = parent->create_object();
2✔
5676
    auto gch = collect_path(o);
2✔
5677
    CHECK(gch.size() == 0);
2✔
5678
    auto parent_ll = o.get_linklist(ck);
2✔
5679
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
5680
    auto gbh = collect_path(o2);
2✔
5681
    CHECK(gbh.size() == 1);
2✔
5682
    CHECK(gbh[0].obj.get_key() == o.get_key());
2✔
5683
    CHECK(gbh[0].col_key == ck);
2✔
5684
    CHECK(gbh[0].index == 0);
2✔
5685
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
5686
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
5687
    auto gah = collect_path(o4);
2✔
5688
    CHECK(gah.size() == 1);
2✔
5689
    CHECK(gah[0].obj.get_key() == o.get_key());
2✔
5690
    CHECK(gah[0].col_key == ck);
2✔
5691
    CHECK(gah[0].index == 0);
2✔
5692
    auto gzh = collect_path(o3);
2✔
5693
    CHECK(gzh.size() == 1);
2✔
5694
    CHECK(gzh[0].obj.get_key() == o.get_key());
2✔
5695
    CHECK(gzh[0].col_key == ck);
2✔
5696
    CHECK(gzh[0].index == 2);
2✔
5697
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
5698
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
5699
    o2_ll.create_and_insert_linked_object(0);
2✔
5700
    o2_ll.create_and_insert_linked_object(0);
2✔
5701
    o3_ll.create_and_insert_linked_object(0);
2✔
5702
    CHECK(table->size() == 6);
2✔
5703
    auto gyh = collect_path(o3_ll.get_object(0));
2✔
5704
    CHECK(gyh.size() == 2);
2✔
5705
    CHECK(gyh[0].obj.get_key() == o.get_key());
2✔
5706
    CHECK(gyh[0].col_key == ck);
2✔
5707
    CHECK(gyh[0].index == 2);
2✔
5708
    CHECK(gyh[1].obj.get_key() == o3.get_key());
2✔
5709
    CHECK(gyh[1].col_key = col_recurse);
2✔
5710
    CHECK(gyh[1].index == 0);
2✔
5711
}
2✔
5712

5713
TEST(Table_IndexOnMixed)
5714
{
2✔
5715
    Timestamp now{std::chrono::system_clock::now()};
2✔
5716
    Group g;
2✔
5717

1✔
5718
    auto bars = g.add_table("bar");
2✔
5719
    auto foos = g.add_table("foo");
2✔
5720
    auto col = foos->add_column(type_Mixed, "any");
2✔
5721
    foos->add_search_index(col);
2✔
5722

1✔
5723
    auto bar = bars->create_object();
2✔
5724

1✔
5725
    auto k0 = foos->create_object().set(col, Mixed()).get_key();
2✔
5726
    auto k1 = foos->create_object().set(col, Mixed(25)).get_key();
2✔
5727
    auto k2 = foos->create_object().set(col, Mixed(123.456f)).get_key();
2✔
5728
    auto k3 = foos->create_object().set(col, Mixed(987.654)).get_key();
2✔
5729
    auto k4 = foos->create_object().set(col, Mixed("Hello")).get_key();
2✔
5730
    auto k5 = foos->create_object().set(col, Mixed(now)).get_key();
2✔
5731
    auto k6 = foos->create_object().set(col, Mixed(Decimal128("2.25"))).get_key();
2✔
5732
    auto k7 = foos->create_object().set(col, Mixed(1)).get_key();
2✔
5733
    auto k8 = foos->create_object().set(col, Mixed(true)).get_key();
2✔
5734
    auto k9 = foos->create_object().set(col, Mixed(bar.get_link())).get_key();
2✔
5735
    auto k10 = foos->create_object().set(col, Mixed(UUID("3b241101-e2bb-4255-8caf-4136c566a962"))).get_key();
2✔
5736

1✔
5737
    CHECK_EQUAL(foos->find_first<Mixed>(col, {}), k0);
2✔
5738
    CHECK_EQUAL(foos->find_first<Mixed>(col, 25), k1);
2✔
5739
    CHECK_EQUAL(foos->find_first<Mixed>(col, 123.456f), k2);
2✔
5740
    CHECK_EQUAL(foos->find_first<Mixed>(col, 987.654), k3);
2✔
5741
    CHECK_EQUAL(foos->find_first<Mixed>(col, "Hello"), k4);
2✔
5742
    CHECK_EQUAL(foos->find_first<Mixed>(col, now), k5);
2✔
5743
    CHECK_EQUAL(foos->find_first<Mixed>(col, Decimal128("2.25")), k6);
2✔
5744
    CHECK_EQUAL(foos->find_first<Mixed>(col, 1), k7);
2✔
5745
    CHECK_EQUAL(foos->find_first<Mixed>(col, true), k8);
2✔
5746
    CHECK_EQUAL(foos->find_first<Mixed>(col, bar.get_link()), k9);
2✔
5747
    CHECK_EQUAL(foos->find_first<Mixed>(col, UUID("3b241101-e2bb-4255-8caf-4136c566a962")), k10);
2✔
5748

1✔
5749
    foos->remove_search_index(col);
2✔
5750

1✔
5751
    CHECK_EQUAL(foos->find_first<Mixed>(col, {}), k0);
2✔
5752
    CHECK_EQUAL(foos->find_first<Mixed>(col, 25), k1);
2✔
5753
    CHECK_EQUAL(foos->find_first<Mixed>(col, 123.456f), k2);
2✔
5754
    CHECK_EQUAL(foos->find_first<Mixed>(col, 987.654), k3);
2✔
5755
    CHECK_EQUAL(foos->find_first<Mixed>(col, "Hello"), k4);
2✔
5756
    CHECK_EQUAL(foos->find_first<Mixed>(col, now), k5);
2✔
5757
    CHECK_EQUAL(foos->find_first<Mixed>(col, Decimal128("2.25")), k6);
2✔
5758
    CHECK_EQUAL(foos->find_first<Mixed>(col, 1), k7);
2✔
5759
    CHECK_EQUAL(foos->find_first<Mixed>(col, true), k8);
2✔
5760
    CHECK_EQUAL(foos->find_first<Mixed>(col, bar.get_link()), k9);
2✔
5761
    CHECK_EQUAL(foos->find_first<Mixed>(col, UUID("3b241101-e2bb-4255-8caf-4136c566a962")), k10);
2✔
5762
}
2✔
5763

5764
TEST(Table_MixedNull)
5765
{
2✔
5766
    Group g;
2✔
5767
    auto foos = g.add_table("foo");
2✔
5768
    auto col = foos->add_column_list(type_Mixed, "any", true);
2✔
5769
    auto obj = foos->create_object();
2✔
5770
    auto list = obj.get_list<Mixed>(col);
2✔
5771
    list.add(Mixed());
2✔
5772
    list.set(0, Mixed(1));
2✔
5773
    list.set(0, Mixed());
2✔
5774
    list.remove(0);
2✔
5775
}
2✔
5776

5777
TEST(Table_InsertWithMixedLink)
5778
{
2✔
5779
    Group g;
2✔
5780
    TableRef dest = g.add_table_with_primary_key("dest", type_Int, "value");
2✔
5781
    TableRef source = g.add_table_with_primary_key("source", type_Int, "value");
2✔
5782
    ColKey mixed_col = source->add_column(type_Mixed, "mixed");
2✔
5783

1✔
5784
    Obj dest_obj = dest->create_object_with_primary_key(0);
2✔
5785

1✔
5786
    Mixed mixed_link = ObjLink{dest->get_key(), dest_obj.get_key()};
2✔
5787
    FieldValues values = {
2✔
5788
        {mixed_col, mixed_link},
2✔
5789
    };
2✔
5790
    source->create_object_with_primary_key(0, std::move(values));
2✔
5791

1✔
5792
    source->clear();
2✔
5793
    dest->clear();
2✔
5794
}
2✔
5795

5796
TEST(Table_SortEncrypted)
5797
{
2✔
5798
    SHARED_GROUP_TEST_PATH(path);
2✔
5799
    Random random(random_int<unsigned long>());
2✔
5800

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

1✔
5804
    auto wt = sg->start_write();
2✔
5805
    auto foos = wt->add_table("foo");
2✔
5806
    auto col_id = foos->add_column(type_String, "id");
2✔
5807
    auto col_b = foos->add_column(type_Bool, "b");
2✔
5808

1✔
5809
    for (int i = 0; i < 10000; i++) {
20,002✔
5810
        auto n = random.draw_int_max(10000);
20,000✔
5811
        foos->create_object().set(col_id, util::to_string(n));
20,000✔
5812
    }
20,000✔
5813
    wt->commit_and_continue_as_read();
2✔
5814
    auto q = foos->where();
2✔
5815
    DescriptorOrdering ordering;
2✔
5816
    ordering.append_sort(SortDescriptor({{col_b}, {col_id}}));
2✔
5817

1✔
5818
    // auto t1 = steady_clock::now();
1✔
5819

1✔
5820
    CALLGRIND_START_INSTRUMENTATION;
2✔
5821
    auto tv = q.find_all(ordering);
2✔
5822
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
5823

1✔
5824
    // auto t2 = steady_clock::now();
1✔
5825

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

5829
TEST(Table_RebuildTable)
5830
{
2✔
5831
    Group g;
2✔
5832
    auto t = g.add_table("foo");
2✔
5833
    auto id = t->add_column(type_Int, "id");
2✔
5834
    for (int64_t i = 1; i < 8; i++) {
16✔
5835
        t->create_object().set(id, i);
14✔
5836
    }
14✔
5837
    t->set_primary_key_column(id);
2✔
5838
}
2✔
5839

5840
TEST(Table_ListOfPrimitivesTransaction)
5841
{
2✔
5842
    SHARED_GROUP_TEST_PATH(path);
2✔
5843
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5844
    DBRef db = DB::create(*hist, path);
2✔
5845

1✔
5846
    auto tr = db->start_write();
2✔
5847
    TableRef t = tr->add_table("table");
2✔
5848
    ColKey int_col = t->add_column_list(type_Int, "integers");
2✔
5849
    ObjKeys keys;
2✔
5850
    t->create_objects(32, keys);
2✔
5851
    auto list = t->get_object(keys[7]).get_list<Int>(int_col);
2✔
5852
    list.add(7);
2✔
5853
    list.add(25);
2✔
5854
    list.add(42);
2✔
5855
    tr->commit_and_continue_as_read();
2✔
5856

1✔
5857
    tr->promote_to_write();
2✔
5858
    list.set(0, 5);
2✔
5859
    tr->commit_and_continue_as_read();
2✔
5860
    CHECK_EQUAL(list.get(0), 5);
2✔
5861
    tr->promote_to_write();
2✔
5862
    list.swap(0, 1);
2✔
5863
    tr->commit_and_continue_as_read();
2✔
5864
    CHECK_EQUAL(list.get(0), 25);
2✔
5865
    tr->promote_to_write();
2✔
5866
    list.move(1, 0);
2✔
5867
    tr->commit_and_continue_as_read();
2✔
5868
    CHECK_EQUAL(list.get(0), 5);
2✔
5869
    tr->promote_to_write();
2✔
5870
    list.remove(1);
2✔
5871
    tr->commit_and_continue_as_read();
2✔
5872
    CHECK_EQUAL(list.get(1), 42);
2✔
5873
    tr->promote_to_write();
2✔
5874
    list.clear();
2✔
5875
    tr->commit_and_continue_as_read();
2✔
5876
    CHECK_EQUAL(list.size(), 0);
2✔
5877
}
2✔
5878

5879
TEST(Table_AsymmetricObjects)
5880
{
2✔
5881
    SHARED_GROUP_TEST_PATH(path);
2✔
5882

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

1✔
5886
    auto tr = sg->start_write();
2✔
5887
    auto table = tr->add_table("mytable", Table::Type::TopLevelAsymmetric);
2✔
5888
    tr->commit_and_continue_as_read();
2✔
5889
    tr->promote_to_write();
2✔
5890
    CHECK(table->is_asymmetric());
2✔
5891
    table->create_object();
2✔
5892
    tr->commit();
2✔
5893

1✔
5894
    tr = sg->start_read();
2✔
5895
    table = tr->get_table("mytable");
2✔
5896
    CHECK(table->is_asymmetric());
2✔
5897

1✔
5898
    tr = sg->start_write();
2✔
5899
    auto table2 = tr->add_table("target table");
2✔
5900
    table = tr->get_table("mytable");
2✔
5901
    // Outgoing link from asymmetric object is allowed.
1✔
5902
    CHECK_NOTHROW(table->add_column(*table2, "link"));
2✔
5903
    // Incoming link to asymmetric object is not allowed.
1✔
5904
    CHECK_THROW(table2->add_column(*table, "link"), LogicError);
2✔
5905
    tr->commit();
2✔
5906
}
2✔
5907

5908
TEST(Table_FullTextIndex)
5909
{
2✔
5910
    SHARED_GROUP_TEST_PATH(path);
2✔
5911
    auto db = DB::create(path);
2✔
5912
    ColKey col;
2✔
5913

1✔
5914
    {
2✔
5915
        auto wt = db->start_write();
2✔
5916

1✔
5917
        auto t = wt->add_table("foo");
2✔
5918
        col = t->add_column(type_String, "str");
2✔
5919
        t->add_fulltext_index(col);
2✔
5920
        auto index = t->get_search_index(col);
2✔
5921
        CHECK(index->is_fulltext_index());
2✔
5922

1✔
5923
        t->create_object().set(col, "This is a test, with  spaces!");
2✔
5924
        t->create_object().set(col, "More testing, with normal spaces");
2✔
5925
        t->create_object().set(col, "ål, ø og æbler");
2✔
5926

1✔
5927
        wt->commit();
2✔
5928
    }
2✔
5929

1✔
5930
    auto rt = db->start_read();
2✔
5931
    auto t = rt->get_table("foo");
2✔
5932
    auto index = t->get_search_index(col);
2✔
5933
    CHECK(index->is_fulltext_index());
2✔
5934
    TableView res = t->find_all_fulltext(col, "spaces with");
2✔
5935
    CHECK_EQUAL(2, res.size());
2✔
5936
}
2✔
5937

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