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

realm / realm-core / daniel.tabacaru_937

27 Sep 2024 06:53AM UTC coverage: 91.124% (+0.02%) from 91.109%
daniel.tabacaru_937

Pull #7983

Evergreen

danieltabacaru
Small refactoring
Pull Request #7983: RCORE-2126 Clear incomplete bootstraps when the connection is established

102826 of 181492 branches covered (56.66%)

49 of 50 new or added lines in 4 files covered. (98.0%)

67 existing lines in 16 files now uncovered.

217244 of 238404 relevant lines covered (91.12%)

5968864.31 hits per line

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

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

19
#include "testsettings.hpp"
20
#ifdef TEST_TABLE
21

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

31
using namespace std::chrono;
32

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

42
#include "util/misc.hpp"
43

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

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

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

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

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

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

93
extern unsigned int unit_test_random_seed;
94

95
namespace {
96

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

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

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

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

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

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

170
            if (m_throw_on_null) {
213✔
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
213✔
175
                return StringData("", 0);
213✔
176
        }
213✔
177
        const char* p = from_value.data();
7,675✔
178
        const char* limit = p + from_value.size();
7,675✔
179
        data.clear();
7,675✔
180
        data.reserve(from_value.size());
7,675✔
181
        while (p != limit)
245,600✔
182
            data.push_back(*p++);
237,925✔
183
        return StringData(&data[0], from_value.size());
7,675✔
184
    }
8,080✔
185
};
186

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

201
            if (m_throw_on_null) {
192✔
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
192✔
206
                return BinaryData("", 0);
192✔
207
        }
192✔
208
        const char* p = from_value.data();
7,676✔
209
        const char* limit = p + from_value.size();
7,676✔
210
        data.clear();
7,676✔
211
        data.reserve(from_value.size());
7,676✔
212
        while (p != limit)
245,632✔
213
            data.push_back(*p++);
237,956✔
214
        return BinaryData(&data[0], from_value.size());
7,676✔
215
    }
8,080✔
216
};
217

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

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

241
TEST(Table_Null)
242
{
2✔
243
    {
2✔
244
        // Check that add_empty_row() adds NULL string as default
245
        Group group;
2✔
246
        TableRef table = group.add_table("test");
2✔
247

248
        table->add_column(type_String, "name", true); // nullable = true
2✔
249
        Obj obj = table->create_object();
2✔
250

251
        CHECK(obj.get<String>("name").is_null());
2✔
252
    }
2✔
253

254
    {
2✔
255
        // Check that add_empty_row() adds empty string as default
256
        Group group;
2✔
257
        TableRef table = group.add_table("test");
2✔
258

259
        auto col = table->add_column(type_String, "name");
2✔
260
        CHECK(!table->is_nullable(col));
2✔
261

262
        Obj obj = table->create_object();
2✔
263
        CHECK(!obj.get<String>(col).is_null());
2✔
264

265
        // Test that inserting null in non-nullable column will throw
266
        CHECK_LOGIC_ERROR(obj.set_null(col), ErrorCodes::PropertyNotNullable);
2✔
267
    }
2✔
268

269
    {
2✔
270
        // Check that add_empty_row() adds null integer as default
271
        Group group;
2✔
272
        TableRef table = group.add_table("table");
2✔
273
        auto col = table->add_column(type_Int, "age", true /*nullable*/);
2✔
274
        CHECK(table->is_nullable(col));
2✔
275

276
        Obj obj = table->create_object();
2✔
277
        CHECK(obj.is_null(col));
2✔
278

279
        // Check that you can obtain a non null value through get<Int>
280
        obj.set(col, 7);
2✔
281
        CHECK_NOT(obj.is_null(col));
2✔
282
        CHECK_EQUAL(obj.get<Int>(col), 7);
2✔
283
    }
2✔
284

285
    {
2✔
286
        // Check that add_empty_row() adds 0 integer as default.
287
        Group group;
2✔
288
        TableRef table = group.add_table("test");
2✔
289
        auto col = table->add_column(type_Int, "age");
2✔
290
        CHECK(!table->is_nullable(col));
2✔
291

292
        Obj obj = table->create_object();
2✔
293
        CHECK(!obj.is_null(col));
2✔
294
        CHECK_EQUAL(0, obj.get<Int>(col));
2✔
295

296
        // Check that inserting null in non-nullable column will throw
297
        CHECK_LOGIC_ERROR(obj.set_null(col), ErrorCodes::PropertyNotNullable);
2✔
298
    }
2✔
299

300
    {
2✔
301
        // Check that add_empty_row() adds NULL binary as default
302
        Group group;
2✔
303
        TableRef table = group.add_table("test");
2✔
304

305
        auto col = table->add_column(type_Binary, "bin", true /*nullable*/);
2✔
306
        CHECK(table->is_nullable(col));
2✔
307

308
        Obj obj = table->create_object();
2✔
309
        CHECK(obj.is_null(col));
2✔
310
    }
2✔
311

312
    {
2✔
313
        // Check that add_empty_row() adds empty binary as default
314
        Group group;
2✔
315
        TableRef table = group.add_table("test");
2✔
316

317
        auto col = table->add_column(type_Binary, "name");
2✔
318
        CHECK(!table->is_nullable(col));
2✔
319

320
        Obj obj = table->create_object();
2✔
321
        CHECK(!obj.get<Binary>(col).is_null());
2✔
322

323
        // Test that inserting null in non-nullable column will throw
324
        CHECK_THROW_ANY(obj.set_null(col));
2✔
325
    }
2✔
326

327
    {
2✔
328
        // Check that link columns are nullable.
329
        Group group;
2✔
330
        TableRef target = group.add_table("target");
2✔
331
        TableRef table = group.add_table("table");
2✔
332

333
        auto col_int = target->add_column(type_Int, "int");
2✔
334
        auto col_link = table->add_column(*target, "link");
2✔
335
        CHECK(table->is_nullable(col_link));
2✔
336
        CHECK(!target->is_nullable(col_int));
2✔
337
    }
2✔
338

339
    {
2✔
340
        // Check that linklist columns are not nullable.
341
        Group group;
2✔
342
        TableRef target = group.add_table("target");
2✔
343
        TableRef table = group.add_table("table");
2✔
344

345
        auto col_int = target->add_column(type_Int, "int");
2✔
346
        auto col_link = table->add_column_list(*target, "link");
2✔
347
        CHECK(!table->is_nullable(col_link));
2✔
348
        CHECK(!target->is_nullable(col_int));
2✔
349
    }
2✔
350
}
2✔
351

352
TEST(Table_DeleteCrash)
353
{
2✔
354
    Group group;
2✔
355
    TableRef table = group.add_table("test");
2✔
356

357
    table->add_column(type_String, "name");
2✔
358
    table->add_column(type_Int, "age");
2✔
359

360
    ObjKey k0 = table->create_object().set_all("Alice", 17).get_key();
2✔
361
    ObjKey k1 = table->create_object().set_all("Bob", 50).get_key();
2✔
362
    table->create_object().set_all("Peter", 44);
2✔
363

364
    table->remove_object(k0);
2✔
365

366
    table->remove_object(k1);
2✔
367
}
2✔
368

369
TEST(Table_OptimizeCrash)
370
{
2✔
371
    // This will crash at the .add() method
372
    Table ttt;
2✔
373
    ttt.add_column(type_Int, "first");
2✔
374
    auto col = ttt.add_column(type_String, "second");
2✔
375
    ttt.enumerate_string_column(col);
2✔
376
    ttt.add_search_index(col);
2✔
377
    ttt.clear();
2✔
378
    ttt.create_object().set_all(1, "AA");
2✔
379
}
2✔
380

381
TEST(Table_DateTimeMinMax)
382
{
2✔
383
    Group g;
2✔
384
    TableRef table = g.add_table("test_table");
2✔
385

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

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

392
    std::vector<Obj> objs(3);
2✔
393
    objs[0] = table->create_object();
2✔
394
    objs[1] = table->create_object();
2✔
395
    objs[2] = table->create_object();
2✔
396

397
    objs[0].set_null(col);
2✔
398
    objs[1].set(col, Timestamp{0, 0});
2✔
399
    objs[2].set(col, Timestamp{2, 2});
2✔
400

401
    CHECK_EQUAL(table->max(col)->get_timestamp(), Timestamp(2, 2));
2✔
402
    CHECK_EQUAL(table->min(col)->get_timestamp(), Timestamp(0, 0));
2✔
403

404
    objs[0].set(col, Timestamp{0, 0});
2✔
405
    objs[1].set_null(col);
2✔
406
    objs[2].set(col, Timestamp{2, 2});
2✔
407

408
    ObjKey idx; // tableview entry that points at the max/min value
2✔
409

410
    CHECK_EQUAL(table->max(col, &idx)->get_timestamp(), Timestamp(2, 2));
2✔
411
    CHECK_EQUAL(idx, objs[2].get_key());
2✔
412
    CHECK_EQUAL(table->min(col, &idx)->get_timestamp(), Timestamp(0, 0));
2✔
413
    CHECK_EQUAL(idx, objs[0].get_key());
2✔
414

415
    objs[0].set_null(col);
2✔
416
    objs[1].set(col, Timestamp{2, 2});
2✔
417
    objs[2].set(col, Timestamp{0, 0});
2✔
418

419
    CHECK_EQUAL(table->max(col)->get_timestamp(), Timestamp(2, 2));
2✔
420
    CHECK_EQUAL(table->min(col)->get_timestamp(), Timestamp(0, 0));
2✔
421

422
    objs[0].set(col, Timestamp{2, 2});
2✔
423
    objs[1].set_null(col);
2✔
424
    objs[2].set(col, Timestamp{0, 0});
2✔
425

426
    CHECK_EQUAL(table->max(col, &idx)->get_timestamp(), Timestamp(2, 2));
2✔
427
    CHECK_EQUAL(idx, objs[0].get_key());
2✔
428
    CHECK_EQUAL(table->min(col, &idx)->get_timestamp(), Timestamp(0, 0));
2✔
429
    CHECK_EQUAL(idx, objs[2].get_key());
2✔
430
}
2✔
431

432

433
TEST(Table_MinMaxSingleNullRow)
434
{
2✔
435
    // To illustrate/document behaviour
436
    Group g;
2✔
437
    TableRef table = g.add_table("test_table");
2✔
438

439
    auto date_col = table->add_column(type_Timestamp, "time", true);
2✔
440
    auto int_col = table->add_column(type_Int, "int", true);
2✔
441
    auto float_col = table->add_column(type_Float, "float", true);
2✔
442
    table->create_object();
2✔
443

444
    ObjKey key;
2✔
445

446
    // Maximum
447
    {
2✔
448
        table->max(date_col, &key); // max on table
2✔
449
        CHECK(key == null_key);
2✔
450
        table->where().find_all().max(date_col, &key); // max on tableview
2✔
451
        CHECK(key == null_key);
2✔
452
        table->where().max(date_col, &key); // max on query
2✔
453
        CHECK(key == null_key);
2✔
454

455
        table->max(int_col, &key); // max on table
2✔
456
        CHECK(key == null_key);
2✔
457
        table->where().find_all().max(int_col, &key); // max on tableview
2✔
458
        CHECK(key == null_key);
2✔
459
        table->where().max(int_col, &key); // max on query
2✔
460
        CHECK(key == null_key);
2✔
461

462
        table->max(float_col, &key); // max on table
2✔
463
        CHECK(key == null_key);
2✔
464
        table->where().find_all().max(float_col, &key); // max on tableview
2✔
465
        CHECK(key == null_key);
2✔
466
        table->where().max(float_col, &key); // max on query
2✔
467
        CHECK(key == null_key);
2✔
468

469
        table->create_object();
2✔
470

471
        CHECK(table->max(date_col)->is_null());        // max on table
2✔
472
        table->where().find_all().max(date_col, &key); // max on tableview
2✔
473
        CHECK(key == null_key);
2✔
474
        table->where().max(date_col, &key); // max on query
2✔
475
        CHECK(key == null_key);
2✔
476
    }
2✔
477

478
    // Minimum
479
    {
2✔
480
        table->min(date_col, &key); // max on table
2✔
481
        CHECK(key == null_key);
2✔
482
        table->where().find_all().min(date_col, &key); // max on tableview
2✔
483
        CHECK(key == null_key);
2✔
484
        table->where().min(date_col, &key); // max on query
2✔
485
        CHECK(key == null_key);
2✔
486

487
        table->min(int_col, &key); // max on table
2✔
488
        CHECK(key == null_key);
2✔
489
        table->where().find_all().min(int_col, &key); // max on tableview
2✔
490
        CHECK(key == null_key);
2✔
491
        table->where().min(int_col, &key); // max on query
2✔
492
        CHECK(key == null_key);
2✔
493

494
        table->min(float_col, &key); // max on table
2✔
495
        CHECK(key == null_key);
2✔
496
        table->where().find_all().min(float_col, &key); // max on tableview
2✔
497
        CHECK(key == null_key);
2✔
498
        table->where().min(float_col, &key); // max on query
2✔
499
        CHECK(key == null_key);
2✔
500

501
        table->create_object();
2✔
502

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

511

512
TEST(TableView_AggregateBugs)
513
{
2✔
514
    // Tests against various aggregate bugs on TableViews: https://github.com/realm/realm-core/pull/2360
515
    {
2✔
516
        Table table;
2✔
517
        auto int_col = table.add_column(type_Int, "ints", true);
2✔
518
        auto double_col = table.add_column(type_Double, "doubles", true);
2✔
519

520
        table.create_object().set_all(1, 1.);
2✔
521
        table.create_object().set_all(2, 2.);
2✔
522
        table.create_object();
2✔
523
        table.create_object().set_all(42, 42.);
2✔
524

525
        auto tv = table.where().not_equal(int_col, 42).find_all();
2✔
526
        CHECK_EQUAL(tv.size(), 3);
2✔
527
        CHECK_EQUAL(tv.max(int_col), 2);
2✔
528

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

534
        // There are currently 3 ways of doing average: on tableview, table and query:
535
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().avg(int_col, &vc)->get_double());
2✔
536
        CHECK_EQUAL(vc, 3);
2✔
537
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().find_all().avg(int_col, &vc)->get_double());
2✔
538
        CHECK_EQUAL(vc, 3);
2✔
539

540
        // Core has an optimization where it executes average directly on the column if there
541
        // are no query conditions. Bypass that here.
542
        CHECK_APPROXIMATELY_EQUAL(table.where().not_equal(int_col, 1).find_all().avg(int_col, &vc)->get_double(),
2✔
543
                                  double(2 + 42) / 2, 0.001);
2✔
544
        CHECK_EQUAL(vc, 2);
2✔
545

546
        // Now doubles
547
        tv = table.where().not_equal(double_col, 42.).find_all();
2✔
548
        CHECK_EQUAL(tv.size(), 3);
2✔
549
        CHECK_EQUAL(tv.max(double_col), 2.);
2✔
550

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

555
        // There are currently 3 ways of doing average: on tableview, table and query:
556
        CHECK_APPROXIMATELY_EQUAL(table.avg(double_col)->get_double(),
2✔
557
                                  table.where().avg(double_col, &vc)->get_double(), 0.001);
2✔
558
        CHECK_EQUAL(vc, 3);
2✔
559

560
        CHECK_APPROXIMATELY_EQUAL(table.avg(double_col)->get_double(),
2✔
561
                                  table.where().find_all().avg(double_col, &vc)->get_double(), 0.001);
2✔
562
        CHECK_EQUAL(vc, 3);
2✔
563

564
        // Core has an optimization where it executes average directly on the column if there
565
        // are no query conditions. Bypass that here.
566
        CHECK_APPROXIMATELY_EQUAL(
2✔
567
            table.where().not_equal(double_col, 1.).find_all().avg(double_col, &vc)->get_double(), (2. + 42.) / 2,
2✔
568
            0.001);
2✔
569
        CHECK_EQUAL(vc, 2);
2✔
570
    }
2✔
571

572
    // Same as above, with null entry first
573
    {
2✔
574
        Table table;
2✔
575
        auto int_col = table.add_column(type_Int, "ints", true);
2✔
576

577
        table.create_object();
2✔
578
        table.create_object().set_all(1);
2✔
579
        table.create_object().set_all(2);
2✔
580
        table.create_object().set_all(42);
2✔
581

582
        auto tv = table.where().not_equal(int_col, 42).find_all();
2✔
583
        CHECK_EQUAL(tv.size(), 3);
2✔
584
        CHECK_EQUAL(tv.max(int_col), 2);
2✔
585

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

589
        // There are currently 3 ways of doing average: on tableview, table and query:
590
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().avg(int_col)->get_double());
2✔
591
        CHECK_EQUAL(table.avg(int_col)->get_double(), table.where().find_all().avg(int_col)->get_double());
2✔
592

593
        // Core has an optimization where it executes average directly on the column if there
594
        // are no query conditions. Bypass that here.
595
        CHECK_APPROXIMATELY_EQUAL(table.where().not_equal(int_col, 1).find_all().avg(int_col)->get_double(),
2✔
596
                                  double(2 + 42) / 2, 0.001);
2✔
597
    }
2✔
598
}
2✔
599

600

601
TEST(Table_AggregateFuzz)
602
{
2✔
603
    // Tests sum, avg, min, max on Table, TableView, Query, for types float, Timestamp, int
604
    for (int iter = 0; iter < 50 + 1000 * TEST_DURATION; iter++) {
102✔
605
        Group g;
100✔
606
        TableRef table = g.add_table("test_table");
100✔
607

608
        auto date_col = table->add_column(type_Timestamp, "time", true);
100✔
609
        auto int_col = table->add_column(type_Int, "int", true);
100✔
610
        auto float_col = table->add_column(type_Float, "float", true);
100✔
611

612
        size_t rows = size_t(fastrand(10));
100✔
613
        std::vector<ObjKey> keys;
100✔
614
        table->create_objects(rows, keys);
100✔
615
        int64_t largest = 0;
100✔
616
        int64_t smallest = 0;
100✔
617
        ObjKey largest_pos = null_key;
100✔
618
        ObjKey smallest_pos = null_key;
100✔
619

620
        double avg = 0;
100✔
621
        int64_t sum = 0;
100✔
622
        size_t nulls = 0;
100✔
623

624
        // Create some rows with values and some rows with just nulls
625
        for (size_t t = 0; t < rows; t++) {
596✔
626
            bool null = (fastrand(1) == 0);
496✔
627
            if (!null) {
496✔
628
                int64_t value = fastrand(10);
254✔
629
                sum += value;
254✔
630
                if (largest_pos == null_key || value > largest) {
254✔
631
                    largest = value;
144✔
632
                    largest_pos = keys[t];
144✔
633
                }
144✔
634
                if (smallest_pos == null_key || value < smallest) {
254✔
635
                    smallest = value;
129✔
636
                    smallest_pos = keys[t];
129✔
637
                }
129✔
638
                table->get_object(keys[t]).set_all(Timestamp(value, 0), value, float(value));
254✔
639
            }
254✔
640
            else {
242✔
641
                nulls++;
242✔
642
            }
242✔
643
        }
496✔
644

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

647
        ObjKey key;
100✔
648
        size_t cnt;
100✔
649
        int64_t i;
100✔
650
        Mixed m;
100✔
651

652
        // Test methods on Table
653
        {
100✔
654
            // Table::max
655
            key = ObjKey(123);
100✔
656
            m = *table->max(float_col, &key);
100✔
657
            CHECK_EQUAL(key, largest_pos);
100✔
658
            if (largest_pos != null_key)
100✔
659
                CHECK_EQUAL(m.get_float(), table->get_object(largest_pos).get<float>(float_col));
87✔
660

661
            key = ObjKey(123);
100✔
662
            m = *table->max(int_col, &key);
100✔
663
            CHECK_EQUAL(key, largest_pos);
100✔
664
            if (largest_pos != null_key)
100✔
665
                CHECK_EQUAL(m.get_int(), table->get_object(largest_pos).get<util::Optional<Int>>(int_col));
87✔
666

667
            key = ObjKey(123);
100✔
668
            m = *table->max(date_col, &key);
100✔
669
            CHECK_EQUAL(key, largest_pos);
100✔
670
            if (largest_pos != null_key)
100✔
671
                CHECK_EQUAL(m.get_timestamp(), table->get_object(largest_pos).get<Timestamp>(date_col));
87✔
672

673
            // Table::min
674
            key = ObjKey(123);
100✔
675
            m = *table->min(float_col, &key);
100✔
676
            CHECK_EQUAL(key, smallest_pos);
100✔
677
            if (smallest_pos != null_key)
100✔
678
                CHECK_EQUAL(m.get_float(), table->get_object(smallest_pos).get<float>(float_col));
87✔
679

680
            key = ObjKey(123);
100✔
681
            m = *table->min(int_col, &key);
100✔
682
            CHECK_EQUAL(key, smallest_pos);
100✔
683
            if (smallest_pos != null_key)
100✔
684
                CHECK_EQUAL(m.get_int(), table->get_object(smallest_pos).get<util::Optional<Int>>(int_col));
87✔
685

686
            key = ObjKey(123);
100✔
687
            m = *table->min(date_col, &key);
100✔
688
            CHECK_EQUAL(key, smallest_pos);
100✔
689
            if (smallest_pos != null_key)
100✔
690
                CHECK_EQUAL(m.get_timestamp(), table->get_object(smallest_pos).get<Timestamp>(date_col));
87✔
691

692
            // Table::avg
693
            double d;
100✔
694

695
            // number of non-null values used in computing the avg or sum
696
            cnt = 123;
100✔
697

698
            // Table::avg
699
            m = *table->avg(float_col, &cnt);
100✔
700
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
701
            if (cnt != 0)
100✔
702
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
87✔
703

704
            cnt = 123;
100✔
705
            m = *table->avg(int_col, &cnt);
100✔
706
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
707
            if (cnt != 0)
100✔
708
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
87✔
709

710
            // Table::sum
711
            d = table->sum(float_col)->get_double();
100✔
712
            CHECK_APPROXIMATELY_EQUAL(d, double(sum), 0.001);
100✔
713

714
            i = table->sum(int_col)->get_int();
100✔
715
            CHECK_EQUAL(i, sum);
100✔
716
        }
100✔
717

718
        // Test methods on TableView
719
        {
100✔
720
            // TableView::max
721
            key = ObjKey(123);
100✔
722
            m = *table->where().find_all().max(float_col, &key);
100✔
723
            CHECK_EQUAL(key, largest_pos);
100✔
724
            if (largest_pos != null_key)
100✔
725
                CHECK_EQUAL(m, table->get_object(largest_pos).get<float>(float_col));
87✔
726

727
            key = ObjKey(123);
100✔
728
            m = *table->where().find_all().max(int_col, &key);
100✔
729
            CHECK_EQUAL(key, largest_pos);
100✔
730
            if (largest_pos != null_key)
100✔
731
                CHECK_EQUAL(m, table->get_object(largest_pos).get<util::Optional<Int>>(int_col));
87✔
732

733
            key = ObjKey(123);
100✔
734
            m = *table->where().find_all().max(date_col, &key);
100✔
735
            CHECK_EQUAL(key, largest_pos);
100✔
736
            if (largest_pos != null_key)
100✔
737
                CHECK_EQUAL(m, table->get_object(largest_pos).get<Timestamp>(date_col));
87✔
738

739
            // TableView::min
740
            key = ObjKey(123);
100✔
741
            m = *table->where().find_all().min(float_col, &key);
100✔
742
            CHECK_EQUAL(key, smallest_pos);
100✔
743
            if (smallest_pos != null_key)
100✔
744
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<float>(float_col));
87✔
745

746
            key = ObjKey(123);
100✔
747
            m = *table->where().find_all().min(int_col, &key);
100✔
748
            CHECK_EQUAL(key, smallest_pos);
100✔
749
            if (smallest_pos != null_key)
100✔
750
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<util::Optional<Int>>(int_col));
87✔
751

752
            key = ObjKey(123);
100✔
753
            m = *table->where().find_all().min(date_col, &key);
100✔
754
            CHECK_EQUAL(key, smallest_pos);
100✔
755
            if (smallest_pos != null_key)
100✔
756
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<Timestamp>(date_col));
87✔
757

758
            // TableView::avg
759
            double d;
100✔
760

761
            // number of non-null values used in computing the avg or sum
762
            key = ObjKey(123);
100✔
763

764
            // TableView::avg
765
            m = *table->where().find_all().avg(float_col, &cnt);
100✔
766
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
767
            if (cnt != 0)
100✔
768
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
87✔
769

770
            cnt = 123;
100✔
771
            m = *table->where().find_all().avg(int_col, &cnt);
100✔
772
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
773
            if (cnt != 0)
100✔
774
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
87✔
775

776
            // TableView::sum
777
            d = table->where().find_all().sum(float_col)->get_double();
100✔
778
            CHECK_APPROXIMATELY_EQUAL(d, double(sum), 0.001);
100✔
779

780
            i = table->where().find_all().sum(int_col)->get_int();
100✔
781
            CHECK_EQUAL(i, sum);
100✔
782
        }
100✔
783

784
        // Test methods on Query
785
        {
100✔
786
            // TableView::max
787
            key = ObjKey(123);
100✔
788
            m = *table->where().max(float_col, &key);
100✔
789
            CHECK_EQUAL(key, largest_pos);
100✔
790
            if (largest_pos != null_key)
100✔
791
                CHECK_EQUAL(m, table->get_object(largest_pos).get<float>(float_col));
87✔
792

793
            key = ObjKey(123);
100✔
794
            m = *table->where().max(int_col, &key);
100✔
795
            CHECK_EQUAL(key, largest_pos);
100✔
796
            if (largest_pos != null_key)
100✔
797
                CHECK_EQUAL(m, table->get_object(largest_pos).get<util::Optional<Int>>(int_col));
87✔
798

799
            key = ObjKey(123);
100✔
800
            m = *table->where().max(date_col, &key);
100✔
801
            CHECK_EQUAL(key, largest_pos);
100✔
802
            if (largest_pos != null_key)
100✔
803
                CHECK_EQUAL(m, table->get_object(largest_pos).get<Timestamp>(date_col));
87✔
804

805
            // TableView::min
806
            key = ObjKey(123);
100✔
807
            m = *table->where().min(float_col, &key);
100✔
808
            CHECK_EQUAL(key, smallest_pos);
100✔
809
            if (smallest_pos != null_key)
100✔
810
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<float>(float_col));
87✔
811

812
            key = ObjKey(123);
100✔
813
            m = *table->where().min(int_col, &key);
100✔
814
            CHECK_EQUAL(key, smallest_pos);
100✔
815
            if (smallest_pos != null_key)
100✔
816
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<util::Optional<Int>>(int_col));
87✔
817

818
            key = ObjKey(123);
100✔
819
            m = *table->where().min(date_col, &key);
100✔
820
            CHECK_EQUAL(key, smallest_pos);
100✔
821
            if (smallest_pos != null_key)
100✔
822
                CHECK_EQUAL(m, table->get_object(smallest_pos).get<Timestamp>(date_col));
87✔
823

824
            // TableView::avg
825
            double d;
100✔
826

827
            // number of non-null values used in computing the avg or sum
828
            cnt = 123;
100✔
829

830
            // TableView::avg
831
            m = *table->where().avg(float_col, &cnt);
100✔
832
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
833
            if (cnt != 0)
100✔
834
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
87✔
835

836
            cnt = 123;
100✔
837
            m = *table->where().avg(int_col, &cnt);
100✔
838
            CHECK_EQUAL(cnt, (rows - nulls));
100✔
839
            if (cnt != 0)
100✔
840
                CHECK_APPROXIMATELY_EQUAL(m.get_double(), avg, 0.001);
87✔
841

842
            // TableView::sum
843
            d = table->where().sum(float_col)->get_double();
100✔
844
            CHECK_APPROXIMATELY_EQUAL(d, double(sum), 0.001);
100✔
845

846
            m = *table->where().sum(int_col);
100✔
847
            CHECK_EQUAL(m, sum);
100✔
848
        }
100✔
849
    }
100✔
850
}
2✔
851

852
TEST(Table_ColumnNameTooLong)
853
{
2✔
854
    Group group;
2✔
855
    TableRef table = group.add_table("foo");
2✔
856
    const size_t buf_size = 64;
2✔
857
    char buf[buf_size];
2✔
858
    memset(buf, 'A', buf_size);
2✔
859
    CHECK_LOGIC_ERROR(table->add_column(type_Int, StringData(buf, buf_size)), ErrorCodes::InvalidName);
2✔
860
    CHECK_LOGIC_ERROR(table->add_column_list(type_Int, StringData(buf, buf_size)), ErrorCodes::InvalidName);
2✔
861
    CHECK_LOGIC_ERROR(table->add_column(*table, StringData(buf, buf_size)), ErrorCodes::InvalidName);
2✔
862

863
    table->add_column(type_Int, StringData(buf, buf_size - 1));
2✔
864
    memset(buf, 'B', buf_size); // Column names must be unique
2✔
865
    table->add_column_list(type_Int, StringData(buf, buf_size - 1));
2✔
866
    memset(buf, 'C', buf_size);
2✔
867
    table->add_column(*table, StringData(buf, buf_size - 1));
2✔
868
}
2✔
869

870
TEST(Table_StringOrBinaryTooBig)
871
{
2✔
872
    Table table;
2✔
873
    auto col_string = table.add_column(type_String, "s");
2✔
874
    auto col_binary = table.add_column(type_Binary, "b");
2✔
875
    Obj obj = table.create_object();
2✔
876

877
    obj.set(col_string, "01234567");
2✔
878

879
    size_t large_bin_size = 0xFFFFF1;
2✔
880
    size_t large_str_size = 0xFFFFF0; // null-terminate reduces max size by 1
2✔
881
    std::unique_ptr<char[]> large_buf(new char[large_bin_size]);
2✔
882
    CHECK_LOGIC_ERROR(obj.set(col_string, StringData(large_buf.get(), large_str_size)), ErrorCodes::LimitExceeded);
2✔
883
    CHECK_LOGIC_ERROR(obj.set(col_binary, BinaryData(large_buf.get(), large_bin_size)), ErrorCodes::LimitExceeded);
2✔
884
    obj.set(col_string, StringData(large_buf.get(), large_str_size - 1));
2✔
885
    obj.set(col_binary, BinaryData(large_buf.get(), large_bin_size - 1));
2✔
886
}
2✔
887

888

889
TEST(Table_Floats)
890
{
2✔
891
    Table table;
2✔
892
    auto float_col = table.add_column(type_Float, "first");
2✔
893
    auto double_col = table.add_column(type_Double, "second");
2✔
894

895
    CHECK_EQUAL(type_Float, table.get_column_type(float_col));
2✔
896
    CHECK_EQUAL(type_Double, table.get_column_type(double_col));
2✔
897
    CHECK_EQUAL("first", table.get_column_name(float_col));
2✔
898
    CHECK_EQUAL("second", table.get_column_name(double_col));
2✔
899

900
    // Test adding a single empty row
901
    // and filling it with values
902
    Obj obj = table.create_object().set_all(1.12f, 102.13);
2✔
903

904
    CHECK_EQUAL(1.12f, obj.get<float>(float_col));
2✔
905
    CHECK_EQUAL(102.13, obj.get<double>(double_col));
2✔
906

907
    // Test adding multiple rows
908
    std::vector<ObjKey> keys;
2✔
909
    table.create_objects(7, keys);
2✔
910
    for (size_t i = 0; i < 7; ++i) {
16✔
911
        table.get_object(keys[i]).set(float_col, 1.12f + 100 * i).set(double_col, 102.13 * 200 * i);
14✔
912
    }
14✔
913

914
    for (size_t i = 0; i < 7; ++i) {
16✔
915
        const float v1 = 1.12f + 100 * i;
14✔
916
        const double v2 = 102.13 * 200 * i;
14✔
917
        Obj o = table.get_object(keys[i]);
14✔
918
        CHECK_EQUAL(v1, o.get<float>(float_col));
14✔
919
        CHECK_EQUAL(v2, o.get<double>(double_col));
14✔
920
    }
14✔
921

922
    table.verify();
2✔
923
}
2✔
924

