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

realm / realm-core / 1737

06 Oct 2023 12:38PM UTC coverage: 91.567% (-0.02%) from 91.591%
1737

push

Evergreen

web-flow
Enable `REALM_ENABLE_GEOSPATIAL`, geoWithin on points for SPM Installation (#7032)

94320 of 173524 branches covered (0.0%)

230508 of 251736 relevant lines covered (91.57%)

6802705.57 hits per line

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

99.85
/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

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

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

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

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

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

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

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

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

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

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

1✔
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_EQUAL(pluto.get_backlink_count(), 0);
2✔
180
        CHECK_EQUAL(lady.get_backlink_count(*persons, col_dict), 1);
2✔
181
        CHECK_EQUAL(lady.get_backlink(*persons, col_dict, 0), adam.get_key());
2✔
182
        CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
183
        CHECK_EQUAL(dict.get("Pet").get<ObjKey>(), lady.get_key());
2✔
184
        lady.remove();
2✔
185
        cmp(dict["Pet"], Mixed());
2✔
186
        CHECK_THROW_ANY(dict.insert("Pet", garfield));
2✔
187
        CHECK_THROW_ANY(dict.insert("Pet", Mixed(ObjKey(27))));
2✔
188

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

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

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

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

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

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

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

1✔
232
    {
2✔
233
        Dictionary dict = adam.get_dictionary(col_dict);
2✔
234
        CHECK(dict.insert("Pet", pluto).second);
2✔
235
        CHECK_EQUAL(pluto.get_backlink_count(), 1);
2✔
236
        CHECK_NOT(dict.insert("Pet", lady).second);
2✔
237
        CHECK_EQUAL(pluto.get_backlink_count(), 0);
2✔
238
        CHECK_EQUAL(lady.get_backlink_count(*persons, col_dict), 1);
2✔
239
        CHECK_EQUAL(lady.get_backlink(*persons, col_dict, 0), adam.get_key());
2✔
240
        CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
241
        lady.remove();
2✔
242
        cmp(dict["Pet"], Mixed());
2✔
243

1✔
244
        // Reinsert lady
1✔
245
        lady = dogs->create_object_with_primary_key("lady");
2✔
246
        dict.insert("Pet", lady);
2✔
247
        lady.invalidate(); // Make lady a tombstone :-(
2✔
248
        cmp(dict["Pet"], Mixed());
2✔
249
        lady = dogs->create_object_with_primary_key("lady");
2✔
250
        cmp(dict["Pet"], Mixed(lady.get_link()));
2✔
251

1✔
252
        auto invalid_link = pluto.get_link();
2✔
253
        pluto.remove();
2✔
254
        CHECK_THROW(dict.insert("Pet", invalid_link), InvalidArgument);
2✔
255

1✔
256
        dict.insert("Pet", Mixed(ObjLink(dogs->get_key(), dogs->get_objkey_from_primary_key("pongo"))));
2✔
257
        cmp(dict["Pet"], Mixed());
2✔
258
        Obj pongo = dogs->create_object_with_primary_key("pongo");
2✔
259
        CHECK_EQUAL(pongo.get_backlink_count(), 1);
2✔
260
        cmp(dict["Pet"], Mixed(pongo.get_link()));
2✔
261
    }
2✔
262
}
2✔
263

264
TEST(Dictionary_Clear)
265
{
2✔
266
    Group g;
2✔
267
    auto dogs = g.add_table_with_primary_key("dog", type_String, "name");
2✔
268
    auto persons = g.add_table_with_primary_key("person", type_String, "name");
2✔
269
    auto col_dict_typed = persons->add_column_dictionary(type_TypedLink, "typed");
2✔
270
    auto col_dict_implicit = persons->add_column_dictionary(*dogs, "implicit");
2✔
271

1✔
272
    Obj adam = persons->create_object_with_primary_key("adam");
2✔
273
    Obj pluto = dogs->create_object_with_primary_key("pluto");
2✔
274
    Obj lady = dogs->create_object_with_primary_key("lady");
2✔
275

1✔
276
    adam.get_dictionary(col_dict_typed).insert("Dog1", pluto);
2✔
277
    adam.get_dictionary(col_dict_implicit).insert("DOg2", lady.get_key());
2✔
278

1✔
279
    CHECK_EQUAL(lady.get_backlink_count(), 1);
2✔
280
    CHECK_EQUAL(pluto.get_backlink_count(), 1);
2✔
281
    persons->clear();
2✔
282
    CHECK_EQUAL(lady.get_backlink_count(), 0);
2✔
283
    CHECK_EQUAL(pluto.get_backlink_count(), 0);
2✔
284
    g.verify();
2✔
285
}
2✔
286

