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

realm / realm-core / 2646

22 Jan 2025 04:07PM UTC coverage: 91.131% (+0.007%) from 91.124%
2646

push

Evergreen

web-flow
Sync access token refreshes shouldn't extend SyncSession lifetime (#8064)

102702 of 181514 branches covered (56.58%)

73 of 73 new or added lines in 3 files covered. (100.0%)

65 existing lines in 15 files now uncovered.

217391 of 238549 relevant lines covered (91.13%)

5759073.66 hits per line

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

99.83
/test/test_dictionary.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2018 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

21
#include <realm.hpp>
22

23
#include "test.hpp"
24
#include <chrono>
25
#include <set>
26
// #include <valgrind/callgrind.h>
27
// valgrind --tool=callgrind --instr-atstart=no ./realm-tests
28

29
using namespace std::chrono;
30

31
using namespace realm;
32
using namespace realm::test_util;
33

34
#ifndef CALLGRIND_START_INSTRUMENTATION
35
#define CALLGRIND_START_INSTRUMENTATION
36
#endif
37

38
#ifndef CALLGRIND_STOP_INSTRUMENTATION
39
#define CALLGRIND_STOP_INSTRUMENTATION
40
#endif
41

42
// Test independence and thread-safety
43
// -----------------------------------
44
//
45
// All tests must be thread safe and independent of each other. This
46
// is required because it allows for both shuffling of the execution
47
// order and for parallelized testing.
48
//
49
// In particular, avoid using std::rand() since it is not guaranteed
50
// to be thread safe. Instead use the API offered in
51
// `test/util/random.hpp`.
52
//
53
// All files created in tests must use the TEST_PATH macro (or one of
54
// its friends) to obtain a suitable file system path. See
55
// `test/util/test_path.hpp`.
56
//
57
//
58
// Debugging and the ONLY() macro
59
// ------------------------------
60
//
61
// A simple way of disabling all tests except one called `Foo`, is to
62
// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the
63
// test suite. Note that you can also use filtering by setting the
64
// environment varible `UNITTEST_FILTER`. See `README.md` for more on
65
// this.
66
//
67
// Another way to debug a particular test, is to copy that test into
68
// `experiments/testcase.cpp` and then run `sh build.sh
69
// check-testcase` (or one of its friends) from the command line.
70

71
extern unsigned int unit_test_random_seed;
72

73
TEST(Dictionary_Basics)
74
{
2✔
75
    Group g;
2✔
76
    auto cmp = [this](Mixed x, Mixed y) {
8✔
77
        CHECK_EQUAL(x, y);
8✔
78
    };
8✔
79

80
    Dictionary dummy;
2✔
81
    CHECK_THROW_ANY(dummy.insert("Hello", "world"));
2✔
82

83
    auto foo = g.add_table("foo");
2✔
84
    auto col_dict = foo->add_column_dictionary(type_Mixed, "dictionaries");
2✔
85

86
    Obj obj1 = foo->create_object();
2✔
87
    Obj obj2 = foo->create_object();
2✔
88
    StringData foo_key("foo.bar", 3); // The '.' must not be considered part of the key
2✔
89

90
    {
2✔
91
        Dictionary dict = obj1.get_dictionary(col_dict);
2✔
92

93
        CHECK_EQUAL(dict.size(), 0);
2✔
94
        CHECK_EQUAL(dict.find_any(9), realm::npos);
2✔
95

96
        CHECK(dict.insert("Hello", 9).second);
2✔
97
        CHECK_EQUAL(dict.size(), 1);
2✔
98
        CHECK_EQUAL(dict.get("Hello").get_int(), 9);
2✔
99
        CHECK(dict.contains("Hello"));
2✔
100
        CHECK_NOT(dict.insert("Hello", 10).second);
2✔
101
        CHECK_EQUAL(dict.get("Hello").get_int(), 10);
2✔
102
        CHECK_EQUAL(dict.find_any(9), realm::npos);
2✔
103
        CHECK_EQUAL(dict.find_any(10), 0);
2✔
104

105
        dict.insert("Goodbye", "cruel world");
2✔
106
        CHECK_EQUAL(dict.size(), 2);
2✔
107
        CHECK_EQUAL(dict["Goodbye"].get_string(), "cruel world");
2✔
108
        CHECK_THROW_ANY(dict.get("Baa").get_string()); // Within range
2✔
109
        CHECK_THROW_ANY(dict.get("Foo").get_string()); // Outside range
2✔
110
        CHECK_THROW_ANY(dict.insert("$foo", ""));      // Must not start with '$'
2✔
111
        CHECK_THROW_ANY(dict.insert("foo.bar", ""));   // Must not contain '.'
2✔
112
        CHECK(dict.insert(foo_key, 9).second);         // This should be ok
2✔
113
    }
2✔
114
    {
2✔
115
        Dictionary dict = obj1.get_dictionary(col_dict);
2✔
116
        CHECK_EQUAL(dict.size(), 3);
2✔
117
        cmp(dict.get("Hello"), 10);
2✔
118
        cmp(dict["Goodbye"], "cruel world");
2✔
119
        auto it = dict.find("puha");
2✔
120
        CHECK(it == dict.end());
2✔
121
        it = dict.find("Goodbye");
2✔
122
        cmp((*it).second, "cruel world");
2✔
123
        dict.erase(it);
2✔
124
        CHECK_EQUAL(dict.size(), 2);
2✔
125
        cmp(dict["Goodbye"], Mixed());
2✔
126
        CHECK_EQUAL(dict.size(), 3);
2✔
127
        dict.erase("foo");
2✔
128
        CHECK_EQUAL(dict.size(), 2);
2✔
129
        dict.clear();
2✔
130
        CHECK_EQUAL(dict.size(), 0);
2✔
131
        // Check that you can insert after clear
132
        CHECK(dict.insert("Hello", 9).second);
2✔
133
        CHECK_EQUAL(dict.size(), 1);
2✔
134
        dict.erase("Hello");
2✔
135
        CHECK_EQUAL(dict.size(), 0);
2✔
136
        CHECK_THROW_ANY(dict.erase("Hello"));   // Dictionary empty
2✔
137
        CHECK_THROW_ANY(dict.erase("$foo"));    // Must not start with '$'
2✔
138
        CHECK_THROW_ANY(dict.erase("foo.bar")); // Must not contain '.'
2✔
139
    }
2✔
140
    {
2✔
141
        Dictionary dict1 = obj1.get_dictionary(col_dict);
2✔
142
        Dictionary dict2 = obj2.get_dictionary(col_dict);
2✔
143
        CHECK_EQUAL(dict2.size(), 0);
2✔
144
        CHECK_THROW_ANY(dict2.get("Baa").get_string());
2✔
145

146
        dict2.insert("Hello", "world");
2✔
147
        dict1.insert("Hello", 9);
2✔
148
        obj2.remove();
2✔
149
        CHECK_NOT(dict2.is_attached());
2✔
150
        CHECK_EQUAL(dict1.size(), 1);
2✔
151
        dict1 = dict2;
2✔
152
        CHECK_NOT(dict1.is_attached());
2✔
153
    }
2✔
154
}
2✔
155