925
TEST(Table_Delete)
926
{
2✔
927
    Table table;
2✔
928

929
    auto col_int = table.add_column(type_Int, "ints");
2✔
930

931
    for (int i = 0; i < 10; ++i) {
22✔
932
        table.create_object(ObjKey(i)).set(col_int, i);
20✔
933
    }
20✔
934

935
    table.remove_object(ObjKey(0));
2✔
936
    table.remove_object(ObjKey(4));
2✔
937
    table.remove_object(ObjKey(7));
2✔
938

939
    CHECK_EQUAL(1, table.get_object(ObjKey(1)).get<int64_t>(col_int));
2✔
940
    CHECK_EQUAL(2, table.get_object(ObjKey(2)).get<int64_t>(col_int));
2✔
941
    CHECK_EQUAL(3, table.get_object(ObjKey(3)).get<int64_t>(col_int));
2✔
942
    CHECK_EQUAL(5, table.get_object(ObjKey(5)).get<int64_t>(col_int));
2✔
943
    CHECK_EQUAL(6, table.get_object(ObjKey(6)).get<int64_t>(col_int));
2✔
944
    CHECK_EQUAL(8, table.get_object(ObjKey(8)).get<int64_t>(col_int));
2✔
945
    CHECK_EQUAL(9, table.get_object(ObjKey(9)).get<int64_t>(col_int));
2✔
946

947
#ifdef REALM_DEBUG
2✔
948
    table.verify();
2✔
949
#endif
2✔
950

951
    // Delete all items one at a time
952
    for (size_t i = 0; i < 10; ++i) {
22✔
953
        try {
20✔
954
            table.remove_object(ObjKey(i));
20✔
955
        }
20✔
956
        catch (...) {
20✔
957
        }
6✔
958
    }
20✔
959

960
    CHECK(table.is_empty());
2✔
961
    CHECK_EQUAL(0, table.size());
2✔
962

963
#ifdef REALM_DEBUG
2✔
964
    table.verify();
2✔
965
#endif
2✔
966
}
2✔
967

968

969
TEST(Table_GetName)
970
{
2✔
971
    // Freestanding tables have no names
972
    {
2✔
973
        Table table;
2✔
974
        CHECK_EQUAL("", table.get_name());
2✔
975
    }
2✔
976

977
    // Direct members of groups do have names
978
    {
2✔
979
        Group group;
2✔
980
        TableRef table = group.add_table("table");
2✔
981
        CHECK_EQUAL("table", table->get_name());
2✔
982
    }
2✔
983
    {
2✔
984
        Group group;
2✔
985
        TableRef foo = group.add_table("foo");
2✔
986
        TableRef bar = group.add_table("bar");
2✔
987
        CHECK_EQUAL("foo", foo->get_name());
2✔
988
        CHECK_EQUAL("bar", bar->get_name());
2✔
989
    }
2✔
990
}
2✔
991

992

993
namespace {
994

995
void setup_multi_table(Table& table, size_t rows, std::vector<ObjKey>& keys, std::vector<ColKey>& column_keys)
996
{
4✔
997
    // Create table with all column types
998
    auto int_col = table.add_column(type_Int, "int");                        //  0
4✔
999
    auto bool_col = table.add_column(type_Bool, "bool");                     //  1
4✔
1000
    auto float_col = table.add_column(type_Float, "float");                  //  2
4✔
1001
    auto double_col = table.add_column(type_Double, "double");               //  3
4✔
1002
    auto string_col = table.add_column(type_String, "string");               //  4
4✔
1003
    auto string_long_col = table.add_column(type_String, "string_long");     //  5
4✔
1004
    auto string_big_col = table.add_column(type_String, "string_big_blobs"); //  6
4✔
1005
    auto string_enum_col = table.add_column(type_String, "string_enum");     //  7 - becomes StringEnumColumn
4✔
1006
    auto bin_col = table.add_column(type_Binary, "binary");                  //  8
4✔
1007
    auto int_null_col = table.add_column(type_Int, "int_null", true);        //  9, nullable = true
4✔
1008
    column_keys.push_back(int_col);
4✔
1009
    column_keys.push_back(bool_col);
4✔
1010
    column_keys.push_back(float_col);
4✔
1011
    column_keys.push_back(double_col);
4✔
1012
    column_keys.push_back(string_col);
4✔
1013
    column_keys.push_back(string_long_col);
4✔
1014
    column_keys.push_back(string_big_col);
4✔
1015
    column_keys.push_back(string_enum_col);
4✔
1016
    column_keys.push_back(bin_col);
4✔
1017
    column_keys.push_back(int_null_col);
4✔
1018

1019
    std::vector<std::string> strings;
4✔
1020
    for (size_t i = 0; i < rows; ++i) {
64✔
1021
        std::stringstream out;
60✔
1022
        out << "string" << i;
60✔
1023
        strings.push_back(out.str());
60✔
1024
    }
60✔
1025

1026
    for (size_t i = 0; i < rows; ++i) {
64✔
1027
        Obj obj = table.create_object();
60✔
1028
        keys.push_back(obj.get_key());
60✔
1029

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

1032
        // int
1033
        obj.set(int_col, int64_t(i * sign));
60✔
1034

1035
        if (i % 4 == 0) {
60✔
1036
            obj.set_null(int_null_col);
16✔
1037
        }
16✔
1038
        else {
44✔
1039
            obj.set(int_null_col, int64_t(i * sign));
44✔
1040
        }
44✔
1041
        // bool
1042
        obj.set(bool_col, (i % 2 ? true : false));
60✔
1043
        // float
1044
        obj.set(float_col, 123.456f * sign);
60✔
1045
        // double
1046
        obj.set(double_col, 9876.54321 * sign);
60✔
1047
        // strings
1048
        std::string str_i(strings[i] + " very long string.........");
60✔
1049
        obj.set(string_col, StringData(strings[i]));
60✔
1050
        obj.set(string_long_col, StringData(str_i));
60✔
1051
        switch (i % 2) {
60✔
1052
            case 0: {
32✔
1053
                std::string s = strings[i];
32✔
1054
                s += " very long string.........";
32✔
1055
                for (int j = 0; j != 4; ++j)
160✔
1056
                    s += " big blobs big blobs big blobs"; // +30
128✔
1057
                obj.set(string_big_col, StringData(s));
32✔
1058
                break;
32✔
1059
            }
×
1060
            case 1:
28✔
1061
                obj.set(string_big_col, StringData(""));
28✔
1062
                break;
28✔
1063
        }
60✔
1064
        // enum
1065
        switch (i % 3) {
60✔
1066
            case 0:
20✔
1067
                obj.set(string_enum_col, "enum1");
20✔
1068
                break;
20✔
1069
            case 1:
20✔
1070
                obj.set(string_enum_col, "enum2");
20✔
1071
                break;
20✔
1072
            case 2:
20✔
1073
                obj.set(string_enum_col, "enum3");
20✔
1074
                break;
20✔
1075
        }
60✔
1076
        obj.set(bin_col, BinaryData("binary", 7));
60✔
1077
    }
60✔
1078

1079
    // We also want a StringEnumColumn
1080
    table.enumerate_string_column(string_enum_col);
4✔
1081
}
4✔
1082

1083
} // anonymous namespace
1084

1085

1086
TEST(Table_DeleteAllTypes)
1087
{
2✔
1088
    Table table;
2✔
1089
    std::vector<ObjKey> keys;
2✔
1090
    std::vector<ColKey> column_keys;
2✔
1091
    setup_multi_table(table, 15, keys, column_keys);
2✔
1092

1093
    // Test Deletes
1094
    table.remove_object(keys[14]);
2✔
1095
    table.remove_object(keys[0]);
2✔
1096
    table.remove_object(keys[5]);
2✔
1097

1098
    CHECK_EQUAL(12, table.size());
2✔
1099

1100
#ifdef REALM_DEBUG
2✔
1101
    table.verify();
2✔
1102
#endif
2✔
1103

1104
    // Test Clear
1105
    table.clear();
2✔
1106
    CHECK_EQUAL(0, table.size());
2✔
1107

1108
#ifdef REALM_DEBUG
2✔
1109
    table.verify();
2✔
1110
#endif
2✔
1111
}
2✔
1112

1113

1114
TEST(Table_MoveAllTypes)
1115
{
2✔
1116
    Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
1117

1118
    Table table;
2✔
1119
    std::vector<ObjKey> keys;
2✔
1120
    std::vector<ColKey> column_keys;
2✔
1121
    setup_multi_table(table, 15, keys, column_keys);
2✔
1122
    table.add_search_index(column_keys[6]);
2✔
1123
    while (!table.is_empty()) {
32✔
1124
        size_t size = keys.size();
30✔
1125
        auto it = keys.begin() + random.draw_int_mod(size);
30✔
1126
        table.remove_object(*it);
30✔
1127
        keys.erase(it);
30✔
1128
        table.verify();
30✔
1129
    }
30✔
1130
}
2✔
1131

1132
TEST(Table_FindAllInt)
1133
{
2✔
1134
    Table table;
2✔
1135

1136
    auto col_int = table.add_column(type_Int, "integers");
2✔
1137

1138
    table.create_object(ObjKey(0)).set(col_int, 10);
2✔
1139
    table.create_object(ObjKey(1)).set(col_int, 20);
2✔
1140
    table.create_object(ObjKey(2)).set(col_int, 10);
2✔
1141
    table.create_object(ObjKey(3)).set(col_int, 20);
2✔
1142
    table.create_object(ObjKey(4)).set(col_int, 10);
2✔
1143
    table.create_object(ObjKey(5)).set(col_int, 20);
2✔
1144
    table.create_object(ObjKey(6)).set(col_int, 10);
2✔
1145
    table.create_object(ObjKey(7)).set(col_int, 20);
2✔
1146
    table.create_object(ObjKey(8)).set(col_int, 10);
2✔
1147
    table.create_object(ObjKey(9)).set(col_int, 20);
2✔
1148

1149
    // Search for a value that does not exits
1150
    auto v0 = table.find_all_int(col_int, 5);
2✔
1151
    CHECK_EQUAL(0, v0.size());
2✔
1152

1153
    // Search for a value with several matches
1154
    auto v = table.find_all_int(col_int, 20);
2✔
1155

1156
    CHECK_EQUAL(5, v.size());
2✔
1157
    CHECK_EQUAL(ObjKey(1), v.get_key(0));
2✔
1158
    CHECK_EQUAL(ObjKey(3), v.get_key(1));
2✔
1159
    CHECK_EQUAL(ObjKey(5), v.get_key(2));
2✔
1160
    CHECK_EQUAL(ObjKey(7), v.get_key(3));
2✔
1161
    CHECK_EQUAL(ObjKey(9), v.get_key(4));
2✔
1162

1163
#ifdef REALM_DEBUG
2✔
1164
    table.verify();
2✔
1165
#endif
2✔
1166
}
2✔
1167

1168
TEST(Table_SortedInt)
1169
{
2✔
1170
    Table table;
2✔
1171

1172
    auto col_int = table.add_column(type_Int, "integers");
2✔
1173

1174
    table.create_object(ObjKey(0)).set(col_int, 10); // 0: 4
2✔
1175
    table.create_object(ObjKey(1)).set(col_int, 20); // 1: 7
2✔
1176
    table.create_object(ObjKey(2)).set(col_int, 0);  // 2: 0
2✔
1177
    table.create_object(ObjKey(3)).set(col_int, 40); // 3: 8
2✔
1178
    table.create_object(ObjKey(4)).set(col_int, 15); // 4: 6
2✔
1179
    table.create_object(ObjKey(5)).set(col_int, 11); // 5: 5
2✔
1180
    table.create_object(ObjKey(6)).set(col_int, 6);  // 6: 3
2✔
1181
    table.create_object(ObjKey(7)).set(col_int, 4);  // 7: 2
2✔
1182
    table.create_object(ObjKey(8)).set(col_int, 99); // 8: 9
2✔
1183
    table.create_object(ObjKey(9)).set(col_int, 2);  // 9: 1
2✔
1184

1185
    // Search for a value that does not exits
1186
    auto v = table.get_sorted_view(col_int);
2✔
1187
    CHECK_EQUAL(table.size(), v.size());
2✔
1188

1189
    CHECK_EQUAL(ObjKey(2), v.get_key(0));
2✔
1190
    CHECK_EQUAL(ObjKey(9), v.get_key(1));
2✔
1191
    CHECK_EQUAL(ObjKey(7), v.get_key(2));
2✔
1192
    CHECK_EQUAL(ObjKey(6), v.get_key(3));
2✔
1193
    CHECK_EQUAL(ObjKey(0), v.get_key(4));
2✔
1194
    CHECK_EQUAL(ObjKey(5), v.get_key(5));
2✔
1195
    CHECK_EQUAL(ObjKey(4), v.get_key(6));
2✔
1196
    CHECK_EQUAL(ObjKey(1), v.get_key(7));
2✔
1197
    CHECK_EQUAL(ObjKey(3), v.get_key(8));
2✔
1198
    CHECK_EQUAL(ObjKey(8), v.get_key(9));
2✔
1199

1200
#ifdef REALM_DEBUG
2✔
1201
    table.verify();
2✔
1202
#endif
2✔
1203
}
2✔
1204

1205

1206
TEST(Table_Sorted_Query_where)
1207
{
2✔
1208
    Table table;
2✔
1209

1210
    auto col_dummy = table.add_column(type_Int, "dummmy");
2✔
1211
    auto col_int = table.add_column(type_Int, "integers");
2✔
1212
    auto col_bool = table.add_column(type_Bool, "booleans");
2✔
1213

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

1225
    // Get a view containing the complete table
1226
    auto v = table.find_all_int(col_dummy, 0);
2✔
1227
    CHECK_EQUAL(table.size(), v.size());
2✔
1228

1229
    // Count booleans
1230
    size_t count_view = table.where(&v).equal(col_bool, false).count();
2✔
1231
    CHECK_EQUAL(4, count_view);
2✔
1232

1233
    auto v_sorted = table.get_sorted_view(col_int);
2✔
1234
    CHECK_EQUAL(table.size(), v_sorted.size());
2✔
1235

1236
#ifdef REALM_DEBUG
2✔
1237
    table.verify();
2✔
1238
#endif
2✔
1239
}
2✔
1240

1241
namespace realm {
1242
template <class T>
1243
T nan(const char* tag)
1244
{
1,200✔
1245
    typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type i;
1,200✔
1246
    uint64_t double_nan = 0x7ff8000000000000;
1,200✔
1247
    i = std::is_same<T, float>::value ? 0x7fc00000 : static_cast<decltype(i)>(double_nan);
1,200✔
1248
    i += *tag;
1,200✔
1249
    return type_punning<T>(i);
1,200✔
1250
}
1,200✔
1251
template <>
1252
Decimal128 nan(const char* init)
1253
{
600✔
1254
    return Decimal128::nan(init);
600✔
1255
}
600✔
1256

1257
template <typename T>
1258
inline bool isnan(T val)
1259
{
1,200✔
1260
    return std::isnan(val);
1,200✔
1261
}
1,200✔
1262
inline bool isnan(Decimal128 val)
1263
{
600✔
1264
    return val.is_nan();
600✔
1265
}
600✔
1266

1267
} // namespace realm
1268

1269
TEST_TYPES(Table_SortFloat, float, double, Decimal128)
1270
{
6✔
1271
    Table table;
6✔
1272
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
6✔
1273
    auto col = table.add_column(type, "value", true);
6✔
1274
    ObjKeys keys;
6✔
1275
    table.create_objects(900, keys);
6✔
1276
    for (size_t i = 0; i < keys.size(); i += 3) {
1,806✔
1277
        table.get_object(keys[i]).set(col, TEST_TYPE(-500.0 + i));
1,800✔
1278
        table.get_object(keys[i + 1]).set_null(col);
1,800✔
1279
        const char nan_tag[] = {char('0' + i % 10), 0};
1,800✔
1280
        table.get_object(keys[i + 2]).set(col, realm::nan<TEST_TYPE>(nan_tag));
1,800✔
1281
    }
1,800✔
1282

1283
    TableView sorted = table.get_sorted_view(SortDescriptor{{{col}}, {true}});
6✔
1284
    CHECK_EQUAL(table.size(), sorted.size());
6✔
1285

1286
    // nulls should appear first,
1287
    // followed by nans, folllowed by the rest of the values in ascending order
1288
    for (size_t i = 0; i < 300; ++i) {
1,806✔
1289
        CHECK(sorted.get_object(i).is_null(col));
1,800✔
1290
    }
1,800✔
1291
    for (size_t i = 300; i < 600; ++i) {
1,806✔
1292
        CHECK(realm::isnan(sorted.get_object(i).get<TEST_TYPE>(col)));
1,800✔
1293
    }
1,800✔
1294
    for (size_t i = 600; i + 1 < 900; ++i) {
1,800✔
1295
        CHECK_GREATER(sorted.get_object(i + 1).get<TEST_TYPE>(col), sorted.get_object(i).get<TEST_TYPE>(col));
1,794✔
1296
    }
1,794✔
1297
}
6✔
1298

1299
TEST_TYPES(Table_Multi_Sort, int64_t, float, double, Decimal128)
1300
{
8✔
1301
    Table table;
8✔
1302
    auto col_0 = table.add_column(ColumnTypeTraits<TEST_TYPE>::id, "first");
8✔
1303
    auto col_1 = table.add_column(ColumnTypeTraits<TEST_TYPE>::id, "second");
8✔
1304

1305
    table.create_object(ObjKey(0)).set_all(TEST_TYPE(1), TEST_TYPE(10));
8✔
1306
    table.create_object(ObjKey(1)).set_all(TEST_TYPE(2), TEST_TYPE(10));
8✔
1307
    table.create_object(ObjKey(2)).set_all(TEST_TYPE(0), TEST_TYPE(10));
8✔
1308
    table.create_object(ObjKey(3)).set_all(TEST_TYPE(2), TEST_TYPE(14));
8✔
1309
    table.create_object(ObjKey(4)).set_all(TEST_TYPE(1), TEST_TYPE(14));
8✔
1310

1311
    std::vector<std::vector<ExtendedColumnKey>> col_ndx1 = {{col_0}, {col_1}};
8✔
1312
    std::vector<bool> asc = {true, true};
8✔
1313

1314
    // (0, 10); (1, 10); (1, 14); (2, 10); (2; 14)
1315
    TableView v_sorted1 = table.get_sorted_view(SortDescriptor{col_ndx1, asc});
8✔
1316
    CHECK_EQUAL(table.size(), v_sorted1.size());
8✔
1317
    CHECK_EQUAL(ObjKey(2), v_sorted1.get_key(0));
8✔
1318
    CHECK_EQUAL(ObjKey(0), v_sorted1.get_key(1));
8✔
1319
    CHECK_EQUAL(ObjKey(4), v_sorted1.get_key(2));
8✔
1320
    CHECK_EQUAL(ObjKey(1), v_sorted1.get_key(3));
8✔
1321
    CHECK_EQUAL(ObjKey(3), v_sorted1.get_key(4));
8✔
1322

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

1325
    // (0, 10); (1, 10); (2, 10); (1, 14); (2, 14)
1326
    TableView v_sorted2 = table.get_sorted_view(SortDescriptor{col_ndx2, asc});
8✔
1327
    CHECK_EQUAL(table.size(), v_sorted2.size());
8✔
1328
    CHECK_EQUAL(ObjKey(2), v_sorted2.get_key(0));
8✔
1329
    CHECK_EQUAL(ObjKey(0), v_sorted2.get_key(1));
8✔
1330
    CHECK_EQUAL(ObjKey(1), v_sorted2.get_key(2));
8✔
1331
    CHECK_EQUAL(ObjKey(4), v_sorted2.get_key(3));
8✔
1332
    CHECK_EQUAL(ObjKey(3), v_sorted2.get_key(4));
8✔
1333
}
8✔
1334

1335
TEST(Table_IndexString)
1336
{
2✔
1337
    Table table;
2✔
1338
    auto col_int = table.add_column(type_Int, "first");
2✔
1339
    auto col_str = table.add_column(type_String, "second");
2✔
1340

1341
    table.add_search_index(col_str);
2✔
1342
    CHECK(table.has_search_index(col_str));
2✔
1343

1344
    ObjKey k0 = table.create_object(ObjKey{}, {{col_int, int(Mon)}, {col_str, "jeff"}}).get_key();
2✔
1345
    ObjKey k1 = table.create_object(ObjKey{}, {{col_str, "jim"}, {col_int, int(Tue)}}).get_key();
2✔
1346
    table.create_object().set_all(int(Wed), "jennifer");
2✔
1347
    table.create_object().set_all(int(Thu), "john");
2✔
1348
    table.create_object().set_all(int(Fri), "jimmy");
2✔
1349
    ObjKey k5 = table.create_object().set_all(int(Sat), "jimbo").get_key();
2✔
1350
    // Use a key where the first has the the second most significant bit set.
1351
    // When this is shifted up and down again, the most significant bit must
1352
    // still be 0.
1353
    ObjKey k6 = table.create_object(ObjKey(1LL << 62)).set_all(int(Sun), "johnny").get_key();
2✔
1354
    table.create_object().set_all(int(Mon), "jennifer"); // duplicate
2✔
1355

1356
    ObjKey r1 = table.find_first_string(col_str, "jimmi");
2✔
1357
    CHECK_EQUAL(null_key, r1);
2✔
1358

1359
    ObjKey r2 = table.find_first_string(col_str, "jeff");
2✔
1360
    ObjKey r3 = table.find_first_string(col_str, "jim");
2✔
1361
    ObjKey r4 = table.find_first_string(col_str, "jimbo");
2✔
1362
    ObjKey r5 = table.find_first_string(col_str, "johnny");
2✔
1363
    CHECK_EQUAL(k0, r2);
2✔
1364
    CHECK_EQUAL(k1, r3);
2✔
1365
    CHECK_EQUAL(k5, r4);
2✔
1366
    CHECK_EQUAL(k6, r5);
2✔
1367

1368
    const size_t c1 = table.count_string(col_str, "jennifer");
2✔
1369
    CHECK_EQUAL(2, c1);
2✔
1370
}
2✔
1371

1372

1373
TEST(Table_IndexStringTwice)
1374
{
2✔
1375
    Table table;
2✔
1376
    table.add_column(type_Int, "first");
2✔
1377
    auto col_str = table.add_column(type_String, "second");
2✔
1378

1379
    table.create_object().set_all(int(Mon), "jeff");
2✔
1380
    table.create_object().set_all(int(Tue), "jim");
2✔
1381
    table.create_object().set_all(int(Wed), "jennifer");
2✔
1382
    table.create_object().set_all(int(Thu), "john");
2✔
1383
    table.create_object().set_all(int(Fri), "jimmy");
2✔
1384
    table.create_object().set_all(int(Sat), "jimbo");
2✔
1385
    table.create_object().set_all(int(Sun), "johnny");
2✔
1386
    table.create_object().set_all(int(Mon), "jennifer"); // duplicate
2✔
1387

1388
    table.add_search_index(col_str);
2✔
1389
    CHECK_EQUAL(true, table.has_search_index(col_str));
2✔
1390
    table.add_search_index(col_str);
2✔
1391
    CHECK_EQUAL(true, table.has_search_index(col_str));
2✔
1392
}
2✔
1393

1394

1395
// Tests Table part of index on Int, OldDateTime and Bool columns. For a more exhaustive
1396
// test of the integer index (bypassing Table), see test_index_string.cpp)
1397
TEST(Table_IndexInteger)
1398
{
2✔
1399
    Table table;
2✔
1400
    ObjKey k;
2✔
1401

1402
    auto col_int = table.add_column(type_Int, "ints");
2✔
1403
    auto col_date = table.add_column(type_Timestamp, "date");
2✔
1404
    auto col_bool = table.add_column(type_Bool, "booleans");
2✔
1405

1406
    std::vector<ObjKey> keys;
2✔
1407
    table.create_objects(13, keys);
2✔
1408

1409
    table.get_object(keys[0]).set(col_int, 3);  // 0
2✔
1410
    table.get_object(keys[1]).set(col_int, 1);  // 1
2✔
1411
    table.get_object(keys[2]).set(col_int, 2);  // 2
2✔
1412
    table.get_object(keys[3]).set(col_int, 2);  // 3
2✔
1413
    table.get_object(keys[4]).set(col_int, 2);  // 4
2✔
1414
    table.get_object(keys[5]).set(col_int, 3);  // 5
2✔
1415
    table.get_object(keys[6]).set(col_int, 3);  // 6
2✔
1416
    table.get_object(keys[7]).set(col_int, 2);  // 7
2✔
1417
    table.get_object(keys[8]).set(col_int, 4);  // 8
2✔
1418
    table.get_object(keys[9]).set(col_int, 2);  // 9
2✔
1419
    table.get_object(keys[10]).set(col_int, 6); // 10
2✔
1420
    table.get_object(keys[11]).set(col_int, 2); // 11
2✔
1421
    table.get_object(keys[12]).set(col_int, 3); // 12
2✔
1422

1423
    table.add_search_index(col_int);
2✔
1424
    CHECK(table.has_search_index(col_int));
2✔
1425
    table.add_search_index(col_date);
2✔
1426
    CHECK(table.has_search_index(col_date));
2✔
1427
    table.add_search_index(col_bool);
2✔
1428
    CHECK(table.has_search_index(col_bool));
2✔
1429

1430
    table.get_object(keys[10]).set(col_date, Timestamp(43, 0));
2✔
1431
    k = table.find_first_timestamp(col_date, Timestamp(43, 0));
2✔
1432
    CHECK_EQUAL(keys[10], k);
2✔
1433

1434
    table.get_object(keys[11]).set(col_bool, true);
2✔
1435
    k = table.find_first_bool(col_bool, true);
2✔
1436
    CHECK_EQUAL(keys[11], k);
2✔
1437

1438
    k = table.find_first_int(col_int, 11);
2✔
1439
    CHECK_EQUAL(null_key, k);
2✔
1440

1441
    k = table.find_first_int(col_int, 3);
2✔
1442
    CHECK_EQUAL(keys[0], k);
2✔
1443

1444
    k = table.find_first_int(col_int, 4);
2✔
1445
    CHECK_EQUAL(keys[8], k);
2✔
1446

1447
    TableView tv = table.find_all_int(col_int, 2);
2✔
1448
    CHECK_EQUAL(6, tv.size());
2✔
1449

1450
    CHECK_EQUAL(keys[2], tv[0].get_key());
2✔
1451
    CHECK_EQUAL(keys[3], tv[1].get_key());
2✔
1452
    CHECK_EQUAL(keys[4], tv[2].get_key());
2✔
1453
    CHECK_EQUAL(keys[7], tv[3].get_key());
2✔
1454
    CHECK_EQUAL(keys[9], tv[4].get_key());
2✔
1455
    CHECK_EQUAL(keys[11], tv[5].get_key());
2✔
1456
}
2✔
1457

1458

1459
TEST(Table_AddInt)
1460
{
2✔
1461
    Table t;
2✔
1462
    auto col_int = t.add_column(type_Int, "i");
2✔
1463
    auto col_int_null = t.add_column(type_Int, "ni", /*nullable*/ true);
2✔
1464
    auto col_mixed = t.add_column(type_Mixed, "m");
2✔
1465
    Obj obj = t.create_object();
2✔
1466

1467
    obj.set(col_mixed, Mixed(5));
2✔
1468

1469
    obj.add_int(col_int, 1);
2✔
1470
    CHECK_EQUAL(obj.get<Int>(col_int), 1);
2✔
1471

1472
    // Check that signed integers wrap around. This invariant is necessary for
1473
    // full commutativity.
1474
    obj.add_int(col_int, Table::max_integer);
2✔
1475
    CHECK_EQUAL(obj.get<Int>(col_int), Table::min_integer);
2✔
1476
    obj.add_int(col_int, -1);
2✔
1477
    CHECK_EQUAL(obj.get<Int>(col_int), Table::max_integer);
2✔
1478

1479
    // add_int() has no effect on a NULL
1480
    CHECK(obj.is_null(col_int_null));
2✔
1481
    CHECK_LOGIC_ERROR(obj.add_int(col_int_null, 123), ErrorCodes::IllegalOperation);
2✔
1482

1483
    obj.add_int(col_mixed, 1);
2✔
1484
    CHECK_EQUAL(obj.get_any(col_mixed).get_int(), 6);
2✔
1485
    obj.set(col_mixed, Mixed("Foo"));
2✔
1486
    CHECK_LOGIC_ERROR(obj.add_int(col_mixed, 123), ErrorCodes::IllegalOperation);
2✔
1487
}
2✔
1488

1489
TEST(Table_AddIntIndexed)
1490
{
2✔
1491
    Table table;
2✔
1492
    auto col = table.add_column(DataType(0), "int_1", false);
2✔
1493
    Obj obj = table.create_object();
2✔
1494
    table.add_search_index(col);
2✔
1495
    obj.add_int(col, 8463800223514590069);
2✔
1496
    obj.remove();
2✔
1497
}
2✔
1498

