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

realm / realm-core / 2555

09 Aug 2024 06:49PM UTC coverage: 91.123% (+0.04%) from 91.087%
2555

push

Evergreen

web-flow
Actually check for unuplaoded changes in no_pending_local_changes() (#7967)

We can have local changesets stored which have already been uploaded and
acknoledged by the server, so checking all of the changesets is incorrect. We
need to instead only check changesets for versions after the current position
of the upload cursor.

102818 of 181588 branches covered (56.62%)

37 of 38 new or added lines in 2 files covered. (97.37%)

42 existing lines in 9 files now uncovered.

217381 of 238557 relevant lines covered (91.12%)

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

170
            if (m_throw_on_null) {
204✔
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
204✔
175
                return StringData("", 0);
204✔
176
        }
204✔
177
        const char* p = from_value.data();
7,681✔
178
        const char* limit = p + from_value.size();
7,681✔
179
        data.clear();
7,681✔
180
        data.reserve(from_value.size());
7,681✔
181
        while (p != limit)
245,792✔
182
            data.push_back(*p++);
238,111✔
183
        return StringData(&data[0], from_value.size());
7,681✔
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)
376✔
199
                return BinaryData();
192✔
200

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

231
            if (m_throw_on_null)
207✔
232
                throw realm::LogicError(ErrorCodes::BrokenInvariant, "Null found");
×
233
            else
207✔
234
                return Timestamp(0, 0);
207✔
235
        }
207✔
236
        return from_value;
7,664✔
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++) {
563✔
626
            bool null = (fastrand(1) == 0);
463✔
627
            if (!null) {
463✔
628
                int64_t value = fastrand(10);
232✔
629
                sum += value;
232✔
630
                if (largest_pos == null_key || value > largest) {
232✔
631
                    largest = value;
134✔
632
                    largest_pos = keys[t];
134✔
633
                }
134✔
634
                if (smallest_pos == null_key || value < smallest) {
232✔
635
                    smallest = value;
123✔
636
                    smallest_pos = keys[t];
123✔
637
                }
123✔
638
                table->get_object(keys[t]).set_all(Timestamp(value, 0), value, float(value));
232✔
639
            }
232✔
640
            else {
231✔
641
                nulls++;
231✔
642
            }
231✔
643
        }
463✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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);
78✔
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);
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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);
78✔
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);
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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));
78✔
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);
78✔
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);
78✔
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,996✔
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,708✔
3243
                auto key_val = test_util::random_int<int64_t>(0, nb_rows * 10);
210,708✔
3244
                if (key_set.count(key_val) == 0) {
210,708✔
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,708✔
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
    {
13,859✔
3918
        std::vector<ObjKey> res;
13,859✔
3919
        Table::Iterator it = table->begin();
13,859✔
3920
        while (it != table->end()) {
1,022,988✔
3921
            if (!it->is_null(col)) {
1,009,129✔
3922
                T v2 = it->get<T>(col);
928,369✔
3923
                if (v == v2) {
928,369✔
3924
                    res.push_back(it->get_key());
223,060✔
3925
                }
223,060✔
3926
            }
928,369✔
3927
            ++it;
1,009,129✔
3928
        };
1,009,129✔
3929
        // res is returned with nrvo optimization
3930
        return res;
13,859✔
3931
    }
13,859✔
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,564✔
3939

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

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

3948
                REALM_ASSERT(a == b);
13,859✔
3949
            }
13,859✔
3950
        }
15,564✔
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,898✔
3972
                bool set_to_null = fastrand(100) < 20;
7,898✔
3973

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

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

3992
            // Edit random object
3993
            if (table->size() > 0) {
16,000✔
3994
                Table::Iterator it = table->begin();
15,564✔
3995
                auto r = fastrand(table->size() - 1);
15,564✔
3996
                // FIXME: Is there a faster way to pick a random object?
3997
                for (unsigned t = 0; t < r; t++) {
586,614✔
3998
                    ++it;
571,050✔
3999
                }
571,050✔
4000
                Obj o = *it;
15,564✔
4001
                bool set_to_null = fastrand(100) < 20;
15,564✔
4002
                if (set_to_null && table->is_nullable(col)) {
15,564✔
4003
                    o.set_null(col);
1,474✔
4004
                }
1,474✔
4005
                else {
14,090✔
4006
                    auto t = create();
14,090✔
4007
                    o.set<T2>(col, t);
14,090✔
4008
                }
14,090✔
4009
            }
15,564✔
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,973✔
4023
        std::string s = realm::util::to_string(fastrand(5));
4,973✔
4024
        return s;
4,973✔
4025
    }
4,973✔
4026
    template <typename Type = T2>
4027
    typename std::enable_if<std::is_same<Type, Timestamp>::value, T2>::type static create()
4028
    {
5,159✔
4029
        return Timestamp(fastrand(3), int32_t(fastrand(3)));
5,159✔
4030
    }
5,159✔
4031
    template <typename Type = T2>