156
TEST(Dictionary_Links)
157
{
2✔
158
    Group g;
2✔
159
    auto cmp = [this](Mixed x, Mixed y) {
10✔
160
        CHECK_EQUAL(x, y);
10✔
161
    };
10✔
162

163
    auto dogs = g.add_table_with_primary_key("dog", type_String, "name");
2✔
164
    auto cats = g.add_table_with_primary_key("cat", type_String, "name");
2✔
165
    auto persons = g.add_table_with_primary_key("person", type_String, "name");
2✔
166
    auto col_dict = persons->add_column_dictionary(*dogs, "dictionaries");
2✔
167

168
    Obj adam = persons->create_object_with_primary_key("adam");
2✔
169
    Obj bernie = persons->create_object_with_primary_key("bernie");
2✔
170
    Obj pluto = dogs->create_object_with_primary_key("pluto");
2✔
171
    Obj lady = dogs->create_object_with_primary_key("lady");
2✔
172
    Obj garfield = cats->create_object_with_primary_key("garfield");
2✔
173

174
    {
2✔
175
        Dictionary dict = adam.get_dictionary(col_dict);
2✔
176
        CHECK(dict.insert("Pet", pluto).second);
2✔
177
        CHECK_EQUAL(pluto.get_backlink_count(), 1);
2✔
178
        CHECK_NOT(dict.insert("Pet", lady).second);
2✔
179
        CHECK(dict.insert("Dog", lady).second); // Have two links pointing to lady
2✔
180
        CHECK_EQUAL(pluto.get_backlink_count(), 0);
2✔
181
        CHECK_EQUAL(lady.get_backlink_count(*persons, col_dict), 2);
2✔
182
        CHECK_EQUAL(lady.get_backlink(*persons, col_dict, 0), adam.get_key());
2✔
183
        CHECK_EQUAL(lady.get_backlink_count(), 2);
2✔
184
        CHECK_EQUAL(dict.get("Pet").get<ObjKey>(), lady.get_key());
2✔
185
        lady.remove();
2✔
186
        cmp(dict["Pet"], Mixed());
2✔
187
        CHECK_THROW_ANY(dict.insert("Pet", garfield));
2✔
188
        CHECK_THROW_ANY(dict.insert("Pet", Mixed(ObjKey(27))));
2✔
189

190
        // Reinsert lady
191
        lady = dogs->create_object_with_primary_key("lady");
2✔
192
        dict.insert("Pet", lady);
2✔
193
        lady.invalidate(); // Make lady a tombstone :-(
2✔
194
        cmp(dict["Pet"], Mixed());
2✔
195
        lady = dogs->create_object_with_primary_key("lady");
2✔
196
        cmp(dict["Pet"].get<ObjKey>(), lady.get_key());
2✔
197

198
        auto invalid_link = pluto.get_link();
2✔
199
        pluto.remove();
2✔
200
        CHECK_THROW(dict.insert("Pet", invalid_link), InvalidArgument);
2✔
201

202
        dict = bernie.get_dictionary(col_dict);
2✔
203
        dict.insert("Pet", lady);
2✔
204
        CHECK_EQUAL(lady.get_backlink_count(), 2);
2✔
205
        adam.remove();
2✔
206
        CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
207
        dict.erase("Pet");
2✔
208
        CHECK_EQUAL(lady.get_backlink_count(), 0);
2✔
209

210
        dict.insert("Pet", dogs->get_objkey_from_primary_key("pongo"));
2✔
211
        cmp(dict["Pet"], Mixed());
2✔
212
        Obj pongo = dogs->create_object_with_primary_key("pongo");
2✔
213
        CHECK_EQUAL(pongo.get_backlink_count(), 1);
2✔
214
        cmp(dict["Pet"].get<ObjKey>(), pongo.get_key());
2✔
215
    }
2✔
216
}
2✔
217