1499
TEST(Table_IndexInt)
1500
{
2✔
1501
    Table table;
2✔
1502
    auto col = table.add_column(type_Int, "first");
2✔
1503

1504
    ObjKey k0 = table.create_object().set(col, 1).get_key();
2✔
1505
    ObjKey k1 = table.create_object().set(col, 15).get_key();
2✔
1506
    ObjKey k2 = table.create_object().set(col, 10).get_key();
2✔
1507
    ObjKey k3 = table.create_object().set(col, 20).get_key();
2✔
1508
    ObjKey k4 = table.create_object().set(col, 11).get_key();
2✔
1509
    ObjKey k5 = table.create_object().set(col, 45).get_key();
2✔
1510
    ObjKey k6 = table.create_object().set(col, 10).get_key();
2✔
1511
    ObjKey k7 = table.create_object().set(col, 0).get_key();
2✔
1512
    ObjKey k8 = table.create_object().set(col, 30).get_key();
2✔
1513
    ObjKey k9 = table.create_object().set(col, 9).get_key();
2✔
1514

1515
    // Create index for column two
1516
    table.add_search_index(col);
2✔
1517

1518
    // Search for a value that does not exits
1519
    ObjKey k = table.find_first_int(col, 2);
2✔
1520
    CHECK_EQUAL(null_key, k);
2✔
1521

1522
    // Find existing values
1523
    CHECK_EQUAL(k0, table.find_first_int(col, 1));
2✔
1524
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1525
    CHECK_EQUAL(k2, table.find_first_int(col, 10));
2✔
1526
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1527
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1528
    CHECK_EQUAL(k5, table.find_first_int(col, 45));
2✔
1529
    // CHECK_EQUAL(6, table.find_first_int(col, 10)); // only finds first match
1530
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1531
    CHECK_EQUAL(k8, table.find_first_int(col, 30));
2✔
1532
    CHECK_EQUAL(k9, table.find_first_int(col, 9));
2✔
1533

1534
    // Change some values
1535
    table.get_object(k2).set(col, 13);
2✔
1536
    table.get_object(k9).set(col, 100);
2✔
1537

1538
    CHECK_EQUAL(k0, table.find_first_int(col, 1));
2✔
1539
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1540
    CHECK_EQUAL(k2, table.find_first_int(col, 13));
2✔
1541
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1542
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1543
    CHECK_EQUAL(k5, table.find_first_int(col, 45));
2✔
1544
    CHECK_EQUAL(k6, table.find_first_int(col, 10));
2✔
1545
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1546
    CHECK_EQUAL(k8, table.find_first_int(col, 30));
2✔
1547
    CHECK_EQUAL(k9, table.find_first_int(col, 100));
2✔
1548

1549
    // Insert values
1550
    ObjKey k10 = table.create_object().set(col, 29).get_key();
2✔
1551
    // TODO: More than add
1552

1553
    CHECK_EQUAL(k0, table.find_first_int(col, 1));
2✔
1554
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1555
    CHECK_EQUAL(k2, table.find_first_int(col, 13));
2✔
1556
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1557
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1558
    CHECK_EQUAL(k5, table.find_first_int(col, 45));
2✔
1559
    CHECK_EQUAL(k6, table.find_first_int(col, 10));
2✔
1560
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1561
    CHECK_EQUAL(k8, table.find_first_int(col, 30));
2✔
1562
    CHECK_EQUAL(k9, table.find_first_int(col, 100));
2✔
1563
    CHECK_EQUAL(k10, table.find_first_int(col, 29));
2✔
1564

1565
    // Delete some values
1566
    table.remove_object(k0);
2✔
1567
    table.remove_object(k5);
2✔
1568
    table.remove_object(k8);
2✔
1569

1570
    CHECK_EQUAL(null_key, table.find_first_int(col, 1));
2✔
1571
    CHECK_EQUAL(k1, table.find_first_int(col, 15));
2✔
1572
    CHECK_EQUAL(k2, table.find_first_int(col, 13));
2✔
1573
    CHECK_EQUAL(k3, table.find_first_int(col, 20));
2✔
1574
    CHECK_EQUAL(k4, table.find_first_int(col, 11));
2✔
1575
    CHECK_EQUAL(null_key, table.find_first_int(col, 45));
2✔
1576
    CHECK_EQUAL(k6, table.find_first_int(col, 10));
2✔
1577
    CHECK_EQUAL(k7, table.find_first_int(col, 0));
2✔
1578
    CHECK_EQUAL(null_key, table.find_first_int(col, 30));
2✔
1579
    CHECK_EQUAL(k9, table.find_first_int(col, 100));
2✔
1580
    CHECK_EQUAL(k10, table.find_first_int(col, 29));
2✔
1581

1582
#ifdef REALM_DEBUG
2✔
1583
    table.verify();
2✔
1584
#endif
2✔
1585
}
2✔
1586

1587
TEST(Table_AutoEnumeration)
1588
{
2✔
1589
    Table table;
2✔
1590

1591
    auto col_int = table.add_column(type_Int, "first");
2✔
1592
    auto col_str = table.add_column(type_String, "second");
2✔
1593

1594
    for (size_t i = 0; i < 5; ++i) {
12✔
1595
        table.create_object().set_all(1, "abd");
10✔
1596
        table.create_object().set_all(2, "eftg");
10✔
1597
        table.create_object().set_all(5, "hijkl");
10✔
1598
        table.create_object().set_all(8, "mnopqr");
10✔
1599
        table.create_object().set_all(9, "stuvxyz");
10✔
1600
    }
10✔
1601

1602
    table.enumerate_string_column(col_str);
2✔
1603

1604
    for (size_t i = 0; i < 5; ++i) {
12✔
1605
        const size_t n = i * 5;
10✔
1606
        CHECK_EQUAL(1, table.get_object(ObjKey(0 + n)).get<Int>(col_int));
10✔
1607
        CHECK_EQUAL(2, table.get_object(ObjKey(1 + n)).get<Int>(col_int));
10✔
1608
        CHECK_EQUAL(5, table.get_object(ObjKey(2 + n)).get<Int>(col_int));
10✔
1609
        CHECK_EQUAL(8, table.get_object(ObjKey(3 + n)).get<Int>(col_int));
10✔
1610
        CHECK_EQUAL(9, table.get_object(ObjKey(4 + n)).get<Int>(col_int));
10✔
1611

1612
        CHECK_EQUAL("abd", table.get_object(ObjKey(0 + n)).get<String>(col_str));
10✔
1613
        CHECK_EQUAL("eftg", table.get_object(ObjKey(1 + n)).get<String>(col_str));
10✔
1614
        CHECK_EQUAL("hijkl", table.get_object(ObjKey(2 + n)).get<String>(col_str));
10✔
1615
        CHECK_EQUAL("mnopqr", table.get_object(ObjKey(3 + n)).get<String>(col_str));
10✔
1616
        CHECK_EQUAL("stuvxyz", table.get_object(ObjKey(4 + n)).get<String>(col_str));
10✔
1617
    }
10✔
1618

1619
    // Verify counts
1620
    const size_t count1 = table.count_string(col_str, "abd");
2✔
1621
    const size_t count2 = table.count_string(col_str, "eftg");
2✔
1622
    const size_t count3 = table.count_string(col_str, "hijkl");
2✔
1623
    const size_t count4 = table.count_string(col_str, "mnopqr");
2✔
1624
    const size_t count5 = table.count_string(col_str, "stuvxyz");
2✔
1625
    CHECK_EQUAL(5, count1);
2✔
1626
    CHECK_EQUAL(5, count2);
2✔
1627
    CHECK_EQUAL(5, count3);
2✔
1628
    CHECK_EQUAL(5, count4);
2✔
1629
    CHECK_EQUAL(5, count5);
2✔
1630

1631
    ObjKey t = table.find_first_string(col_str, "eftg");
2✔
1632
    CHECK_EQUAL(ObjKey(1), t);
2✔
1633

1634
    auto tv = table.find_all_string(col_str, "eftg");
2✔
1635
    CHECK_EQUAL(5, tv.size());
2✔
1636
    CHECK_EQUAL("eftg", tv.get_object(0).get<String>(col_str));
2✔
1637
    CHECK_EQUAL("eftg", tv.get_object(1).get<String>(col_str));
2✔
1638
    CHECK_EQUAL("eftg", tv.get_object(2).get<String>(col_str));
2✔
1639
    CHECK_EQUAL("eftg", tv.get_object(3).get<String>(col_str));
2✔
1640
    CHECK_EQUAL("eftg", tv.get_object(4).get<String>(col_str));
2✔
1641

1642
    Obj obj = table.create_object();
2✔
1643
    CHECK_EQUAL(0, obj.get<Int>(col_int));
2✔
1644
    CHECK_EQUAL("", obj.get<String>(col_str));
2✔
1645
}
2✔
1646

1647

1648
TEST(Table_AutoEnumerationOptimize)
1649
{
2✔
1650
    Table t;
2✔
1651
    auto col0 = t.add_column(type_String, "col1");
2✔
1652
    auto col1 = t.add_column(type_String, "col2");
2✔
1653
    auto col2 = t.add_column(type_String, "col3");
2✔
1654
    auto col3 = t.add_column(type_String, "col4");
2✔
1655

1656
    // Insert non-optimizable strings
1657
    std::string s;
2✔
1658
    std::vector<ObjKey> keys;
2✔
1659
    t.create_objects(10, keys);
2✔
1660
    for (Obj o : t) {
20✔
1661
        o.set_all(s.c_str(), s.c_str(), s.c_str(), s.c_str());
20✔
1662
        s += "x";
20✔
1663
    }
20✔
1664

1665
    // AutoEnumerate in reverse order
1666
    for (Obj o : t) {
20✔
1667
        o.set(col3, "test");
20✔
1668
    }
20✔
1669
    t.enumerate_string_column(col3);
2✔
1670
    for (Obj o : t) {
20✔
1671
        o.set(col2, "test");
20✔
1672
    }
20✔
1673
    t.enumerate_string_column(col2);
2✔
1674
    for (Obj o : t) {
20✔
1675
        o.set(col1, "test");
20✔
1676
    }
20✔
1677
    t.enumerate_string_column(col1);
2✔
1678
    for (Obj o : t) {
20✔
1679
        o.set(col0, "test");
20✔
1680
    }
20✔
1681
    t.enumerate_string_column(col0);
2✔
1682

1683
    for (Obj o : t) {
20✔
1684
        CHECK_EQUAL("test", o.get<String>(col0));
20✔
1685
        CHECK_EQUAL("test", o.get<String>(col1));
20✔
1686
        CHECK_EQUAL("test", o.get<String>(col2));
20✔
1687
        CHECK_EQUAL("test", o.get<String>(col3));
20✔
1688
    }
20✔
1689

1690
#ifdef REALM_DEBUG
2✔
1691
    t.verify();
2✔
1692
#endif
2✔
1693
}
2✔
1694

1695
TEST(Table_OptimizeCompare)
1696
{
2✔
1697
    Table t1, t2;
2✔
1698
    auto col_t1 = t1.add_column(type_String, "str");
2✔
1699
    auto col_t2 = t2.add_column(type_String, "str");
2✔
1700

1701
    std::vector<ObjKey> keys_t1;
2✔
1702
    std::vector<ObjKey> keys_t2;
2✔
1703
    t1.create_objects(100, keys_t1);
2✔
1704
    for (Obj o : t1) {
200✔
1705
        o.set(col_t1, "foo");
200✔
1706
    }
200✔
1707
    t2.create_objects(100, keys_t2);
2✔
1708
    for (Obj o : t2) {
200✔
1709
        o.set(col_t2, "foo");
200✔
1710
    }
200✔
1711
    t1.enumerate_string_column(col_t1);
2✔
1712
    CHECK(t1 == t2);
2✔
1713
    Obj obj1 = t1.get_object(keys_t1[50]);
2✔
1714
    Obj obj2 = t2.get_object(keys_t2[50]);
2✔
1715
    obj1.set(col_t1, "bar");
2✔
1716
    CHECK(t1 != t2);
2✔
1717
    obj1.set(col_t1, "foo");
2✔
1718
    CHECK(t1 == t2);
2✔
1719
    obj2.set(col_t2, "bar");
2✔
1720
    CHECK(t1 != t2);
2✔
1721
    obj2.set(col_t2, "foo");
2✔
1722
    CHECK(t1 == t2);
2✔
1723
}
2✔
1724

1725

1726
TEST(Table_SlabAlloc)
1727
{
2✔
1728
    SlabAlloc alloc;
2✔
1729
    alloc.attach_empty();
2✔
1730
    Table table(alloc);
2✔
1731

1732
    auto col_int0 = table.add_column(type_Int, "int0");
2✔
1733
    auto col_int1 = table.add_column(type_Int, "int1");
2✔
1734
    auto col_bool = table.add_column(type_Bool, "bool");
2✔
1735
    auto col_int2 = table.add_column(type_Int, "int2");
2✔
1736

1737
    Obj obj = table.create_object().set_all(0, 10, true, int(Wed));
2✔
1738
    CHECK_EQUAL(0, obj.get<Int>(col_int0));
2✔
1739
    CHECK_EQUAL(10, obj.get<Int>(col_int1));
2✔
1740
    CHECK_EQUAL(true, obj.get<Bool>(col_bool));
2✔
1741
    CHECK_EQUAL(Wed, obj.get<Int>(col_int2));
2✔
1742

1743
    // Add some more rows
1744
    table.create_object().set_all(1, 10, true, int(Wed));
2✔
1745
    ObjKey k0 = table.create_object().set_all(2, 20, true, int(Wed)).get_key();
2✔
1746
    table.create_object().set_all(3, 10, true, int(Wed));
2✔
1747
    ObjKey k1 = table.create_object().set_all(4, 20, true, int(Wed)).get_key();
2✔
1748
    table.create_object().set_all(5, 10, true, int(Wed));
2✔
1749

1750
    // Delete some rows
1751
    table.remove_object(k0);
2✔
1752
    table.remove_object(k1);
2✔
1753

1754
#ifdef REALM_DEBUG
2✔
1755
    table.verify();
2✔
1756
#endif
2✔
1757
}
2✔
1758

1759
TEST(Table_NullInEnum)
1760
{
2✔
1761
    Group group;
2✔
1762
    TableRef table = group.add_table("test");
2✔
1763
    auto col = table->add_column(type_String, "second", true);
2✔
1764

1765
    for (size_t c = 0; c < 100; c++) {
202✔
1766
        table->create_object().set(col, "hello");
200✔
1767
    }
200✔
1768

1769
    size_t r;
2✔
1770

1771
    r = table->where().equal(col, "hello").count();
2✔
1772
    CHECK_EQUAL(100, r);
2✔
1773

1774
    Obj obj50 = table->get_object(ObjKey(50));
2✔
1775
    obj50.set<String>(col, realm::null());
2✔
1776
    r = table->where().equal(col, "hello").count();
2✔
1777
    CHECK_EQUAL(99, r);
2✔
1778

1779
    table->enumerate_string_column(col);
2✔
1780

1781
    obj50.set<String>(col, realm::null());
2✔
1782
    r = table->where().equal(col, "hello").count();
2✔
1783
    CHECK_EQUAL(99, r);
2✔
1784

1785
    obj50.set<String>(col, "hello");
2✔
1786
    r = table->where().equal(col, "hello").count();
2✔
1787
    CHECK_EQUAL(100, r);
2✔
1788

1789
    obj50.set<String>(col, realm::null());
2✔
1790
    r = table->where().equal(col, "hello").count();
2✔
1791
    CHECK_EQUAL(99, r);
2✔
1792

1793
    r = table->where().equal(col, realm::null()).count();
2✔
1794
    CHECK_EQUAL(1, r);
2✔
1795

1796
    table->get_object(ObjKey(55)).set(col, realm::null());
2✔
1797
    r = table->where().equal(col, realm::null()).count();
2✔
1798
    CHECK_EQUAL(2, r);
2✔
1799

1800
    r = table->where().equal(col, "hello").count();
2✔
1801
    CHECK_EQUAL(98, r);
2✔
1802

1803
    table->remove_object(ObjKey(55));
2✔
1804
    r = table->where().equal(col, realm::null()).count();
2✔
1805
    CHECK_EQUAL(1, r);
2✔
1806
}
2✔
1807

1808

1809
TEST(Table_DateAndBinary)
1810
{
2✔
1811
    Table t;
2✔
1812
    auto col_date = t.add_column(type_Timestamp, "date");
2✔
1813
    auto col_bin = t.add_column(type_Binary, "bin");
2✔
1814

1815
    const size_t size = 10;
2✔
1816
    char data[size];
2✔
1817
    for (size_t i = 0; i < size; ++i)
22✔
1818
        data[i] = static_cast<char>(i);
20✔
1819
    t.create_object().set_all(Timestamp(8, 0), BinaryData(data, size));
2✔
1820
    Obj obj = *t.begin();
2✔
1821
    CHECK_EQUAL(obj.get<Timestamp>(col_date), Timestamp(8, 0));
2✔
1822
    BinaryData bin = obj.get<Binary>(col_bin);
2✔
1823
    CHECK_EQUAL(bin.size(), size);
2✔
1824
    CHECK(std::equal(bin.data(), bin.data() + size, data));
2✔
1825

1826
    // Test that 64-bit dates are preserved
1827
    Timestamp date(std::numeric_limits<int64_t>::max() - 400, 0);
2✔
1828
    obj.set(col_date, date);
2✔
1829
    CHECK_EQUAL(obj.get<Timestamp>(col_date), date);
2✔
1830
}
2✔
1831

1832
#if TEST_DURATION > 0
1833
#define TBL_SIZE REALM_MAX_BPNODE_SIZE * 10
1834
#else
1835
#define TBL_SIZE 10
24✔
1836
#endif // TEST_DURATION
1837

1838
TEST(Table_Aggregates)
1839
{
2✔
1840
    Table table;
2✔
1841
    auto int_col = table.add_column(type_Int, "c_int");
2✔
1842
    auto float_col = table.add_column(type_Float, "c_float");
2✔
1843
    auto double_col = table.add_column(type_Double, "c_double");
2✔
1844
    auto str_col = table.add_column(type_String, "c_string");
2✔
1845
    auto decimal_col = table.add_column(type_Decimal, "c_decimal");
2✔
1846
    int64_t i_sum = 0;
2✔
1847
    double f_sum = 0;
2✔
1848
    double d_sum = 0;
2✔
1849
    Decimal128 decimal_sum(0);
2✔
1850

1851
    for (int i = 0; i < TBL_SIZE; i++) {
22✔
1852
        table.create_object().set_all(5987654, 4.0f, 3.0, "Hello", Decimal128(7.7));
20✔
1853
        i_sum += 5987654;
20✔
1854
        f_sum += 4.0f;
20✔
1855
        d_sum += 3.0;
20✔
1856
        decimal_sum += Decimal128(7.7);
20✔
1857
    }
20✔
1858
    table.create_object().set_all(1, 1.1f, 1.2, "Hi", Decimal128(8.9));
2✔
1859
    table.create_object().set_all(987654321, 11.0f, 12.0, "Goodbye", Decimal128(10.1));
2✔
1860
    table.create_object().set_all(5, 4.0f, 3.0, "Hey", Decimal128("1.12e23"));
2✔
1861
    i_sum += 1 + 987654321 + 5;
2✔
1862
    f_sum += double(1.1f) + double(11.0f) + double(4.0f);
2✔
1863
    d_sum += 1.2 + 12.0 + 3.0;
2✔
1864
    decimal_sum += Decimal128(8.9) + Decimal128(10.1) + Decimal128("1.12e23");
2✔
1865
    double size = TBL_SIZE + 3;
2✔
1866

1867
    double epsilon = std::numeric_limits<double>::epsilon();
2✔
1868

1869
    // count
1870
    CHECK_EQUAL(1, table.count_int(int_col, 987654321));
2✔
1871
    CHECK_EQUAL(1, table.count_float(float_col, 11.0f));
2✔
1872
    CHECK_EQUAL(1, table.count_double(double_col, 12.0));
2✔
1873
    CHECK_EQUAL(1, table.count_string(str_col, "Goodbye"));
2✔
1874
    CHECK_EQUAL(1, table.count_decimal(decimal_col, Decimal128("1.12e23")));
2✔
1875
    ObjKey ret;
2✔
1876
    // minimum
1877
    CHECK_EQUAL(1, table.min(int_col, &ret)->get_int());
2✔
1878
    CHECK(ret && table.get_object(ret).get<Int>(int_col) == 1);
2✔
1879
    ret = ObjKey();
2✔
1880
    CHECK_EQUAL(1.1f, table.min(float_col, &ret)->get_float());
2✔
1881
    CHECK(ret);
2✔
1882
    CHECK_EQUAL(table.get_object(ret).get<Float>(float_col), 1.1f);
2✔
1883
    ret = ObjKey();
2✔
1884
    CHECK_EQUAL(1.2, table.min(double_col, &ret)->get_double());
2✔
1885
    CHECK(ret);
2✔
1886
    CHECK_EQUAL(table.get_object(ret).get<Double>(double_col), 1.2);
2✔
1887
    ret = ObjKey();
2✔
1888
    CHECK_EQUAL(Decimal128(7.7), table.min(decimal_col, &ret)->get_decimal());
2✔
1889
    CHECK(ret);
2✔
1890
    CHECK_EQUAL(table.get_object(ret).get<Decimal128>(decimal_col), Decimal128(7.7));
2✔
1891

1892
    // maximum
1893
    ret = ObjKey();
2✔
1894
    CHECK_EQUAL(987654321, table.max(int_col, &ret)->get_int());
2✔
1895
    CHECK(ret);
2✔
1896
    CHECK_EQUAL(table.get_object(ret).get<Int>(int_col), 987654321);
2✔
1897
    ret = ObjKey();
2✔
1898
    CHECK_EQUAL(11.0f, table.max(float_col, &ret)->get_float());
2✔
1899
    CHECK(ret);
2✔
1900
    CHECK_EQUAL(11.0f, table.get_object(ret).get<Float>(float_col));
2✔
1901
    ret = ObjKey();
2✔
1902
    CHECK_EQUAL(12.0, table.max(double_col, &ret)->get_double());
2✔
1903
    CHECK(ret);
2✔
1904
    CHECK_EQUAL(12.0, table.get_object(ret).get<Double>(double_col));
2✔
1905
    ret = ObjKey();
2✔
1906
    CHECK_EQUAL(Decimal128("1.12e23"), table.max(decimal_col, &ret)->get_decimal());
2✔
1907
    CHECK(ret);
2✔
1908
    CHECK_EQUAL(Decimal128("1.12e23"), table.get_object(ret).get<Decimal128>(decimal_col));
2✔
1909
    // sum
1910
    CHECK_APPROXIMATELY_EQUAL(double(i_sum), double(table.sum(int_col)->get_int()), 10 * epsilon);
2✔
1911
    CHECK_APPROXIMATELY_EQUAL(f_sum, table.sum(float_col)->get_double(), 10 * epsilon);
2✔
1912
    CHECK_APPROXIMATELY_EQUAL(d_sum, table.sum(double_col)->get_double(), 10 * epsilon);
2✔
1913
    CHECK_EQUAL(decimal_sum, table.sum(decimal_col)->get_decimal());
2✔
1914
    // average
1915
    size_t count = realm::npos;
2✔
1916
    CHECK_APPROXIMATELY_EQUAL(i_sum / size, table.avg(int_col, &count)->get_double(), 10 * epsilon);
2✔
1917
    CHECK_EQUAL(count, size);
2✔
1918
    count = realm::npos;
2✔
1919
    CHECK_APPROXIMATELY_EQUAL(f_sum / size, table.avg(float_col, &count)->get_double(), 10 * epsilon);
2✔
1920
    CHECK_EQUAL(count, size);
2✔
1921
    count = realm::npos;
2✔
1922
    CHECK_APPROXIMATELY_EQUAL(d_sum / size, table.avg(double_col, &count)->get_double(), 10 * epsilon);
2✔
1923
    CHECK_EQUAL(count, size);
2✔
1924
    count = realm::npos;
2✔
1925
    CHECK_EQUAL(decimal_sum / Decimal128(size), table.avg(decimal_col, &count)->get_decimal());
2✔
1926
    CHECK_EQUAL(count, size);
2✔
1927
}
2✔
1928

1929
TEST(Table_Aggregates2)
1930
{
2✔
1931
    Table table;
2✔
1932
    auto int_col = table.add_column(type_Int, "c_count");
2✔
1933
    int c = -420;
2✔
1934
    int s = 0;
2✔
1935
    while (c < -20) {
802✔
1936
        table.create_object().set(int_col, c);
800✔
1937
        s += c;
800✔
1938
        c++;
800✔
1939
    }
800✔
1940

1941
    CHECK_EQUAL(-420, table.min(int_col)->get_int());
2✔
1942
    CHECK_EQUAL(-21, table.max(int_col)->get_int());
2✔
1943
    CHECK_EQUAL(s, table.sum(int_col)->get_int());
2✔
1944
}
2✔
1945

1946
// Test Table methods max, min, avg, sum, on both nullable and non-nullable columns
1947
TEST(Table_Aggregates3)
1948
{
2✔
1949
    bool nullable = false;
2✔
1950

1951
    for (int i = 0; i < 2; i++) {
6✔
1952
        // First we test everything with columns being nullable and with each column having at least 1 null
1953
        // Then we test everything with non-nullable columns where the null entries will instead be just
1954
        // 0, 0.0, etc.
1955
        nullable = (i == 1);
4✔
1956

1957
        Group g;
4✔
1958
        TableRef table = g.add_table("Inventory");
4✔
1959

1960
        auto col_price = table->add_column(type_Int, "Price", nullable);
4✔
1961
        auto col_shipping = table->add_column(type_Float, "Shipping", nullable);
4✔
1962
        auto col_rating = table->add_column(type_Double, "Rating", nullable);
4✔
1963
        auto col_date = table->add_column(type_Timestamp, "Delivery date", nullable);
4✔
1964

1965
        Obj obj0 = table->create_object(ObjKey(0));
4✔
1966
        Obj obj1 = table->create_object(ObjKey(1));
4✔
1967
        Obj obj2 = table->create_object(ObjKey(2));
4✔
1968

1969
        obj0.set(col_price, 1);
4✔
1970
        // table->set_null(0, 1);
1971
        obj2.set(col_price, 3);
4✔
1972

1973
        // table->set_null(1, 0);
1974
        // table->set_null(1, 1);
1975
        obj2.set(col_shipping, 30.f);
4✔
1976

1977
        obj0.set(col_rating, 1.1);
4✔
1978
        obj1.set(col_rating, 2.2);
4✔
1979
        // table->set_null(2, 2);
1980

1981
        obj0.set(col_date, Timestamp(2, 2));
4✔
1982
        // table->set_null(4, 1);
1983
        obj2.set(col_date, Timestamp(6, 6));
4✔
1984

1985
        size_t count;
4✔
1986
        ObjKey pos;
4✔
1987
        if (nullable) {
4✔
1988
            // max
1989
            pos = ObjKey(123);
2✔
1990
            CHECK_EQUAL(table->max(col_price)->get_int(), 3);
2✔
1991
            CHECK_EQUAL(table->max(col_price, &pos)->get_int(), 3);
2✔
1992
            CHECK_EQUAL(pos, ObjKey(2));
2✔
1993

1994
            pos = ObjKey(123);
2✔
1995
            CHECK_EQUAL(table->max(col_shipping)->get_float(), 30.f);
2✔
1996
            CHECK_EQUAL(table->max(col_shipping, &pos)->get_float(), 30.f);
2✔
1997
            CHECK_EQUAL(pos, ObjKey(2));
2✔
1998

1999
            pos = ObjKey(123);
2✔
2000
            CHECK_EQUAL(table->max(col_rating)->get_double(), 2.2);
2✔
2001
            CHECK_EQUAL(table->max(col_rating, &pos)->get_double(), 2.2);
2✔
2002
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2003

2004
            pos = ObjKey(123);
2✔
2005
            CHECK_EQUAL(table->max(col_date)->get_timestamp(), Timestamp(6, 6));
2✔
2006
            CHECK_EQUAL(table->max(col_date, &pos)->get_timestamp(), Timestamp(6, 6));
2✔
2007
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2008

2009
            // min
2010
            pos = ObjKey(123);
2✔
2011
            CHECK_EQUAL(table->min(col_price)->get_int(), 1);
2✔
2012
            CHECK_EQUAL(table->min(col_price, &pos)->get_int(), 1);
2✔
2013
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2014

2015
            pos = ObjKey(123);
2✔
2016
            CHECK_EQUAL(table->min(col_shipping)->get_float(), 30.f);
2✔
2017
            CHECK_EQUAL(table->min(col_shipping, &pos)->get_float(), 30.f);
2✔
2018
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2019

2020
            pos = ObjKey(123);
2✔
2021
            CHECK_EQUAL(table->min(col_rating)->get_double(), 1.1);
2✔
2022
            CHECK_EQUAL(table->min(col_rating, &pos)->get_double(), 1.1);
2✔
2023
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2024

2025
            pos = ObjKey(123);
2✔
2026
            CHECK_EQUAL(table->min(col_date)->get_timestamp(), Timestamp(2, 2));
2✔
2027
            CHECK_EQUAL(table->min(col_date, &pos)->get_timestamp(), Timestamp(2, 2));
2✔
2028
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2029

2030
            // average
2031
            count = 123;
2✔
2032
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_price)->get_double(), (1 + 3) / 2., 0.01);
2✔
2033
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_price, &count)->get_double(), (1 + 3) / 2., 0.01);
2✔
2034
            CHECK_EQUAL(count, 2);
2✔
2035

2036
            count = 123;
2✔
2037
            CHECK_EQUAL(table->avg(col_shipping)->get_double(), 30.f);
2✔
2038
            CHECK_EQUAL(table->avg(col_shipping, &count)->get_double(), 30.f);
2✔
2039
            CHECK_EQUAL(count, 1);
2✔
2040

2041
            count = 123;
2✔
2042
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_rating)->get_double(), (1.1 + 2.2) / 2., 0.01);
2✔
2043
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_rating, &count)->get_double(), (1.1 + 2.2) / 2., 0.01);
2✔
2044
            CHECK_EQUAL(count, 2);
2✔
2045

2046
            // sum
2047
            CHECK_EQUAL(table->sum(col_price)->get_int(), 4);
2✔
2048
            CHECK_EQUAL(table->sum(col_shipping)->get_double(), 30.f);
2✔
2049
            CHECK_APPROXIMATELY_EQUAL(table->sum(col_rating)->get_double(), 1.1 + 2.2, 0.01);
2✔
2050
        }
2✔
2051
        else { // not nullable
2✔
2052
            // max
2053
            pos = ObjKey(123);
2✔
2054
            CHECK_EQUAL(table->max(col_price, &pos)->get_int(), 3);
2✔
2055
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2056

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

2061
            pos = ObjKey(123);
2✔
2062
            CHECK_EQUAL(table->max(col_rating, &pos)->get_double(), 2.2);
2✔
2063
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2064

2065
            pos = ObjKey(123);
2✔
2066
            CHECK_EQUAL(table->max(col_date, &pos)->get_timestamp(), Timestamp(6, 6));
2✔
2067
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2068

2069
            // min
2070
            pos = ObjKey(123);
2✔
2071
            CHECK_EQUAL(table->min(col_price, &pos)->get_int(), 0);
2✔
2072
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2073

2074
            pos = ObjKey(123);
2✔
2075
            CHECK_EQUAL(table->min(col_shipping, &pos)->get_float(), 0.f);
2✔
2076
            CHECK_EQUAL(pos, ObjKey(0));
2✔
2077

2078
            pos = ObjKey(123);
2✔
2079
            CHECK_EQUAL(table->min(col_rating, &pos)->get_double(), 0.);
2✔
2080
            CHECK_EQUAL(pos, ObjKey(2));
2✔
2081

2082
            pos = ObjKey(123);
2✔
2083
            // Timestamp(0, 0) is default value for non-nullable column
2084
            CHECK_EQUAL(table->min(col_date, &pos)->get_timestamp(), Timestamp(0, 0));
2✔
2085
            CHECK_EQUAL(pos, ObjKey(1));
2✔
2086

2087
            // average
2088
            count = 123;
2✔
2089
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_price, &count)->get_double(), (1 + 3 + 0) / 3., 0.01);
2✔
2090
            CHECK_EQUAL(count, 3);
2✔
2091

2092
            count = 123;
2✔
2093
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_shipping, &count)->get_double(), 30.f / 3., 0.01);
2✔
2094
            CHECK_EQUAL(count, 3);
2✔
2095

2096
            count = 123;
2✔
2097
            CHECK_APPROXIMATELY_EQUAL(table->avg(col_rating, &count)->get_double(), (1.1 + 2.2 + 0.) / 3., 0.01);
2✔
2098
            CHECK_EQUAL(count, 3);
2✔
2099

2100
            // sum
2101
            CHECK_EQUAL(table->sum(col_price)->get_int(), 4);
2✔
2102
            CHECK_EQUAL(table->sum(col_shipping)->get_double(), 30.f);
2✔
2103
            CHECK_APPROXIMATELY_EQUAL(table->sum(col_rating)->get_double(), 1.1 + 2.2, 0.01);