287
TEST(Dictionary_Transaction)
288
{
2✔
289
    SHARED_GROUP_TEST_PATH(path);
2✔
290
    auto hist = make_in_realm_history();
2✔
291
    DBRef db = DB::create(*hist, path);
2✔
292
    ObjKey k0, k2;
2✔
293
    ColKey col_dict;
2✔
294
    auto cmp = [this](Mixed x, Mixed y) {
6✔
295
        CHECK_EQUAL(x, y);
6✔
296
    };
6✔
297

1✔
298
    auto rt = db->start_read();
2✔
299
    {
2✔
300
        WriteTransaction wt(db);
2✔
301
        auto foo = wt.add_table("foo");
2✔
302
        col_dict = foo->add_column_dictionary(type_Mixed, "dictionaries");
2✔
303

1✔
304
        Obj obj0 = foo->create_object();
2✔
305
        Obj obj1 = foo->create_object();
2✔
306
        Obj obj2 = foo->create_object();
2✔
307
        k0 = obj0.get_key();
2✔
308
        k2 = obj2.get_key();
2✔
309
        Dictionary dict = obj0.get_dictionary(col_dict);
2✔
310
        dict.insert("Hello", 9);
2✔
311
        dict.insert("Goodbye", "cruel world");
2✔
312

1✔
313
        dict = obj1.get_dictionary(col_dict);
2✔
314
        dict.insert("Link", obj2.get_link());
2✔
315
        wt.commit();
2✔
316
    }
2✔
317
    rt->advance_read();
2✔
318
    rt->verify();
2✔
319
    ConstTableRef table = rt->get_table("foo");
2✔
320
    Dictionary dict;
2✔
321
    dict = table->get_object(k0).get_dictionary(col_dict);
2✔
322
    cmp(dict.get("Hello"), 9);
2✔
323
    cmp(dict.get("Goodbye"), "cruel world");
2✔
324

1✔
325
    {
2✔
326
        WriteTransaction wt(db);
2✔
327
        auto foo = wt.get_table("foo");
2✔
328
        Dictionary d = foo->get_object(k0).get_dictionary(col_dict);
2✔
329
        d.insert("Good morning", "sunshine");
2✔
330
        foo->remove_object(k2); // Nullifies link in obj1.dictionaries["Link"]
2✔
331
        wt.commit();
2✔
332
    }
2✔
333
    rt->advance_read();
2✔
334
    rt->verify();
2✔
335
    // rt->to_json(std::cout);
1✔
336
    cmp(dict.get("Good morning"), "sunshine");
2✔
337

1✔
338
    {
2✔
339
        auto wt = db->start_write();
2✔
340
        auto foo = wt->get_table("foo");
2✔
341
        Dictionary d = foo->get_object(k0).get_dictionary(col_dict);
2✔
342
        d.clear();
2✔
343

1✔
344
        wt->commit_and_continue_as_read();
2✔
345
        wt->promote_to_write();
2✔
346
        wt->verify();
2✔
347
    }
2✔
348
}
2✔
349