218
TEST(Dictionary_TypedLinks)
219
{
2✔
220
    Group g;
2✔
221
    auto cmp = [this](Mixed x, Mixed y) {
10✔
222
        CHECK_EQUAL(x, y);
10✔
223
    };
10✔
224

225
    auto dogs = g.add_table_with_primary_key("dog", type_String, "name");
2✔
226
    auto persons = g.add_table_with_primary_key("person", type_String, "name");
2✔
227
    auto col_dict = persons->add_column_dictionary(type_Mixed, "dictionaries");
2✔
228

229
    Obj adam = persons->create_object_with_primary_key("adam");
2✔
230
    Obj pluto = dogs->create_object_with_primary_key("pluto");
2✔
231
    Obj lady = dogs->create_object_with_primary_key("lady");
2✔
232

233
    {
2✔
234
        Dictionary dict = adam.get_dictionary(col_dict);
2✔
235
        CHECK(dict.insert("Pet", pluto).second);
2✔
236
        CHECK_EQUAL(pluto.get_backlink_count(), 1);
2✔
237
        CHECK_NOT(dict.insert("Pet", lady).second);
2✔
238
        CHECK_EQUAL(pluto.get_backlink_count(), 0);
2✔
239
        CHECK_EQUAL(lady.get_backlink_count(*persons, col_dict), 1);
2✔
240
        CHECK_EQUAL(lady.get_backlink(*persons, col_dict, 0), adam.get_key());
2✔
241
        CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
242
        // make sure that inserting the same link again does nothing
243
        CHECK_NOT(dict.insert("Pet", lady).second);
2✔
244
        CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
245
        lady.remove();
2✔
246
        cmp(dict["Pet"], Mixed());
2✔
247

248
        // Reinsert lady
249
        lady = dogs->create_object_with_primary_key("lady");
2✔
250
        dict.insert("Pet", lady);
2✔
251
        dict.insert("Pet", lady);
2✔
252
        CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
253
        lady.invalidate(); // Make lady a tombstone :-(
2✔
254
        cmp(dict["Pet"], Mixed());
2✔
255
        lady = dogs->create_object_with_primary_key("lady");
2✔
256
        cmp(dict["Pet"], Mixed(lady.get_link()));
2✔
257
        CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
258

259
        auto invalid_link = pluto.get_link();
2✔
260
        pluto.remove();
2✔
261
        CHECK_THROW(dict.insert("Pet", invalid_link), InvalidArgument);
2✔
262

263
        dict.insert("Pet", Mixed(ObjLink(dogs->get_key(), dogs->get_objkey_from_primary_key("pongo"))));
2✔
264
        cmp(dict["Pet"], Mixed());
2✔
265
        Obj pongo = dogs->create_object_with_primary_key("pongo");
2✔
266
        CHECK_EQUAL(pongo.get_backlink_count(), 1);
2✔
267
        cmp(dict["Pet"], Mixed(pongo.get_link()));
2✔
268
    }
2✔
269
}
2✔
270