2✔
2104
        }
2✔
2105
    }
4✔
2106
}
2✔
2107

2108
TEST(Table_EmptyMinmax)
2109
{
2✔
2110
    Group g;
2✔
2111
    TableRef table = g.add_table("");
2✔
2112
    auto col = table->add_column(type_Timestamp, "date");
2✔
2113

2114
    ObjKey min_key;
2✔
2115
    bool is_null = table->min(col, &min_key)->is_null();
2✔
2116
    CHECK_EQUAL(min_key, null_key);
2✔
2117
    CHECK(is_null);
2✔
2118

2119
    ObjKey max_key;
2✔
2120
    is_null = table->max(col, &max_key)->is_null();
2✔
2121
    CHECK_EQUAL(max_key, null_key);
2✔
2122
    CHECK(is_null);
2✔
2123
}
2✔
2124

2125
TEST(Table_EnumStringInsertEmptyRow)
2126
{
2✔
2127
    Table table;
2✔
2128
    auto col_str = table.add_column(type_String, "strings");
2✔
2129
    for (int i = 0; i < 128; ++i)
258✔
2130
        table.create_object().set(col_str, "foo");
256✔
2131

2132
    CHECK_EQUAL(0, table.get_num_unique_values(col_str));
2✔
2133
    table.enumerate_string_column(col_str);
2✔
2134
    // Make sure we now have an enumerated strings column
2135
    CHECK_EQUAL(1, table.get_num_unique_values(col_str));
2✔
2136
    Obj obj = table.create_object();
2✔
2137
    CHECK_EQUAL("", obj.get<String>(col_str));
2✔
2138
    CHECK_EQUAL(2, table.get_num_unique_values(col_str));
2✔
2139
}
2✔
2140

2141
TEST(Table_AddColumnWithThreeLevelBptree)
2142
{
2✔
2143
    Table table;
2✔
2144
    std::vector<ObjKey> keys;
2✔
2145
    table.add_column(type_Int, "int0");
2✔
2146
    table.create_objects(REALM_MAX_BPNODE_SIZE * REALM_MAX_BPNODE_SIZE + 1, keys);
2✔
2147
    table.add_column(type_Int, "int1");
2✔
2148
    table.verify();
2✔
2149
}
2✔
2150

2151
TEST(Table_DeleteObjectsInFirstCluster)
2152
{
2✔
2153
    // Designed to exercise logic if cluster size is 4
2154
    Table table;
2✔
2155
    table.add_column(type_Int, "int0");
2✔
2156

2157
    ObjKeys keys;
2✔
2158
    table.create_objects(32, keys);
2✔
2159

2160
    // delete objects in first cluster
2161
    table.remove_object(keys[2]);
2✔
2162
    table.remove_object(keys[1]);
2✔
2163
    table.remove_object(keys[3]);
2✔
2164
    table.remove_object(keys[0]);
2✔
2165

2166
    table.create_object(ObjKey(1)); // Must not throw
2✔
2167

2168
    // Replace root node
2169
    while (table.size() > 16)
28✔
2170
        table.begin()->remove();
26✔
2171

2172
    // table.dump_objects();
2173
    table.create_object(ObjKey(1)); // Must not throw
2✔
2174
}
2✔
2175

2176
TEST(Table_ClearWithTwoLevelBptree)
2177
{
2✔
2178
    Table table;
2✔
2179
    std::vector<ObjKey> keys;
2✔
2180
    table.add_column(type_String, "strings");
2✔
2181
    table.create_objects(REALM_MAX_BPNODE_SIZE + 1, keys);
2✔
2182
    table.clear();
2✔
2183
    table.verify();
2✔
2184
}
2✔
2185

2186
TEST(Table_IndexStringDelete)
2187
{
2✔
2188
    Table t;
2✔
2189
    auto col = t.add_column(type_String, "str");
2✔
2190
    t.add_search_index(col);
2✔
2191

2192
    for (size_t i = 0; i < 1000; ++i) {
2,002✔
2193
        std::string out(util::to_string(i));
2,000✔
2194
        t.create_object().set<String>(col, out);
2,000✔
2195
    }
2,000✔
2196

2197
    t.clear();
2✔
2198

2199
    for (size_t i = 0; i < 1000; ++i) {
2,002✔
2200
        std::string out(util::to_string(i));
2,000✔
2201
        t.create_object().set<String>(col, out);
2,000✔
2202
    }
2,000✔
2203
}
2✔
2204

2205

2206
TEST(Table_NullableChecks)
2207
{
2✔
2208
    Table t;
2✔
2209
    TableView tv;
2✔
2210
    constexpr bool nullable = true;
2✔
2211
    auto str_col = t.add_column(type_String, "str", nullable);
2✔
2212
    auto int_col = t.add_column(type_Int, "int", nullable);
2✔
2213
    auto bool_col = t.add_column(type_Bool, "bool", nullable);
2✔
2214
    auto ts_col = t.add_column(type_Timestamp, "timestamp", nullable);
2✔
2215
    auto float_col = t.add_column(type_Float, "float", nullable);
2✔
2216
    auto double_col = t.add_column(type_Double, "double", nullable);
2✔
2217
    auto binary_col = t.add_column(type_Binary, "binary", nullable);
2✔
2218

2219
    Obj obj = t.create_object();
2✔
2220
    StringData sd; // construct a null reference
2✔
2221
    Timestamp ts;  // null
2✔
2222
    BinaryData bd; // null
2✔
2223
    obj.set(str_col, sd);
2✔
2224
    obj.set(int_col, realm::null());
2✔
2225
    obj.set(bool_col, realm::null());
2✔
2226
    obj.set(ts_col, ts);
2✔
2227
    obj.set(float_col, realm::null());
2✔
2228
    obj.set(double_col, realm::null());
2✔
2229
    obj.set(binary_col, bd);
2✔
2230

2231
    // is_null is always reliable regardless of type
2232
    CHECK(obj.is_null(str_col));
2✔
2233
    CHECK(obj.is_null(int_col));
2✔
2234
    CHECK(obj.is_null(bool_col));
2✔
2235
    CHECK(obj.is_null(ts_col));
2✔
2236
    CHECK(obj.is_null(float_col));
2✔
2237
    CHECK(obj.is_null(double_col));
2✔
2238
    CHECK(obj.is_null(binary_col));
2✔
2239

2240
    StringData str0 = obj.get<String>(str_col);
2✔
2241
    CHECK(str0.is_null());
2✔
2242
    util::Optional<int64_t> int0 = obj.get<util::Optional<int64_t>>(int_col);
2✔
2243
    CHECK(!int0);
2✔
2244
    util::Optional<bool> bool0 = obj.get<util::Optional<bool>>(bool_col);
2✔
2245
    CHECK(!bool0);
2✔
2246
    Timestamp ts0 = obj.get<Timestamp>(ts_col);
2✔
2247
    CHECK(ts0.is_null());
2✔
2248
    util::Optional<float> float0 = obj.get<util::Optional<float>>(float_col);
2✔
2249
    CHECK(!float0);
2✔
2250
    util::Optional<double> double0 = obj.get<util::Optional<double>>(double_col);
2✔
2251
    CHECK(!double0);
2✔
2252
    BinaryData binary0 = obj.get<Binary>(binary_col);
2✔
2253
    CHECK(binary0.is_null());
2✔
2254
}
2✔
2255

2256

2257
TEST(Table_Nulls)
2258
{
2✔
2259
    // 'round' lets us run this entire test both with and without index and with/without optimize/enum
2260
    for (size_t round = 0; round < 5; round++) {
12✔
2261
        Table t;
10✔
2262
        TableView tv;
10✔
2263
        auto col_str = t.add_column(type_String, "str", true /*nullable*/);
10✔
2264

2265
        if (round == 1)
10✔
2266
            t.add_search_index(col_str);
2✔
2267
        else if (round == 2)
8✔
2268
            t.enumerate_string_column(col_str);
2✔
2269
        else if (round == 3) {
6✔
2270
            t.add_search_index(col_str);
2✔
2271
            t.enumerate_string_column(col_str);
2✔
2272
        }
2✔
2273
        else if (round == 4) {
4✔
2274
            t.enumerate_string_column(col_str);
2✔
2275
            t.add_search_index(col_str);
2✔
2276
        }
2✔
2277

2278
        std::vector<ObjKey> keys;
10✔
2279
        t.create_objects(3, keys);
10✔
2280
        t.get_object(keys[0]).set(col_str, "foo"); // short strings
10✔
2281
        t.get_object(keys[1]).set(col_str, "");
10✔
2282
        t.get_object(keys[2]).set(col_str, StringData()); // null
10✔
2283

2284
        CHECK_EQUAL(1, t.count_string(col_str, "foo"));
10✔
2285
        CHECK_EQUAL(1, t.count_string(col_str, ""));
10✔
2286
        CHECK_EQUAL(1, t.count_string(col_str, realm::null()));
10✔
2287

2288
        CHECK_EQUAL(keys[0], t.find_first_string(col_str, "foo"));
10✔
2289
        CHECK_EQUAL(keys[1], t.find_first_string(col_str, ""));
10✔
2290
        CHECK_EQUAL(keys[2], t.find_first_string(col_str, realm::null()));
10✔
2291

2292
        tv = t.find_all_string(col_str, "foo");
10✔
2293
        CHECK_EQUAL(1, tv.size());
10✔
2294
        CHECK_EQUAL(keys[0], tv.get_key(0));
10✔
2295
        tv = t.find_all_string(col_str, "");
10✔
2296
        CHECK_EQUAL(1, tv.size());
10✔
2297
        CHECK_EQUAL(keys[1], tv.get_key(0));
10✔
2298
        tv = t.find_all_string(col_str, realm::null());
10✔
2299
        CHECK_EQUAL(1, tv.size());
10✔
2300
        CHECK_EQUAL(keys[2], tv.get_key(0));
10✔
2301

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

2305
        CHECK_EQUAL(1, t.count_string(col_str, string_medium));
10✔
2306
        CHECK_EQUAL(1, t.count_string(col_str, ""));
10✔
2307
        CHECK_EQUAL(1, t.count_string(col_str, realm::null()));
10✔
2308

2309
        CHECK_EQUAL(keys[0], t.find_first_string(col_str, string_medium));
10✔
2310
        CHECK_EQUAL(keys[1], t.find_first_string(col_str, ""));
10✔
2311
        CHECK_EQUAL(keys[2], t.find_first_string(col_str, realm::null()));
10✔
2312

2313
        tv = t.find_all_string(col_str, string_medium);
10✔
2314
        CHECK_EQUAL(1, tv.size());
10✔
2315
        CHECK_EQUAL(keys[0], tv.get_key(0));
10✔
2316
        tv = t.find_all_string(col_str, "");
10✔
2317
        CHECK_EQUAL(1, tv.size());
10✔
2318
        CHECK_EQUAL(keys[1], tv.get_key(0));
10✔
2319
        tv = t.find_all_string(col_str, realm::null());
10✔
2320
        CHECK_EQUAL(1, tv.size());
10✔
2321
        CHECK_EQUAL(keys[2], tv.get_key(0));
10✔
2322

2323

2324
        // long strings (>= 64)
2325
        const char* string_long = "xxxxxxxxxxYYYYYYYYYYxxxxxxxxxxYYYYYYYYYYxxxxxxxxxxYYYYYYYYYYxxxxxxxxxx";
10✔
2326
        t.get_object(keys[0]).set(col_str, string_long);
10✔
2327

2328
        CHECK_EQUAL(1, t.count_string(col_str, string_long));
10✔
2329
        CHECK_EQUAL(1, t.count_string(col_str, ""));
10✔
2330
        CHECK_EQUAL(1, t.count_string(col_str, realm::null()));
10✔
2331

2332
        CHECK_EQUAL(keys[0], t.find_first_string(col_str, string_long));
10✔
2333
        CHECK_EQUAL(keys[1], t.find_first_string(col_str, ""));
10✔
2334
        CHECK_EQUAL(keys[2], t.find_first_string(col_str, realm::null()));
10✔
2335

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

2347
    {
2✔
2348
        Table t;
2✔
2349
        auto col_int = t.add_column(type_Int, "int", true);         // nullable = true
2✔
2350
        auto col_bool = t.add_column(type_Bool, "bool", true);      // nullable = true
2✔
2351
        auto col_date = t.add_column(type_Timestamp, "date", true); // nullable = true
2✔
2352

2353
        Obj obj0 = t.create_object();
2✔
2354
        Obj obj1 = t.create_object();
2✔
2355
        ObjKey k0 = obj0.get_key();
2✔
2356
        ObjKey k1 = obj1.get_key();
2✔
2357

2358
        obj0.set(col_int, 65);
2✔
2359
        obj0.set(col_bool, false);
2✔
2360
        obj0.set(col_date, Timestamp(3, 0));
2✔
2361

2362
        CHECK_EQUAL(65, obj0.get<Int>(col_int));
2✔
2363
        CHECK_EQUAL(false, obj0.get<Bool>(col_bool));
2✔
2364
        CHECK_EQUAL(Timestamp(3, 0), obj0.get<Timestamp>(col_date));
2✔
2365

2366
        CHECK_EQUAL(65, t.max(col_int)->get_int());
2✔
2367
        CHECK_EQUAL(65, t.min(col_int)->get_int());
2✔
2368
        CHECK_EQUAL(Timestamp(3, 0), t.max(col_date)->get_timestamp());
2✔
2369
        CHECK_EQUAL(Timestamp(3, 0), t.min(col_date)->get_timestamp());
2✔
2370

2371
        CHECK_NOT(obj0.is_null(col_int));
2✔
2372
        CHECK_NOT(obj0.is_null(col_bool));
2✔
2373
        CHECK_NOT(obj0.is_null(col_date));
2✔
2374

2375
        CHECK_THROW_ANY(obj1.get<Int>(col_int));
2✔
2376
        CHECK(obj1.is_null(col_int));
2✔
2377
        CHECK(obj1.is_null(col_bool));
2✔
2378
        CHECK(obj1.is_null(col_date));
2✔
2379

2380
        CHECK_EQUAL(k1, t.find_first_null(col_int));
2✔
2381
        CHECK_EQUAL(k1, t.find_first_null(col_bool));
2✔
2382
        CHECK_EQUAL(k1, t.find_first_null(col_date));
2✔
2383

2384
        CHECK_EQUAL(null_key, t.find_first_int(col_int, -1));
2✔
2385
        CHECK_EQUAL(null_key, t.find_first_bool(col_bool, true));
2✔
2386
        CHECK_EQUAL(null_key, t.find_first_timestamp(col_date, Timestamp(5, 0)));
2✔
2387

2388
        CHECK_EQUAL(k0, t.find_first_int(col_int, 65));
2✔
2389
        CHECK_EQUAL(k0, t.find_first_bool(col_bool, false));
2✔
2390
        CHECK_EQUAL(k0, t.find_first_timestamp(col_date, Timestamp(3, 0)));
2✔
2391

2392
        obj0.set_null(col_int);
2✔
2393
        obj0.set_null(col_bool);
2✔
2394
        obj0.set_null(col_date);
2✔
2395

2396
        CHECK(obj0.is_null(col_int));
2✔
2397
        CHECK(obj0.is_null(col_bool));
2✔
2398
        CHECK(obj0.is_null(col_date));
2✔
2399
    }
2✔
2400
    {
2✔
2401
        Table t;
2✔
2402
        auto col_float = t.add_column(type_Float, "float", true);    // nullable = true
2✔
2403
        auto col_double = t.add_column(type_Double, "double", true); // nullable = true
2✔
2404

2405
        Obj obj0 = t.create_object();
2✔
2406
        Obj obj1 = t.create_object();
2✔
2407
        ObjKey k0 = obj0.get_key();
2✔
2408
        ObjKey k1 = obj1.get_key();
2✔
2409

2410
        obj0.set_all(1.23f, 12.3);
2✔
2411

2412
        CHECK_EQUAL(1.23f, obj0.get<float>(col_float));
2✔
2413
        CHECK_EQUAL(12.3, obj0.get<double>(col_double));
2✔
2414

2415
        CHECK_EQUAL(1.23f, t.max(col_float)->get_float());
2✔
2416
        CHECK_EQUAL(1.23f, t.min(col_float)->get_float());
2✔
2417
        CHECK_EQUAL(12.3, t.max(col_double)->get_double());
2✔
2418
        CHECK_EQUAL(12.3, t.min(col_double)->get_double());
2✔
2419

2420
        CHECK_NOT(obj0.is_null(col_float));
2✔
2421
        CHECK_NOT(obj0.is_null(col_double));
2✔
2422

2423
        CHECK(obj1.is_null(col_float));
2✔
2424
        CHECK(obj1.is_null(col_double));
2✔
2425

2426
        CHECK_EQUAL(k1, t.find_first_null(col_float));
2✔
2427
        CHECK_EQUAL(k1, t.find_first_null(col_double));
2✔
2428

2429
        CHECK_EQUAL(null_key, t.find_first_float(col_float, 2.22f));
2✔
2430
        CHECK_EQUAL(null_key, t.find_first_double(col_double, 2.22));
2✔
2431

2432
        CHECK_EQUAL(k0, t.find_first_float(col_float, 1.23f));
2✔
2433
        CHECK_EQUAL(k0, t.find_first_double(col_double, 12.3));
2✔
2434

2435
        util::Optional<Float> f_val = 5.f;
2✔
2436
        obj0.set(col_float, f_val);
2✔
2437
        CHECK_NOT(obj0.is_null(col_float));
2✔
2438
        CHECK_EQUAL(obj0.get<Optional<float>>(col_float), 5.f);
2✔
2439

2440
        util::Optional<Double> d_val = 5.;
2✔
2441
        obj0.set(col_double, d_val);
2✔
2442
        CHECK_NOT(obj0.is_null(col_double));
2✔
2443
        CHECK_EQUAL(obj0.get<Optional<double>>(col_double), 5.);
2✔
2444

2445
        obj0.set_null(col_float);
2✔
2446
        obj0.set_null(col_double);
2✔
2447

2448
        CHECK(obj0.is_null(col_float));
2✔
2449
        CHECK(obj0.is_null(col_double));
2✔
2450
    }
2✔
2451
}
2✔
2452

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

2462
    // First a simple trigger of `Assertion failed: value <= 0xFFFFFFL [26000016, 16777215]`
2463
    {
2✔
2464
        BPlusTree<BinaryData> c(Allocator::get_default());
2✔
2465
        c.create();
2✔
2466

2467
        c.add(BinaryData(buf.get(), 13000000));
2✔
2468
        c.set(0, BinaryData(buf.get(), 14000000));
2✔
2469

2470
        c.destroy();
2✔
2471
    }
2✔
2472

2473
    // Now a small fuzzy test to catch other such bugs
2474
    {
2✔
2475
        Table t;
2✔
2476
        std::vector<ObjKey> keys;
2✔
2477
        auto col_bin = t.add_column(type_Binary, "Binaries", true);
2✔
2478

2479
        for (size_t j = 0; j < 100; j++) {
202✔
2480
            size_t r = (j * 123456789 + 123456789) % 100;
200✔
2481
            if (r < 20) {
200✔
2482
                keys.push_back(t.create_object().get_key());
40✔
2483
            }
40✔
2484
            else if (t.size() > 0 && t.size() < 5) {
160✔
2485
                // Set only if there are no more than 4 rows, else it takes up too much space on devices (4 * 16 MB
2486
                // worst case now)
2487
                size_t row = (j * 123456789 + 123456789) % t.size();
102✔
2488
                size_t len = (j * 123456789 + 123456789) % 16000000;
102✔
2489
                BinaryData bd;
102✔
2490
                bd = BinaryData(buf.get(), len);
102✔
2491
                t.get_object(keys[row]).set(col_bin, bd);
102✔
2492
            }
102✔
2493
            else if (t.size() >= 4) {
58✔
2494
                t.clear();
6✔
2495
                keys.clear();
6✔
2496
            }
6✔
2497
        }
200✔
2498
    }
2✔
2499
}
2✔
2500

2501

2502
TEST(Table_DetachedAccessor)
2503
{
2✔
2504
    Group group;
2✔
2505
    TableRef table = group.add_table("table");
2✔
2506
    auto col_int = table->add_column(type_Int, "i");
2✔
2507
    auto col_str = table->add_column(type_String, "s");
2✔
2508
    table->add_column(type_Binary, "b");
2✔
2509
    table->add_column(*table, "l");
2✔
2510
    ObjKey key0 = table->create_object().get_key();
2✔
2511
    Obj obj1 = table->create_object();
2✔
2512
    group.remove_table("table");
2✔
2513

2514
    CHECK_THROW(table->clear(), InvalidTableRef);
2✔
2515
    CHECK_THROW(table->add_search_index(col_int), InvalidTableRef);
2✔
2516
    CHECK_THROW(table->remove_search_index(col_int), InvalidTableRef);
2✔
2517
    CHECK_THROW(table->get_object(key0), InvalidTableRef);
2✔
2518
    CHECK_THROW_ANY(obj1.set(col_str, "hello"));
2✔
2519
}
2✔
2520

2521
TEST(Table_addRowsToTableWithNoColumns)
2522
{
2✔
2523
    Group g; // type_Link must be part of a group
2✔
2524
    TableRef t = g.add_table("t");
2✔
2525

2526
    t->create_object();
2✔
2527
    CHECK_EQUAL(t->size(), 1);
2✔
2528
    auto col = t->add_column(type_String, "str_col");
2✔
2529
    t->create_object();
2✔
2530
    CHECK_EQUAL(t->size(), 2);
2✔
2531
    t->add_search_index(col);
2✔
2532
    t->create_object();
2✔
2533
    CHECK_EQUAL(t->size(), 3);
2✔
2534
    t->remove_column(col);
2✔
2535
    CHECK_EQUAL(t->size(), 3);
2✔
2536

2537
    // Check that links are nulled when connected table is cleared
2538
    TableRef u = g.add_table("u");
2✔
2539
    auto col_link = u->add_column(*t, "link from u to t");
2✔
2540
    Obj obj = u->create_object();
2✔
2541
    CHECK_EQUAL(u->size(), 1);
2✔
2542
    CHECK_EQUAL(t->size(), 3);
2✔
2543
    CHECK_LOGIC_ERROR(obj.set(col_link, ObjKey(45)), ErrorCodes::KeyNotFound);
2✔
2544
    CHECK(obj.is_null(col_link));
2✔
2545
    CHECK_EQUAL(t->size(), 3);
2✔
2546
    ObjKey k = t->create_object().get_key();
2✔
2547
    obj.set(col_link, k);
2✔
2548
    CHECK_EQUAL(obj.get<ObjKey>(col_link), k);
2✔
2549
    CHECK(!obj.is_null(col_link));
2✔
2550
    CHECK_EQUAL(t->size(), 4);
2✔
2551
    t->clear();
2✔
2552
    CHECK_EQUAL(t->size(), 0);
2✔
2553
    CHECK_EQUAL(u->size(), 1);
2✔
2554
    CHECK(obj.is_null(col_link));
2✔
2555
    u->remove_column(col_link);
2✔
2556
}
2✔
2557

2558

2559
TEST(Table_getVersionCounterAfterRowAccessor)
2560
{
2✔
2561
    Table t;
2✔
2562
    auto col_bool = t.add_column(type_Bool, "bool", true);
2✔
2563
    auto col_int = t.add_column(type_Int, "int", true);
2✔
2564
    auto col_string = t.add_column(type_String, "string", true);
2✔
2565
    auto col_float = t.add_column(type_Float, "float", true);
2✔
2566
    auto col_double = t.add_column(type_Double, "double", true);
2✔
2567
    auto col_binary = t.add_column(type_Binary, "binary", true);
2✔
2568
    auto col_date = t.add_column(type_Timestamp, "timestamp", true);
2✔
2569

2570
    Obj obj = t.create_object();
2✔
2571

2572
    int_fast64_t ver = t.get_content_version();
2✔
2573
    int_fast64_t newVer;
2✔
2574

2575
    auto check_ver_bump = [&]() {
16✔
2576
        newVer = t.get_content_version();
16✔
2577
        CHECK_GREATER(newVer, ver);
16✔
2578
        ver = newVer;
16✔
2579
    };
16✔
2580

2581
    obj.set<Bool>(col_bool, true);
2✔
2582
    check_ver_bump();
2✔
2583

2584
    obj.set<Int>(col_int, 42);
2✔
2585
    check_ver_bump();
2✔
2586

2587
    obj.set<String>(col_string, "foo");
2✔
2588
    check_ver_bump();
2✔
2589

2590
    obj.set<Float>(col_float, 0.42f);
2✔
2591
    check_ver_bump();
2✔
2592

2593
    obj.set<Double>(col_double, 0.42);
2✔
2594
    check_ver_bump();
2✔
2595

2596
    obj.set<Binary>(col_binary, BinaryData("binary", 7));
2✔
2597
    check_ver_bump();
2✔
2598

2599
    obj.set<Timestamp>(col_date, Timestamp(777, 888));
2✔
2600
    check_ver_bump();
2✔
2601

2602
    obj.set_null(col_string);
2✔
2603
    check_ver_bump();
2✔
2604
}
2✔
2605

2606

2607
TEST(Table_object_basic)
2608
{
2✔
2609
    Table table;
2✔
2610
    auto int_col = table.add_column(type_Int, "int");
2✔
2611
    auto intnull_col = table.add_column(type_Int, "intnull", true);
2✔
2612

2613
    char data[10];
2✔
2614
    memset(data, 0x5a, 10);
2✔
2615
    BinaryData bin_data(data, 10);
2✔
2616
    BinaryData bin_zero(data, 0);
2✔
2617

2618
    table.create_object(ObjKey(5)).set_all(100, 7);
2✔
2619
    CHECK_EQUAL(table.size(), 1);
2✔
2620
    CHECK_THROW(table.create_object(ObjKey(5)), KeyAlreadyUsed);
2✔
2621
    CHECK_EQUAL(table.size(), 1);
2✔
2622
    table.create_object(ObjKey(2));
2✔
2623
    Obj x = table.create_object(ObjKey(7));
2✔
2624
    table.create_object(ObjKey(8));
2✔
2625
    table.create_object(ObjKey(10));
2✔
2626
    table.create_object(ObjKey(6));
2✔
2627

2628
    Obj y = table.get_object(ObjKey(5));
2✔
2629

2630
    // Int
2631
    CHECK(!x.is_null(int_col));
2✔
2632
    CHECK_EQUAL(0, x.get<int64_t>(int_col));
2✔
2633
    CHECK(x.is_null(intnull_col));
2✔
2634

2635
    CHECK_EQUAL(100, y.get<int64_t>(int_col));
2✔
2636
    CHECK(!y.is_null(intnull_col));
2✔
2637
    CHECK_EQUAL(7, y.get<util::Optional<int64_t>>(intnull_col));
2✔
2638
    y.set_null(intnull_col);
2✔
2639
    CHECK(y.is_null(intnull_col));
2✔
2640

2641
    // Boolean
2642
    auto bool_col = table.add_column(type_Bool, "bool");
2✔
2643
    auto boolnull_col = table.add_column(type_Bool, "boolnull", true);
2✔
2644
    y.set(bool_col, true);
2✔
2645
    y.set(boolnull_col, false);
2✔
2646

2647
    CHECK(!x.is_null(bool_col));
2✔
2648
    CHECK_EQUAL(false, x.get<Bool>(bool_col));
2✔
2649
    CHECK(x.is_null(boolnull_col));
2✔
2650

2651
    CHECK_EQUAL(true, y.get<Bool>(bool_col));
2✔
2652
    CHECK(!y.is_null(boolnull_col));
2✔
2653
    auto bool_val = y.get<util::Optional<Bool>>(boolnull_col);
2✔
2654
    CHECK_EQUAL(true, bool(bool_val));
2✔
2655
    CHECK_EQUAL(false, *bool_val);
2✔
2656
    y.set_null(boolnull_col);
2✔
2657
    CHECK(y.is_null(boolnull_col));
2✔
2658

2659
    // Float
2660
    auto float_col = table.add_column(type_Float, "float");
2✔
2661
    auto floatnull_col = table.add_column(type_Float, "floatnull", true);
2✔
2662
    y.set(float_col, 2.7182818f);
2✔
2663
    y.set(floatnull_col, 3.1415927f);
2✔
2664

2665
    CHECK(!x.is_null(float_col));
2✔
2666
    CHECK_EQUAL(0.0f, x.get<Float>(float_col));
2✔
2667
    CHECK(x.is_null(floatnull_col));
2✔
2668

2669
    CHECK_EQUAL(2.7182818f, y.get<Float>(float_col));
2✔
2670
    CHECK(!y.is_null(floatnull_col));
2✔
2671
    CHECK_EQUAL(3.1415927f, y.get<util::Optional<Float>>(floatnull_col));
2✔
2672
    y.set_null(floatnull_col);
2✔
2673
    CHECK(y.is_null(floatnull_col));
2✔
2674

2675
    // Double
2676
    auto double_col = table.add_column(type_Double, "double");
2✔
2677
    auto doublenull_col = table.add_column(type_Double, "doublenull", true);
2✔
2678
    y.set(double_col, 2.718281828459045);
2✔
2679
    y.set(doublenull_col, 3.141592653589793);
2✔
2680

2681
    CHECK(!x.is_null(double_col));
2✔
2682
    CHECK_EQUAL(0.0f, x.get<Double>(double_col));
2✔
2683
    CHECK(x.is_null(doublenull_col));
2✔
2684

2685
    CHECK_EQUAL(2.718281828459045, y.get<Double>(double_col));
2✔
2686
    CHECK(!y.is_null(doublenull_col));
2✔
2687
    CHECK_EQUAL(3.141592653589793, y.get<util::Optional<Double>>(doublenull_col));
2✔
2688
    y.set_null(doublenull_col);
2✔
2689
    CHECK(y.is_null(doublenull_col));
2✔
2690

2691
    // String
2692
    auto str_col = table.add_column(type_String, "str");
2✔
2693
    auto strnull_col = table.add_column(type_String, "strnull", true);
2✔
2694
    y.set(str_col, "Hello");
2✔
2695
    y.set(strnull_col, "World");
2✔
2696

2697
    CHECK(!x.is_null(str_col));
2✔
2698
    CHECK_EQUAL("", x.get<String>(str_col));
2✔
2699
    CHECK(x.is_null(strnull_col));
2✔
2700

2701
    CHECK_EQUAL("Hello", y.get<String>(str_col));
2✔
2702
    CHECK(!y.is_null(strnull_col));
2✔
2703
    CHECK_EQUAL("World", y.get<String>(strnull_col));
2✔
2704
    y.set_null(strnull_col);
2✔
2705
    CHECK(y.is_null(strnull_col));
2✔
2706

2707
    // Upgrade to medium leaf
2708
    y.set(str_col, "This is a fine day");
2✔
2709
    CHECK_EQUAL("This is a fine day", y.get<String>(str_col));
2✔
2710
    CHECK(!y.is_null(str_col));
2✔
2711

2712
    // Binary
2713
    auto bin_col = table.add_column(type_Binary, "bin");
2✔
2714
    auto binnull_col = table.add_column(type_Binary, "binnull", true);
2✔
2715
    y.set(bin_col, bin_data);
2✔
2716
    y.set(binnull_col, bin_data);
2✔
2717

2718
    CHECK(!x.is_null(bin_col));
2✔
2719
    CHECK_EQUAL(bin_zero, x.get<Binary>(bin_col));
2✔
2720
    CHECK(x.is_null(binnull_col));
2✔
2721

2722
    CHECK_EQUAL(bin_data, y.get<Binary>(bin_col));
2✔
2723
    CHECK(!y.is_null(binnull_col));
2✔
2724
    CHECK_EQUAL(bin_data, y.get<Binary>(binnull_col));
2✔
2725
    y.set_null(binnull_col);
2✔
2726
    CHECK(y.is_null(binnull_col));
2✔
2727

2728
    // Upgrade from small to big
2729
    char big_data[100];
2✔
2730
    memset(big_data, 0xa5, 100);
2✔
2731
    BinaryData bin_data_big(big_data, 100);
2✔
2732
    x.set(bin_col, bin_data);
2✔
2733
    y.set(bin_col, bin_data_big);
2✔
2734
    CHECK_EQUAL(bin_data, x.get<Binary>(bin_col));
2✔
2735
    CHECK_EQUAL(bin_data_big, y.get<Binary>(bin_col));
2✔
2736
    CHECK(!y.is_null(bin_col));
2✔
2737

2738
    // Timestamp
2739
    auto ts_col = table.add_column(type_Timestamp, "ts");
2✔
2740
    auto tsnull_col = table.add_column(type_Timestamp, "tsnull", true);
2✔
2741
    y.set(ts_col, Timestamp(123, 456));
2✔
2742
    y.set(tsnull_col, Timestamp(789, 10));
2✔
2743

2744
    CHECK(!x.is_null(ts_col));
2✔
2745
    CHECK_EQUAL(Timestamp(0, 0), x.get<Timestamp>(ts_col));
2✔
2746
    CHECK(x.is_null(tsnull_col));
2✔
2747

2748
    CHECK_EQUAL(Timestamp(123, 456), y.get<Timestamp>(ts_col));
2✔
2749
    CHECK(!y.is_null(tsnull_col));
2✔
2750
    CHECK_EQUAL(Timestamp(789, 10), y.get<Timestamp>(tsnull_col));
2✔
2751
    y.set_null(binnull_col);
2✔
2752
    CHECK(y.is_null(binnull_col));
2✔
2753

2754
    // Check that accessing a removed object will throw
2755
    table.remove_object(ObjKey(5));
2✔
2756
    CHECK_THROW(y.get<int64_t>(intnull_col), KeyNotFound);
2✔
2757

2758
    CHECK(table.get_object(ObjKey(8)).is_null(intnull_col));
2✔
2759
}
2✔
2760