350
TEST(Dictionary_Aggregate)
351
{
2✔
352
    SHARED_GROUP_TEST_PATH(path);
2✔
353
    auto hist = make_in_realm_history();
2✔
354
    DBRef db = DB::create(*hist, path);
2✔
355
    auto tr = db->start_write();
2✔
356
    auto foo = tr->add_table("foo");
2✔
357
    auto col_dict = foo->add_column_dictionary(type_Int, "dictionaries");
2✔
358

1✔
359
    Obj obj1 = foo->create_object();
2✔
360
    Obj obj2 = foo->create_object();
2✔
361
    Dictionary dict = obj1.get_dictionary(col_dict);
2✔
362
    std::vector<int64_t> random_idx(100);
2✔
363
    std::iota(random_idx.begin(), random_idx.end(), 0);
2✔
364
    std::shuffle(random_idx.begin(), random_idx.end(), std::mt19937(unit_test_random_seed));
2✔
365

1✔
366
    for (int i = 0; i < 100; i++) {
202✔
367
        dict.insert(util::to_string(i), random_idx[i]);
200✔
368
    }
200✔
369

1✔
370
    std::vector<size_t> indices;
2✔
371
    dict.sort(indices, true);
2✔
372
    int64_t last = -1;
2✔
373
    for (size_t ndc : indices) {
200✔
374
        int64_t val = dict.get_any(ndc).get_int();
200✔
375
        CHECK_GREATER(val, last);
200✔
376
        last = val;
200✔
377
    }
200✔
378
    tr->commit_and_continue_as_read();
2✔
379

1✔
380
    size_t ndx;
2✔
381
    auto max = dict.max(&ndx);
2✔
382
    CHECK(max);
2✔
383
    CHECK_EQUAL(max->get_int(), 99);
2✔
384

1✔
385
    auto min = dict.min(&ndx);
2✔
386
    CHECK(min);
2✔
387
    CHECK_EQUAL(min->get_int(), 0);
2✔
388

1✔
389
    size_t cnt;
2✔
390
    auto sum = dict.sum(&cnt);
2✔
391
    CHECK(sum);
2✔
392
    CHECK_EQUAL(cnt, 100);
2✔
393
    CHECK_EQUAL(sum->get_int(), 50 * 99);
2✔
394

1✔
395
    auto avg = dict.avg(&cnt);
2✔
396
    CHECK(avg);
2✔
397
    CHECK_EQUAL(cnt, 100);
2✔
398
    CHECK_EQUAL(avg->get_double(), double(50 * 99) / 100);
2✔
399

1✔
400
    dict = obj2.get_dictionary(col_dict);
2✔
401
    max = dict.max(&ndx);
2✔
402
    CHECK(max);
2✔
403
    CHECK(max->is_null());
2✔
404
    CHECK_EQUAL(ndx, realm::npos);
2✔
405
}
2✔
406

407
NONCONCURRENT_TEST(Dictionary_Performance)
408
{
2✔
409
    size_t nb_reps = 1000;
2✔
410

1✔
411
    Group g;
2✔
412
    auto foo = g.add_table("foo");
2✔
413
    auto col_dict = foo->add_column_dictionary(type_Int, "dictionaries", false);
2✔
414

1✔
415
    Obj obj1 = foo->create_object();
2✔
416
    Dictionary dict = obj1.get_dictionary(col_dict);
2✔
417
    std::vector<int64_t> random_idx(nb_reps);
2✔
418
    std::vector<std::string> random_str(nb_reps);
2✔
419
    std::iota(random_idx.begin(), random_idx.end(), 0);
2✔
420
    std::shuffle(random_idx.begin(), random_idx.end(), std::mt19937(unit_test_random_seed));
2✔
421

1✔
422
    std::transform(random_idx.begin(), random_idx.end(), random_str.begin(), [](int64_t i) {
2,000✔
423
        return std::string("s") + util::to_string(i);
2,000✔
424
    });
2,000✔
425

1✔
426
    auto t1 = steady_clock::now();
2✔
427

1✔
428
    int64_t i = 0;
2✔
429
    for (auto& str : random_str) {
2,000✔
430
        dict.insert(str, i++);
2,000✔
431
    }
2,000✔
432

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

1✔
435
    CALLGRIND_START_INSTRUMENTATION;
2✔
436
    i = 0;
2✔
437
    for (auto& str : random_str) {
2,000✔
438
        CHECK_EQUAL(dict.get(str), Mixed(i++));
2,000✔
439
    }
2,000✔
440
    CALLGRIND_STOP_INSTRUMENTATION;
2✔
441

1✔
442
    auto t3 = steady_clock::now();
2✔
443

1✔
444
    std::cout << nb_reps << " values in dictionary" << std::endl;
2✔
445
    std::cout << "    insertion: " << duration_cast<nanoseconds>(t2 - t1).count() / nb_reps << " ns/val" << std::endl;
2✔
446
    std::cout << "    lookup: " << duration_cast<nanoseconds>(t3 - t2).count() / nb_reps << " ns/val" << std::endl;
2✔
447
}
2✔
448