271
TEST(Dictionary_Clear)
272
{
2✔
273
    Group g;
2✔
274
    auto dogs = g.add_table_with_primary_key("dog", type_String, "name");
2✔
275
    auto persons = g.add_table_with_primary_key("person", type_String, "name");
2✔
276
    auto col_dict_mixed = persons->add_column_dictionary(type_Mixed, "any");
2✔
277
    auto col_dict_implicit = persons->add_column_dictionary(*dogs, "implicit");
2✔
278

279
    Obj adam = persons->create_object_with_primary_key("adam");
2✔
280
    Obj pluto = dogs->create_object_with_primary_key("pluto");
2✔
281
    Obj lady = dogs->create_object_with_primary_key("lady");
2✔
282

283
    adam.get_dictionary(col_dict_mixed).insert("Dog1", pluto);
2✔
284
    adam.get_dictionary(col_dict_implicit).insert("DOg2", lady.get_key());
2✔
285

286
    CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
287
    CHECK_EQUAL(pluto.get_backlink_count(), 1);
2✔
288
    persons->clear();
2✔
289
    CHECK_EQUAL(lady.get_backlink_count(), 0);
2✔
290
    CHECK_EQUAL(pluto.get_backlink_count(), 0);
2✔
291
    g.verify();
2✔
292
}
2✔
293

294
TEST(Dictionary_Transaction)
295
{
2✔
296
    SHARED_GROUP_TEST_PATH(path);
2✔
297
    auto hist = make_in_realm_history();
2✔
298
    DBRef db = DB::create(*hist, path);
2✔
299
    ObjKey k0, k2;
2✔
300
    ColKey col_dict;
2✔
301
    auto cmp = [this](Mixed x, Mixed y) {
6✔
302
        CHECK_EQUAL(x, y);
6✔
303
    };
6✔
304

305
    auto rt = db->start_read();
2✔
306
    {
2✔
307
        WriteTransaction wt(db);
2✔
308
        auto foo = wt.add_table("foo");
2✔
309
        col_dict = foo->add_column_dictionary(type_Mixed, "dictionaries");
2✔
310

311
        Obj obj0 = foo->create_object();
2✔
312
        Obj obj1 = foo->create_object();
2✔
313
        Obj obj2 = foo->create_object();
2✔
314
        k0 = obj0.get_key();
2✔
315
        k2 = obj2.get_key();
2✔
316
        Dictionary dict = obj0.get_dictionary(col_dict);
2✔
317
        dict.insert("Hello", 9);
2✔
318
        dict.insert("Goodbye", "cruel world");
2✔
319

320
        dict = obj1.get_dictionary(col_dict);
2✔
321
        dict.insert("Link", obj2.get_link());
2✔
322
        wt.commit();
2✔
323
    }
2✔
324
    rt->advance_read();
2✔
325
    rt->verify();
2✔
326
    ConstTableRef table = rt->get_table("foo");
2✔
327
    Dictionary dict;
2✔
328
    dict = table->get_object(k0).get_dictionary(col_dict);
2✔
329
    cmp(dict.get("Hello"), 9);
2✔
330
    cmp(dict.get("Goodbye"), "cruel world");
2✔
331

332
    {
2✔
333
        WriteTransaction wt(db);
2✔
334
        auto foo = wt.get_table("foo");
2✔
335
        Dictionary d = foo->get_object(k0).get_dictionary(col_dict);
2✔
336
        d.insert("Good morning", "sunshine");
2✔
337
        foo->remove_object(k2); // Nullifies link in obj1.dictionaries["Link"]
2✔
338
        wt.commit();
2✔
339
    }
2✔
340
    rt->advance_read();
2✔
341
    rt->verify();
2✔
342
    // rt->to_json(std::cout);
343
    cmp(dict.get("Good morning"), "sunshine");
2✔
344

345
    {
2✔
346
        auto wt = db->start_write();
2✔
347
        auto foo = wt->get_table("foo");
2✔
348
        Dictionary d = foo->get_object(k0).get_dictionary(col_dict);
2✔
349
        d.clear();
2✔
350

351
        wt->commit_and_continue_as_read();
2✔
352
        wt->promote_to_write();
2✔
353
        wt->verify();
2✔
354
    }
2✔
355
}
2✔
356