4032
    typename std::enable_if<std::is_same<Type, int64_t>::value, T2>::type static create()
4033
    {
5,127✔
4034
        return fastrand(5);
5,127✔
4035
    }
5,127✔
4036

4037
    template <typename Type = T2>
4038
    typename std::enable_if<std::is_same<Type, bool>::value, T2>::type static create()
4039
    {
5,141✔
4040
        return fastrand(100) > 50;
5,141✔
4041
    }
5,141✔
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,470✔
4115
    return test_util::random_int<T>();
31,470✔
4116
}
31,470✔
4117

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

4127
template <>
4128
bool generate_value()
4129
{
31,432✔
4130
    return test_util::random_int<int>() & 0x1;
31,432✔
4131
}
31,432✔
4132
template <>
4133
float generate_value()
4134
{
31,458✔
4135
    return float(1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000)));
31,458✔
4136
}
31,458✔
4137
template <>
4138
double generate_value()
4139
{
31,537✔
4140
    return 1.0 * test_util::random_int<int>() / (test_util::random_int<int>(1, 1000));
31,537✔
4141
}
31,537✔
4142
template <>
4143
Timestamp generate_value()
4144
{
31,450✔
4145
    return Timestamp(test_util::random_int<int>(0, 1000000), test_util::random_int<int>(0, 1000000000));
31,450✔
4146
}
31,450✔
4147
template <>
4148
Decimal128 generate_value()
4149
{
31,370✔
4150
    return Decimal128(test_util::random_int<int>(-100000, 100000));
31,370✔
4151
}
31,370✔
4152
template <>
4153
ObjectId generate_value()
4154
{
31,448✔
4155
    return ObjectId::gen();
31,448✔
4156
}
31,448✔
4157
template <>
4158
UUID generate_value()
4159
{
31,407✔
4160
    std::string str;
31,407✔
4161
    str.resize(36);
31,407✔
4162
    std::generate<std::string::iterator, char (*)()>(str.begin(), str.end(), []() -> char {
1,129,735✔
4163
        char c = test_util::random_int<char>(0, 15);
1,129,735✔
4164
        return c >= 10 ? (c - 10 + 'a') : (c + '0');
1,129,735✔
4165
    });
1,129,735✔
4166
    str.at(8) = '-';
31,407✔
4167
    str.at(13) = '-';
31,407✔
4168
    str.at(18) = '-';
31,407✔
4169
    str.at(23) = '-';
31,407✔
4170
    return UUID(str.c_str());
31,407✔
4171
}
31,407✔
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,278✔
4187
    ManagedStorage(std::string&& v)
4188
        : storage(std::move(v))
31,392✔
4189
        , value(storage)
31,392✔
4190
    {
62,882✔
4191
    }
62,882✔
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,773✔
4205
                storage.assign(v.data(), v.data() + v.size());
15,385✔
4206
            }
15,385✔
4207
            value = T(storage);
15,773✔
4208
        }
15,773✔
4209
    }
16,160✔
4210
    ManagedStorage& operator=(const ManagedStorage& other)
4211
    {
138,104✔
4212
        storage = other.storage;
138,104✔
4213
        value = other.value ? T(storage) : T();
138,104✔
4214
        return *this;
138,104✔
4215
    }
138,104✔
4216
    ManagedStorage& operator=(ManagedStorage&& other)
4217
    {
2,763,478✔
4218
        storage = std::move(other.storage);
2,763,478✔
4219
        value = other.value ? T(storage) : T();
2,763,478✔
4220
        return *this;
2,763,478✔
4221
    }
2,763,478✔
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
    {
162,991✔
4246
        if (optional && (test_util::random_int<int>() % 10) == 0) {
162,991!
4247
            return managed<T>{T()};
4,755✔
4248
        }
4,755✔
4249
        else {
158,236✔
4250
            return managed<T>{generate_value<T>()};
158,236✔
4251
        }
158,236✔
4252
    }
162,991✔
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,631✔
4274
        }
1,631✔
4275
        else {
31,449✔
4276
            return generate_value<std::string>();
31,449✔
4277
        }
31,449✔
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,310✔
4295
        else
76,790✔
4296
            return managed<Optional<T>>{generate_value<T>()};
76,790✔
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,463✔
4719
            if (index < keys.size()) {
6,463✔
4720
                auto k = keys[index];
5,727✔
4721
                if (i == 4) {
5,727✔
4722
                    t.remove_object(k);
1,424✔
4723
                    keys.erase(keys.begin() + index);
1,424✔
4724
                    if (index == 0)
1,424✔
UNCOV
4725
                        iter = t.begin();
×
4726
                    i = 0;
1,424✔
4727
                }
1,424✔
4728
                else {
4,303✔
4729
                    iter.go(index);
4,303✔
4730
                    CHECK_EQUAL(k, iter->get_key());
4,303✔
4731
                }
4,303✔
4732
                i++;
5,727✔
4733
            }
5,727✔
4734
        }
6,463✔
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