449
TEST(Dictionary_Tombstones)
450
{
2✔
451
    Group g;
2✔
452
    auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
453
    auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
454
    auto col_int = bars->add_column(type_Int, "value");
2✔
455
    ColKey col_dict = foos->add_column_dictionary(*bars, "dict");
2✔
456

1✔
457
    auto foo = foos->create_object_with_primary_key(123);
2✔
458
    auto a = bars->create_object_with_primary_key("a").set(col_int, 1);
2✔
459
    auto b = bars->create_object_with_primary_key("b").set(col_int, 2);
2✔
460

1✔
461
    auto dict = foo.get_dictionary(col_dict);
2✔
462
    dict.insert("a", a);
2✔
463
    dict.insert("b", b);
2✔
464

1✔
465
    auto q = bars->where(dict).equal(col_int, 1);
2✔
466
    CHECK_EQUAL(q.count(), 1);
2✔
467

1✔
468
    a.invalidate();
2✔
469

1✔
470
    CHECK_EQUAL(dict.size(), 2);
2✔
471
    CHECK((*dict.find("a")).second.is_null());
2✔
472

1✔
473
    CHECK(dict.find("b") != dict.end());
2✔
474

1✔
475
    CHECK_EQUAL(q.count(), 0);
2✔
476
}
2✔
477

478
TEST(Dictionary_UseAfterFree)
479
{
2✔
480
    Group g;
2✔
481
    auto foos = g.add_table("Foo");
2✔
482
    ColKey col_dict = foos->add_column_dictionary(type_String, "dict");
2✔
483

1✔
484
    auto foo = foos->create_object();
2✔
485
    auto dict = foo.get_dictionary(col_dict);
2✔
486
    dict.insert("a", "monkey");
2✔
487
    dict.insert("b", "lion");
2✔
488
    dict.insert("c", "à");
2✔
489

1✔
490
    Query q;
2✔
491
    {
2✔
492
        auto str = std::make_unique<std::string>("à");
2✔
493
        auto col = foos->column<Dictionary>(col_dict);
2✔
494
        q = col.equal(StringData(*str), true); // A copy of the string must be taken here
2✔
495
    }
2✔
496
    CHECK_EQUAL(q.count(), 1);
2✔
497
}
2✔
498

499
NONCONCURRENT_TEST(Dictionary_HashCollision)
500
{
2✔
501
    constexpr int64_t nb_entries = 100;
2✔
502
    Group g;
2✔
503
    auto foos = g.add_table("Foo");
2✔
504
    ColKey col_dict = foos->add_column_dictionary(type_Int, "dict");
2✔
505

1✔
506
    auto foo = foos->create_object();
2✔
507
    auto dict = foo.get_dictionary(col_dict);
2✔
508
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
509
        std::string key = "key" + util::to_string(i);
200✔
510
        dict.insert(Mixed(key), i);
200✔
511
        dict.erase(key);
200✔
512
        dict.insert(Mixed(key), i);
200✔
513
    }
200✔
514

1✔
515
    // g.to_json(std::cout);
1✔
516

1✔
517
    // Check that values can be read back
1✔
518
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
519
        std::string key = "key" + util::to_string(i);
200✔
520
        CHECK_EQUAL(dict[key].get_int(), i);
200✔
521
    }
200✔
522

1✔
523
    // And these keys should not exist
1✔
524
    for (int64_t i = nb_entries; i < nb_entries + 20; i++) {
42✔
525
        std::string key = "key" + util::to_string(i);
40✔
526
        CHECK_NOT(dict.contains(key));
40✔
527
    }
40✔
528