357
TEST(Dictionary_Aggregate)
358
{
2✔
359
    SHARED_GROUP_TEST_PATH(path);
2✔
360
    auto hist = make_in_realm_history();
2✔
361
    DBRef db = DB::create(*hist, path);
2✔
362
    auto tr = db->start_write();
2✔
363
    auto foo = tr->add_table("foo");
2✔
364
    auto col_dict = foo->add_column_dictionary(type_Int, "dictionaries");
2✔
365

366
    Obj obj1 = foo->create_object();
2✔
367
    Obj obj2 = foo->create_object();
2✔
368
    Dictionary dict = obj1.get_dictionary(col_dict);
2✔
369
    std::vector<int64_t> random_idx(100);
2✔
370
    std::iota(random_idx.begin(), random_idx.end(), 0);
2✔
371
    std::shuffle(random_idx.begin(), random_idx.end(), std::mt19937(unit_test_random_seed));
2✔
372

373
    for (int i = 0; i < 100; i++) {
202✔
374
        dict.insert(util::to_string(i), random_idx[i]);
200✔
375
    }
200✔
376

377
    std::vector<size_t> indices;
2✔
378
    dict.sort(indices, true);
2✔
379
    int64_t last = -1;
2✔
380
    for (size_t ndc : indices) {
200✔
381
        int64_t val = dict.get_any(ndc).get_int();
200✔
382
        CHECK_GREATER(val, last);
200✔
383
        last = val;
200✔
384
    }
200✔
385
    tr->commit_and_continue_as_read();
2✔
386

387
    size_t ndx;
2✔
388
    auto max = dict.max(&ndx);
2✔
389
    CHECK(max);
2✔
390
    CHECK_EQUAL(max->get_int(), 99);
2✔
391

392
    auto min = dict.min(&ndx);
2✔
393
    CHECK(min);
2✔
394
    CHECK_EQUAL(min->get_int(), 0);
2✔
395

396
    size_t cnt;
2✔
397
    auto sum = dict.sum(&cnt);
2✔
398
    CHECK(sum);
2✔
399
    CHECK_EQUAL(cnt, 100);
2✔
400
    CHECK_EQUAL(sum->get_int(), 50 * 99);
2✔
401

402
    auto avg = dict.avg(&cnt);
2✔
403
    CHECK(avg);
2✔
404
    CHECK_EQUAL(cnt, 100);
2✔
405
    CHECK_EQUAL(avg->get_double(), double(50 * 99) / 100);
2✔
406

407
    dict = obj2.get_dictionary(col_dict);
2✔
408
    max = dict.max(&ndx);
2✔
409
    CHECK(max);
2✔
410
    CHECK(max->is_null());
2✔
411
    CHECK_EQUAL(ndx, realm::npos);
2✔
412
}
2✔
413

414
NONCONCURRENT_TEST(Dictionary_Performance)
415
{
2✔
416
    size_t nb_reps = 1000;
2✔
417

418
    Group g;
2✔
419
    auto foo = g.add_table("foo");
2✔
420
    auto col_dict = foo->add_column_dictionary(type_Int, "dictionaries", false);
2✔
421

422
    Obj obj1 = foo->create_object();
2✔
423
    Dictionary dict = obj1.get_dictionary(col_dict);
2✔
424
    std::vector<int64_t> random_idx(nb_reps);
2✔
425
    std::vector<std::string> random_str(nb_reps);
2✔
426
    std::iota(random_idx.begin(), random_idx.end(), 0);
2✔
427
    std::shuffle(random_idx.begin(), random_idx.end(), std::mt19937(unit_test_random_seed));
2✔
428

429
    std::transform(random_idx.begin(), random_idx.end(), random_str.begin(), [](int64_t i) {
2,000✔
430
        return std::string("s") + util::to_string(i);
2,000✔
431
    });
2,000✔
432

433
    auto t1 = steady_clock::now();
2✔
434

435
    int64_t i = 0;
2✔
436
    for (auto& str : random_str) {
2,000✔
437
        dict.insert(str, i++);
2,000✔
438
    }
2,000✔
439

440
    auto t2 = steady_clock::now();
2✔
441

442
    CALLGRIND_START_INSTRUMENTATION;
2✔
443
    i = 0;
2✔
444
    for (auto& str : random_str) {
2,000✔
445
        CHECK_EQUAL(dict.get(str), Mixed(i++));
2,000✔
446
    }
2,000✔
447
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
448

449
    auto t3 = steady_clock::now();
2✔
450

451
    std::cout << nb_reps << " values in dictionary" << std::endl;
2✔
452
    std::cout << "    insertion: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_reps << " ns/val" << std::endl;
2✔
453
    std::cout << "    lookup: " << duration_cast<nanoseconds>(t3 - t2).count() / nb_reps << " ns/val" << std::endl;
2✔
454
}
2✔
455

456
TEST(Dictionary_Tombstones)
457
{
2✔
458
    Group g;
2✔
459
    auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
460
    auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
461
    auto col_int = bars->add_column(type_Int, "value");
2✔
462
    ColKey col_dict = foos->add_column_dictionary(*bars, "dict");
2✔
463

464
    auto foo = foos->create_object_with_primary_key(123);
2✔
465
    auto a = bars->create_object_with_primary_key("a").set(col_int, 1);
2✔
466
    auto b = bars->create_object_with_primary_key("b").set(col_int, 2);
2✔
467

468
    auto dict = foo.get_dictionary(col_dict);
2✔
469
    dict.insert("a", a);
2✔
470
    dict.insert("b", b);
2✔
471

472
    auto q = bars->where(dict).equal(col_int, 1);
2✔
473
    CHECK_EQUAL(q.count(), 1);
2✔
474

475
    a.invalidate();
2✔
476

477
    CHECK_EQUAL(dict.size(), 2);
2✔
478
    CHECK((*dict.find("a")).second.is_null());
2✔
479

480
    CHECK(dict.find("b") != dict.end());
2✔
481

482
    CHECK_EQUAL(q.count(), 0);
2✔
483
}
2✔
484