2761

2762
TEST(Table_ObjectsWithNoColumns)
2763
{
2✔
2764
    Table table;
2✔
2765
    std::vector<ObjKey> keys;
2✔
2766
    table.create_objects(REALM_MAX_BPNODE_SIZE * 2, keys);
2✔
2767
    CHECK_NOT(table.is_empty());
2✔
2768
    CHECK_EQUAL(table.size(), REALM_MAX_BPNODE_SIZE * 2);
2✔
2769
    for (ObjKey k : keys) {
4,000✔
2770
        Obj obj = table.get_object(k);
4,000✔
2771
        CHECK(obj.is_valid());
4,000✔
2772
        obj.remove();
4,000✔
2773
        CHECK(!obj.is_valid());
4,000✔
2774
    }
4,000✔
2775
    CHECK(table.is_empty());
2✔
2776
    CHECK_EQUAL(table.size(), 0);
2✔
2777
}
2✔
2778

2779
TEST(Table_remove_column)
2780
{
2✔
2781
    Table table;
2✔
2782
    table.add_column(type_Int, "int1");
2✔
2783
    auto int2_col = table.add_column(type_Int, "int2");
2✔
2784
    table.add_column(type_Int, "int3");
2✔
2785

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

2788
    CHECK_EQUAL(obj.get<int64_t>("int1"), 100);
2✔
2789
    CHECK_EQUAL(obj.get<int64_t>("int2"), 7);
2✔
2790
    CHECK_EQUAL(obj.get<int64_t>("int3"), 25);
2✔
2791

2792
    table.remove_column(int2_col);
2✔
2793

2794
    CHECK_EQUAL(obj.get<int64_t>("int1"), 100);
2✔
2795
    CHECK_THROW(obj.get<int64_t>("int2"), LogicError);
2✔
2796
    CHECK_EQUAL(obj.get<int64_t>("int3"), 25);
2✔
2797
    table.add_column(type_Int, "int4");
2✔
2798
    CHECK_EQUAL(obj.get<int64_t>("int4"), 0);
2✔
2799
}
2✔
2800

2801
TEST(Table_object_merge_nodes)
2802
{
2✔
2803
    // This test works best for REALM_MAX_BPNODE_SIZE == 8.
2804
    // To be used mostly as a help when debugging new implementation
2805

2806
    int nb_rows = REALM_MAX_BPNODE_SIZE * 8;
2✔
2807
    Table table;
2✔
2808
    std::vector<int64_t> key_set;
2✔
2809
    auto c0 = table.add_column(type_Int, "int1");
2✔
2810
    auto c1 = table.add_column(type_Int, "int2", true);
2✔
2811

2812
    for (int i = 0; i < nb_rows; i++) {
16,002✔
2813
        table.create_object(ObjKey(i)).set_all(i << 1, i << 2);
16,000✔
2814
        key_set.push_back(i);
16,000✔
2815
    }
16,000✔
2816

2817
    for (int i = 0; i < nb_rows; i++) {
16,002✔
2818
        auto key_index = test_util::random_int<int64_t>(0, key_set.size() - 1);
16,000✔
2819
        auto it = key_set.begin() + int(key_index);
16,000✔
2820

2821
        // table.dump_objects();
2822
        // std::cout << "Key to remove: " << std::hex << *it << std::dec << std::endl;
2823

2824
        table.remove_object(ObjKey(*it));
16,000✔
2825
        key_set.erase(it);
16,000✔
2826
        for (unsigned j = 0; j < key_set.size(); j += 23) {
2,805,916✔
2827
            int64_t key_val = key_set[j];
2,789,916✔
2828
            Obj o = table.get_object(ObjKey(key_val));
2,789,916✔
2829
            CHECK_EQUAL(key_val << 1, o.get<int64_t>(c0));
2,789,916✔
2830
            CHECK_EQUAL(key_val << 2, o.get<util::Optional<int64_t>>(c1));
2,789,916✔
2831
        }
2,789,916✔
2832
    }
16,000✔
2833
}
2✔
2834

2835
TEST(Table_object_forward_iterator)
2836
{
2✔
2837
    int nb_rows = 1024;
2✔
2838
    Table table;
2✔
2839
    auto c0 = table.add_column(type_Int, "int1");
2✔
2840
    auto c1 = table.add_column(type_Int, "int2", true);
2✔
2841

2842
    for (int i = 0; i < nb_rows; i++) {
2,050✔
2843
        table.create_object(ObjKey(i));
2,048✔
2844
    }
2,048✔
2845

2846
    size_t tree_size = 0;
2✔
2847
    auto f = [&tree_size](const Cluster* cluster) {
8✔
2848
        tree_size += cluster->node_size();
8✔
2849
        return IteratorControl::AdvanceToNext;
8✔
2850
    };
8✔
2851
    table.traverse_clusters(f);
2✔
2852
    CHECK_EQUAL(tree_size, size_t(nb_rows));
2✔
2853

2854
    for (Obj o : table) {
2,048✔
2855
        int64_t key_value = o.get_key().value;
2,048✔
2856
        o.set_all(key_value << 1, key_value << 2);
2,048✔
2857
    }
2,048✔
2858

2859
    // table.dump_objects();
2860

2861
    size_t ndx = 0;
2✔
2862
    for (Obj o : table) {
2,048✔
2863
        int64_t key_value = o.get_key().value;
2,048✔
2864
        // std::cout << "Key value: " << std::hex << key_value << std::dec << std::endl;
2865
        CHECK_EQUAL(key_value << 1, o.get<int64_t>(c0));
2,048✔
2866
        CHECK_EQUAL(key_value << 2, o.get<util::Optional<int64_t>>(c1));
2,048✔
2867

2868
        Obj x = table.get_object(ndx);
2,048✔
2869
        CHECK_EQUAL(o.get_key(), x.get_key());
2,048✔
2870
        CHECK_EQUAL(o.get<int64_t>(c0), x.get<int64_t>(c0));
2,048✔
2871
        ndx++;
2,048✔
2872
    }
2,048✔
2873

2874
    auto it = table.begin();
2✔
2875
    while (it != table.end()) {
2,050✔
2876
        int64_t val = it->get_key().value;
2,048✔
2877
        // Delete every 7th object
2878
        if ((val % 7) == 0) {
2,048✔
2879
            table.remove_object(it);
294✔
2880
        }
294✔
2881
        ++it;
2,048✔
2882
    }
2,048✔
2883
    CHECK_EQUAL(table.size(), nb_rows * 6 / 7);
2✔
2884

2885
    auto it1 = table.begin();
2✔
2886
    ObjKey key = it1->get_key();
2✔
2887
    ++it1;
2✔
2888
    int64_t val = it1->get<int64_t>(c0);
2✔
2889
    table.remove_object(key);
2✔
2890
    CHECK_EQUAL(val, it1->get<int64_t>(c0));
2✔
2891

2892
    val = (it1 + 2)->get<int64_t>(c0);
2✔
2893
    table.remove_object(it1);
2✔
2894
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
2895
    // Still invalid
2896
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
2897
    it1 += 0;
2✔
2898
    // Still invalid
2899
    CHECK_THROW_ANY(it1->get<int64_t>(c0));
2✔
2900
    it1 += 2;
2✔
2901
    CHECK_EQUAL(val, it1->get<int64_t>(c0));
2✔
2902
}
2✔
2903

2904
TEST(Table_object_by_index)
2905
{
2✔
2906
    Table table;
2✔
2907

2908
    ObjKeys keys({17, 4, 345, 65, 1, 46, 93, 43, 76, 123, 33, 42, 99, 53, 52, 256, 2}); // 17 elements
2✔
2909
    std::map<ObjKey, size_t> positions;
2✔
2910
    table.create_objects(keys);
2✔
2911
    size_t sz = table.size();
2✔
2912
    CHECK_EQUAL(sz, keys.size());
2✔
2913
    for (size_t i = 0; i < sz; i++) {
36✔
2914
        Obj o = table.get_object(i);
34✔
2915
        auto it = std::find(keys.begin(), keys.end(), o.get_key());
34✔
2916
        CHECK(it != keys.end());
34✔
2917
        positions.emplace(o.get_key(), i);
34✔
2918
    }
34✔
2919
    for (auto k : keys) {
34✔
2920
        size_t ndx = table.get_object_ndx(k);
34✔
2921
        CHECK_EQUAL(ndx, positions[k]);
34✔
2922
    }
34✔
2923
}
2✔
2924

2925
// String query benchmark
2926
NONCONCURRENT_TEST(Table_QuickSort2)
2927
{
2✔
2928
    Table ttt;
2✔
2929
    auto strings = ttt.add_column(type_String, "2");
2✔
2930

2931
    for (size_t t = 0; t < 1000; t++) {
2,002✔
2932
        Obj o = ttt.create_object();
2,000✔
2933
        std::string s = util::to_string(t % 100);
2,000✔
2934
        o.set<StringData>(strings, s);
2,000✔
2935
    }
2,000✔
2936

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

2939
    auto t1 = steady_clock::now();
2✔
2940

2941
    CALLGRIND_START_INSTRUMENTATION;
2✔
2942

2943
    size_t nb_reps = 1000;
2✔
2944
    for (size_t t = 0; t < nb_reps; t++) {
2,002✔
2945
        TableView tv = q.find_all();
2,000✔
2946
        CHECK_EQUAL(tv.size(), 10);
2,000✔
2947
    }
2,000✔
2948

2949
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
2950

2951
    auto t2 = steady_clock::now();
2✔
2952

2953
    std::cout << nb_reps << " repetitions of find_all" << std::endl;
2✔
2954
    std::cout << "    time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_reps << " ns/rep" << std::endl;
2✔
2955
}
2✔
2956

2957
NONCONCURRENT_TEST(Table_object_sequential)
2958
{
2✔
2959
#ifdef PERFORMACE_TESTING
2960
    int nb_rows = 10'000'000;
2961
    int num_runs = 1;
2962
#else
2963
    int nb_rows = 100'000;
2✔
2964
    int num_runs = 1;
2✔
2965
#endif
2✔
2966
    SHARED_GROUP_TEST_PATH(path);
2✔
2967
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
2968
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
2969
    ColKey c0;
2✔
2970
    ColKey c1;
2✔
2971

2972
    CALLGRIND_START_INSTRUMENTATION;
2✔
2973

2974
    std::cout << nb_rows << " rows - sequential" << std::endl;
2✔
2975

2976
    {
2✔
2977
        WriteTransaction wt(sg);
2✔
2978
        auto table = wt.add_table("test");
2✔
2979

2980
        c0 = table->add_column(type_Int, "int1");
2✔
2981
        c1 = table->add_column(type_Int, "int2", true);
2✔
2982

2983

2984
        auto t1 = steady_clock::now();
2✔
2985

2986
        for (int i = 0; i < nb_rows; i++) {
200,002✔
2987
            table->create_object(ObjKey(i)).set_all(i << 1, i << 2);
200,000✔
2988
        }
200,000✔
2989

2990
        auto t2 = steady_clock::now();
2✔
2991
        std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
2992
                  << std::endl;
2✔
2993

2994
        CHECK_EQUAL(table->size(), nb_rows);
2✔
2995
        wt.commit();
2✔
2996
    }
2✔
2997
    {
2✔
2998
        auto t1 = steady_clock::now();
2✔
2999
        sg->compact();
2✔
3000
        auto t2 = steady_clock::now();
2✔
3001
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3002
    }
2✔
3003
    {
2✔
3004
        ReadTransaction rt(sg);
2✔
3005
        auto table = rt.get_table("test");
2✔
3006

3007
        auto t1 = steady_clock::now();
2✔
3008

3009
        for (int j = 0; j < num_runs; ++j) {
4✔
3010
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3011
                table->get_object(ObjKey(i));
200,000✔
3012
            }
200,000✔
3013
        }
2✔
3014

3015
        auto t2 = steady_clock::now();
2✔
3016

3017
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3018
                  << " ns/key" << std::endl;
2✔
3019
    }
2✔
3020

3021
    {
2✔
3022
        ReadTransaction rt(sg);
2✔
3023
        auto table = rt.get_table("test");
2✔
3024

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

3027
        for (int j = 0; j < num_runs; ++j) {
4✔
3028
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3029
                const Obj o = table->get_object(ObjKey(i));
200,000✔
3030
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3031
            }
200,000✔
3032
        }
2✔
3033

3034
        auto t2 = steady_clock::now();
2✔
3035

3036
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3037
                  << " ns/key" << std::endl;
2✔
3038
    }
2✔
3039

3040
    {
2✔
3041
        ReadTransaction rt(sg);
2✔
3042
        auto table = rt.get_table("test");
2✔
3043

3044
        auto t1 = steady_clock::now();
2✔
3045

3046
        for (int j = 0; j < num_runs; ++j) {
4✔
3047
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3048
                const Obj o = table->get_object(ObjKey(i));
200,000✔
3049
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3050
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3051
            }
200,000✔
3052
        }
2✔
3053

3054
        auto t2 = steady_clock::now();
2✔
3055

3056
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3057
                  << " ns/key" << std::endl;
2✔
3058
    }
2✔
3059

3060
    {
2✔
3061
        WriteTransaction wt(sg);
2✔
3062
        auto table = wt.get_table("test");
2✔
3063

3064
        auto t1 = steady_clock::now();
2✔
3065

3066
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3067
            Obj o = table->get_object(ObjKey(i));
200,000✔
3068
            o.set(c0, i << 2).set(c1, i << 1);
200,000✔
3069
        }
200,000✔
3070

3071
        auto t2 = steady_clock::now();
2✔
3072

3073
        std::cout << "   update time   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3074
                  << std::endl;
2✔
3075
        wt.commit();
2✔
3076
    }
2✔
3077

3078
    {
2✔
3079
        WriteTransaction wt(sg);
2✔
3080
        auto table = wt.get_table("test");
2✔
3081

3082
        auto t1 = steady_clock::now();
2✔
3083

3084
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3085
            table->remove_object(ObjKey(i));
200,000✔
3086
#ifdef REALM_DEBUG
200,000✔
3087
            CHECK_EQUAL(table->size(), nb_rows - i - 1);
200,000✔
3088

3089
            for (int j = i + 1; j < nb_rows; j += nb_rows / 100) {
10,299,800✔
3090
                Obj o = table->get_object(ObjKey(j));
10,099,800✔
3091
                CHECK_EQUAL(j << 2, o.get<int64_t>(c0));
10,099,800✔
3092
                CHECK_EQUAL(j << 1, o.get<util::Optional<int64_t>>(c1));
10,099,800✔
3093
            }
10,099,800✔
3094

3095
#endif
200,000✔
3096
        }
200,000✔
3097
        auto t2 = steady_clock::now();
2✔
3098
        std::cout << "   erase time    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3099
                  << std::endl;
2✔
3100

3101
        wt.commit();
2✔
3102
    }
2✔
3103

3104
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3105
}
2✔
3106

3107
NONCONCURRENT_TEST(Table_object_seq_rnd)
3108
{
2✔
3109
#ifdef PERFORMACE_TESTING
3110
    size_t rows = 1'000'000;
3111
    int runs = 100; // runs for building scenario
3112
#else
3113
    size_t rows = 100'000;
2✔
3114
    int runs = 100;
2✔
3115
#endif
2✔
3116
    int64_t next_key = 0;
2✔
3117
    std::vector<int64_t> key_values;
2✔
3118
    std::set<int64_t> key_set;
2✔
3119
    SHARED_GROUP_TEST_PATH(path);
2✔
3120
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3121
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3122
    ColKey c0;
2✔
3123
    {
2✔
3124
        std::cout << "Establishing scenario seq ins/rnd erase " << std::endl;
2✔
3125
        WriteTransaction wt(sg);
2✔
3126
        auto table = wt.add_table("test");
2✔
3127
        c0 = table->add_column(type_Int, "int1");
2✔
3128

3129
        for (int run = 0; run < runs; ++run) {
202✔
3130
            if (key_values.size() < rows) { // expanding by 2%!
200✔
3131
                for (size_t n = 0; n < rows / 50; ++n) {
400,200✔
3132
                    auto key_val = next_key++;
400,000✔
3133
                    key_values.push_back(key_val);
400,000✔
3134
                    key_set.insert(key_val);
400,000✔
3135
                    table->create_object(ObjKey(key_val)).set_all(key_val << 1);
400,000✔
3136
                }
400,000✔
3137
            }
200✔
3138
            // do 1% random deletions
3139
            for (size_t n = 0; n < rows / 100; ++n) {
200,200✔
3140
                auto index = test_util::random_int<size_t>(0, key_values.size() - 1);
200,000✔
3141
                auto key_val = key_values[index];
200,000✔
3142
                if (index < key_values.size() - 1)
200,000✔
3143
                    key_values[index] = key_values.back();
199,988✔
3144
                key_values.pop_back();
200,000✔
3145
                table->remove_object(ObjKey(key_val));
200,000✔
3146
            }
200,000✔
3147
        }
200✔
3148
        wt.commit();
2✔
3149
    }
2✔
3150
    // scenario established!
3151
    int nb_rows = int(key_values.size());
2✔
3152
#ifdef PERFORMACE_TESTING
3153
    int num_runs = 10; // runs for timing access
3154
#else
3155
    int num_runs = 1; // runs for timing access
2✔
3156
#endif
2✔
3157
    {
2✔
3158
        auto t1 = steady_clock::now();
2✔
3159
        sg->compact();
2✔
3160
        auto t2 = steady_clock::now();
2✔
3161
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3162
    }
2✔
3163
    std::cout << "Scenario has " << nb_rows << " rows. Timing...." << std::endl;
2✔
3164
    {
2✔
3165
        ReadTransaction rt(sg);
2✔
3166
        auto table = rt.get_table("test");
2✔
3167

3168
        auto t1 = steady_clock::now();
2✔
3169

3170
        for (int j = 0; j < num_runs; ++j) {
4✔
3171
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3172
                table->get_object(ObjKey(key_values[i]));
200,000✔
3173
            }
200,000✔
3174
        }
2✔
3175

3176
        auto t2 = steady_clock::now();
2✔
3177

3178
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3179
                  << " ns/key" << std::endl;
2✔
3180
    }
2✔
3181

3182
    {
2✔
3183
        ReadTransaction rt(sg);
2✔
3184
        auto table = rt.get_table("test");
2✔
3185

3186
        auto t1 = steady_clock::now();
2✔
3187

3188
        for (int j = 0; j < num_runs; ++j) {
4✔
3189
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3190
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3191
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3192
            }
200,000✔
3193
        }
2✔
3194

3195
        auto t2 = steady_clock::now();
2✔
3196

3197
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3198
                  << " ns/key" << std::endl;
2✔
3199
    }
2✔
3200

3201
    {
2✔
3202
        ReadTransaction rt(sg);
2✔
3203
        auto table = rt.get_table("test");
2✔
3204

3205
        auto t1 = steady_clock::now();
2✔
3206

3207
        for (int j = 0; j < num_runs; ++j) {
4✔
3208
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3209
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3210
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3211
                CHECK_EQUAL(key_values[i] << 1, o.get<int64_t>(c0));
200,000✔
3212
            }
200,000✔
3213
        }
2✔
3214

3215
        auto t2 = steady_clock::now();
2✔
3216

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

3222
NONCONCURRENT_TEST(Table_object_random)
3223
{
2✔
3224
#ifdef PERFORMACE_TESTING
3225
    int nb_rows = 1'000'000;
3226
    int num_runs = 10;
3227
#else
3228
    int nb_rows = 100'000;
2✔
3229
    int num_runs = 1;
2✔
3230
#endif
2✔
3231
    SHARED_GROUP_TEST_PATH(path);
2✔
3232
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3233
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
3234
    ColKey c0;
2✔
3235
    ColKey c1;
2✔
3236
    std::vector<int64_t> key_values;
2✔
3237

3238
    {
2✔
3239
        std::set<int64_t> key_set;
2✔
3240
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3241
            bool ok = false;
200,000✔
3242
            while (!ok) {
410,765✔
3243
                auto key_val = test_util::random_int<int64_t>(0, nb_rows * 10);
210,765✔
3244
                if (key_set.count(key_val) == 0) {
210,765✔
3245
                    key_values.push_back(key_val);
200,000✔
3246
                    key_set.insert(key_val);
200,000✔
3247
                    ok = true;
200,000✔
3248
                }
200,000✔
3249
            }
210,765✔
3250
        }
200,000✔
3251
    }
2✔
3252

3253
    CALLGRIND_START_INSTRUMENTATION;
2✔
3254

3255
    std::cout << nb_rows << " rows - random" << std::endl;
2✔
3256

3257
    {
2✔
3258
        WriteTransaction wt(sg);
2✔
3259
        auto table = wt.add_table("test");
2✔
3260

3261
        c0 = table->add_column(type_Int, "int1");
2✔
3262
        c1 = table->add_column(type_Int, "int2", true);
2✔
3263

3264

3265
        auto t1 = steady_clock::now();
2✔
3266

3267
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3268
            table->create_object(ObjKey(key_values[i])).set_all(i << 1, i << 2);
200,000✔
3269
        }
200,000✔
3270

3271

3272
        auto t2 = steady_clock::now();
2✔
3273
        std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3274
                  << std::endl;
2✔
3275

3276
        CHECK_EQUAL(table->size(), nb_rows);
2✔
3277
        wt.commit();
2✔
3278
    }
2✔
3279
    {
2✔
3280
        auto t1 = steady_clock::now();
2✔
3281
        sg->compact();
2✔
3282
        auto t2 = steady_clock::now();
2✔
3283
        std::cout << "  compaction time: " << duration_cast<milliseconds>(t2 - t1).count() << " ms" << std::endl;
2✔
3284
    }
2✔
3285

3286
    {
2✔
3287
        ReadTransaction rt(sg);
2✔
3288
        auto table = rt.get_table("test");
2✔
3289

3290
        auto t1 = steady_clock::now();
2✔
3291

3292
        for (int j = 0; j < num_runs; ++j) {
4✔
3293
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3294
                table->get_object(ObjKey(key_values[i]));
200,000✔
3295
            }
200,000✔
3296
        }
2✔
3297

3298
        auto t2 = steady_clock::now();
2✔
3299

3300
        std::cout << "   lookup obj    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3301
                  << " ns/key" << std::endl;
2✔
3302
    }
2✔
3303

3304
    {
2✔
3305
        ReadTransaction rt(sg);
2✔
3306
        auto table = rt.get_table("test");
2✔
3307

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

3310
        for (int j = 0; j < num_runs; ++j) {
4✔
3311
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3312
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3313
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3314
            }
200,000✔
3315
        }
2✔
3316

3317
        auto t2 = steady_clock::now();
2✔
3318

3319
        std::cout << "   lookup field  : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3320
                  << " ns/key" << std::endl;
2✔
3321
    }
2✔
3322

3323
    {
2✔
3324
        ReadTransaction rt(sg);
2✔
3325
        auto table = rt.get_table("test");
2✔
3326

3327
        auto t1 = steady_clock::now();
2✔
3328

3329
        for (int j = 0; j < num_runs; ++j) {
4✔
3330
            for (int i = 0; i < nb_rows; i++) {
200,002✔
3331
                const Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3332
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3333
                CHECK_EQUAL(i << 1, o.get<int64_t>(c0));
200,000✔
3334
            }
200,000✔
3335
        }
2✔
3336

3337
        auto t2 = steady_clock::now();
2✔
3338

3339
        std::cout << "   lookup same   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows / num_runs
2✔
3340
                  << " ns/key" << std::endl;
2✔
3341
    }
2✔
3342

3343
    {
2✔
3344
        WriteTransaction wt(sg);
2✔
3345
        auto table = wt.get_table("test");
2✔
3346

3347
        auto t1 = steady_clock::now();
2✔
3348

3349
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3350
            Obj o = table->get_object(ObjKey(key_values[i]));
200,000✔
3351
            o.set(c0, i << 2).set(c1, i << 1);
200,000✔
3352
        }
200,000✔
3353

3354
        auto t2 = steady_clock::now();
2✔
3355

3356
        std::cout << "   update time   : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3357
                  << std::endl;
2✔
3358
        wt.commit();
2✔
3359
    }
2✔
3360

3361
    {
2✔
3362
        WriteTransaction wt(sg);
2✔
3363
        auto table = wt.get_table("test");
2✔
3364

3365
        auto t1 = steady_clock::now();
2✔
3366

3367
        for (int i = 0; i < nb_rows; i++) {
200,002✔
3368
            table->remove_object(ObjKey(key_values[i]));
200,000✔
3369
#ifdef REALM_DEBUG
200,000✔
3370
            CHECK_EQUAL(table->size(), nb_rows - i - 1);
200,000✔
3371
            for (int j = i + 1; j < nb_rows; j += nb_rows / 100) {
10,299,800✔
3372
                Obj o = table->get_object(ObjKey(key_values[j]));
10,099,800✔
3373
                CHECK_EQUAL(j << 2, o.get<int64_t>(c0));
10,099,800✔
3374
                CHECK_EQUAL(j << 1, o.get<util::Optional<int64_t>>(c1));
10,099,800✔
3375
            }
10,099,800✔
3376
#endif
200,000✔
3377
        }
200,000✔
3378
        auto t2 = steady_clock::now();
2✔
3379
        std::cout << "   erase time    : " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3380
                  << std::endl;
2✔
3381

3382
        wt.commit();
2✔
3383
    }
2✔
3384

3385
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3386
}
2✔
3387

3388
TEST(Table_CollisionMapping)
3389
{
2✔
3390

3391
#if REALM_EXERCISE_OBJECT_ID_COLLISION
3392
    bool expect_collisions = true;
3393
#else
3394
    bool expect_collisions = false;
2✔
3395
#endif
2✔
3396

3397
    // This number corresponds to the mask used to calculate "optimistic"
3398
    // object IDs. See `ObjectIDProvider::get_optimistic_local_id_hashed`.
3399
    const size_t num_objects_with_guaranteed_collision = 0xff;
2✔
3400

3401
    SHARED_GROUP_TEST_PATH(path);
2✔
3402

3403
    {
2✔
3404
        DBRef sg = DB::create(path);
2✔
3405
        {
2✔
3406
            auto wt = sg->start_write();
2✔
3407
            TableRef t0 = wt->add_table_with_primary_key("class_t0", type_String, "pk");
2✔
3408

3409
            char buffer[12];
2✔
3410
            for (size_t i = 0; i < num_objects_with_guaranteed_collision; ++i) {
512✔
3411
                const char* in = reinterpret_cast<char*>(&i);
510✔
3412
                size_t len = base64_encode({in, sizeof(i)}, buffer);
510✔
3413

3414
                t0->create_object_with_primary_key(StringData{buffer, len});
510✔
3415
            }
510✔
3416
            wt->commit();
2✔
3417
        }
2✔
3418

3419
        {
2✔
3420
            ReadTransaction rt{sg};
2✔
3421
            ConstTableRef t0 = rt.get_table("class_t0");
2✔
3422
            // Check that at least one object exists where the 63rd bit is set.
3423
            size_t num_object_keys_with_63rd_bit_set = 0;
2✔
3424
            uint64_t bit63 = 0x4000000000000000;
2✔
3425
            for (Obj obj : *t0) {
510✔
3426
                if (obj.get_key().value & bit63)
510✔
3427
                    ++num_object_keys_with_63rd_bit_set;
×
3428
            }
510✔
3429
            CHECK(!expect_collisions || num_object_keys_with_63rd_bit_set > 0);
2!
3430
        }
2✔
3431
    }
2✔
3432

3433
    // Check that locally allocated IDs are properly persisted
3434
    {
2✔
3435
        DBRef sg_2 = DB::create(path);
2✔
3436
        {
2✔
3437
            WriteTransaction wt{sg_2};
2✔
3438
            TableRef t0 = wt.get_table("class_t0");
2✔
3439

3440
            // Make objects with primary keys that do not already exist but are guaranteed
3441
            // to cause further collisions.
3442
            char buffer[12];
2✔
3443
            for (size_t i = 0; i < num_objects_with_guaranteed_collision; ++i) {
512✔
3444
                size_t foo = num_objects_with_guaranteed_collision + i;
510✔
3445
                const char* in = reinterpret_cast<char*>(&foo);
510✔
3446
                size_t len = base64_encode({in, sizeof(foo)}, buffer);
510✔
3447

3448
                t0->create_object_with_primary_key(StringData{buffer, len});
510✔
3449
            }
510✔
3450
            wt.commit();
2✔
3451
        }
2✔
3452
        {
2✔
3453
            WriteTransaction wt{sg_2};
2✔
3454
            TableRef t0 = wt.get_table("class_t0");
2✔
3455

3456
            // Find an object with collision key
3457
            std::string pk;
2✔
3458
            ObjKey key;
2✔
3459
            uint64_t bit63 = 0x4000000000000000;
2✔
3460
            for (Obj obj : *t0) {
1,020✔
3461
                if (obj.get_key().value & bit63) {
1,020✔
3462
                    key = obj.get_key();
×
3463
                    pk = obj.get<String>("pk");
×
3464
                    obj.remove();
×
3465
                    break;
×
3466
                }
×
3467
            }
1,020✔
3468

3469
            if (key) {
2✔
3470
                // Insert object again - should get a different key
3471
                auto obj = t0->create_object_with_primary_key(pk);
×
3472
                CHECK_NOT_EQUAL(obj.get_key(), key);
×
3473
            }
×
3474

3475
            wt.commit();
2✔
3476
        }
2✔
3477
    }
2✔
3478
}
2✔
3479