1✔
529
    // Check that a query can find matching key and value
1✔
530
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
531
        std::string key = "key" + util::to_string(i);
200✔
532
        Query q = (foos->column<Dictionary>(col_dict).key(key) == Mixed(i));
200✔
533
        CHECK_EQUAL(q.count(), 1);
200✔
534
    }
200✔
535

1✔
536
    // Check that dict.find works
1✔
537
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
538
        std::string key = "key" + util::to_string(i);
200✔
539
        auto it = dict.find(key);
200✔
540
        CHECK_EQUAL((*it).second.get_int(), i);
200✔
541
    }
200✔
542

1✔
543
    // And these keys should not be found
1✔
544
    for (int64_t i = nb_entries; i < nb_entries + 20; i++) {
42✔
545
        std::string key = "key" + util::to_string(i);
40✔
546
        CHECK(dict.find(key) == dict.end());
40✔
547
    }
40✔
548

1✔
549
    auto check_aggregates = [&]() {
4✔
550
        int64_t expected_sum = nb_entries * (nb_entries - 1) / 2;
4✔
551
        size_t count = 0;
4✔
552
        util::Optional<Mixed> actual_sum = dict.sum(&count);
4✔
553
        CHECK(actual_sum);
4✔
554
        CHECK(actual_sum && *actual_sum == Mixed{expected_sum});
4✔
555
        CHECK_EQUAL(count, nb_entries);
4✔
556
        Query q = (foos->column<Dictionary>(col_dict).sum() == Mixed(expected_sum));
4✔
557
        CHECK_EQUAL(q.count(), 1);
4✔
558

2✔
559
        util::Optional<Mixed> actual_min = dict.min();
4✔
560
        CHECK(actual_min && *actual_min == Mixed{0});
4✔
561
        q = (foos->column<Dictionary>(col_dict).min() == Mixed(0));
4✔
562
        CHECK_EQUAL(q.count(), 1);
4✔
563

2✔
564
        util::Optional<Mixed> actual_max = dict.max();
4✔
565
        CHECK(actual_max && *actual_max == Mixed{nb_entries - 1});
4✔
566
        q = (foos->column<Dictionary>(col_dict).max() == Mixed(nb_entries - 1));
4✔
567
        CHECK_EQUAL(q.count(), 1);
4✔
568

2✔
569
        util::Optional<Mixed> actual_avg = dict.avg(&count);
4✔
570
        Mixed expected_avg{Decimal128(expected_sum) / nb_entries};
4✔
571
        CHECK_EQUAL(count, nb_entries);
4✔
572
        CHECK(actual_avg && *actual_avg == expected_avg);
4✔
573
        q = (foos->column<Dictionary>(col_dict).average() == expected_avg);
4✔
574
        CHECK_EQUAL(q.count(), 1);
4✔
575
    };
4✔
576

1✔
577
    check_aggregates();
2✔
578

1✔
579
    // Update with new values
1✔
580
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
581
        std::string key = "key" + util::to_string(i);
200✔
582
        dict.insert(Mixed(key), nb_entries - i - 1);
200✔
583
    }
200✔
584

1✔
585
    check_aggregates();
2✔
586

1✔
587
    // Check that values was updated properly
1✔
588
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
589
        std::string key = "key" + util::to_string(i);
200✔
590
        CHECK_EQUAL(dict[key].get_int(), nb_entries - i - 1);
200✔
591
    }
200✔
592

1✔
593
    // Now erase one entry at a time and check that the rest of the values are ok
1✔
594
    for (int64_t i = 0; i < nb_entries; i++) {
202✔
595
        std::string key = "key" + util::to_string(i);
200✔
596
        dict.erase(key);
200✔
597
        CHECK_EQUAL(dict.size(), nb_entries - i - 1);
200✔
598

100✔
599
        // Check that remaining entries still can be found
100✔
600
        for (int64_t j = i + 1; j < nb_entries; j++) {
10,100✔
601
            std::string key_j = "key" + util::to_string(j);
9,900✔
602
            CHECK_EQUAL(dict[key_j].get_int(), nb_entries - j - 1);
9,900✔
603
        }
9,900✔
604
    }