485
TEST(Dictionary_UseAfterFree)
486
{
2✔
487
    Group g;
2✔
488
    auto foos = g.add_table("Foo");
2✔
489
    ColKey col_dict = foos->add_column_dictionary(type_String, "dict");
2✔
490

491
    auto foo = foos->create_object();
2✔
492
    auto dict = foo.get_dictionary(col_dict);
2✔
493
    dict.insert("a", "monkey");
2✔
494
    dict.insert("b", "lion");
2✔
495
    dict.insert("c", "à");
2✔
496

497
    Query q;
2✔
498
    {
2✔
499
        auto str = std::make_unique<std::string>("à");
2✔
500
        auto col = foos->column<Dictionary>(col_dict);
2✔
501
        q = col.equal(StringData(*str), true); // A copy of the string must be taken here
2✔
502
    }
2✔
503
    CHECK_EQUAL(q.count(), 1);
2✔
504
}
2✔
505

506
NONCONCURRENT_TEST(Dictionary_HashCollision)
507
{
2✔
508
    constexpr int64_t nb_entries = 100;
2✔
509
    Group g;
2✔
510
    auto foos = g.add_table("Foo");
2✔
511
    ColKey col_dict = foos->add_column_dictionary(type_Int, "dict");
2✔
512

513
    auto foo = foos->create_object();
2✔
514
    auto dict = foo.get_dictionary(col_dict);
2✔
515
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
516
        std::string key = "key" + util::to_string(i);
200✔
517
        dict.insert(Mixed(key), i);
200✔
518
        dict.erase(key);
200✔
519
        dict.insert(Mixed(key), i);
200✔
520
    }
200✔
521

522
    // g.to_json(std::cout);
523

524
    // Check that values can be read back
525
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
526
        std::string key = "key" + util::to_string(i);
200✔
527
        CHECK_EQUAL(dict[key].get_int(), i);
200✔
528
    }
200✔
529

530
    // And these keys should not exist
531
    for (int64_t i = nb_entries; i < nb_entries + 20; i++) {
42✔
532
        std::string key = "key" + util::to_string(i);
40✔
533
        CHECK_NOT(dict.contains(key));
40✔
534
    }
40✔
535

536
    // Check that a query can find matching key and value
537
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
538
        std::string key = "key" + util::to_string(i);
200✔
539
        Query q = (foos->column<Dictionary>(col_dict).key(key) == Mixed(i));
200✔
540
        CHECK_EQUAL(q.count(), 1);
200✔
541
    }
200✔
542

543
    // Check that dict.find works
544
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
545
        std::string key = "key" + util::to_string(i);
200✔
546
        auto it = dict.find(key);
200✔
547
        CHECK_EQUAL((*it).second.get_int(), i);
200✔
548
    }
200✔
549

550
    // And these keys should not be found
551
    for (int64_t i = nb_entries; i < nb_entries + 20; i++) {
42✔
552
        std::string key = "key" + util::to_string(i);
40✔
553
        CHECK(dict.find(key) == dict.end());
40✔
554
    }
40✔
555

556
    auto check_aggregates = [&]() {
4✔
557
        int64_t expected_sum = nb_entries * (nb_entries - 1) / 2;
4✔
558
        size_t count = 0;
4✔
559
        util::Optional<Mixed> actual_sum = dict.sum(&count);
4✔
560
        CHECK(actual_sum);
4✔
561
        CHECK(actual_sum && *actual_sum == Mixed{expected_sum});
4✔
562
        CHECK_EQUAL(count, nb_entries);
4✔
563
        Query q = (foos->column<Dictionary>(col_dict).sum() == Mixed(expected_sum));
4✔
564
        CHECK_EQUAL(q.count(), 1);
4✔
565

566
        util::Optional<Mixed> actual_min = dict.min();
4✔
567
        CHECK(actual_min && *actual_min == Mixed{0});
4✔
568
        q = (foos->column<Dictionary>(col_dict).min() == Mixed(0));
4✔
569
        CHECK_EQUAL(q.count(), 1);
4✔
570

571
        util::Optional<Mixed> actual_max = dict.max();
4✔
572
        CHECK(actual_max && *actual_max == Mixed{nb_entries - 1});
4✔
573
        q = (foos->column<Dictionary>(col_dict).max() == Mixed(nb_entries - 1));
4✔
574
        CHECK_EQUAL(q.count(), 1);
4✔
575

576
        util::Optional<Mixed> actual_avg = dict.avg(&count);
4✔
577
        Mixed expected_avg{Decimal128(expected_sum) / nb_entries};
4✔
578
        CHECK_EQUAL(count, nb_entries);
4✔
579
        CHECK(actual_avg && *actual_avg == expected_avg);
4✔
580
        q = (foos->column<Dictionary>(col_dict).average() == expected_avg);
4✔
581
        CHECK_EQUAL(q.count(), 1);
4✔
582
    };