3480
TEST(Table_CreateObjectWithPrimaryKeyDidCreate)
3481
{
2✔
3482
    SHARED_GROUP_TEST_PATH(path);
2✔
3483
    DBRef sg = DB::create(path);
2✔
3484

3485
    auto wt = sg->start_write();
2✔
3486
    TableRef string_table = wt->add_table_with_primary_key("string pk", type_String, "pk", true);
2✔
3487

3488
    bool did_create = false;
2✔
3489
    string_table->create_object_with_primary_key(StringData("1"), &did_create);
2✔
3490
    CHECK(did_create);
2✔
3491
    string_table->create_object_with_primary_key(StringData("1"), &did_create);
2✔
3492
    CHECK_NOT(did_create);
2✔
3493
    string_table->create_object_with_primary_key(StringData("2"), &did_create);
2✔
3494
    CHECK(did_create);
2✔
3495
    string_table->create_object_with_primary_key(StringData(), &did_create);
2✔
3496
    CHECK(did_create);
2✔
3497
    string_table->create_object_with_primary_key(StringData(), &did_create);
2✔
3498
    CHECK_NOT(did_create);
2✔
3499

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

3502
    did_create = false;
2✔
3503
    int_table->create_object_with_primary_key(1, &did_create);
2✔
3504
    CHECK(did_create);
2✔
3505
    int_table->create_object_with_primary_key(1, &did_create);
2✔
3506
    CHECK_NOT(did_create);
2✔
3507
    int_table->create_object_with_primary_key(2, &did_create);
2✔
3508
    CHECK(did_create);
2✔
3509
    int_table->create_object_with_primary_key(util::Optional<int64_t>(), &did_create);
2✔
3510
    CHECK(did_create);
2✔
3511
    int_table->create_object_with_primary_key(util::Optional<int64_t>(), &did_create);
2✔
3512
    CHECK_NOT(did_create);
2✔
3513
}
2✔
3514

3515
TEST(Table_PrimaryKeyIndexBug)
3516
{
2✔
3517
    Group g;
2✔
3518
    TableRef table = g.add_table("table");
2✔
3519
    TableRef origin = g.add_table("origin");
2✔
3520
    auto col_id = table->add_column(type_String, "id");
2✔
3521
    auto col_link = origin->add_column(*table, "link");
2✔
3522
    table->add_search_index(col_id);
2✔
3523
    // Create an object where bit 62 is set in the ObkKey
3524
    auto obj = table->create_object(ObjKey(0x40083f0f9b0fb598)).set(col_id, "hallo");
2✔
3525
    origin->create_object().set(col_link, obj.get_key());
2✔
3526

3527
    auto q = origin->link(col_link).column<String>(col_id) == "hallo";
2✔
3528
    auto cnt = q.count();
2✔
3529
    CHECK_EQUAL(cnt, 1);
2✔
3530
}
2✔
3531

3532
NONCONCURRENT_TEST(Table_PrimaryKeyString)
3533
{
2✔
3534
#ifdef REALM_DEBUG
2✔
3535
    int nb_rows = 1000;
2✔
3536
#else
3537
    int nb_rows = 100000;
3538
#endif
3539
    SHARED_GROUP_TEST_PATH(path);
2✔
3540

3541
    DBRef sg = DB::create(path);
2✔
3542
    auto wt = sg->start_write();
2✔
3543
    TableRef t0 = wt->add_table_with_primary_key("class_t0", type_String, "pk");
2✔
3544
    auto pk_col = t0->get_primary_key_column();
2✔
3545

3546
    auto t1 = steady_clock::now();
2✔
3547
    CALLGRIND_START_INSTRUMENTATION;
2✔
3548

3549
    for (int i = 0; i < nb_rows; ++i) {
2,002✔
3550
        std::string pk = "KEY_" + util::to_string(i);
2,000✔
3551
        t0->create_object_with_primary_key(pk);
2,000✔
3552
    }
2,000✔
3553

3554
    auto t2 = steady_clock::now();
2✔
3555

3556
    for (int i = 0; i < nb_rows; ++i) {
2,002✔
3557
        std::string pk = "KEY_" + util::to_string(i);
2,000✔
3558
        ObjKey k = t0->find_first(pk_col, StringData(pk));
2,000✔
3559
#ifdef REALM_DEBUG
2,000✔
3560
        CHECK(t0->is_valid(k));
2,000✔
3561
#else
3562
        CHECK(k);
3563
#endif
3564
    }
2,000✔
3565

3566
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
3567
    auto t3 = steady_clock::now();
2✔
3568
    std::cout << "   insertion time: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_rows << " ns/key"
2✔
3569
              << std::endl;
2✔
3570
    std::cout << "   lookup time: " << duration_cast<nanoseconds>(t3 - t2).count() / nb_rows << " ns/key"
2✔
3571
              << std::endl;
2✔
3572
    wt->commit();
2✔
3573
}
2✔
3574

3575
TEST(Table_3)
3576
{
2✔
3577
    Table table;
2✔
3578

3579
    auto col_int0 = table.add_column(type_Int, "first");
2✔
3580
    auto col_int1 = table.add_column(type_Int, "second");
2✔
3581
    auto col_bool2 = table.add_column(type_Bool, "third");
2✔
3582
    auto col_int3 = table.add_column(type_Int, "fourth");
2✔
3583

3584
    for (int64_t i = 0; i < 100; ++i) {
202✔
3585
        table.create_object(ObjKey(i)).set_all(i, 10, true, int(Wed));
200✔
3586
    }
200✔
3587

3588
    // Test column searching
3589
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int0, 0));
2✔
3590
    CHECK_EQUAL(ObjKey(50), table.find_first_int(col_int0, 50));
2✔
3591
    CHECK_EQUAL(null_key, table.find_first_int(col_int0, 500));
2✔
3592
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int1, 10));
2✔
3593
    CHECK_EQUAL(null_key, table.find_first_int(col_int1, 100));
2✔
3594
    CHECK_EQUAL(ObjKey(0), table.find_first_bool(col_bool2, true));
2✔
3595
    CHECK_EQUAL(null_key, table.find_first_bool(col_bool2, false));
2✔
3596
    CHECK_EQUAL(ObjKey(0), table.find_first_int(col_int3, Wed));
2✔
3597
    CHECK_EQUAL(null_key, table.find_first_int(col_int3, Mon));
2✔
3598

3599
#ifdef REALM_DEBUG
2✔
3600
    table.verify();
2✔
3601
#endif
2✔
3602
}
2✔
3603

3604

3605
TEST(Table_4)
3606
{
2✔
3607
    Table table;
2✔
3608
    auto c0 = table.add_column(type_String, "strings");
2✔
3609
    const char* hello_hello = "HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHello";
2✔
3610

3611
    table.create_object(ObjKey(5)).set(c0, "Hello");
2✔
3612
    table.create_object(ObjKey(7)).set(c0, hello_hello);
2✔
3613

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

3616
    // Test string column searching
3617
    CHECK_EQUAL(ObjKey(7), table.find_first_string(c0, hello_hello));
2✔
3618
    CHECK_EQUAL(null_key, table.find_first_string(c0, "Foo"));
2✔
3619

3620
#ifdef REALM_DEBUG
2✔
3621
    table.verify();
2✔
3622
#endif
2✔
3623
}
2✔
3624

3625
// Very basic sanity check of search index when you add, remove and set objects
3626
TEST(Table_SearchIndexFindFirst)
3627
{
2✔
3628
    Table table;
2✔
3629

3630
    auto c1 = table.add_column(type_Int, "a");
2✔
3631
    auto c2 = table.add_column(type_Int, "b", true);
2✔
3632
    auto c3 = table.add_column(type_String, "c");
2✔
3633
    auto c4 = table.add_column(type_String, "d", true);
2✔
3634
    auto c5 = table.add_column(type_Bool, "e");
2✔
3635
    auto c6 = table.add_column(type_Bool, "f", true);
2✔
3636
    auto c7 = table.add_column(type_Timestamp, "g");
2✔
3637
    auto c8 = table.add_column(type_Timestamp, "h", true);
2✔
3638

3639
    Obj o0 = table.create_object();
2✔
3640
    Obj o1 = table.create_object();
2✔
3641
    Obj o2 = table.create_object();
2✔
3642
    Obj o3 = table.create_object();
2✔
3643

3644
    o0.set_all(100, 100, "100", "100", false, false, Timestamp(100, 100), Timestamp(100, 100));
2✔
3645
    o1.set_all(200, 200, "200", "200", true, true, Timestamp(200, 200), Timestamp(200, 200));
2✔
3646
    o2.set_all(200, 200, "200", "200", true, true, Timestamp(200, 200), Timestamp(200, 200));
2✔
3647
    CHECK(o3.is_null(c2));
2✔
3648
    CHECK(o3.is_null(c4));
2✔
3649
    CHECK(o3.is_null(c6));
2✔
3650
    CHECK(o3.is_null(c8));
2✔
3651

3652
    table.add_search_index(c1);
2✔
3653
    table.add_search_index(c2);
2✔
3654
    table.add_search_index(c3);
2✔
3655
    table.add_search_index(c4);
2✔
3656
    table.add_search_index(c5);
2✔
3657
    table.add_search_index(c6);
2✔
3658
    table.add_search_index(c7);
2✔
3659
    table.add_search_index(c8);
2✔
3660

3661
    // Non-nullable integers
3662
    CHECK_EQUAL(table.find_first_int(c1, 100), o0.get_key());
2✔
3663
    CHECK_EQUAL(table.find_first_int(c1, 200), o1.get_key());
2✔
3664
    // Uninitialized non-nullable integers equal 0
3665
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3666

3667
    // Nullable integers
3668
    CHECK_EQUAL(table.find_first_int(c2, 100), o0.get_key());
2✔
3669
    CHECK_EQUAL(table.find_first_int(c2, 200), o1.get_key());
2✔
3670
    // FIXME: Waiting for fix outside scope of search index PR
3671
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
3672

3673
    // Non-nullable strings
3674
    CHECK_EQUAL(table.find_first_string(c3, "100"), o0.get_key());
2✔
3675
    CHECK_EQUAL(table.find_first_string(c3, "200"), o1.get_key());
2✔
3676
    // Uninitialized non-nullable strings equal ""
3677
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3678

3679
    // Nullable strings
3680
    CHECK_EQUAL(table.find_first_string(c4, "100"), o0.get_key());
2✔
3681
    CHECK_EQUAL(table.find_first_string(c4, "200"), o1.get_key());
2✔
3682
    // FIXME: Waiting for fix outside scope of search index PR
3683
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
3684

3685
    // Non-nullable bools
3686
    CHECK_EQUAL(table.find_first_bool(c5, false), o0.get_key());
2✔
3687
    CHECK_EQUAL(table.find_first_bool(c5, true), o1.get_key());
2✔
3688

3689
    // Nullable bools
3690
    CHECK_EQUAL(table.find_first_bool(c6, false), o0.get_key());
2✔
3691
    CHECK_EQUAL(table.find_first_bool(c6, true), o1.get_key());
2✔
3692
    // FIXME: Waiting for fix outside scope of search index PR
3693
    // CHECK_EQUAL(table.find_first_null(5), o3.get_key());
3694

3695
    // Non-nullable Timestamp
3696
    CHECK_EQUAL(table.find_first_timestamp(c7, Timestamp(100, 100)), o0.get_key());
2✔
3697
    CHECK_EQUAL(table.find_first_timestamp(c7, Timestamp(200, 200)), o1.get_key());
2✔
3698

3699
    // Nullable Timestamp
3700
    CHECK_EQUAL(table.find_first_timestamp(c8, Timestamp(100, 100)), o0.get_key());
2✔
3701
    CHECK_EQUAL(table.find_first_timestamp(c8, Timestamp(200, 200)), o1.get_key());
2✔
3702
    // FIXME: Waiting for fix outside scope of search index PR
3703
    // CHECK_EQUAL(table.find_first_null(7), o3.get_key());
3704

3705
    // Remove object and see if things still work
3706
    // *******************************************************************************
3707
    table.remove_object(o0.get_key());
2✔
3708

3709
    // Integers
3710
    CHECK_EQUAL(table.find_first_int(c1, 100), null_key);
2✔
3711
    CHECK_EQUAL(table.find_first_int(c1, 200), o1.get_key());
2✔
3712
    // Uninitialized non-nullable integers equal 0
3713
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3714

3715
    CHECK_EQUAL(table.find_first_int(c2, 200), o1.get_key());
2✔
3716
    // FIXME: Waiting for fix outside scope of search index PR
3717
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
3718

3719
    // Non-nullable strings
3720
    CHECK_EQUAL(table.find_first_string(c3, "100"), null_key);
2✔
3721
    CHECK_EQUAL(table.find_first_string(c3, "200"), o1.get_key());
2✔
3722
    // Uninitialized non-nullable strings equal ""
3723
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3724

3725
    // Nullable strings
3726
    CHECK_EQUAL(table.find_first_string(c4, "100"), null_key);
2✔
3727
    CHECK_EQUAL(table.find_first_string(c4, "200"), o1.get_key());
2✔
3728
    // FIXME: Waiting for fix outside scope of search index PR
3729
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
3730

3731
    // Non-nullable bools
3732
    // default value for non-nullable bool is false, so o3 is a match
3733
    CHECK_EQUAL(table.find_first_bool(c5, false), o3.get_key());
2✔
3734
    CHECK_EQUAL(table.find_first_bool(c5, true), o1.get_key());
2✔
3735

3736
    // Nullable bools
3737
    CHECK_EQUAL(table.find_first_bool(c6, false), null_key);
2✔
3738
    CHECK_EQUAL(table.find_first_bool(c6, true), o1.get_key());
2✔
3739

3740
    // Call "set" and see if things still work
3741
    // *******************************************************************************
3742
    o1.set_all(500, 500, "500", "500");
2✔
3743
    o2.set_all(600, 600, "600", "600");
2✔
3744

3745
    CHECK_EQUAL(table.find_first_int(c1, 500), o1.get_key());
2✔
3746
    CHECK_EQUAL(table.find_first_int(c1, 600), o2.get_key());
2✔
3747
    // Uninitialized non-nullable integers equal 0
3748
    CHECK_EQUAL(table.find_first_int(c1, 0), o3.get_key());
2✔
3749
    CHECK_EQUAL(table.find_first_int(c2, 500), o1.get_key());
2✔
3750
    // FIXME: Waiting for fix outside scope of search index PR
3751
    // CHECK_EQUAL(table.find_first_null(1), o3.get_key());
3752

3753
    // Non-nullable strings
3754
    CHECK_EQUAL(table.find_first_string(c3, "500"), o1.get_key());
2✔
3755
    CHECK_EQUAL(table.find_first_string(c3, "600"), o2.get_key());
2✔
3756
    // Uninitialized non-nullable strings equal ""
3757
    CHECK_EQUAL(table.find_first_string(c3, ""), o3.get_key());
2✔
3758

3759
    // Nullable strings
3760
    CHECK_EQUAL(table.find_first_string(c4, "500"), o1.get_key());
2✔
3761
    CHECK_EQUAL(table.find_first_string(c4, "600"), o2.get_key());
2✔
3762
    // FIXME: Waiting for fix outside scope of search index PR
3763
    // CHECK_EQUAL(table.find_first_null(3), o3.get_key());
3764

3765
    // Remove four of the indexes through remove_search_index() call. Let other four remain to see
3766
    // if they leak memory when Table goes out of scope (needs leak detector)
3767
    table.remove_search_index(c1);
2✔
3768
    table.remove_search_index(c2);
2✔
3769
    table.remove_search_index(c3);
2✔
3770
    table.remove_search_index(c4);
2✔
3771
}
2✔
3772

3773
TEST(Table_SearchIndexFindAll)
3774
{
2✔
3775
    Table table;
2✔
3776
    auto col_int = table.add_column(type_Int, "integers");
2✔
3777
    auto col_str = table.add_column(type_String, "strings");
2✔
3778
    // Add index before creating objects
3779
    table.add_search_index(col_int);
2✔
3780
    table.add_search_index(col_str);
2✔
3781

3782
    ObjKeys keys;
2✔
3783
    table.create_objects(100, keys);
2✔
3784
    for (auto o : table) {
200✔
3785
        int64_t key_value = o.get_key().value;
200✔
3786
        o.set(col_int, key_value);
200✔
3787
        // When node size is 4 the objects with "Hello" will be in 2 clusters
3788
        if (key_value > 21 && key_value < 28) {
200✔
3789
            o.set(col_str, "Hello");
12✔
3790
        }
12✔
3791
    }
200✔
3792

3793
    auto tv = table.find_all_string(col_str, "Hello");
2✔
3794
    CHECK_EQUAL(tv.size(), 6);
2✔
3795
}
2✔
3796

3797
TEST(Table_QueryNullOnNonNullSearchIndex)
3798
{
2✔
3799
    Group g;
2✔
3800
    TableRef t = g.add_table("table");
2✔
3801
    ColKey col0 = t->add_column(type_Int, "value", false);
2✔
3802
    ColKey col_link = t->add_column(*t, "link");
2✔
3803
    t->add_search_index(col0);
2✔
3804

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

3807
    for (Int v : values) {
10✔
3808
        auto obj = t->create_object();
10✔
3809
        obj.set(col0, v);
10✔
3810
        obj.set(col_link, obj.get_key());
10✔
3811
    }
10✔
3812

3813
    for (Int v : values) {
10✔
3814
        Query q0 = t->column<Int>(col0) == v;
10✔
3815
        CHECK_EQUAL(q0.count(), 1);
10✔
3816
        Query q1 = t->link(col_link).column<Int>(col0) == v;
10✔
3817
        CHECK_EQUAL(q1.count(), 1);
10✔
3818
        Query q2 = t->link(col_link).link(col_link).column<Int>(col0) == v;
10✔
3819
        CHECK_EQUAL(q2.count(), 1);
10✔
3820
        Query q3 = t->where().equal(col0, v);
10✔
3821
        CHECK_EQUAL(q3.count(), 1);
10✔
3822
    }
10✔
3823

3824
    {
2✔
3825
        Query q0 = t->column<Int>(col0) == realm::null();
2✔
3826
        CHECK_EQUAL(q0.count(), 0);
2✔
3827
        Query q1 = t->link(col_link).column<Int>(col0) == realm::null();
2✔
3828
        CHECK_EQUAL(q1.count(), 0);
2✔
3829
        Query q2 = t->link(col_link).link(col_link).column<Int>(col0) == realm::null();
2✔
3830
        CHECK_EQUAL(q2.count(), 0);
2✔
3831
    }
2✔
3832
}
2✔
3833

3834
TEST_TYPES(Table_QuerySearchEqualsNull, Prop<Int>, Prop<double>, Prop<float>, Prop<ObjectId>, Prop<Timestamp>,
3835
           Prop<StringData>, Prop<BinaryData>, Prop<Decimal128>, Prop<UUID>, Nullable<Int>, Nullable<double>,
3836
           Nullable<float>, Nullable<ObjectId>, Nullable<Timestamp>, Nullable<StringData>, Nullable<BinaryData>,
3837
           Nullable<Decimal128>, Nullable<UUID>, Indexed<Int>, Indexed<ObjectId>, Indexed<Timestamp>,
3838
           Indexed<StringData>, Indexed<UUID>, NullableIndexed<Int>, NullableIndexed<ObjectId>,
3839
           NullableIndexed<Timestamp>, NullableIndexed<StringData>, NullableIndexed<UUID>)
3840
{
56✔
3841
    using type = typename TEST_TYPE::type;
56✔
3842
    using underlying_type = typename TEST_TYPE::underlying_type;
56✔
3843
    TestValueGenerator gen;
56✔
3844
    Group g;
56✔
3845
    TableRef t = g.add_table("table");
56✔
3846
    ColKey col0 = t->add_column(TEST_TYPE::data_type, "value", TEST_TYPE::is_nullable);
56✔
3847
    ColKey col1 = t->add_column_list(TEST_TYPE::data_type, "values", TEST_TYPE::is_nullable);
56✔
3848
    ColKey col_link = t->add_column(*t, "link");
56✔
3849

3850
    if constexpr (TEST_TYPE::is_indexed) {
56✔
3851
        t->add_search_index(col0);
20✔
3852
    }
20✔
3853

3854
    std::vector<underlying_type> values = gen.values_from_int<underlying_type>({9, 4, 2, 7});
56✔
3855
    underlying_type default_non_null_value = TEST_TYPE::default_non_nullable_value();
56✔
3856
    values.push_back(default_non_null_value);
56✔
3857
    std::vector<size_t> indices;
56✔
3858

3859
    for (underlying_type v : values) {
280✔
3860
        auto obj = t->create_object();
280✔
3861
        obj.set(col0, v);
280✔
3862
        obj.set(col_link, obj.get_key());
280✔
3863
    }
280✔
3864

3865
    constexpr size_t num_defaults_added = 3;
56✔
3866
    for (size_t i = 0; i < num_defaults_added; ++i) {
224✔
3867
        auto obj = t->create_object();
168✔
3868
        obj.set(col_link, obj.get_key());
168✔
3869
    }
168✔
3870
    auto list = t->begin()->get_list<type>(col1);
56✔
3871
    for (underlying_type v : values) {
280✔
3872
        list.add(v);
280✔
3873
    }
280✔
3874

3875
    constexpr size_t num_nulls = TEST_TYPE::is_nullable ? num_defaults_added : 0;
56✔
3876
    constexpr size_t num_default_non_nullables = TEST_TYPE::is_nullable ? 1 : num_defaults_added + 1;
56✔
3877

3878
    for (underlying_type v : values) {
280✔
3879
        const size_t num_expected = (v == default_non_null_value ? num_default_non_nullables : 1);
280✔
3880
        Query q0 = t->column<underlying_type>(col0) == v;
280✔
3881
        CHECK_EQUAL(q0.count(), num_expected);
280✔
3882
        Query q1 = t->link(col_link).column<underlying_type>(col0) == v;
280✔
3883
        CHECK_EQUAL(q1.count(), num_expected);
280✔
3884
        Query q2 = t->link(col_link).link(col_link).column<underlying_type>(col0) == v;
280✔
3885
        CHECK_EQUAL(q2.count(), num_expected);
280✔
3886
        Query q3 = t->where().equal(col0, v);
280✔
3887
        CHECK_EQUAL(q3.count(), num_expected);
280✔
3888
        Query q4 = t->column<Lst<underlying_type>>(col1) == v;
280✔
3889
        CHECK_EQUAL(q4.count(), 1);
280✔
3890
    }
280✔
3891

3892
    {
56✔
3893
        Query q0 = t->column<underlying_type>(col0) == realm::null();
56✔
3894
        CHECK_EQUAL(q0.count(), num_nulls);
56✔
3895
        Query q1 = t->link(col_link).column<underlying_type>(col0) == realm::null();
56✔
3896
        CHECK_EQUAL(q1.count(), num_nulls);
56✔
3897
        Query q2 = t->link(col_link).link(col_link).column<underlying_type>(col0) == realm::null();
56✔
3898
        CHECK_EQUAL(q2.count(), num_nulls);
56✔
3899
        Query q3 = t->column<underlying_type>(col0) == default_non_null_value;
56✔
3900
        CHECK_EQUAL(q3.count(), num_default_non_nullables);
56✔
3901
        Query q4 = t->link(col_link).column<underlying_type>(col0) == default_non_null_value;
56✔
3902
        CHECK_EQUAL(q4.count(), num_default_non_nullables);
56✔
3903
        Query q5 = t->link(col_link).link(col_link).column<underlying_type>(col0) == default_non_null_value;
56✔
3904
        CHECK_EQUAL(q5.count(), num_default_non_nullables);
56✔
3905
    }
56✔
3906
}
56✔
3907

3908
namespace {
3909

3910
template <class T, bool nullable>
3911
struct Tester {
3912
    using T2 = typename util::RemoveOptional<T>::type;
3913

3914
    static ColKey col;
3915

3916
    static std::vector<ObjKey> find_all_reference(TableRef table, T v)
3917
    {
14,500✔
3918
        std::vector<ObjKey> res;
14,500✔
3919
        Table::Iterator it = table->begin();
14,500✔
3920
        while (it != table->end()) {
1,129,537✔
3921
            if (!it->is_null(col)) {
1,115,037✔
3922
                T v2 = it->get<T>(col);
1,013,552✔
3923
                if (v == v2) {
1,013,552✔
3924
                    res.push_back(it->get_key());
243,549✔
3925
                }
243,549✔
3926
            }
1,013,552✔
3927
            ++it;
1,115,037✔
3928
        };
1,115,037✔
3929
        // res is returned with nrvo optimization
3930
        return res;
14,500✔
3931
    }
14,500✔
3932

3933
    static void validate(TableRef table)
3934
    {
16,000✔
3935
        Table::Iterator it = table->begin();
16,000✔
3936

3937
        if (it != table->end()) {
16,000✔
3938
            auto v = it->get<T>(col);
15,613✔
3939

3940
            if (!it->is_null(col)) {
15,613✔
3941
                std::vector<ObjKey> res;
14,500✔
3942
                table->get_search_index(col)->find_all(res, v, false);
14,500✔
3943
                std::vector<ObjKey> ref = find_all_reference(table, v);
14,500✔
3944

3945
                size_t a = ref.size();
14,500✔
3946
                size_t b = res.size();
14,500✔
3947

3948
                REALM_ASSERT(a == b);
14,500✔
3949
            }
14,500✔
3950
        }
15,613✔
3951
    }
16,000✔
3952

3953
    static void run(DBRef db, realm::DataType type)
3954
    {
16✔
3955
        auto trans = db->start_write();
16✔
3956
        auto table = trans->add_table("my_table");
16✔
3957
        col = table->add_column(type, "name", nullable);
16✔
3958
        table->add_search_index(col);
16✔
3959
        const size_t iters = 1000;
16✔
3960

3961
        bool add_trend = true;
16✔
3962

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

3965
            if (iter == iters / 2) {
16,000✔
3966
                add_trend = false;
16✔
3967
            }
16✔
3968

3969
            // Add object (with 60% probability, so we grow the object count over time)
3970
            if (fastrand(100) < (add_trend ? 80 : 20)) {
16,000✔
3971
                Obj o = table->create_object();
7,915✔
3972
                bool set_to_null = fastrand(100) < 20;
7,915✔
3973

3974
                if (!set_to_null) {
7,915✔
3975
                    auto t = create();
6,354✔
3976
                    o.set<T2>(col, t);
6,354✔
3977
                }
6,354✔
3978
            }
7,915✔
3979

3980
            // Remove random object
3981
            if (fastrand(100) < 50 && table->size() > 0) {
16,000✔
3982
                Table::Iterator it = table->begin();
7,774✔
3983
                auto r = fastrand(table->size() - 1);
7,774✔
3984
                // FIXME: Is there a faster way to pick a random object?
3985
                for (unsigned t = 0; t < r; t++) {
297,951✔
3986
                    ++it;
290,177✔
3987
                }
290,177✔
3988
                Obj o = *it;
7,774✔
3989
                table->remove_object(o.get_key());
7,774✔
3990
            }
7,774✔
3991

3992
            // Edit random object
3993
            if (table->size() > 0) {
16,000✔
3994
                Table::Iterator it = table->begin();
15,613✔
3995
                auto r = fastrand(table->size() - 1);
15,613✔
3996
                // FIXME: Is there a faster way to pick a random object?
3997
                for (unsigned t = 0; t < r; t++) {
607,962✔
3998
                    ++it;
592,349✔
3999
                }
592,349✔
4000
                Obj o = *it;
15,613✔
4001
                bool set_to_null = fastrand(100) < 20;
15,613✔
4002
                if (set_to_null && table->is_nullable(col)) {
15,613✔
4003
                    o.set_null(col);
1,595✔
4004
                }
1,595✔
4005
                else {
14,018✔
4006
                    auto t = create();
14,018✔
4007
                    o.set<T2>(col, t);
14,018✔
4008
                }
14,018✔
4009
            }
15,613✔
4010

4011
            if (iter % (iters / 1000) == 0) {
16,000✔
4012
                validate(table);
16,000✔
4013
            }
16,000✔
4014
        }
16,000✔
4015
        trans->rollback();
16✔
4016
    }
16✔
4017

4018

4019
    // Create random data element of any type supported by the search index
4020
    template <typename Type = T2>
4021
    typename std::enable_if<std::is_same<Type, StringData>::value, std::string>::type static create()
4022
    {
4,938✔
4023
        std::string s = realm::util::to_string(fastrand(5));
4,938✔
4024
        return s;
4,938✔
4025
    }
4,938✔
4026
    template <typename Type = T2>
4027
    typename std::enable_if<std::is_same<Type, Timestamp>::value, T2>::type static create()
4028
    {
5,090✔
4029
        return Timestamp(fastrand(3), int32_t(fastrand(3)));
5,090✔
4030
    }
5,090✔
4031
    template <typename Type = T2>
4032
    typename std::enable_if<std::is_same<Type, int64_t>::value, T2>::type static create()
4033
    {
5,227✔
4034
        return fastrand(5);
5,227✔
4035
    }
5,227✔
4036

4037
    template <typename Type = T2>
4038
    typename std::enable_if<std::is_same<Type, bool>::value, T2>::type static create()
4039
    {
5,117✔
4040
        return fastrand(100) > 50;
5,117✔
4041
    }
5,117✔
4042
};
4043

4044
template <class T, bool nullable>
4045
ColKey Tester<T, nullable>::col;
4046
} // namespace
4047

4048
// The run() method will first add lots of objects, and then remove them. This will test
4049
// both node splits and empty leaf destruction and get good search index code coverage
4050
TEST(Table_search_index_fuzzer)
4051
{
2✔
4052
    // Syntax for Tester<T, nullable>:
4053
    // T:         Type that must be used in calls too Obj::get<T>
4054
    // nullable:  If the columns must be is nullable or not
4055
    // Obj::set() will be automatically be called with set<RemoveOptional<T>>()
4056

4057
    SHARED_GROUP_TEST_PATH(path);
2✔
4058
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4059
    auto db = DB::create(*hist, path);
2✔
4060
    Tester<bool, false>::run(db, type_Bool);
2✔
4061
    Tester<Optional<bool>, true>::run(db, type_Bool);
2✔
4062

4063
    Tester<int64_t, false>::run(db, type_Int);
2✔
4064
    Tester<Optional<int64_t>, true>::run(db, type_Int);
2✔
4065

4066
    // Self-contained null state
4067
    Tester<Timestamp, false>::run(db, type_Timestamp);
2✔
4068
    Tester<Timestamp, true>::run(db, type_Timestamp);
2✔
4069

4070
    // Self-contained null state
4071
    Tester<StringData, true>::run(db, type_String);
2✔
4072
    Tester<StringData, false>::run(db, type_String);
2✔
4073
}
2✔
4074

