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

realm / realm-core / 2451

29 Jun 2024 12:42AM UTC coverage: 90.99% (+0.03%) from 90.961%
2451

push

Evergreen

web-flow
RCORE-2175 RCORE-2176 RCORE-2182 RCORE-2083 Fix several problems with object removal and links (#7830)

* fix change of mode from Strong to All when deleting embedded objects that link to a tombstone

* fix remove recursive to follow mixed links

* fix removal of backlinks from mixed in large clusters

* add more test coverage to verify single link removal

* Replace ObjKey with RowKey in selected places

* fix a few more type casts

* touchup changelog

---------

Co-authored-by: Jørgen Edelbo <jorgen.edelbo@mongodb.com>

102368 of 180506 branches covered (56.71%)

371 of 382 new or added lines in 8 files covered. (97.12%)

50 existing lines in 11 files now uncovered.

215226 of 236537 relevant lines covered (90.99%)

5659844.03 hits per line

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

98.52
/test/test_mixed_null_assertions.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2020 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
#include <realm/array_mixed.hpp>
23

24
#include "test.hpp"
25
#include "test_types_helper.hpp"
26

27
using namespace realm;
28
using namespace realm::util;
29
using namespace realm::test_util;
30

31
/*************************************************************************
32
 *
33
 * This test set validate that sets and lists dont hit an assert exception
34
 * when operating with Mixed.
35
 *
36
 * See: https://github.com/realm/realm-core/issues/4304
37
 *
38
 **************************************************************************/
39

40
TEST(Set_Mixed_do_erase)
41
{
2✔
42
    Group g;
2✔
43

44
    auto t = g.add_table("foo");
2✔
45
    t->add_column_set(type_Mixed, "mixeds");
2✔
46
    auto obj = t->create_object();
2✔
47

48
    auto set = obj.get_set<Mixed>("mixeds");
2✔
49

50
    set.insert(util::none);
2✔
51
    set.erase_null();
2✔
52
}
2✔
53

54
TEST(List_Mixed_do_set)
55
{
2✔
56
    Group g;
2✔
57

58
    auto t = g.add_table("foo");
2✔
59
    t->add_column_list(type_Mixed, "mixeds");
2✔
60
    auto obj = t->create_object();
2✔
61

62
    auto set = obj.get_list<Mixed>("mixeds");
2✔
63

64
    set.insert_null(0);
2✔
65
    set.set(0, Mixed("hello world"));
2✔
66
    auto val = set.get(0);
2✔
67
    CHECK(val.is_type(type_String));
2✔
68
    CHECK_EQUAL(val.get_string(), "hello world");
2✔
69
    set.set(0, Mixed(BinaryData("hello world", 11)));
2✔
70
    val = set.get(0);
2✔
71
    CHECK(val.is_type(type_Binary));
2✔
72
    CHECK_EQUAL(val.get_binary(), BinaryData("hello world", 11));
2✔
73
}
2✔
74

75
TEST(List_Mixed_do_insert)
76
{
2✔
77
    Group g;
2✔
78

79
    auto t = g.add_table("foo");
2✔
80
    t->add_column_list(type_Mixed, "mixeds");
2✔
81
    auto obj = t->create_object();
2✔
82

83
    auto list = obj.get_list<Mixed>("mixeds");
2✔
84

85
    list.insert_null(0);
2✔
86
    list.insert(0, Mixed("hello world"));
2✔
87
}
2✔
88

89
TEST(Mixed_List_unresolved_as_null)
90
{
2✔
91
    Group g;
2✔
92
    auto t = g.add_table("foo");
2✔
93
    t->add_column_list(type_Mixed, "mixeds");
2✔
94
    auto obj = t->create_object();
2✔
95
    auto obj1 = t->create_object();
2✔
96

97
    auto list = obj.get_list<Mixed>("mixeds");
2✔
98

99
    list.insert_null(0);
2✔
100
    list.insert(1, Mixed{"test"});
2✔
101
    list.insert(2, obj1);
2✔
102
    obj1.invalidate();
2✔
103

104
    CHECK_EQUAL(list.size(), 3);
2✔
105

106
    {
2✔
107
        // find all mixed nulls or unresolved link should work the same way
108
        std::vector<size_t> found;
2✔
109
        auto check_results = [&](std::vector<size_t> expected) -> bool {
2✔
110
            if (found.size() != expected.size())
2✔
111
                return false;
×
112

113
            std::sort(found.begin(), found.end());
2✔
114
            std::sort(expected.begin(), expected.end());
2✔
115
            for (size_t i = 0; i < found.size(); ++i) {
6✔
116
                if (found[i] != expected[i])
4✔
117
                    return false;
×
118
            }
4✔
119
            return true;
2✔
120
        };
2✔
121
        list.find_all(realm::null(), [&](size_t pos) {
4✔
122
            found.push_back(pos);
4✔
123
        });
4✔
124
        CHECK_EQUAL(check_results({0, 2}), true);
2✔
125
    }
2✔
126

127
    {
2✔
128
        // find null or find unresolved link diverge, different objects should be returned
129
        auto index = list.find_any(realm::null());
2✔
130
        CHECK_EQUAL(index, 0);
2✔
131
        index = list.find_first(obj1);
2✔
132
        CHECK_EQUAL(index, 2);
2✔
133
        // but both should look like nulls
134
        CHECK_EQUAL(list.is_null(0), true);
2✔
135
        CHECK_EQUAL(list.is_null(2), true);
2✔
136
    }
2✔
137

138
    {
2✔
139
        std::vector<size_t> indices{0, 1, 2};
2✔
140
        list.sort(indices);
2✔
141
        CHECK_EQUAL(indices.size(), 3);
2✔
142
        CHECK_EQUAL(indices.at(0), 0);
2✔
143
        CHECK_EQUAL(indices.at(1), 2);
2✔
144
        CHECK_EQUAL(indices.at(2), 1);
2✔
145
        CHECK_EQUAL(list.is_null(indices[0]), true);
2✔
146
        CHECK_EQUAL(list.is_null(indices[1]), true);
2✔
147
        CHECK_EQUAL(list.is_null(indices[2]), false);
2✔
148
    }
2✔
149

150
    {
2✔
151
        std::vector<size_t> indices{0, 1, 2};
2✔
152
        list.distinct(indices);
2✔
153
        CHECK_EQUAL(indices.size(), 2);
2✔
154
        CHECK_EQUAL(indices.at(0), 0);
2✔
155
        CHECK_EQUAL(indices.at(1), 1);
2✔
156
        CHECK_EQUAL(list.is_null(indices[0]), true);
2✔
157
        CHECK_EQUAL(list.is_null(indices[1]), false);
2✔
158
        CHECK_EQUAL(list.find_any(realm::null()), 0);
2✔
159
    }
2✔
160

161
    {
2✔
162
        list.remove(0);
2✔
163
        CHECK_EQUAL(list.find_any(obj1), 1);
2✔
164
        list.remove(1);
2✔
165
        CHECK_EQUAL(list.find_any(realm::null()), npos);
2✔
166
        CHECK_EQUAL(list.size(), 1);
2✔
167
    }
2✔
168

169
    {
2✔
170
        Group g;
2✔
171
        auto t = g.add_table("foo");
2✔
172
        t->add_column_list(type_Mixed, "mixeds");
2✔
173
        auto obj = t->create_object();
2✔
174
        auto obj1 = t->create_object();
2✔
175
        auto list = obj.get_list<Mixed>("mixeds");
2✔
176

177
        list.insert(0, obj1);
2✔
178
        list.insert_null(1);
2✔
179
        obj1.invalidate();
2✔
180

181
        auto index_any = list.find_any(realm::null());
2✔
182
        auto index_first = list.find_first(realm::null());
2✔
183
        CHECK_EQUAL(index_any, 0);
2✔
184
        CHECK_EQUAL(index_first, 0);
2✔
185
    }
2✔
186

187
    {
2✔
188
        Group g;
2✔
189
        auto t = g.add_table("foo");
2✔
190
        t->add_column_list(type_Mixed, "mixeds");
2✔
191
        auto obj = t->create_object();
2✔
192
        auto obj1 = t->create_object();
2✔
193
        auto list = obj.get_list<Mixed>("mixeds");
2✔
194

195
        list.insert(0, obj1);
2✔
196
        obj1.invalidate();
2✔
197
        auto index_any = list.find_any(realm::null());
2✔
198
        auto index_first = list.find_first(realm::null());
2✔
199
        CHECK_EQUAL(index_any, 0);
2✔
200
        CHECK_EQUAL(index_first, 0);
2✔
201
    }
2✔
202
}
2✔
203

204
TEST(Mixed_Set_unresolved_links)
205
{
2✔
206
    Group g;
2✔
207

208
    auto t = g.add_table("foo");
2✔
209
    t->add_column_set(type_Mixed, "mixeds");
2✔
210
    auto obj = t->create_object();
2✔
211
    auto obj1 = t->create_object();
2✔
212
    auto obj2 = t->create_object();
2✔
213
    auto set = obj.get_set<Mixed>("mixeds");
2✔
214
    auto [it, success] = set.insert(Mixed{obj1});
2✔
215
    obj1.invalidate();
2✔
216

217
    CHECK_EQUAL(success, true);
2✔
218
    auto [it1, success1] = set.insert(Mixed{"test"});
2✔
219
    CHECK_EQUAL(success1, true);
2✔
220

221
    {
2✔
222
        // null can be inserted in the set
223
        CHECK_EQUAL(set.size(), 2);
2✔
224
        auto [it, success] = set.insert(Mixed{});
2✔
225
        CHECK_EQUAL(success, true);
2✔
226
        auto [it1, success1] = set.insert_null();
2✔
227
        CHECK_EQUAL(success1, false);
2✔
228
        CHECK_EQUAL(set.size(), 3);
2✔
229
    }
2✔
230

231
    {
2✔
232
        int cnt = 0;
2✔
233
        set.find_all(realm::null(), [this, &set, &cnt](size_t pos) {
2✔
234
            CHECK(pos != not_found);
2✔
235
            CHECK_EQUAL(set.is_null(pos), true);
2✔
236
            cnt += 1;
2✔
237
        });
2✔
238
        CHECK_EQUAL(cnt, 1);
2✔
239
    }
2✔
240

241
    {
2✔
242
        auto index = set.find_any(realm::null());
2✔
243
        CHECK(index != not_found);
2✔
244
        CHECK_EQUAL(set.is_null(index), true);
2✔
245
    }
2✔
246

247
    {
2✔
248
        auto [it, success] = set.insert(Mixed{obj2});
2✔
249
        obj2.invalidate();
2✔
250
        CHECK_EQUAL(success, true);
2✔
251
        CHECK_EQUAL(set.size(), 4);
2✔
252
        std::vector<size_t> indices{1, 0, 2, 3};
2✔
253
        set.sort(indices);
2✔
254
        CHECK_EQUAL(indices.size(), 4);
2✔
255
        CHECK_EQUAL(indices[0], 0);
2✔
256
        CHECK_EQUAL(indices[1], 1);
2✔
257
        CHECK_EQUAL(indices[2], 2);
2✔
258
        CHECK_EQUAL(indices[3], 3);
2✔
259
    }
2✔
260

261
    {
2✔
262
        // erase null but there are only unresolved links in the set
263
        Group g;
2✔
264
        auto t = g.add_table("foo");
2✔
265
        t->add_column_set(type_Mixed, "mixeds");
2✔
266
        auto obj = t->create_object();
2✔
267
        auto obj1 = t->create_object();
2✔
268
        auto obj2 = t->create_object();
2✔
269
        auto set = obj.get_set<Mixed>("mixeds");
2✔
270
        set.insert(obj1);
2✔
271
        set.insert(obj2);
2✔
272
        CHECK_EQUAL(set.size(), 2);
2✔
273
        obj1.invalidate();
2✔
274
        obj2.invalidate();
2✔
275
        // this should be treated as null, but for set of mixed we decided to leave unresolved exposed
276
        CHECK_EQUAL(set.is_null(0), false);
2✔
277
        CHECK_EQUAL(set.is_null(1), false);
2✔
278
        set.insert(Mixed{1});
2✔
279
        CHECK_EQUAL(set.size(), 3);
2✔
280
        set.erase_null();
2✔
281
        CHECK_EQUAL(set.size(), 3);
2✔
282
        set.erase(Mixed{});
2✔
283
        CHECK_EQUAL(set.size(), 3);
2✔
284
    }
2✔
285

286
    {
2✔
287
        // erase null when there are unresolved and nulls
288
        Group g;
2✔
289
        auto t = g.add_table("foo");
2✔
290
        t->add_column_set(type_Mixed, "mixeds");
2✔
291
        auto obj = t->create_object();
2✔
292
        auto obj1 = t->create_object();
2✔
293
        auto obj2 = t->create_object();
2✔
294
        auto set = obj.get_set<Mixed>("mixeds");
2✔
295
        set.insert(obj1);
2✔
296
        set.insert(obj2);
2✔
297
        set.insert(Mixed{});
2✔
298
        CHECK_EQUAL(set.size(), 3);
2✔
299
        obj1.invalidate();
2✔
300
        obj2.invalidate();
2✔
301
        size_t cnt = 0;
2✔
302
        set.find_all(Mixed{}, [this, &set, &cnt](size_t index) {
2✔
303
            CHECK_EQUAL(index, 0);
2✔
304
            CHECK_EQUAL(set.is_null(index), true);
2✔
305
            cnt += 1;
2✔
306
        });
2✔
307
        CHECK_EQUAL(cnt, 1);
2✔
308
        set.erase(Mixed{});
2✔
309
        CHECK_EQUAL(set.size(), 2);
2✔
310
    }
2✔
311

312
    {
2✔
313
        // assure that random access iterator does not return an unresolved link
314
        Group g;
2✔
315
        auto t = g.add_table("foo");
2✔
316
        t->add_column_set(type_Mixed, "mixeds");
2✔
317
        auto obj = t->create_object();
2✔
318
        auto obj1 = t->create_object();
2✔
319
        auto obj2 = t->create_object();
2✔
320
        auto set = obj.get_set<Mixed>("mixeds");
2✔
321
        set.insert(obj1);
2✔
322
        set.insert(obj2);
2✔
323
        obj1.invalidate();
2✔
324
        obj2.invalidate();
2✔
325
        set.insert(Mixed{});
2✔
326
        size_t unresolved = 0;
2✔
327
        size_t null = 0;
2✔
328
        for (auto& mixed : set) {
6✔
329
            if (mixed.is_null())
6✔
330
                null += 1;
2✔
331
            if (mixed.is_unresolved_link())
6✔
332
                unresolved += 1;
4✔
333
        }
6✔
334
        CHECK_EQUAL(null, 1);
2✔
335
        CHECK_EQUAL(unresolved, 2);
2✔
336
    }
2✔
337
}
2✔
338

339
TEST(Mixed_nullify_removes_backlinks_crash)
340
{
2✔
341
    Group g;
2✔
342
    auto source_table = g.add_table_with_primary_key("source", type_Int, "_id");
2✔
343
    auto dest_table = g.add_table_with_primary_key("dest", type_Int, "_id");
2✔
344
    ColKey mixed_col = source_table->add_column(type_Mixed, "mixed");
2✔
345
    auto source_obj = source_table->create_object_with_primary_key({0});
2✔
346
    auto dest_obj = dest_table->create_object_with_primary_key({1});
2✔
347
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
348
    source_obj.set(mixed_col, Mixed{ObjLink{dest_table->get_key(), dest_obj.get_key()}});
2✔
349
    CHECK(dest_obj.get_backlink_count() == 1);
2✔
350
    source_obj.set_null(mixed_col); // needs to remove backlinks!
2✔
351
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
352
    dest_obj.remove(); // triggers an assertion failure if the backlink was not removed
2✔
353
    source_obj.remove();
2✔
354
}
2✔
355

356
TEST(Mixed_nullify_removes_backlinks_exception)
357
{
2✔
358
    Group g;
2✔
359
    auto source_table = g.add_table_with_primary_key("source", type_Int, "_id");
2✔
360
    auto dest_table = g.add_table_with_primary_key("dest", type_Int, "_id");
2✔
361
    ColKey mixed_col = source_table->add_column(type_Mixed, "mixed");
2✔
362
    auto source_obj = source_table->create_object_with_primary_key({0});
2✔
363
    auto dest_obj = dest_table->create_object_with_primary_key({1});
2✔
364
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
365
    source_obj.set(mixed_col, Mixed{ObjLink{dest_table->get_key(), dest_obj.get_key()}});
2✔
366
    CHECK(dest_obj.get_backlink_count() == 1);
2✔
367
    source_obj.set_null(mixed_col); // needs to remove backlinks!
2✔
368
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
369
    source_obj.remove();
2✔
370
    dest_obj.remove(); // if the backlink was not removed, this creates an exception "key not found"
2✔
371
}
2✔
372

373
TEST(Mixed_nullify_and_invalidate_crash)
374
{
2✔
375
    Group g;
2✔
376
    auto source_table = g.add_table_with_primary_key("source", type_Int, "_id");
2✔
377
    auto dest_table = g.add_table_with_primary_key("dest", type_Int, "_id");
2✔
378
    ColKey mixed_col = source_table->add_column(type_Mixed, "mixed");
2✔
379
    auto source_obj = source_table->create_object_with_primary_key({0});
2✔
380
    auto dest_obj = dest_table->create_object_with_primary_key({1});
2✔
381
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
382
    source_obj.set(mixed_col, Mixed{ObjLink{dest_table->get_key(), dest_obj.get_key()}});
2✔
383
    CHECK(dest_obj.get_backlink_count() == 1);
2✔
384
    source_obj.set_null(mixed_col); // needs to remove backlinks!
2✔
385
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
386
    dest_obj.invalidate(); // triggers an assertion failure if the backlink was not removed
2✔
387
    auto resurrected = dest_table->create_object_with_primary_key({1});
2✔
388
    CHECK(source_obj.is_null(mixed_col));
2✔
389
    source_obj.remove();
2✔
390
    resurrected.remove();
2✔
391
}
2✔
392

393
TEST(Mixed_nullify_and_invalidate_exception)
394
{
2✔
395
    Group g;
2✔
396
    auto source_table = g.add_table_with_primary_key("source", type_Int, "_id");
2✔
397
    auto dest_table = g.add_table_with_primary_key("dest", type_Int, "_id");
2✔
398
    ColKey mixed_col = source_table->add_column(type_Mixed, "mixed");
2✔
399
    auto source_obj = source_table->create_object_with_primary_key({0});
2✔
400
    auto dest_obj = dest_table->create_object_with_primary_key({1});
2✔
401
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
402
    source_obj.set(mixed_col, Mixed{ObjLink{dest_table->get_key(), dest_obj.get_key()}});
2✔
403
    CHECK(dest_obj.get_backlink_count() == 1);
2✔
404
    source_obj.set_null(mixed_col); // needs to remove backlinks!
2✔
405
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
406
    dest_obj.invalidate(); // triggers an exception "key not found" if the backlink was not removed
2✔
407
    auto resurrected = dest_table->create_object_with_primary_key({1});
2✔
408
    CHECK(source_obj.is_null(mixed_col));
2✔
409
    CHECK(resurrected.get_backlink_count() == 0);
2✔
410
    resurrected.remove();
2✔
411
}
2✔
412

413
TEST(Mixed_set_non_link_exception)
414
{
2✔
415
    Group g;
2✔
416
    auto source_table = g.add_table_with_primary_key("source", type_Int, "_id");
2✔
417
    auto dest_table = g.add_table_with_primary_key("dest", type_Int, "_id");
2✔
418
    ColKey mixed_col = source_table->add_column(type_Mixed, "mixed");
2✔
419
    auto source_obj = source_table->create_object_with_primary_key({0});
2✔
420
    auto dest_obj = dest_table->create_object_with_primary_key({1});
2✔
421
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
422
    source_obj.set(mixed_col, Mixed{ObjLink{dest_table->get_key(), dest_obj.get_key()}});
2✔
423
    CHECK(dest_obj.get_backlink_count() == 1);
2✔
424
    source_obj.set(mixed_col, Mixed{0}); // needs to remove backlinks!
2✔
425
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
426
    source_obj.remove();
2✔
427
    dest_obj.remove(); // triggers an exception "key not found" if the backlink was not removed
2✔
428
}
2✔
429

430
TEST(Mixed_set_non_link_assertion)
431
{
2✔
432
    Group g;
2✔
433
    auto source_table = g.add_table_with_primary_key("source", type_Int, "_id");
2✔
434
    auto dest_table = g.add_table_with_primary_key("dest", type_Int, "_id");
2✔
435
    ColKey mixed_col = source_table->add_column(type_Mixed, "mixed");
2✔
436
    auto source_obj = source_table->create_object_with_primary_key({0});
2✔
437
    auto dest_obj = dest_table->create_object_with_primary_key({1});
2✔
438
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
439
    source_obj.set(mixed_col, Mixed{ObjLink{dest_table->get_key(), dest_obj.get_key()}});
2✔
440
    CHECK(dest_obj.get_backlink_count() == 1);
2✔
441
    source_obj.set(mixed_col, Mixed{0}); // needs to remove backlinks!
2✔
442
    CHECK(dest_obj.get_backlink_count() == 0);
2✔
443
    dest_obj.remove(); // triggers an assertion failure if the backlink was not removed
2✔
444
    source_obj.remove();
2✔
445
}
2✔
446

447
TEST(Mixed_LinkSelfAssignment)
448
{
2✔
449
    Group g;
2✔
450
    auto source = g.add_table("source");
2✔
451
    auto dest = g.add_table("dest");
2✔
452
    ColKey mixed_col = source->add_column(type_Mixed, "mixed");
2✔
453
    auto source_obj = source->create_object();
2✔
454
    auto dest_obj = dest->create_object();
2✔
455

456
    CHECK_EQUAL(dest_obj.get_backlink_count(), 0);
2✔
457

458
    source_obj.set(mixed_col, Mixed{ObjLink{dest->get_key(), dest_obj.get_key()}});
2✔
459
    CHECK_EQUAL(dest_obj.get_backlink_count(), 1);
2✔
460

461
    // Re-assign the same link, which should not update backlinks
462
    source_obj.set(mixed_col, Mixed{ObjLink{dest->get_key(), dest_obj.get_key()}});
2✔
463
    CHECK_EQUAL(dest_obj.get_backlink_count(), 1);
2✔
464

465
    dest_obj.remove();
2✔
466
    CHECK_EQUAL(source_obj.get<Mixed>(mixed_col), Mixed());
2✔
467
    source_obj.remove();
2✔
468
}
2✔
469

470
TEST(Mixed_EmbeddedLstMixedRecursiveDelete)
471
{
2✔
472
    Group g;
2✔
473
    auto top1 = g.add_table_with_primary_key("source", type_String, "_id");
2✔
474
    auto embedded = g.add_table("embedded", Table::Type::Embedded);
2✔
475
    auto top2 = g.add_table_with_primary_key("top2", type_String, "_id");
2✔
476
    auto top3 = g.add_table_with_primary_key("top3", type_String, "_id");
2✔
477

478
    ColKey top1_lst_col = top1->add_column_list(*embedded, "groups");
2✔
479
    ColKey embedded_lst_col = embedded->add_column_list(type_Mixed, "items");
2✔
480
    auto source_obj1 = top1->create_object_with_primary_key("top1_obj1");
2✔
481

482
    auto top2_obj1 = top2->create_object_with_primary_key("top2_obj1");
2✔
483
    auto top2_obj2 = top2->create_object_with_primary_key("top2_obj2");
2✔
484
    auto top2_obj3 = top2->create_object_with_primary_key("top2_obj3");
2✔
485
    auto top2_obj4 = top2->create_object_with_primary_key("top2_obj4");
2✔
486

487
    auto top3_obj1 = top3->create_object_with_primary_key("top3_obj1");
2✔
488
    auto top3_obj2 = top3->create_object_with_primary_key("top3_obj2");
2✔
489
    auto top3_obj3 = top3->create_object_with_primary_key("top3_obj3");
2✔
490
    auto top3_obj4 = top3->create_object_with_primary_key("top3_obj4");
2✔
491

492
    {
2✔
493
        LnkLst top1_lst = source_obj1.get_linklist(top1_lst_col);
2✔
494
        auto embedded1 = top1_lst.create_and_insert_linked_object(0);
2✔
495
        auto embedded2 = top1_lst.create_and_insert_linked_object(0);
2✔
496
        auto embedded3 = top1_lst.create_and_insert_linked_object(0);
2✔
497

498
        auto e1_lst = embedded1.get_list<Mixed>(embedded_lst_col);
2✔
499
        e1_lst.add(ObjLink(top2->get_key(), top2_obj1.get_key()));
2✔
500
        e1_lst.add(ObjLink(top2->get_key(), top2_obj2.get_key()));
2✔
501
        e1_lst.add(ObjLink(top2->get_key(), top2_obj3.get_key()));
2✔
502
        e1_lst.add(ObjLink(top2->get_key(), top2_obj4.get_key()));
2✔
503

504
        auto e2_lst = embedded2.get_list<Mixed>(embedded_lst_col);
2✔
505
        e2_lst.add(ObjLink(top3->get_key(), top3_obj1.get_key()));
2✔
506
        e2_lst.add(ObjLink(top3->get_key(), top3_obj2.get_key()));
2✔
507
        e2_lst.add(ObjLink(top3->get_key(), top3_obj3.get_key()));
2✔
508
        e2_lst.add(ObjLink(top3->get_key(), top3_obj4.get_key()));
2✔
509

510
        auto e3_lst = embedded3.get_list<Mixed>(embedded_lst_col);
2✔
511
        e3_lst.add(ObjLink(top2->get_key(), top2_obj1.get_key()));
2✔
512
        e3_lst.add(ObjLink(top2->get_key(), top2_obj2.get_key()));
2✔
513
        e3_lst.add(ObjLink(top2->get_key(), top2_obj3.get_key()));
2✔
514
        e3_lst.add(ObjLink(top2->get_key(), top2_obj4.get_key()));
2✔
515
    }
2✔
516
    std::vector<ObjKey> keys_to_delete = {source_obj1.get_key()};
2✔
517

518
    CHECK_EQUAL(top2_obj1.get_backlink_count(), 2);
2✔
519
    CHECK_EQUAL(top2_obj2.get_backlink_count(), 2);
2✔
520
    CHECK_EQUAL(top2_obj3.get_backlink_count(), 2);
2✔
521
    CHECK_EQUAL(top2_obj4.get_backlink_count(), 2);
2✔
522

523
    CHECK_EQUAL(top3_obj1.get_backlink_count(), 1);
2✔
524
    CHECK_EQUAL(top3_obj2.get_backlink_count(), 1);
2✔
525
    CHECK_EQUAL(top3_obj3.get_backlink_count(), 1);
2✔
526
    CHECK_EQUAL(top3_obj4.get_backlink_count(), 1);
2✔
527

528
    top2_obj3.invalidate();
2✔
529

530
    _impl::TableFriend::batch_erase_objects(*top1, keys_to_delete);
2✔
531

532
    CHECK(top2_obj1.is_valid());
2✔
533
    CHECK(top2_obj2.is_valid());
2✔
534
    CHECK_NOT(top2_obj3.is_valid());
2✔
535
    CHECK(top2_obj4.is_valid());
2✔
536

537
    CHECK_EQUAL(top2_obj1.get_backlink_count(), 0);
2✔
538
    CHECK_EQUAL(top2_obj2.get_backlink_count(), 0);
2✔
539
    CHECK_EQUAL(top2_obj4.get_backlink_count(), 0);
2✔
540

541
    CHECK(top3_obj1.is_valid());
2✔
542
    CHECK(top3_obj2.is_valid());
2✔
543
    CHECK(top3_obj3.is_valid());
2✔
544
    CHECK(top3_obj4.is_valid());
2✔
545

546
    CHECK_EQUAL(top3_obj1.get_backlink_count(), 0);
2✔
547
    CHECK_EQUAL(top3_obj2.get_backlink_count(), 0);
2✔
548
    CHECK_EQUAL(top3_obj3.get_backlink_count(), 0);
2✔
549
    CHECK_EQUAL(top3_obj4.get_backlink_count(), 0);
2✔
550
}
2✔
551

552
TEST(Mixed_SingleLinkRecursiveDelete)
553
{
2✔
554
    Group g;
2✔
555
    auto top1 = g.add_table_with_primary_key("source", type_String, "_id");
2✔
556
    auto top2 = g.add_table_with_primary_key("top2", type_String, "_id");
2✔
557

558
    ColKey top1_mixed_col = top1->add_column(type_Mixed, "mixed");
2✔
559
    auto top1_obj1 = top1->create_object_with_primary_key("top1_obj1");
2✔
560
    auto top2_obj1 = top2->create_object_with_primary_key("top2_obj1");
2✔
561

562
    top1_obj1.set<Mixed>(top1_mixed_col, ObjLink{top2->get_key(), top2_obj1.get_key()});
2✔
563

564
    CHECK_EQUAL(top2_obj1.get_backlink_count(), 1);
2✔
565

566
    top1->remove_object_recursive(top1_obj1.get_key());
2✔
567

568
    CHECK_NOT(top1_obj1.is_valid());
2✔
569
    CHECK_EQUAL(top1->size(), 0);
2✔
570
    CHECK_NOT(top2_obj1.is_valid());
2✔
571
    CHECK_EQUAL(top2->size(), 0);
2✔
572
}
2✔
573

574
static void find_nested_links(Lst<Mixed>&, std::vector<ObjLink>&);
575

576
static void find_nested_links(Dictionary& dict, std::vector<ObjLink>& links)
577
{
51,998✔
578
    for (size_t i = 0; i < dict.size(); ++i) {
171,983✔
579
        auto [key, val] = dict.get_pair(i);
119,985✔
580
        if (val.is_type(type_TypedLink)) {
119,985✔
581
            links.push_back(val.get_link());
47,999✔
582
        }
47,999✔
583
        else if (val.is_type(type_List)) {
71,986✔
NEW
584
            std::shared_ptr<Lst<Mixed>> list_i = dict.get_list(i);
×
NEW
585
            find_nested_links(*list_i, links);
×
NEW
586
        }
×
587
        else if (val.is_type(type_Dictionary)) {
71,986✔
588
            DictionaryPtr dict_i = dict.get_dictionary(key.get_string());
16,000✔
589
            find_nested_links(*dict_i, links);
16,000✔
590
        }
16,000✔
591
    }
119,985✔
592
}
51,998✔
593

594
static void find_nested_links(Lst<Mixed>& list, std::vector<ObjLink>& links)
595
{
51,999✔
596
    for (size_t i = 0; i < list.size(); ++i) {
171,999✔
597
        Mixed val = list.get(i);
120,000✔
598
        if (val.is_type(type_TypedLink)) {
120,000✔
599
            links.push_back(val.get_link());
48,000✔
600
        }
48,000✔
601
        else if (val.is_type(type_List)) {
72,000✔
602
            std::shared_ptr<Lst<Mixed>> list_i = list.get_list(i);
16,000✔
603
            find_nested_links(*list_i, links);
16,000✔
604
        }
16,000✔
605
        else if (val.is_type(type_Dictionary)) {
56,000✔
NEW
606
            DictionaryPtr dict_i = list.get_dictionary(i);
×
NEW
607
            find_nested_links(*dict_i, links);
×
NEW
608
        }
×
609
    }
120,000✔
610
}
51,999✔
611

612
struct ListOfMixedLinks {
613
    void init_table(TableRef table)
614
    {
4✔
615
        m_col_key = table->add_column_list(type_Mixed, "list_of_mixed");
4✔
616
    }
4✔
617
    void set_links(Obj from, std::vector<ObjLink> to)
618
    {
12,000✔
619
        Lst<Mixed> lst = from.get_list<Mixed>(m_col_key);
12,000✔
620
        for (ObjLink& link : to) {
16,000✔
621
            lst.add(link);
16,000✔
622
        }
16,000✔
623
    }
12,000✔
624
    void get_links(Obj from, std::vector<ObjLink>& links)
625
    {
12,000✔
626
        Lst<Mixed> lst = from.get_list<Mixed>(m_col_key);
12,000✔
627
        find_nested_links(lst, links);
12,000✔
628
    }
12,000✔
629
    ColKey m_col_key;
630
};
631

632
struct DictionaryOfMixedLinks {
633
    void init_table(TableRef table)
634
    {
4✔
635
        m_col_key = table->add_column_dictionary(type_Mixed, "dict_of_mixed");
4✔
636
    }
4✔
637
    void set_links(Obj from, std::vector<ObjLink> to)
638
    {
12,000✔
639
        size_t count = 0;
12,000✔
640
        Dictionary dict = from.get_dictionary(m_col_key);
12,000✔
641
        for (ObjLink& link : to) {
16,000✔
642
            dict.insert(util::format("key_%1", count++), link);
16,000✔
643
        }
16,000✔
644
    }
12,000✔
645
    void get_links(Obj from, std::vector<ObjLink>& links)
646
    {
12,000✔
647
        Dictionary dict = from.get_dictionary(m_col_key);
12,000✔
648
        find_nested_links(dict, links);
12,000✔
649
    }
12,000✔
650
    ColKey m_col_key;
651
};
652

653
struct NestedDictionary {
654
    void init_table(TableRef table)
655
    {
4✔
656
        m_col_key = table->add_column(type_Mixed, "nested_dictionary");
4✔
657
    }
4✔
658
    void set_links(Obj from, std::vector<ObjLink> to)
659
    {
12,000✔
660
        from.set_collection(m_col_key, CollectionType::Dictionary);
12,000✔
661
        size_t count = 0;
12,000✔
662
        Dictionary dict = from.get_dictionary(m_col_key);
12,000✔
663
        for (ObjLink& link : to) {
16,000✔
664
            dict.insert(util::format("key_%1", count++), link);
16,000✔
665
        }
16,000✔
666
    }
12,000✔
667
    void get_links(Obj from, std::vector<ObjLink>& links)
668
    {
12,000✔
669
        Dictionary dict = from.get_dictionary(m_col_key);
12,000✔
670
        find_nested_links(dict, links);
12,000✔
671
    }
12,000✔
672
    ColKey m_col_key;
673
};
674

675
struct NestedList {
676
    void init_table(TableRef table)
677
    {
4✔
678
        m_col_key = table->add_column(type_Mixed, "nested_list");
4✔
679
    }
4✔
680
    void set_links(Obj from, std::vector<ObjLink> to)
681
    {
12,000✔
682
        from.set_collection(m_col_key, CollectionType::List);
12,000✔
683
        Lst<Mixed> list = from.get_list<Mixed>(m_col_key);
12,000✔
684
        for (ObjLink& link : to) {
16,000✔
685
            list.add(link);
16,000✔
686
        }
16,000✔
687
    }
12,000✔
688
    void get_links(Obj from, std::vector<ObjLink>& links)
689
    {
12,000✔
690
        Lst<Mixed> list = from.get_list<Mixed>(m_col_key);
12,000✔
691
        find_nested_links(list, links);
12,000✔
692
    }
12,000✔
693
    ColKey m_col_key;
694
};
695

696
struct NestedListOfLists {
697
    void init_table(TableRef table)
698
    {
4✔
699
        m_col_key = table->add_column(type_Mixed, "nested_lols");
4✔
700
    }
4✔
701
    void set_links(Obj from, std::vector<ObjLink> to)
702
    {
12,000✔
703
        from.set_collection(m_col_key, CollectionType::List);
12,000✔
704
        Lst<Mixed> list = from.get_list<Mixed>(m_col_key);
12,000✔
705
        list.add("lol_0");
12,000✔
706
        list.add("lol_1");
12,000✔
707

708
        for (ObjLink& link : to) {
16,000✔
709
            size_t list_ndx = list.size();
16,000✔
710
            list.insert_collection(list_ndx, CollectionType::List);
16,000✔
711

712
            std::shared_ptr<Lst<Mixed>> list_n = list.get_list(list_ndx);
16,000✔
713
            list_n->add(util::format("lol_%1_0", list_ndx));
16,000✔
714
            list_n->add(util::format("lol_%1_1", list_ndx));
16,000✔
715
            list_n->add(link);
16,000✔
716
        }
16,000✔
717
    }
12,000✔
718
    void get_links(Obj from, std::vector<ObjLink>& links)
719
    {
12,000✔
720
        Lst<Mixed> list = from.get_list<Mixed>(m_col_key);
12,000✔
721
        find_nested_links(list, links);
12,000✔
722
    }
12,000✔
723
    ColKey m_col_key;
724
};
725

726
struct NestedDictOfDicts {
727
    void init_table(TableRef table)
728
    {
4✔
729
        m_col_key = table->add_column(type_Mixed, "nested_dods");
4✔
730
    }
4✔
731
    void set_links(Obj from, std::vector<ObjLink> to)
732
    {
12,000✔
733
        from.set_collection(m_col_key, CollectionType::Dictionary);
12,000✔
734
        Dictionary dict = from.get_dictionary(m_col_key);
12,000✔
735
        dict.insert("dict_0", 0);
12,000✔
736
        dict.insert("dict_1", 1);
12,000✔
737

738
        for (ObjLink& link : to) {
16,000✔
739
            std::string key = util::format("dict_%1", dict.size());
16,000✔
740
            dict.insert_collection(key, CollectionType::Dictionary);
16,000✔
741

742
            DictionaryPtr dict_n = dict.get_dictionary(key);
16,000✔
743
            dict_n->insert("key0", 0);
16,000✔
744
            dict_n->insert("key1", "value 1");
16,000✔
745
            dict_n->insert("link", link);
16,000✔
746
        }
16,000✔
747
    }
12,000✔
748
    void get_links(Obj from, std::vector<ObjLink>& links)
749
    {
12,000✔
750
        Dictionary dict = from.get_dictionary(m_col_key);
12,000✔
751
        find_nested_links(dict, links);
12,000✔
752
    }
12,000✔
753
    ColKey m_col_key;
754
};
755

756
struct SingleLink {
757
    void init_table(TableRef) {}
2✔
758
    void set_links(Obj from, std::vector<ObjLink> to)
759
    {
8,000✔
760
        REALM_ASSERT(to.size() == 1);
8,000✔
761
        TableRef src_table = from.get_table();
8,000✔
762
        if (!m_col_key) {
8,000✔
763
            TableRef dest_table = src_table->get_parent_group()->get_table(to[0].get_table_key());
2✔
764
            m_col_key = src_table->add_column(*dest_table, "link");
2✔
765
        }
2✔
766
        TableKey dest_table_key = src_table->get_opposite_table_key(m_col_key);
8,000✔
767
        REALM_ASSERT(to[0].get_table_key() == dest_table_key);
8,000✔
768
        from.set<ObjKey>(m_col_key, to[0].get_obj_key());
8,000✔
769
    }
8,000✔
770
    void get_links(Obj from, std::vector<ObjLink>& links)
771
    {
8,000✔
772
        if (!m_col_key) {
8,000✔
NEW
773
            return;
×
NEW
774
        }
×
775
        TableRef src_table = from.get_table();
8,000✔
776
        TableKey dest_table_key = src_table->get_opposite_table_key(m_col_key);
8,000✔
777
        ObjKey link = from.get<ObjKey>(m_col_key);
8,000✔
778
        links.push_back(ObjLink{dest_table_key, link});
8,000✔
779
    }
8,000✔
780
    ColKey m_col_key;
781
};
782

783
TEST_TYPES(Mixed_ContainerOfLinksFromLargeCluster, ListOfMixedLinks, DictionaryOfMixedLinks, NestedDictionary,
784
           NestedList, NestedListOfLists, NestedDictOfDicts)
785
{
12✔
786
    Group g;
12✔
787
    auto top1 = g.add_table_with_primary_key("top1", type_String, "_id");
12✔
788
    auto top2 = g.add_table_with_primary_key("top2", type_String, "_id");
12✔
789
    TEST_TYPE type;
12✔
790
    type.init_table(top1);
12✔
791

792
    constexpr size_t num_objects = 2000; // more than BPNODE_SIZE
12✔
793

794
    for (size_t i = 0; i < num_objects; ++i) {
24,012✔
795
        auto top1_obj = top1->create_object_with_primary_key(util::format("top1_%1", i));
24,000✔
796
        auto top2_obj1 = top2->create_object_with_primary_key(util::format("top2_1_%1", i));
24,000✔
797
        auto top2_obj2 = top2->create_object_with_primary_key(util::format("top2_2_%1", i));
24,000✔
798

799
        std::vector<ObjLink> dest_links = {ObjLink(top2->get_key(), top2_obj1.get_key()),
24,000✔
800
                                           ObjLink(top2->get_key(), top2_obj2.get_key())};
24,000✔
801
        type.set_links(top1_obj, dest_links);
24,000✔
802
    }
24,000✔
803

804
    auto remove_one_object = [&](size_t ndx) {
24,000✔
805
        Obj obj_to_remove = top1->get_object(ndx);
24,000✔
806
        std::vector<ObjLink> links;
24,000✔
807
        type.get_links(obj_to_remove, links);
24,000✔
808
        CHECK_EQUAL(links.size(), 2);
24,000✔
809
        ObjLink link_1 = links[0];
24,000✔
810
        ObjLink link_2 = links[1];
24,000✔
811
        CHECK_EQUAL(link_1.get_table_key(), top2->get_key());
24,000✔
812
        CHECK_EQUAL(link_2.get_table_key(), top2->get_key());
24,000✔
813

814
        Obj obj_linked1 = top2->get_object(link_1.get_obj_key());
24,000✔
815
        Obj obj_linked2 = top2->get_object(link_2.get_obj_key());
24,000✔
816
        CHECK_EQUAL(obj_linked1.get_backlink_count(), 1);
24,000✔
817
        CHECK_EQUAL(obj_linked2.get_backlink_count(), 1);
24,000✔
818

819
        obj_to_remove.remove();
24,000✔
820
        CHECK_NOT(obj_to_remove.is_valid());
24,000✔
821
        CHECK_EQUAL(obj_linked1.get_backlink_count(), 0);
24,000✔
822
        CHECK_EQUAL(obj_linked2.get_backlink_count(), 0);
24,000✔
823
    };
24,000✔
824

825
    // erase at random, to exercise the collapse/join of cluster leafs
826
    Random random(test_util::random_int<unsigned long>()); // Seed from slow global generator
12✔
827
    while (!top1->is_empty()) {
24,012✔
828
        size_t rnd = random.draw_int_mod(top1->size());
24,000✔
829
        remove_one_object(rnd);
24,000✔
830
    }
24,000✔
831
}
12✔
832

833
TEST_TYPES(Mixed_RemoveRecursiveSingleLink, SingleLink, ListOfMixedLinks, DictionaryOfMixedLinks, NestedDictionary,
834
           NestedList, NestedListOfLists, NestedDictOfDicts)
835
{
14✔
836
    Group g;
14✔
837
    auto top1 = g.add_table_with_primary_key("top1", type_String, "_id");
14✔
838
    auto top2 = g.add_table_with_primary_key("top2", type_String, "_id");
14✔
839
    TEST_TYPE type;
14✔
840
    type.init_table(top1);
14✔
841

842
    constexpr size_t num_src_objects = 4000; // more than BPNODE_SIZE
14✔
843
    constexpr size_t num_dst_objects = 1000;
14✔
844
    for (size_t i = 0; i < num_src_objects; ++i) {
56,014✔
845
        auto top1_obj = top1->create_object_with_primary_key(util::format("top1_%1", i));
56,000✔
846

847
        // get or create
848
        auto top2_obj = top2->create_object_with_primary_key(util::format("top2_%1", i % num_dst_objects));
56,000✔
849

850
        std::vector<ObjLink> dest_links = {ObjLink(top2->get_key(), top2_obj.get_key())};
56,000✔
851
        type.set_links(top1_obj, dest_links);
56,000✔
852
    }
56,000✔
853

854
    auto remove_one_object = [&](size_t ndx) {
56,000✔
855
        Obj obj_to_remove = top1->get_object(ndx);
56,000✔
856
        std::vector<ObjLink> links;
56,000✔
857
        type.get_links(obj_to_remove, links);
56,000✔
858
        CHECK_EQUAL(links.size(), 1);
56,000✔
859
        ObjLink link_1 = links[0];
56,000✔
860
        CHECK_EQUAL(link_1.get_table_key(), top2->get_key());
56,000✔
861

862
        Obj obj_linked = top2->get_object(link_1.get_obj_key());
56,000✔
863
        size_t previous_backlink_count = obj_linked.get_backlink_count();
56,000✔
864
        CHECK_NOT_EQUAL(previous_backlink_count, 0);
56,000✔
865

866
        top1->remove_object_recursive(obj_to_remove.get_key());
56,000✔
867
        CHECK_NOT(obj_to_remove.is_valid());
56,000✔
868
        if (previous_backlink_count == 1) {
56,000✔
869
            CHECK_NOT(obj_linked.is_valid());
14,000✔
870
        }
14,000✔
871
        else {
42,000✔
872
            CHECK(obj_linked.is_valid());
42,000✔
873
            size_t new_backlink_count = obj_linked.get_backlink_count();
42,000✔
874
            CHECK_EQUAL(new_backlink_count, previous_backlink_count - 1);
42,000✔
875
        }
42,000✔
876
    };
56,000✔
877

878
    // erase at random, to exercise the collapse/join of cluster leafs
879
    Random random(test_util::random_int<unsigned long>()); // Seed from slow global generator
14✔
880
    while (!top1->is_empty()) {
56,014✔
881
        size_t rnd = random.draw_int_mod(top1->size());
56,000✔
882
        remove_one_object(rnd);
56,000✔
883
    }
56,000✔
884
    CHECK_EQUAL(top1->size(), 0);
14✔
885
    CHECK_EQUAL(top2->size(), 0);
14✔
886
}
14✔
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