4✔
583

584
    check_aggregates();
2✔
585

586
    // Update with new values
587
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
588
        std::string key = "key" + util::to_string(i);
200✔
589
        dict.insert(Mixed(key), nb_entries - i - 1);
200✔
590
    }
200✔
591

592
    check_aggregates();
2✔
593

594
    // Check that values was updated properly
595
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
596
        std::string key = "key" + util::to_string(i);
200✔
597
        CHECK_EQUAL(dict[key].get_int(), nb_entries - i - 1);
200✔
598
    }
200✔
599

600
    // Now erase one entry at a time and check that the rest of the values are ok
601
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
602
        std::string key = "key" + util::to_string(i);
200✔
603
        dict.erase(key);
200✔
604
        CHECK_EQUAL(dict.size(), nb_entries - i - 1);
200✔
605

606
        // Check that remaining entries still can be found
607
        for (int64_t j = i + 1; j < nb_entries; j++) {
10,100✔
608
            std::string key_j = "key" + util::to_string(j);
9,900✔
609
            CHECK_EQUAL(dict[key_j].get_int(), nb_entries - j - 1);
9,900✔
610
        }
9,900✔
611
    }
200✔
612
}
2✔
613

614
class ModelDict {
615
public:
616
    // random generators
617
    std::string get_rnd_used_key()
618
    {
1,276✔
619
        if (the_map.size() == 0)
1,276✔
UNCOV
620
            return std::string();
×
621
        size_t idx = size_t(rnd()) % the_map.size();
1,276✔
622
        auto it = the_map.begin();
1,276✔
623
        while (idx--) {
223,650✔
624
            it++;
222,374✔
625
        }
222,374✔
626
        return it->first;
1,276✔
627
    }
1,276✔
628
    std::string get_rnd_unused_key()
629
    {
2,724✔
630
        int64_t key_i;
2,724✔
631
        std::string key;
2,724✔
632
        do {
2,724✔
633
            key_i = rnd();
2,724✔
634
            key = std::to_string(key_i);
2,724✔
635
        } while (the_map.find(key) != the_map.end());
2,724✔
636
        return key;
2,724✔
637
    }
2,724✔
638
    Mixed get_rnd_value()
639
    {
2,724✔
640
        int64_t v = rnd();
2,724✔
641
        return Mixed(v);
2,724✔
642
    }
2,724✔
643
    // model access
644
    Mixed get_value_by_key(std::string key)
645
    {
1,276✔
646
        auto it = the_map.find(key);
1,276✔
647
        REALM_ASSERT(it != the_map.end());
1,276✔
648
        return it->second;
1,276✔
649
    }
1,276✔
650
    bool insert(std::string key, Mixed value)
651
    {
2,724✔
652
        the_map[key] = value;
2,724✔
653
        return true;
2,724✔
654
    }
2,724✔
655
    bool erase(std::string key)
656
    {
1,276✔
657
        the_map.erase(key);
1,276✔
658
        return true;
1,276✔
659
    }
1,276✔
660

661
    std::map<std::string, Mixed> the_map;
662
    util::UniqueFunction<int64_t(void)> rnd = std::mt19937(unit_test_random_seed);
663
};
664

665
NONCONCURRENT_TEST(Dictionary_HashRandomOpsTransaction)
666
{
2✔
667
    ModelDict model;
2✔
668
    SHARED_GROUP_TEST_PATH(path);
2✔
669
    auto hist = make_in_realm_history();
2✔
670
    DBRef db = DB::create(*hist, path);
2✔
671
    auto tr = db->start_write();
2✔
672
    ColKey col_dict;
2✔
673
    Dictionary dict;
2✔
674
    {
2✔
675
        // initial setup
676
        auto foos = tr->add_table("Foo");
2✔
677
        col_dict = foos->add_column_dictionary(type_Int, "dict");
2✔
678

679
        auto foo = foos->create_object();
2✔
680
        dict = foo.get_dictionary(col_dict);
2✔
681
        tr->commit_and_continue_as_read();
2✔
682
    }
2✔
683
    auto tr2 = db->start_read();
2✔
684
    auto dict2 = tr2->get_table("Foo")->get_object(0).get_dictionary(col_dict);
2✔
685
    auto random_op = [&](Dictionary& dict) {
4,000✔
686
        if (model.rnd() % 3) { // 66%
4,000✔
687
            auto k = model.get_rnd_unused_key();
2,724✔
688
            auto v = model.get_rnd_value();
2,724✔
689
            model.insert(k, v);
2,724✔
690
            dict.insert(k, v);
2,724✔
691
        }
2,724✔
692
        else { // 33%
1,276✔
693
            auto k = model.get_rnd_used_key();
1,276✔
694
            if (!k.empty()) {
1,276✔
695
                auto v = model.get_value_by_key(k);
1,276✔
696
                CHECK(dict.get(k) == v);
1,276✔
697
                dict.erase(k);
1,276✔
698
                model.erase(k);
1,276✔
699
            }
1,276✔
700
        }
1,276✔
701
    };
4,000✔
702
    for (int it = 0; it < 1000; it++) {
2,002✔
703
        tr2->promote_to_write();
2,000✔
704
        {
2,000✔
705
            random_op(dict2);
2,000✔
706
        }
2,000✔
707
        tr2->commit_and_continue_as_read();
2,000✔
708
        tr->promote_to_write();
2,000✔
709
        tr->verify();
2,000✔
710
        {
2,000✔
711
            random_op(dict);
2,000✔
712
        }
2,000✔
713
        tr->commit_and_continue_as_read();
2,000✔
714
        tr->verify();
2,000✔
715
    }
2,000✔
716
}
2✔
717