4075
TEST(Table_StaleColumnKey)
4076
{
2✔
4077
    Table table;
2✔
4078

4079
    auto col = table.add_column(type_Int, "age");
2✔
4080

4081
    Obj obj = table.create_object();
2✔
4082
    obj.set(col, 5);
2✔
4083

4084
    table.remove_column(col);
2✔
4085
    // col is now obsolete
4086
    table.add_column(type_Int, "score");
2✔
4087
    CHECK_THROW_ANY(obj.get<Int>(col));
2✔
4088
}
2✔
4089

4090
TEST(Table_KeysRow)
4091
{
2✔
4092
    Table table;
2✔
4093
    auto col_int = table.add_column(type_Int, "int");
2✔
4094
    auto col_string = table.add_column(type_String, "string", true);
2✔
4095
    table.add_search_index(col_int);
2✔
4096
    table.add_search_index(col_string);
2✔
4097

4098
    table.create_object(ObjKey(7), {{col_int, 123}, {col_string, "Hello, "}});
2✔
4099
    table.create_object(ObjKey(9), {{col_int, 456}, {col_string, StringData()}});
2✔
4100

4101
    auto i = table.find_first_int(col_int, 123);
2✔
4102
    CHECK_EQUAL(i, ObjKey(7));
2✔
4103
    i = table.find_first_int(col_int, 456);
2✔
4104
    CHECK_EQUAL(i, ObjKey(9));
2✔
4105

4106
    i = table.find_first_string(col_string, "Hello, ");
2✔
4107
    CHECK_EQUAL(i, ObjKey(7));
2✔
4108
    i = table.find_first_string(col_string, StringData());
2✔
4109
    CHECK_EQUAL(i, ObjKey(9));
2✔
4110
}
2✔
4111

4112
template <typename T>
4113
T generate_value()
4114
{
31,461✔
4115
    return test_util::random_int<T>();
31,461✔
4116
}
31,461✔
4117

4118
template <>
4119
std::string generate_value()
4120
{
62,835✔
4121
    std::string str;
62,835✔
4122
    str.resize(31);
62,835✔
4123
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), &test_util::random_int<char>);
62,835✔
4124
    return str;
62,835✔
4125
}
62,835✔
4126

4127
template <>
4128
bool generate_value()
4129
{
31,387✔
4130
    return test_util::random_int<int>() & 0x1;
31,387✔
4131
}
31,387✔
4132
template <>
4133
float generate_value()
4134
{
31,472✔
4135
    return float(1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000)));
31,472✔
4136
}
31,472✔
4137
template <>
4138
double generate_value()
4139
{
31,387✔
4140
    return 1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000));
31,387✔
4141
}
31,387✔
4142
template <>
4143
Timestamp generate_value()
4144
{
31,405✔
4145
    return Timestamp(test_util::random_int<int>(0, 1000000), test_util::random_int<int>(0, 1000000000));
31,405✔
4146
}
31,405✔
4147
template <>
4148
Decimal128 generate_value()
4149
{
31,427✔
4150
    return Decimal128(test_util::random_int<int>(-100000, 100000));
31,427✔
4151
}
31,427✔
4152
template <>
4153
ObjectId generate_value()
4154
{
31,469✔
4155
    return ObjectId::gen();
31,469✔
4156
}
31,469✔
4157
template <>
4158
UUID generate_value()
4159
{
31,404✔
4160
    std::string str;
31,404✔
4161
    str.resize(36);
31,404✔
4162
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), []() -> char {
1,130,544✔
4163
        char c = test_util::random_int<char>(0, 15);
1,130,544✔
4164
        return c >= 10 ? (c - 10 + 'a') : (c + '0');
1,130,544✔
4165
    });
1,130,544✔
4166
    str.at(8) = '-';
31,404✔
4167
    str.at(13) = '-';
31,404✔
4168
    str.at(18) = '-';
31,404✔
4169
    str.at(23) = '-';
31,404✔
4170
    return UUID(str.c_str());
31,404✔
4171
}
31,404✔
4172

4173
// helper object taking care of destroying memory underlying StringData and BinaryData
4174
// just a passthrough for other types
4175
template <typename T>
4176
struct managed {
4177
    T value;
4178
};
4179

4180
template <typename T>
4181
struct ManagedStorage {
4182
    std::string storage;
4183
    T value;
4184

4185
    ManagedStorage() {}
40,160✔
4186
    ManagedStorage(null) {}
3,325✔
4187
    ManagedStorage(std::string&& v)
4188
        : storage(std::move(v))
31,423✔
4189
        , value(storage)
31,423✔
4190
    {
62,835✔
4191
    }
62,835✔
4192
    ManagedStorage(const ManagedStorage& other)
4193
    {
137,308✔
4194
        *this = other;
137,308✔
4195
    }
137,308✔
4196
    ManagedStorage(ManagedStorage&& other)
4197
    {
16,792✔
4198
        *this = std::move(other);
16,792✔
4199
    }
16,792✔
4200

4201
    ManagedStorage(T v)
4202
    {
16,160✔
4203
        if (v) {
16,160✔
4204
            if (v.size()) {
15,756✔
4205
                storage.assign(v.data(), v.data() + v.size());
15,351✔
4206
            }
15,351✔
4207
            value = T(storage);
15,756✔
4208
        }
15,756✔
4209
    }
16,160✔
4210
    ManagedStorage& operator=(const ManagedStorage& other)
4211
    {
138,103✔
4212
        storage = other.storage;
138,103✔
4213
        value = other.value ? T(storage) : T();
138,103✔
4214
        return *this;
138,103✔
4215
    }
138,103✔
4216
    ManagedStorage& operator=(ManagedStorage&& other)
4217
    {
2,763,819✔
4218
        storage = std::move(other.storage);
2,763,819✔
4219
        value = other.value ? T(storage) : T();
2,763,819✔
4220
        return *this;
2,763,819✔
4221
    }
2,763,819✔
4222
};
4223

4224
template <>
4225
struct managed<StringData> : ManagedStorage<StringData> {
4226
    using ManagedStorage::ManagedStorage;
4227
};
4228
template <>
4229
struct managed<BinaryData> : ManagedStorage<BinaryData> {
4230
    using ManagedStorage::ManagedStorage;
4231
};
4232

4233

4234
template <typename T>
4235
void check_values(TestContext& test_context, Lst<T>& lst, std::vector<managed<T>>& reference)
4236
{
400✔
4237
    CHECK_EQUAL(lst.size(), reference.size());
400✔
4238
    for (unsigned j = 0; j < reference.size(); ++j)
341,440✔
4239
        CHECK_EQUAL(lst.get(j), reference[j].value);
341,040✔
4240
}
400✔
4241

4242
template <typename T>
4243
struct generator {
4244
    static managed<T> get(bool optional)
4245
    {
163,000✔
4246
        if (optional && (test_util::random_int<int>() % 10) == 0) {
163,000!
4247
            return managed<T>{T()};
4,780✔
4248
        }
4,780✔
4249
        else {
158,220✔
4250
            return managed<T>{generate_value<T>()};
158,220✔
4251
        }
158,220✔
4252
    }
163,000✔
4253
};
4254

4255
template <>
4256
struct generator<StringData> {
4257
    static managed<StringData> get(bool optional)
4258
    {
33,080✔
4259
        if (optional && (test_util::random_int<int>() % 10) == 0) {
33,080✔
4260
            return managed<StringData>(null());
1,647✔
4261
        }
1,647✔
4262
        else {
31,433✔
4263
            return generate_value<std::string>();
31,433✔
4264
        }
31,433✔
4265
    }
33,080✔
4266
};
4267

4268
template <>
4269
struct generator<BinaryData> {
4270
    static managed<BinaryData> get(bool optional)
4271
    {
33,080✔
4272
        if (optional && (test_util::random_int<int>() % 10) == 0) {
33,080✔
4273
            return managed<BinaryData>(null());
1,678✔
4274
        }
1,678✔
4275
        else {
31,402✔
4276
            return generate_value<std::string>();
31,402✔
4277
        }
31,402✔
4278
    }
33,080✔
4279
};
4280

4281
template <>
4282
struct generator<ObjectId> {
4283
    static managed<ObjectId> get(bool)
4284
    {
16,540✔
4285
        return managed<ObjectId>{generate_value<ObjectId>()};
16,540✔
4286
    }
16,540✔
4287
};
4288

4289
template <typename T>
4290
struct generator<Optional<T>> {
4291
    static managed<Optional<T>> get(bool)
4292
    {
85,100✔
4293
        if ((test_util::random_int<int>() % 10) == 0)
85,100✔
4294
            return managed<Optional<T>>{Optional<T>()};
8,448✔
4295
        else
76,652✔
4296
            return managed<Optional<T>>{generate_value<T>()};
76,652✔
4297
    }
85,100✔
4298
};
4299

4300
// specialize for Optional<StringData> and Optional<BinaryData> just to trigger errors if ever used
4301
template <>
4302
struct generator<Optional<StringData>> {};
4303
template <>
4304
struct generator<Optional<BinaryData>> {};
4305

4306
template <typename T>
4307
void test_lists(TestContext& test_context, DBRef sg, const realm::DataType type_id, bool optional = false)
4308
{
40✔
4309
    auto t = sg->start_write();
40✔
4310
    auto table = t->add_table("the_table");
40✔
4311
    auto col = table->add_column_list(type_id, "the column", optional);
40✔
4312
    Obj o = table->create_object();
40✔
4313
    Lst<T> lst = o.get_list<T>(col);
40✔
4314
    std::vector<managed<T>> reference;
40✔
4315
    for (int j = 0; j < 1000; ++j) {
40,040✔
4316
        managed<T> value = generator<T>::get(optional);
40,000✔
4317
        lst.add(value.value);
40,000✔
4318
        reference.push_back(value);
40,000✔
4319
    }
40,000✔
4320
    check_values(test_context, lst, reference);
40✔
4321
    for (int j = 0; j < 100; ++j) {
4,040✔
4322
        managed<T> value = generator<T>::get(optional);
4,000✔
4323
        lst.insert(493, value.value);
4,000✔
4324
        value = generator<T>::get(optional);
4,000✔
4325
        lst.set(493, value.value);
4,000✔
4326
        reference.insert(reference.begin() + 493, value);
4,000✔
4327
    }
4,000✔
4328
    check_values(test_context, lst, reference);
40✔
4329
    for (int j = 0; j < 100; ++j) {
4,040✔
4330
        lst.remove(142);
4,000✔
4331
        reference.erase(reference.begin() + 142);
4,000✔
4332
    }
4,000✔
4333
    check_values(test_context, lst, reference);
40✔
4334
    for (int disp = 0; disp < 4; ++disp) {
200✔
4335
        for (int j = 250 + disp; j > 50; j -= 3) {
10,960✔
4336
            lst.remove(j);
10,800✔
4337
            reference.erase(reference.begin() + j);
10,800✔
4338
        }
10,800✔
4339
        check_values(test_context, lst, reference);
160✔
4340
    }
160✔
4341
    auto it = reference.begin();
40✔
4342
    for (auto value : lst) {
29,200✔
4343
        CHECK(value == it->value);
29,200✔
4344
        ++it;
29,200✔
4345
    }
29,200✔
4346
    for (size_t j = lst.size(); j >= 100; --j) {
25,280✔
4347
        lst.remove(j - 1);
25,240✔
4348
        reference.pop_back();
25,240✔
4349
    }
25,240✔
4350
    check_values(test_context, lst, reference);
40✔
4351
    while (size_t sz = lst.size()) {
4,000✔
4352
        lst.remove(sz - 1);
3,960✔
4353
        reference.pop_back();
3,960✔
4354
    }
3,960✔
4355
    CHECK_EQUAL(0, reference.size());
40✔
4356
    t->rollback();
40✔
4357
}
40✔
4358

4359
TEST(Table_List_Ops)
4360
{
2✔
4361
    SHARED_GROUP_TEST_PATH(path);
2✔
4362

4363
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4364
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4365

4366
    test_lists<int64_t>(test_context, sg, type_Int);
2✔
4367
    test_lists<StringData>(test_context, sg, type_String);
2✔
4368
    test_lists<BinaryData>(test_context, sg, type_Binary);
2✔
4369
    test_lists<bool>(test_context, sg, type_Bool);
2✔
4370
    test_lists<float>(test_context, sg, type_Float);
2✔
4371
    test_lists<double>(test_context, sg, type_Double);
2✔
4372
    test_lists<Timestamp>(test_context, sg, type_Timestamp);
2✔
4373
    test_lists<Decimal128>(test_context, sg, type_Decimal);
2✔
4374
    test_lists<ObjectId>(test_context, sg, type_ObjectId);
2✔
4375
    test_lists<UUID>(test_context, sg, type_UUID);
2✔
4376

4377
    test_lists<Optional<int64_t>>(test_context, sg, type_Int, true);
2✔
4378
    test_lists<StringData>(test_context, sg, type_String, true); // always Optional?
2✔
4379
    test_lists<BinaryData>(test_context, sg, type_Binary, true); // always Optional?
2✔
4380
    test_lists<Optional<bool>>(test_context, sg, type_Bool, true);
2✔
4381
    test_lists<Optional<float>>(test_context, sg, type_Float, true);
2✔
4382
    test_lists<Optional<double>>(test_context, sg, type_Double, true);
2✔
4383
    test_lists<Timestamp>(test_context, sg, type_Timestamp, true); // always Optional?
2✔
4384
    test_lists<Decimal128>(test_context, sg, type_Decimal, true);
2✔
4385
    test_lists<Optional<ObjectId>>(test_context, sg, type_ObjectId, true);
2✔
4386
    test_lists<Optional<UUID>>(test_context, sg, type_UUID, true);
2✔
4387
}
2✔
4388

4389
template <typename T>
4390
void check_table_values(TestContext& test_context, TableRef t, ColKey col, std::map<int, managed<T>>& reference)
4391
{
200✔
4392
    if (t->size() != reference.size()) {
200✔
4393
        std::cout << "gah" << std::endl;
×
4394
    }
×
4395
    CHECK_EQUAL(t->size(), reference.size());
200✔
4396
    for (auto it : reference) {
480,800✔
4397
        T value = it.second.value;
480,800✔
4398
        Obj o = t->get_object(ObjKey(it.first));
480,800✔
4399
        CHECK_EQUAL(o.get<T>(col), value);
480,800✔
4400
    }
480,800✔
4401
}
200✔
4402

4403
template <typename T>
4404
void test_tables(TestContext& test_context, DBRef sg, const realm::DataType type_id, bool optional = false)
4405
{
40✔
4406
    auto t = sg->start_write();
40✔
4407
    auto table = t->add_table("the_table");
40✔
4408
    auto col = table->add_column(type_id, "the column", optional);
40✔
4409
    std::map<int, managed<T>> reference;
40✔
4410

4411
    // insert elements 0 - 999
4412
    for (int j = 0; j < 1000; ++j) {
40,040✔
4413
        managed<T> value = generator<T>::get(optional);
40,000✔
4414
        table->create_object(ObjKey(j)).set_all(value.value);
40,000✔
4415
        reference[j] = std::move(value);
40,000✔
4416
    }
40,000✔
4417
    // insert elements 10000 - 10999
4418
    for (int j = 10000; j < 11000; ++j) {
40,040✔
4419
        managed<T> value = generator<T>::get(optional);
40,000✔
4420
        table->create_object(ObjKey(j)).set_all(value.value);
40,000✔
4421
        reference[j] = std::move(value);
40,000✔
4422
    }
40,000✔
4423
    // insert in between previous groups
4424
    for (int j = 4000; j < 7000; ++j) {
120,040✔
4425
        managed<T> value = generator<T>::get(optional);
120,000✔
4426
        table->create_object(ObjKey(j)).set_all(value.value);
120,000✔
4427
        reference[j] = std::move(value);
120,000✔
4428
    }
120,000✔
4429
    check_table_values(test_context, table, col, reference);
40✔
4430

4431
    // modify values
4432
    for (int j = 0; j < 11000; j += 100) {
4,440✔
4433
        auto it = reference.find(j);
4,400✔
4434
        if (it == reference.end()) // skip over holes in the key range
4,400✔
4435
            continue;
2,400✔
4436
        managed<T> value = generator<T>::get(optional);
2,000✔
4437
        table->get_object(ObjKey(j)).set<T>(col, value.value);
2,000✔
4438
        it->second = value;
2,000✔
4439
    }
2,000✔
4440
    check_table_values(test_context, table, col, reference);
40✔
4441

4442
    // remove chunk in the middle
4443
    for (int j = 1000; j < 10000; ++j) {
360,040✔
4444
        auto it = reference.find(j);
360,000✔
4445
        if (it == reference.end()) // skip over holes in the key range
360,000✔
4446
            continue;
240,000✔
4447
        table->remove_object(ObjKey(j));
120,000✔
4448
        reference.erase(it);
120,000✔
4449
    }
120,000✔
4450
    check_table_values(test_context, table, col, reference);
40✔
4451
    t->rollback();
40✔
4452
}
40✔
4453

4454
TEST(Table_Ops)
4455
{
2✔
4456
    SHARED_GROUP_TEST_PATH(path);
2✔
4457

4458
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4459
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4460

4461
    test_tables<int64_t>(test_context, sg, type_Int);
2✔
4462
    test_tables<StringData>(test_context, sg, type_String);
2✔
4463
    test_tables<BinaryData>(test_context, sg, type_Binary);
2✔
4464
    test_tables<bool>(test_context, sg, type_Bool);
2✔
4465
    test_tables<float>(test_context, sg, type_Float);
2✔
4466
    test_tables<double>(test_context, sg, type_Double);
2✔
4467
    test_tables<Timestamp>(test_context, sg, type_Timestamp);
2✔
4468
    test_tables<Decimal128>(test_context, sg, type_Decimal);
2✔
4469
    test_tables<ObjectId>(test_context, sg, type_ObjectId);
2✔
4470
    test_tables<UUID>(test_context, sg, type_UUID);
2✔
4471

4472
    test_tables<Optional<int64_t>>(test_context, sg, type_Int, true);
2✔
4473
    test_tables<StringData>(test_context, sg, type_String, true); // always Optional?
2✔
4474
    test_tables<BinaryData>(test_context, sg, type_Binary, true); // always Optional?
2✔
4475
    test_tables<Optional<bool>>(test_context, sg, type_Bool, true);
2✔
4476
    test_tables<Optional<float>>(test_context, sg, type_Float, true);
2✔
4477
    test_tables<Optional<double>>(test_context, sg, type_Double, true);
2✔
4478
    test_tables<Timestamp>(test_context, sg, type_Timestamp, true); // always Optional?
2✔
4479
    test_tables<Decimal128>(test_context, sg, type_Decimal, true);
2✔
4480
    test_tables<Optional<ObjectId>>(test_context, sg, type_ObjectId, true);
2✔
4481
    test_tables<UUID>(test_context, sg, type_UUID, true);
2✔
4482
}
2✔
4483

4484
template <typename TFrom, typename TTo>
4485
void test_dynamic_conversion(TestContext& test_context, DBRef sg, realm::DataType type_id, bool from_nullable,
4486
                             bool to_nullable)
4487
{
80✔
4488
    // Create values of type TFrom and ask for dynamic conversion to TTo
4489
    auto t = sg->start_write();
80✔
4490
    auto table = t->add_table("the_table");
80✔
4491
    auto col_from = table->add_column(type_id, "the column", from_nullable);
80✔
4492
    if (type_id == type_String) {
80✔
4493
        table->add_search_index(col_from);
8✔
4494
    }
8✔
4495
    std::map<int, managed<TTo>> reference;
80✔
4496
    value_copier<TFrom, TTo> copier(false);
80✔
4497
    for (int j = 0; j < 10; ++j) {
880✔
4498
        managed<TFrom> value = generator<TFrom>::get(from_nullable);
800✔
4499
        table->create_object(ObjKey(j)).set_all(value.value);
800✔
4500
        TTo conv_value = copier(
800✔
4501
            value.value, to_nullable); // one may argue that using the same converter for ref and dut is.. mmmh...
800✔
4502
        reference[j] = managed<TTo>{conv_value};
800✔
4503
    }
800✔
4504
    auto col_to = table->set_nullability(col_from, to_nullable, false);
80✔
4505
    if (type_id == type_String) {
80✔
4506
        CHECK(table->has_search_index(col_to));
8✔
4507
    }
8✔
4508
    check_table_values(test_context, table, col_to, reference);
80✔
4509
    t->rollback();
80✔
4510
}
80✔
4511

4512
template <typename TFrom, typename TTo>
4513
void test_dynamic_conversion_list(TestContext& test_context, DBRef sg, realm::DataType type_id, bool from_nullable,
4514
                                  bool to_nullable)
4515
{
80✔
4516
    // Create values of type TFrom and ask for dynamic conversion to TTo
4517
    auto t = sg->start_write();
80✔
4518
    auto table = t->add_table("the_table");
80✔
4519
    auto col_from = table->add_column_list(type_id, "the column", from_nullable);
80✔
4520
    Obj o = table->create_object();
80✔
4521
    table->create_object(); // This object will have an empty list
80✔
4522
    Lst<TFrom> from_lst = o.get_list<TFrom>(col_from);
80✔
4523
    std::vector<managed<TTo>> reference;
80✔
4524
    value_copier<TFrom, TTo> copier(false);
80✔
4525
    for (int j = 0; j < 1000; ++j) {
80,080✔
4526
        managed<TFrom> value = generator<TFrom>::get(from_nullable);
80,000✔
4527
        from_lst.add(value.value);
80,000✔
4528
        TTo conv_value = copier(value.value, to_nullable);
80,000✔
4529
        reference.push_back(managed<TTo>{conv_value});
80,000✔
4530
    }
80,000✔
4531
    auto col_to = table->set_nullability(col_from, to_nullable, false);
80✔
4532
    Lst<TTo> to_lst = o.get_list<TTo>(col_to);
80✔
4533
    check_values(test_context, to_lst, reference);
80✔
4534
    t->rollback();
80✔
4535
}
80✔
4536

4537
template <typename T>
4538
void test_dynamic_conversion_combi(TestContext& test_context, DBRef sg, realm::DataType type_id)
4539
{
10✔
4540
    test_dynamic_conversion<T, Optional<T>>(test_context, sg, type_id, false, true);
10✔
4541
    test_dynamic_conversion<Optional<T>, T>(test_context, sg, type_id, true, false);
10✔
4542
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, false);
10✔
4543
    test_dynamic_conversion<Optional<T>, Optional<T>>(test_context, sg, type_id, true, true);
10✔
4544
}
10✔
4545

4546
template <typename T>
4547
void test_dynamic_conversion_combi_sametype(TestContext& test_context, DBRef sg, realm::DataType type_id)
4548
{
10✔
4549
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, true);
10✔
4550
    test_dynamic_conversion<T, T>(test_context, sg, type_id, true, false);
10✔
4551
    test_dynamic_conversion<T, T>(test_context, sg, type_id, false, false);
10✔
4552
    test_dynamic_conversion<T, T>(test_context, sg, type_id, true, true);
10✔
4553
}
10✔
4554

4555
template <typename T>
4556
void test_dynamic_conversion_list_combi(TestContext& test_context, DBRef sg, realm::DataType type_id)
4557
{
10✔
4558
    test_dynamic_conversion_list<T, Optional<T>>(test_context, sg, type_id, false, true);
10✔
4559
    test_dynamic_conversion_list<Optional<T>, T>(test_context, sg, type_id, true, false);
10✔
4560
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, false);
10✔
4561
    test_dynamic_conversion_list<Optional<T>, Optional<T>>(test_context, sg, type_id, true, true);
10✔
4562
}
10✔
4563

4564
template <typename T>
4565
void test_dynamic_conversion_list_combi_sametype(TestContext& test_context, DBRef sg, realm::DataType type_id)
4566
{
10✔
4567
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, true);
10✔
4568
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, true, false);
10✔
4569
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, false, false);
10✔
4570
    test_dynamic_conversion_list<T, T>(test_context, sg, type_id, true, true);
10✔
4571
}
10✔
4572

4573
TEST(Table_Column_DynamicConversions)
4574
{
2✔
4575
    SHARED_GROUP_TEST_PATH(path);
2✔
4576

4577
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4578
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4579

4580
    test_dynamic_conversion_combi<int64_t>(test_context, sg, type_Int);
2✔
4581
    test_dynamic_conversion_combi<float>(test_context, sg, type_Float);
2✔
4582
    test_dynamic_conversion_combi<double>(test_context, sg, type_Double);
2✔
4583
    test_dynamic_conversion_combi<bool>(test_context, sg, type_Bool);
2✔
4584
    test_dynamic_conversion_combi<ObjectId>(test_context, sg, type_ObjectId);
2✔
4585

4586
    test_dynamic_conversion_combi_sametype<StringData>(test_context, sg, type_String);
2✔
4587
    test_dynamic_conversion_combi_sametype<BinaryData>(test_context, sg, type_Binary);
2✔
4588
    test_dynamic_conversion_combi_sametype<Timestamp>(test_context, sg, type_Timestamp);
2✔
4589
    test_dynamic_conversion_combi_sametype<Decimal128>(test_context, sg, type_Decimal);
2✔
4590
    test_dynamic_conversion_combi_sametype<UUID>(test_context, sg, type_UUID);
2✔
4591
    // lists...:
4592
    test_dynamic_conversion_list_combi<int64_t>(test_context, sg, type_Int);
2✔
4593
    test_dynamic_conversion_list_combi<float>(test_context, sg, type_Float);
2✔
4594
    test_dynamic_conversion_list_combi<double>(test_context, sg, type_Double);
2✔
4595
    test_dynamic_conversion_list_combi<bool>(test_context, sg, type_Bool);
2✔
4596
    test_dynamic_conversion_list_combi<ObjectId>(test_context, sg, type_ObjectId);
2✔
4597

4598
    test_dynamic_conversion_list_combi_sametype<StringData>(test_context, sg, type_String);
2✔
4599
    test_dynamic_conversion_list_combi_sametype<BinaryData>(test_context, sg, type_Binary);
2✔
4600
    test_dynamic_conversion_list_combi_sametype<Timestamp>(test_context, sg, type_Timestamp);
2✔
4601
    test_dynamic_conversion_list_combi_sametype<Decimal128>(test_context, sg, type_Decimal);
2✔
4602
    test_dynamic_conversion_list_combi_sametype<UUID>(test_context, sg, type_UUID);
2✔
4603
}
2✔
4604

4605
/*
4606
TEST(Table_Column_Conversions)
4607
{
4608
    SHARED_GROUP_TEST_PATH(path);
4609

4610
    std::unique_ptr<Replication> hist(make_in_realm_history());
4611
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
4612

4613
    test_column_conversion<int64_t, Optional<int64_t>>(test_context, sg, type_Int);
4614
    test_column_conversion<float, Optional<float>>(test_context, sg, type_Float);
4615
    test_column_conversion<double, Optional<double>>(test_context, sg, type_Double);
4616
    test_column_conversion<bool, Optional<bool>>(test_context, sg, type_Bool);
4617
    test_column_conversion<StringData, StringData>(test_context, sg, type_String);
4618
    test_column_conversion<BinaryData, BinaryData>(test_context, sg, type_Binary);
4619
    test_column_conversion<Timestamp, Timestamp>(test_context, sg, type_Timestamp);
4620

4621
    test_column_conversion_optional<int64_t>(test_context, sg, type_Int);
4622
    test_column_conversion_optional<float>(test_context, sg, type_Float);
4623
    test_column_conversion_optional<double>(test_context, sg, type_Double);
4624
    test_column_conversion_optional<bool>(test_context, sg, type_Bool);
4625

4626
    test_column_conversion_sametype<StringData>(test_context, sg, type_String);
4627
    test_column_conversion_sametype<BinaryData>(test_context, sg, type_Binary);
4628
    test_column_conversion_sametype<Timestamp>(test_context, sg, type_Timestamp);
4629

4630
}
4631
*/
4632

4633
TEST(Table_ChangePKNullability)
4634
{
2✔
4635
    SHARED_GROUP_TEST_PATH(path);
2✔
4636

4637
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4638
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4639

4640
    auto wt = sg->start_write();
2✔
4641
    auto table = wt->add_table_with_primary_key("foo", type_String, "id", false);
2✔
4642

4643
    table->create_object_with_primary_key("Paul");
2✔
4644
    table->create_object_with_primary_key("John");
2✔
4645
    table->create_object_with_primary_key("George");
2✔
4646
    table->create_object_with_primary_key("Ringo");
2✔
4647

4648
    auto pk_col = table->get_primary_key_column();
2✔
4649
    pk_col = table->set_nullability(pk_col, true, true);
2✔
4650
    CHECK(pk_col.is_nullable());
2✔
4651

4652
    table->create_object_with_primary_key("");
2✔
4653
    table->create_object_with_primary_key({});
2✔
4654

4655
    std::string message;
2✔
4656
    CHECK_THROW_ANY_GET_MESSAGE(table->set_nullability(pk_col, false, true), message);
2✔
4657
    CHECK_EQUAL(message, "Objects in 'foo' has null value(s) in property 'id'");
2✔
4658

4659
    table->get_object_with_primary_key({}).remove();
2✔
4660
    table->set_nullability(pk_col, false, true);
2✔
4661
}
2✔
4662

4663
TEST(Table_MultipleObjs)
4664
{
2✔
4665
    SHARED_GROUP_TEST_PATH(path);
2✔
4666

4667
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4668
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4669

4670
    auto tr = sg->start_write();
2✔
4671
    auto table = tr->add_table("my_table");
2✔
4672
    auto col = table->add_column_list(*table, "the links");
2✔
4673
    auto col_int = table->add_column_list(type_String, "the integers");
2✔
4674
    auto obj_key = table->create_object().get_key();
2✔
4675
    tr->commit();
2✔
4676
    tr = sg->start_write();
2✔
4677
    table = tr->get_table("my_table");
2✔
4678
    auto obj = table->get_object(obj_key);
2✔
4679
    auto list_1 = obj.get_linklist(col);
2✔
4680
    auto list_2 = obj.get_linklist(col);
2✔
4681

4682
    auto list_3 = obj.get_list<StringData>(col_int);
2✔
4683
    auto list_4 = obj.get_list<StringData>(col_int);
2✔
4684
    std::string s = "42";
2✔
4685
    StringData ss(s.data(), s.size());
2✔
4686
    list_3.add(ss);
2✔
4687
    CHECK_EQUAL(list_4.get(0), ss);
2✔
4688

4689
    list_1.add(obj_key);
2✔
4690
    CHECK_EQUAL(list_1.get(0), obj_key);
2✔
4691
    CHECK_EQUAL(list_2.get(0), obj_key);
2✔
4692
}
2✔
4693