200✔
605
}
2✔
606

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

654
    std::map<std::string, Mixed> the_map;
655
    util::UniqueFunction<int64_t(void)> rnd = std::mt19937(unit_test_random_seed);
656
};
657

658
NONCONCURRENT_TEST(Dictionary_HashRandomOpsTransaction)
659
{
2✔
660
    ModelDict model;
2✔
661
    SHARED_GROUP_TEST_PATH(path);
2✔
662
    auto hist = make_in_realm_history();
2✔
663
    DBRef db = DB::create(*hist, path);
2✔
664
    auto tr = db->start_write();
2✔
665
    ColKey col_dict;
2✔
666
    Dictionary dict;
2✔
667
    {
2✔
668
        // initial setup
1✔
669
        auto foos = tr->add_table("Foo");
2✔
670
        col_dict = foos->add_column_dictionary(type_Int, "dict");
2✔
671

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

711
static void do_Dictionary_HashCollisionTransaction(realm::test_util::unit_test::TestContext& test_context,
712
                                                   int64_t nb_entries)
713
{
4✔
714
    SHARED_GROUP_TEST_PATH(path);
4✔
715
    auto hist = make_in_realm_history();
4✔
716
    DBRef db = DB::create(*hist, path);
4✔
717

2✔
718
    {
4✔
719
        auto tr = db->start_write();
4✔
720
        auto foos = tr->add_table("Foo");
4✔
721
        auto bars = tr->add_table("Bar");
4✔
722
        ColKey col_dict = foos->add_column_dictionary(*bars, "dict");
4✔
723
        ColKey col_int = bars->add_column(type_Int, "ints");
4✔
724

2✔
725
        for (int64_t i = 0; i < nb_entries; i++) {
1,204✔
726
            bars->create_object().set(col_int, i);
1,200✔
727
        }
1,200✔
728

2✔
729
        auto foo = foos->create_object();
4✔
730
        auto dict = foo.get_dictionary(col_dict);
4✔
731
        for (int64_t i = 0; i < nb_entries; i++) {
1,204✔
732
            std::string key = "key" + util::to_string(i);
1,200✔
733
            dict.insert(Mixed(key), bars->find_first_int(col_int, i));
1,200✔
734
        }
1,200✔
735
        tr->commit();
4✔
736
    }
4✔
737

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

2✔
752
    auto rt = db->start_read();
4✔
753
    for (int64_t i = 0; i < nb_entries; i++) {
1,204✔
754
        rt->promote_to_write();
1,200✔
755

600✔
756
        auto foos = rt->get_table("Foo");
1,200✔
757
        auto bars = rt->get_table("Bar");
1,200✔
758
        ColKey col_dict = foos->get_column_key("dict");
1,200✔
759
        ColKey col_int = bars->get_column_key("ints");
1,200✔
760
        auto dict = foos->begin()->get_dictionary(col_dict);
1,200✔
761

600✔
762
        std::string key = "key" + util::to_string(i);
1,200✔
763
        dict.erase(key);
1,200✔
764
        CHECK_EQUAL(dict.size(), nb_entries - i - 1);
1,200✔
765
        bars->remove_object(bars->find_first_int(col_int, i));
1,200✔
766

600✔
767
        rt->commit_and_continue_as_read();
1,200✔
768

600✔
769
        for (int64_t j = i + 1; j < nb_entries; j++) {
260,600✔
770
            std::string key_j = "key" + util::to_string(j);
259,400✔
771
            auto obj_key = dict[key_j].get<ObjKey>();
259,400✔
772
            CHECK_EQUAL(bars->get_object(obj_key).get<Int>("ints"), j);
259,400✔
773
        }
259,400✔
774
    }
1,200✔
775
}
4✔
776

777
NONCONCURRENT_TEST(Dictionary_HashCollisionTransaction)
778
{
2✔
779
    do_Dictionary_HashCollisionTransaction(test_context, 100); // One node cluster
2✔
780
    do_Dictionary_HashCollisionTransaction(test_context, 500); // Three node cluster
2✔
781
}
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