718
static void do_Dictionary_HashCollisionTransaction(realm::test_util::unit_test::TestContext& test_context,
719
                                                   int64_t nb_entries)
720
{
4✔
721
    SHARED_GROUP_TEST_PATH(path);
4✔
722
    auto hist = make_in_realm_history();
4✔
723
    DBRef db = DB::create(*hist, path);
4✔
724

725
    {
4✔
726
        auto tr = db->start_write();
4✔
727
        auto foos = tr->add_table("Foo");
4✔
728
        auto bars = tr->add_table("Bar");
4✔
729
        ColKey col_dict = foos->add_column_dictionary(*bars, "dict");
4✔
730
        ColKey col_int = bars->add_column(type_Int, "ints");
4✔
731

732
        for (int64_t i = 0; i < nb_entries; i++) {
1,204✔
733
            bars->create_object().set(col_int, i);
1,200✔
734
        }
1,200✔
735

736
        auto foo = foos->create_object();
4✔
737
        auto dict = foo.get_dictionary(col_dict);
4✔
738
        for (int64_t i = 0; i < nb_entries; i++) {
1,204✔
739
            std::string key = "key" + util::to_string(i);
1,200✔
740
            dict.insert(Mixed(key), bars->find_first_int(col_int, i));
1,200✔
741
        }
1,200✔
742
        tr->commit();
4✔
743
    }
4✔
744

745
    {
4✔
746
        auto rt = db->start_read();
4✔
747
        auto foos = rt->get_table("Foo");
4✔
748
        auto bars = rt->get_table("Bar");
4✔
749
        ColKey col_dict = foos->get_column_key("dict");
4✔
750
        ColKey col_int = bars->get_column_key("ints");
4✔
751
        auto dict = foos->begin()->get_dictionary(col_dict);
4✔
752
        for (int64_t i = 0; i < nb_entries; i++) {
1,204✔
753
            std::string key = "key" + util::to_string(i);
1,200✔
754
            auto obj_key = dict[key].get<ObjKey>();
1,200✔
755
            CHECK_EQUAL(bars->get_object(obj_key).get<Int>(col_int), i);
1,200✔
756
        }
1,200✔
757
    }
4✔
758

759
    auto rt = db->start_read();
4✔
760
    for (int64_t i = 0; i < nb_entries; i++) {
1,204✔
761
        rt->promote_to_write();
1,200✔
762

763
        auto foos = rt->get_table("Foo");
1,200✔
764
        auto bars = rt->get_table("Bar");
1,200✔
765
        ColKey col_dict = foos->get_column_key("dict");
1,200✔
766
        ColKey col_int = bars->get_column_key("ints");
1,200✔
767
        auto dict = foos->begin()->get_dictionary(col_dict);
1,200✔
768

769
        std::string key = "key" + util::to_string(i);
1,200✔
770
        dict.erase(key);
1,200✔
771
        CHECK_EQUAL(dict.size(), nb_entries - i - 1);
1,200✔
772
        bars->remove_object(bars->find_first_int(col_int, i));
1,200✔
773

774
        rt->commit_and_continue_as_read();
1,200✔
775

776
        for (int64_t j = i + 1; j < nb_entries; j++) {
260,600✔
777
            std::string key_j = "key" + util::to_string(j);
259,400✔
778
            auto obj_key = dict[key_j].get<ObjKey>();
259,400✔
779
            CHECK_EQUAL(bars->get_object(obj_key).get<Int>("ints"), j);
259,400✔
780
        }
259,400✔
781
    }
1,200✔
782
}
4✔
783

784
NONCONCURRENT_TEST(Dictionary_HashCollisionTransaction)
785
{
2✔
786
    do_Dictionary_HashCollisionTransaction(test_context, 100); // One node cluster
2✔
787
    do_Dictionary_HashCollisionTransaction(test_context, 500); // Three node cluster
2✔
788
}
2✔
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