4694
TEST(Table_IteratorRandomAccess)
4695
{
2✔
4696
    Table t;
2✔
4697

4698
    ObjKeys keys;
2✔
4699
    t.create_objects(1000, keys);
2✔
4700

4701
    auto key = keys.begin();
2✔
4702
    auto iter = t.begin();
2✔
4703
    for (size_t pos = 0; (pos + 3) < 1000; pos += 3) {
668✔
4704
        CHECK_EQUAL(iter->get_key(), *key);
666✔
4705
        iter += 3;
666✔
4706
        key += 3;
666✔
4707
    }
666✔
4708

4709
    // random access
4710
    for (int j = 0; j < 5; j++) {
12✔
4711
        std::vector<size_t> random_idx(keys.size());
10✔
4712
        std::iota(random_idx.begin(), random_idx.end(), 0);
10✔
4713
        // unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
4714
        // std::cout << "Seed " << seed << std::endl;
4715
        std::shuffle(random_idx.begin(), random_idx.end(), std::mt19937(unit_test_random_seed));
10✔
4716
        iter = t.begin();
10✔
4717
        int i = 0;
10✔
4718
        for (auto index : random_idx) {
6,456✔
4719
            if (index < keys.size()) {
6,456✔
4720
                auto k = keys[index];
5,721✔
4721
                if (i == 4) {
5,721✔
4722
                    t.remove_object(k);
1,423✔
4723
                    keys.erase(keys.begin() + index);
1,423✔
4724
                    if (index == 0)
1,423✔
UNCOV
4725
                        iter = t.begin();
×
4726
                    i = 0;
1,423✔
4727
                }
1,423✔
4728
                else {
4,298✔
4729
                    iter.go(index);
4,298✔
4730
                    CHECK_EQUAL(k, iter->get_key());
4,298✔
4731
                }
4,298✔
4732
                i++;
5,721✔
4733
            }
5,721✔
4734
        }
6,456✔
4735
    }
10✔
4736

4737
    iter.go(0);
2✔
4738
    auto iter200 = iter + 200;
2✔
4739
    CHECK_EQUAL(keys[200], iter200->get_key());
2✔
4740
}
2✔
4741

4742
TEST(Table_EmbeddedObjects)
4743
{
2✔
4744
    SHARED_GROUP_TEST_PATH(path);
2✔
4745

4746
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4747
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4748

4749
    auto tr = sg->start_write();
2✔
4750
    auto table = tr->add_table("mytable", Table::Type::Embedded);
2✔
4751
    tr->commit_and_continue_as_read();
2✔
4752
    tr->promote_to_write();
2✔
4753
    CHECK(table->is_embedded());
2✔
4754
    CHECK_THROW(table->create_object(), LogicError);
2✔
4755
    tr->rollback();
2✔
4756

4757
    tr = sg->start_read();
2✔
4758
    table = tr->get_table("mytable");
2✔
4759
    CHECK(table->is_embedded());
2✔
4760
}
2✔
4761

4762
TEST(Table_EmbeddedObjectCreateAndDestroy)
4763
{
2✔
4764
    SHARED_GROUP_TEST_PATH(path);
2✔
4765

4766
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4767
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4768

4769
    {
2✔
4770
        auto tr = sg->start_write();
2✔
4771
        auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4772
        auto col_recurse = table->add_column(*table, "theRecursiveBit");
2✔
4773
        CHECK_THROW(table->create_object(), LogicError);
2✔
4774
        auto parent = tr->add_table("myParentStuff");
2✔
4775
        auto ck = parent->add_column(*table, "theGreatColumn");
2✔
4776
        Obj o = parent->create_object();
2✔
4777
        Obj o2 = o.create_and_set_linked_object(ck);
2✔
4778
        Obj o3 = o2.create_and_set_linked_object(col_recurse);
2✔
4779
        auto parent_obj = o2.get_parent_object();
2✔
4780
        CHECK_EQUAL(o.get_key(), parent_obj.get_key());
2✔
4781
        parent_obj = o3.get_parent_object();
2✔
4782
        CHECK_EQUAL(o2.get_key(), parent_obj.get_key());
2✔
4783
        CHECK(table->size() == 2);
2✔
4784
        tr->commit();
2✔
4785
    }
2✔
4786
    {
2✔
4787
        auto tr = sg->start_write();
2✔
4788
        auto table = tr->get_table("myEmbeddedStuff");
2✔
4789
        auto parent = tr->get_table("myParentStuff");
2✔
4790
        CHECK(table->size() == 2);
2✔
4791
        auto first = parent->begin();
2✔
4792
        first->set("theGreatColumn", ObjKey());
2✔
4793
        CHECK(table->size() == 0);
2✔
4794
        // do not commit
4795
    }
2✔
4796
    {
2✔
4797
        auto tr = sg->start_write();
2✔
4798
        auto table = tr->get_table("myEmbeddedStuff");
2✔
4799
        auto parent = tr->get_table("myParentStuff");
2✔
4800
        CHECK(table->size() == 2);
2✔
4801
        auto first = parent->begin();
2✔
4802
        first->remove();
2✔
4803
        CHECK(table->size() == 0);
2✔
4804
        // do not commit
4805
    }
2✔
4806
    {
2✔
4807
        // Sync operations
4808
        auto tr = sg->start_write();
2✔
4809
        auto table = tr->get_table("myEmbeddedStuff");
2✔
4810
        auto parent = tr->get_table("myParentStuff");
2✔
4811
        CHECK(table->size() == 2);
2✔
4812
        auto first = parent->begin();
2✔
4813
        first->invalidate();
2✔
4814
        CHECK(table->size() == 0);
2✔
4815
        // do not commit
4816
    }
2✔
4817
}
2✔
4818

4819
TEST(Table_EmbeddedObjectCreateAndDestroyList)
4820
{
2✔
4821
    SHARED_GROUP_TEST_PATH(path);
2✔
4822

4823
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4824
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4825

4826
    auto tr = sg->start_write();
2✔
4827
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4828
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
4829
    CHECK_THROW(table->create_object(), LogicError);
2✔
4830
    auto parent = tr->add_table("myParentStuff");
2✔
4831
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
4832
    Obj o = parent->create_object();
2✔
4833
    auto parent_ll = o.get_linklist(ck);
2✔
4834
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
4835
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
4836
    parent_ll.create_and_insert_linked_object(0);
2✔
4837
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
4838
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
4839
    o2_ll.create_and_insert_linked_object(0);
2✔
4840
    o2_ll.create_and_insert_linked_object(0);
2✔
4841
    o3_ll.create_and_insert_linked_object(0);
2✔
4842

4843
    tr->commit_and_continue_as_read();
2✔
4844
    tr->verify();
2✔
4845

4846
    tr->promote_to_write();
2✔
4847
    CHECK(table->size() == 6);
2✔
4848
    parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
4849
    CHECK(!o2.is_valid());
2✔
4850
    CHECK(table->size() == 4);
2✔
4851
    parent_ll.clear();
2✔
4852
    CHECK(table->size() == 0);
2✔
4853
    parent_ll.create_and_insert_linked_object(0);
2✔
4854
    parent_ll.create_and_insert_linked_object(1);
2✔
4855
    CHECK(table->size() == 2);
2✔
4856
    o.remove();
2✔
4857
    CHECK(table->size() == 0);
2✔
4858
    tr->commit();
2✔
4859
}
2✔
4860

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

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

4868
    auto tr = sg->start_write();
2✔
4869
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4870
    auto col_recurse = table->add_column_dictionary(*table, "theRecursiveBit");
2✔
4871
    CHECK_THROW(table->create_object(), LogicError);
2✔
4872
    auto parent = tr->add_table("myParentStuff");
2✔
4873
    auto ck = parent->add_column_dictionary(*table, "theGreatColumn");
2✔
4874
    auto ck1 = parent->add_column(*table, "theLesserColumn");
2✔
4875
    Obj o = parent->create_object();
2✔
4876
    auto parent_dict = o.get_dictionary(ck);
2✔
4877
    Obj o2 = parent_dict.create_and_insert_linked_object("one");
2✔
4878
    Obj o4 = o.create_and_set_linked_object(ck1);
2✔
4879

4880
    auto obj_path = o2.get_path();
2✔
4881
    CHECK_EQUAL(obj_path.path_from_top.size(), 2);
2✔
4882
    CHECK_EQUAL(obj_path.path_from_top[0], ck);
2✔
4883
    CHECK_EQUAL(obj_path.path_from_top[1], "one");
2✔
4884

4885
    Obj o3 = parent_dict.create_and_insert_linked_object("two");
2✔
4886
    parent_dict.create_and_insert_linked_object("three");
2✔
4887

4888
    CHECK_EQUAL(parent_dict.get_object("one").get_key(), o2.get_key());
2✔
4889

4890
    auto o2_dict = o2.get_dictionary(col_recurse);
2✔
4891
    auto o3_dict = o3.get_dictionary(col_recurse);
2✔
4892
    auto o4_dict = o4.get_dictionary(col_recurse);
2✔
4893
    o2_dict.create_and_insert_linked_object("foo1");
2✔
4894
    o2_dict.create_and_insert_linked_object("foo2");
2✔
4895
    o3_dict.create_and_insert_linked_object("foo3");
2✔
4896
    o4_dict.create_and_insert_linked_object("foo4");
2✔
4897

4898
    obj_path = o2_dict.get_object("foo1").get_path();
2✔
4899
    CHECK_EQUAL(obj_path.path_from_top.size(), 4);
2✔
4900
    CHECK_EQUAL(obj_path.path_from_top[0], ck);
2✔
4901
    CHECK_EQUAL(obj_path.path_from_top[1], "one");
2✔
4902
    CHECK_EQUAL(obj_path.path_from_top[2], "theRecursiveBit");
2✔
4903
    CHECK_EQUAL(obj_path.path_from_top[3], "foo1");
2✔
4904

4905
    obj_path = o4_dict.get_object("foo4").get_path();
2✔
4906
    CHECK_EQUAL(obj_path.path_from_top.size(), 3);
2✔
4907
    CHECK_EQUAL(obj_path.path_from_top[0], ck1);
2✔
4908
    CHECK_EQUAL(obj_path.path_from_top[1], "theRecursiveBit");
2✔
4909
    CHECK_EQUAL(obj_path.path_from_top[2], "foo4");
2✔
4910

4911
    tr->commit_and_continue_as_read();
2✔
4912
    tr->verify();
2✔
4913

4914
    tr->promote_to_write();
2✔
4915
    CHECK_EQUAL(table->size(), 8);
2✔
4916
    parent_dict.create_and_insert_linked_object("one"); // implicitly remove entry for 02
2✔
4917
    CHECK(!o2.is_valid());
2✔
4918
    CHECK_EQUAL(table->size(), 6);
2✔
4919
    parent_dict.clear();
2✔
4920
    CHECK_EQUAL(table->size(), 2);
2✔
4921
    parent_dict.create_and_insert_linked_object("four");
2✔
4922
    parent_dict.create_and_insert_linked_object("five");
2✔
4923
    CHECK_EQUAL(table->size(), 4);
2✔
4924
    o.remove();
2✔
4925
    CHECK_EQUAL(table->size(), 0);
2✔
4926
    tr->commit();
2✔
4927
}
2✔
4928

4929
TEST(Table_EmbeddedObjectNotifications)
4930
{
2✔
4931
    SHARED_GROUP_TEST_PATH(path);
2✔
4932

4933
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4934
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4935

4936
    auto tr = sg->start_write();
2✔
4937
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
4938
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
4939
    CHECK_THROW(table->create_object(), LogicError);
2✔
4940
    auto parent = tr->add_table("myParentStuff");
2✔
4941
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
4942
    Obj o = parent->create_object();
2✔
4943
    auto parent_ll = o.get_linklist(ck);
2✔
4944
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
4945
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
4946
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
4947
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
4948
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
4949
    o2_ll.create_and_insert_linked_object(0);
2✔
4950
    o2_ll.create_and_insert_linked_object(0);
2✔
4951
    o3_ll.create_and_insert_linked_object(0);
2✔
4952
    CHECK(table->size() == 6);
2✔
4953
    Obj o5 = parent_ll.create_and_set_linked_object(1); // implicitly remove entry for 02
2✔
4954
    CHECK(!o2.is_valid());
2✔
4955
    CHECK(table->size() == 4);
2✔
4956
    // now the notifications...
4957
    int calls = 0;
2✔
4958
    tr->set_cascade_notification_handler([&](const Group::CascadeNotification& notification) {
6✔
4959
        CHECK_EQUAL(0, notification.links.size());
6✔
4960
        if (calls == 0) {
6✔
4961
            CHECK_EQUAL(1, notification.rows.size());
2✔
4962
            CHECK_EQUAL(parent->get_key(), notification.rows[0].table_key);
2✔
4963
            CHECK_EQUAL(o.get_key(), notification.rows[0].key);
2✔
4964
        }
2✔
4965
        else if (calls == 1) {
4✔
4966
            CHECK_EQUAL(3, notification.rows.size());
2✔
4967
            for (auto& row : notification.rows)
2✔
4968
                CHECK_EQUAL(table->get_key(), row.table_key);
6✔
4969
            CHECK_EQUAL(o4.get_key(), notification.rows[0].key);
2✔
4970
            CHECK_EQUAL(o5.get_key(), notification.rows[1].key);
2✔
4971
            CHECK_EQUAL(o3.get_key(), notification.rows[2].key);
2✔
4972
        }
2✔
4973
        else if (calls == 2) {
2✔
4974
            CHECK_EQUAL(1, notification.rows.size()); // from o3
2✔
4975
            for (auto& row : notification.rows)
2✔
4976
                CHECK_EQUAL(table->get_key(), row.table_key);
2✔
4977
            // don't bother checking the keys...
4978
        }
2✔
4979
        ++calls;
6✔
4980
    });
6✔
4981

4982
    o.remove();
2✔
4983
    CHECK(calls == 3);
2✔
4984
    tr->commit();
2✔
4985
}
2✔
4986
TEST(Table_EmbeddedObjectTableClearNotifications)
4987
{
2✔
4988
    SHARED_GROUP_TEST_PATH(path);
2✔
4989

4990
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
4991
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
4992

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

5033
    parent->clear();
2✔
5034
    CHECK(calls == 2);
2✔
5035
    CHECK_EQUAL(parent->size(), 0);
2✔
5036
    tr->commit();
2✔
5037
}
2✔
5038

5039
TEST(Table_EmbeddedObjectPath)
5040
{
2✔
5041
    auto collect_path = [](const Obj& o) {
10✔
5042
        return o.get_fat_path();
10✔
5043
    };
10✔
5044

5045
    SHARED_GROUP_TEST_PATH(path);
2✔
5046

5047
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5048
    DBRef sg = DB::create(*hist, path, DBOptions(crypt_key()));
2✔
5049

5050
    auto tr = sg->start_write();
2✔
5051
    auto table = tr->add_table("myEmbeddedStuff", Table::Type::Embedded);
2✔
5052
    auto col_recurse = table->add_column_list(*table, "theRecursiveBit");
2✔
5053
    CHECK_THROW(table->create_object(), LogicError);
2✔
5054
    auto parent = tr->add_table("myParentStuff");
2✔
5055
    auto ck = parent->add_column_list(*table, "theGreatColumn");
2✔
5056
    Obj o = parent->create_object();
2✔
5057
    auto gch = collect_path(o);
2✔
5058
    CHECK(gch.size() == 0);
2✔
5059
    auto parent_ll = o.get_linklist(ck);
2✔
5060
    Obj o2 = parent_ll.create_and_insert_linked_object(0);
2✔
5061
    auto gbh = collect_path(o2);
2✔
5062
    CHECK(gbh.size() == 1);
2✔
5063
    CHECK(gbh[0].obj.get_key() == o.get_key());
2✔
5064
    CHECK(gbh[0].col_key == ck);
2✔
5065
    CHECK(gbh[0].index == 0);
2✔
5066
    Obj o3 = parent_ll.create_and_insert_linked_object(1);
2✔
5067
    Obj o4 = parent_ll.create_and_insert_linked_object(0);
2✔
5068
    auto gah = collect_path(o4);
2✔
5069
    CHECK(gah.size() == 1);
2✔
5070
    CHECK(gah[0].obj.get_key() == o.get_key());
2✔
5071
    CHECK(gah[0].col_key == ck);
2✔
5072
    CHECK(gah[0].index == 0);
2✔
5073
    auto gzh = collect_path(o3);
2✔
5074
    CHECK(gzh.size() == 1);
2✔
5075
    CHECK(gzh[0].obj.get_key() == o.get_key());
2✔
5076
    CHECK(gzh[0].col_key == ck);
2✔
5077
    CHECK(gzh[0].index == 2);
2✔
5078
    auto o2_ll = o2.get_linklist(col_recurse);
2✔
5079
    auto o3_ll = o3.get_linklist(col_recurse);
2✔
5080
    o2_ll.create_and_insert_linked_object(0);
2✔
5081
    o2_ll.create_and_insert_linked_object(0);
2✔
5082
    o3_ll.create_and_insert_linked_object(0);
2✔
5083
    CHECK(table->size() == 6);
2✔
5084
    auto gyh = collect_path(o3_ll.get_object(0));
2✔
5085
    CHECK(gyh.size() == 2);
2✔
5086
    CHECK(gyh[0].obj.get_key() == o.get_key());
2✔
5087
    CHECK(gyh[0].col_key == ck);
2✔
5088
    CHECK(gyh[0].index == 2);
2✔
5089
    CHECK(gyh[1].obj.get_key() == o3.get_key());
2✔
5090
    CHECK(gyh[1].col_key = col_recurse);
2✔
5091
    CHECK(gyh[1].index == 0);
2✔
5092
}
2✔
5093

5094
TEST(Table_IndexOnMixed)
5095
{
2✔
5096
    Timestamp now{std::chrono::system_clock::now()};
2✔
5097
    Group g;
2✔
5098

5099
    auto bars = g.add_table("bar");
2✔
5100
    auto foos = g.add_table("foo");
2✔
5101
    auto col = foos->add_column(type_Mixed, "any");
2✔
5102
    foos->add_search_index(col);
2✔
5103

5104
    auto bar = bars->create_object();
2✔
5105

5106
    auto k0 = foos->create_object().set(col, Mixed()).get_key();
2✔
5107
    auto k1 = foos->create_object().set(col, Mixed(25)).get_key();
2✔
5108
    auto k2 = foos->create_object().set(col, Mixed(123.456f)).get_key();
2✔
5109
    auto k3 = foos->create_object().set(col, Mixed(987.654)).get_key();
2✔
5110
    auto k4 = foos->create_object().set(col, Mixed("Hello")).get_key();
2✔
5111
    auto k5 = foos->create_object().set(col, Mixed(now)).get_key();
2✔
5112
    auto k6 = foos->create_object().set(col, Mixed(Decimal128("2.25"))).get_key();
2✔
5113
    auto k7 = foos->create_object().set(col, Mixed(1)).get_key();
2✔
5114
    auto k8 = foos->create_object().set(col, Mixed(true)).get_key();
2✔
5115
    auto k9 = foos->create_object().set(col, Mixed(bar.get_link())).get_key();
2✔
5116
    auto k10 = foos->create_object().set(col, Mixed(UUID("3b241101-e2bb-4255-8caf-4136c566a962"))).get_key();
2✔
5117

5118
    CHECK_EQUAL(foos->find_first<Mixed>(col, {}), k0);
2✔
5119
    CHECK_EQUAL(foos->find_first<Mixed>(col, 25), k1);
2✔
5120
    CHECK_EQUAL(foos->find_first<Mixed>(col, 123.456f), k2);
2✔
5121
    CHECK_EQUAL(foos->find_first<Mixed>(col, 987.654), k3);
2✔
5122
    CHECK_EQUAL(foos->find_first<Mixed>(col, "Hello"), k4);
2✔
5123
    CHECK_EQUAL(foos->find_first<Mixed>(col, now), k5);
2✔
5124
    CHECK_EQUAL(foos->find_first<Mixed>(col, Decimal128("2.25")), k6);
2✔
5125
    CHECK_EQUAL(foos->find_first<Mixed>(col, 1), k7);
2✔
5126
    CHECK_EQUAL(foos->find_first<Mixed>(col, true), k8);
2✔
5127
    CHECK_EQUAL(foos->find_first<Mixed>(col, bar.get_link()), k9);
2✔
5128
    CHECK_EQUAL(foos->find_first<Mixed>(col, UUID("3b241101-e2bb-4255-8caf-4136c566a962")), k10);
2✔
5129

5130
    foos->remove_search_index(col);
2✔
5131

5132
    CHECK_EQUAL(foos->find_first<Mixed>(col, {}), k0);
2✔
5133
    CHECK_EQUAL(foos->find_first<Mixed>(col, 25), k1);
2✔
5134
    CHECK_EQUAL(foos->find_first<Mixed>(col, 123.456f), k2);
2✔
5135
    CHECK_EQUAL(foos->find_first<Mixed>(col, 987.654), k3);
2✔
5136
    CHECK_EQUAL(foos->find_first<Mixed>(col, "Hello"), k4);
2✔
5137
    CHECK_EQUAL(foos->find_first<Mixed>(col, now), k5);
2✔
5138
    CHECK_EQUAL(foos->find_first<Mixed>(col, Decimal128("2.25")), k6);
2✔
5139
    CHECK_EQUAL(foos->find_first<Mixed>(col, 1), k7);
2✔
5140
    CHECK_EQUAL(foos->find_first<Mixed>(col, true), k8);
2✔
5141
    CHECK_EQUAL(foos->find_first<Mixed>(col, bar.get_link()), k9);
2✔
5142
    CHECK_EQUAL(foos->find_first<Mixed>(col, UUID("3b241101-e2bb-4255-8caf-4136c566a962")), k10);
2✔
5143
}
2✔
5144

5145
TEST(Table_MixedNull)
5146
{
2✔
5147
    Group g;
2✔
5148
    auto foos = g.add_table("foo");
2✔
5149
    auto col = foos->add_column_list(type_Mixed, "any", true);
2✔
5150
    auto obj = foos->create_object();
2✔
5151
    auto list = obj.get_list<Mixed>(col);
2✔
5152
    list.add(Mixed());
2✔
5153
    list.set(0, Mixed(1));
2✔
5154
    list.set(0, Mixed());
2✔
5155
    list.remove(0);
2✔
5156
}
2✔
5157

5158
TEST(Table_InsertWithMixedLink)
5159
{
2✔
5160
    Group g;
2✔
5161
    TableRef dest = g.add_table_with_primary_key("dest", type_Int, "value");
2✔
5162
    TableRef source = g.add_table_with_primary_key("source", type_Int, "value");
2✔
5163
    ColKey mixed_col = source->add_column(type_Mixed, "mixed");
2✔
5164

5165
    Obj dest_obj = dest->create_object_with_primary_key(0);
2✔
5166

5167
    Mixed mixed_link = ObjLink{dest->get_key(), dest_obj.get_key()};
2✔
5168
    FieldValues values = {
2✔
5169
        {mixed_col, mixed_link},
2✔
5170
    };
2✔
5171
    source->create_object_with_primary_key(0, std::move(values));
2✔
5172

5173
    source->clear();
2✔
5174
    dest->clear();
2✔
5175
}
2✔
5176

5177
TEST(Table_SortEncrypted)
5178
{
2✔
5179
    SHARED_GROUP_TEST_PATH(path);
2✔
5180
    Random random(random_int<unsigned long>());
2✔
5181

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

5185
    auto wt = sg->start_write();
2✔
5186
    auto foos = wt->add_table("foo");
2✔
5187
    auto col_id = foos->add_column(type_String, "id");
2✔
5188
    auto col_b = foos->add_column(type_Bool, "b");
2✔
5189

5190
    for (int i = 0; i < 10000; i++) {
20,002✔
5191
        auto n = random.draw_int_max(10000);
20,000✔
5192
        foos->create_object().set(col_id, util::to_string(n));
20,000✔
5193
    }
20,000✔
5194
    wt->commit_and_continue_as_read();
2✔
5195
    auto q = foos->where();
2✔
5196
    DescriptorOrdering ordering;
2✔
5197
    ordering.append_sort(SortDescriptor({{col_b}, {col_id}}));
2✔
5198

5199
    // auto t1 = steady_clock::now();
5200

5201
    CALLGRIND_START_INSTRUMENTATION;
2✔
5202
    auto tv = q.find_all(ordering);
2✔
5203
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
5204

5205
    // auto t2 = steady_clock::now();
5206

5207
    // std::cout << "time: " << duration_cast<microseconds>(t2 - t1).count() << " us" << std::endl;
5208
}
2✔
5209

5210
TEST(Table_RebuildTable)
5211
{
2✔
5212
    Group g;
2✔
5213
    auto t = g.add_table("foo");
2✔
5214
    auto id = t->add_column(type_Int, "id");
2✔
5215
    for (int64_t i = 1; i < 8; i++) {
16✔
5216
        t->create_object().set(id, i);
14✔
5217
    }
14✔
5218
    t->set_primary_key_column(id);
2✔
5219
}
2✔
5220

5221
TEST(Table_ListOfPrimitivesTransaction)
5222
{
2✔
5223
    SHARED_GROUP_TEST_PATH(path);
2✔
5224
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
5225
    DBRef db = DB::create(*hist, path);
2✔
5226

5227
    auto tr = db->start_write();
2✔
5228
    TableRef t = tr->add_table("table");
2✔
5229
    ColKey int_col = t->add_column_list(type_Int, "integers");
2✔
5230
    ObjKeys keys;
2✔
5231
    t->create_objects(32, keys);
2✔
5232
    auto list = t->get_object(keys[7]).get_list<Int>(int_col);
2✔
5233
    list.add(7);
2✔
5234
    list.add(25);
2✔
5235
    list.add(42);
2✔
5236
    tr->commit_and_continue_as_read();
2✔
5237

5238
    tr->promote_to_write();
2✔
5239
    list.set(0, 5);
2✔
5240
    tr->commit_and_continue_as_read();
2✔
5241
    CHECK_EQUAL(list.get(0), 5);
2✔
5242
    tr->promote_to_write();
2✔
5243
    list.swap(0, 1);
2✔
5244
    tr->commit_and_continue_as_read();
2✔
5245
    CHECK_EQUAL(list.get(0), 25);
2✔
5246
    tr->promote_to_write();
2✔
5247
    list.move(1, 0);
2✔
5248
    tr->commit_and_continue_as_read();
2✔
5249
    CHECK_EQUAL(list.get(0), 5);
2✔
5250
    tr->promote_to_write();
2✔
5251
    list.remove(1);
2✔
5252
    tr->commit_and_continue_as_read();
2✔
5253
    CHECK_EQUAL(list.get(1), 42);
2✔
5254
    tr->promote_to_write();
2✔
5255
    list.clear();
2✔
5256
    tr->commit_and_continue_as_read();
2✔
5257
    CHECK_EQUAL(list.size(), 0);
2✔
5258
}
2✔
5259

5260
TEST(Table_AsymmetricObjects)
5261
{
2✔
5262
    SHARED_GROUP_TEST_PATH(path);
2✔
5263

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

5267
    auto tr = sg->start_write();
2✔
5268
    auto table = tr->add_table("mytable", Table::Type::TopLevelAsymmetric);
2✔
5269
    tr->commit_and_continue_as_read();
2✔
5270
    tr->promote_to_write();
2✔
5271
    CHECK(table->is_asymmetric());
2✔
5272
    table->create_object();
2✔
5273
    tr->commit();
2✔
5274

5275
    tr = sg->start_read();
2✔
5276
    table = tr->get_table("mytable");
2✔
5277
    CHECK(table->is_asymmetric());
2✔
5278

5279
    tr = sg->start_write();
2✔
5280
    auto table2 = tr->add_table("target table");
2✔
5281
    table = tr->get_table("mytable");
2✔
5282
    // Outgoing link from asymmetric object is allowed.
5283
    CHECK_NOTHROW(table->add_column(*table2, "link"));
2✔
5284
    // Incoming link to asymmetric object is not allowed.
5285
    CHECK_THROW(table2->add_column(*table, "link"), LogicError);
2✔
5286
    tr->commit();
2✔
5287
}
2✔
5288

5289
TEST(Table_FullTextIndex)
5290
{
2✔
5291
    SHARED_GROUP_TEST_PATH(path);
2✔
5292
    auto db = DB::create(path);
2✔
5293
    ColKey col;
2✔
5294

5295
    {
2✔
5296
        auto wt = db->start_write();
2✔
5297

5298
        auto t = wt->add_table("foo");
2✔
5299
        col = t->add_column(type_String, "str");
2✔
5300
        t->add_fulltext_index(col);
2✔
5301
        auto index = t->get_string_index(col);
2✔
5302
        CHECK(index->is_fulltext_index());
2✔
5303

5304
        t->create_object().set(col, "This is a test, with  spaces!");
2✔
5305
        t->create_object().set(col, "More testing, with normal spaces");
2✔
5306
        t->create_object().set(col, "ål, ø og æbler");
2✔
5307

5308
        wt->commit();
2✔
5309
    }
2✔
5310

5311
    auto rt = db->start_read();
2✔
5312
    auto t = rt->get_table("foo");
2✔
5313
    auto index = t->get_string_index(col);
2✔
5314
    CHECK(index->is_fulltext_index());
2✔
5315
    TableView res = t->find_all_fulltext(col, "spaces with");
2✔
5316
    CHECK_EQUAL(2, res.size());
2✔
5317
}
2✔
5318

5319
TEST(Table_LoggingMutations)
5320
{
2✔
5321
    std::stringstream buffer;
2✔
5322
    SHARED_GROUP_TEST_PATH(path);
2✔
5323
    DBOptions options;
2✔
5324
    options.logger = std::make_shared<StreamLogger>(buffer);
2✔
5325
    options.logger->set_level_threshold("Realm", util::Logger::Level::all);
2✔
5326
    auto db = DB::create(make_in_realm_history(), path, options);
2✔
5327
    ColKey col;
2✔
5328
    ColKey col_int;
2✔
5329

5330
    {
2✔
5331
        auto wt = db->start_write();
2✔
5332

5333
        auto t = wt->add_table_with_primary_key("foo", type_Int, "id");
2✔
5334
        col = t->add_column(type_Mixed, "any");
2✔
5335
        col_int = t->add_column(type_Int, "int");
2✔
5336

5337
        auto dict =
2✔
5338
            t->create_object_with_primary_key(1).set_collection(col, CollectionType::Dictionary).get_dictionary(col);
2✔
5339
        dict.insert("hello", "world");
2✔
5340

5341
        auto list =
2✔
5342
            t->create_object_with_primary_key(2).set_collection(col, CollectionType::List).get_list<Mixed>(col);
2✔
5343
        list.add(47.50);
2✔
5344

5345
        std::vector<char> str_data(90);
2✔
5346
        std::iota(str_data.begin(), str_data.end(), ' ');
2✔
5347
        t->create_object_with_primary_key(5).set_any(col, StringData(str_data.data(), str_data.size()));
2✔
5348

5349
        std::vector<char> bin_data(50);
2✔
5350
        std::iota(bin_data.begin(), bin_data.end(), 0);
2✔
5351
        t->create_object_with_primary_key(6).set_any(col, BinaryData(bin_data.data(), bin_data.size()));
2✔
5352

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

5355
        wt->commit();
2✔
5356
    }
2✔
5357
    {
2✔
5358
        // Try to serialize a query with a constraining view
5359
        auto rt = db->start_read();
2✔
5360
        auto table = rt->get_table("foo");
2✔
5361
        TableView tv = table->find_all_int(col_int, 0);
2✔
5362
        table->where(&tv).equal(col_int, 0).count();
2✔
5363
    }
2✔
5364

5365
    auto str = buffer.str();
2✔
5366
    // std::cout << str << std::endl;
5367
    CHECK(str.find("abcdefghijklmno ...") != std::string::npos);
2✔
5368
    CHECK(str.find("14 15 16 17 18 19 ...") != std::string::npos);
2✔
5369
    CHECK(str.find("2023-09-20 10:53:35") != std::string::npos);
2✔
5370
    CHECK(str.find("VIEW { 5 element(s) }") != std::string::npos);
2✔
5371
    CHECK(str.find("Set 'any' to dictionary") != std::string::npos);
2✔
5372
    CHECK(str.find("Set 'any' to list") != std::string::npos);
2✔
5373
}
2✔
5374

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