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

realm / realm-core / nicola.cabiddu_1042

27 Sep 2023 06:04PM UTC coverage: 91.085% (-1.8%) from 92.915%
nicola.cabiddu_1042

Pull #6766

Evergreen

nicola-cab
Fix logic for dictionaries
Pull Request #6766: Client Reset for collections in mixed / nested collections

97276 of 178892 branches covered (0.0%)

1994 of 2029 new or added lines in 7 files covered. (98.28%)

4556 existing lines in 112 files now uncovered.

237059 of 260260 relevant lines covered (91.09%)

6321099.55 hits per line

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

95.26
/src/realm/object_converter.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2022 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 <realm/object_converter.hpp>
20

21
#include <realm/dictionary.hpp>
22
#include <realm/list.hpp>
23
#include <realm/set.hpp>
24

25
#include <realm/util/flat_map.hpp>
26
namespace realm::converters {
27

28
// Takes two lists, src and dst, and makes dst equal src. src is unchanged.
29
void InterRealmValueConverter::copy_list(const LstBase& src, LstBase& dst, bool* update_out) const
30
{
16,236✔
31
    // The two arrays are compared by finding the longest common prefix and
8,118✔
32
    // suffix.  The middle section differs between them and is made equal by
8,118✔
33
    // updating the middle section of dst.
8,118✔
34
    //
8,118✔
35
    // Example:
8,118✔
36
    // src = abcdefghi
8,118✔
37
    // dst = abcxyhi
8,118✔
38
    // The common prefix is abc. The common suffix is hi. xy is replaced by defg.
8,118✔
39

8,118✔
40
    bool updated = false;
16,236✔
41
    size_t len_src = src.size();
16,236✔
42
    size_t len_dst = dst.size();
16,236✔
43
    size_t len_min = std::min(len_src, len_dst);
16,236✔
44

8,118✔
45
    size_t ndx = 0;
16,236✔
46
    size_t suffix_len = 0;
16,236✔
47

8,118✔
48
    while (ndx < len_min && cmp_src_to_dst(src.get_any(ndx), dst.get_any(ndx), nullptr, update_out) == 0) {
29,070✔
49
        ndx++;
12,834✔
50
    }
12,834✔
51

8,118✔
52
    size_t suffix_len_max = len_min - ndx;
16,236✔
53

8,118✔
54
    while (suffix_len < suffix_len_max &&
16,818✔
55
           cmp_src_to_dst(src.get_any(len_src - 1 - suffix_len), dst.get_any(len_dst - 1 - suffix_len), nullptr,
9,339✔
56
                          update_out) == 0) {
1,221✔
57
        suffix_len++;
582✔
58
    }
582✔
59

8,118✔
60
    len_min -= (ndx + suffix_len);
16,236✔
61

8,118✔
62
    for (size_t i = 0; i < len_min; i++) {
18,840✔
63
        InterRealmValueConverter::ConversionResult converted_src;
2,604✔
64
        if (cmp_src_to_dst(src.get_any(ndx), dst.get_any(ndx), &converted_src, update_out)) {
2,604✔
65
            if (converted_src.requires_new_embedded_object) {
2,316✔
NEW
66
                auto lnklist = dynamic_cast<LnkLst*>(&dst);
×
67
                REALM_ASSERT(lnklist); // this is the only type of list that supports embedded objects
×
68
                Obj embedded = lnklist->create_and_set_linked_object(ndx);
×
69
                track_new_embedded(converted_src.src_embedded_to_check, embedded);
×
70
            }
×
71
            else {
2,316✔
72
                dst.set_any(ndx, converted_src.converted_value);
2,316✔
73
            }
2,316✔
74
            updated = true;
2,316✔
75
        }
2,316✔
76
        ndx++;
2,604✔
77
    }
2,604✔
78

8,118✔
79
    // New elements must be inserted in dst.
8,118✔
80
    while (len_dst < len_src) {
19,716✔
81
        InterRealmValueConverter::ConversionResult converted_src;
3,480✔
82
        cmp_src_to_dst(src.get_any(ndx), Mixed{}, &converted_src, update_out);
3,480✔
83
        if (converted_src.requires_new_embedded_object) {
3,480✔
84
            auto lnklist = dynamic_cast<LnkLst*>(&dst);
432✔
85
            REALM_ASSERT(lnklist); // this is the only type of list that supports embedded objects
432✔
86
            Obj embedded = lnklist->create_and_insert_linked_object(ndx);
432✔
87
            track_new_embedded(converted_src.src_embedded_to_check, embedded);
432✔
88
        }
432✔
89
        else {
3,048✔
90
            dst.insert_any(ndx, converted_src.converted_value);
3,048✔
91
        }
3,048✔
92
        len_dst++;
3,480✔
93
        ndx++;
3,480✔
94
        updated = true;
3,480✔
95
    }
3,480✔
96
    // Excess elements must be removed from ll_dst.
8,118✔
97
    if (len_dst > len_src) {
16,236✔
98
        dst.remove(len_src - suffix_len, len_dst - suffix_len);
972✔
99
        updated = true;
972✔
100
    }
972✔
101

8,118✔
102
    REALM_ASSERT(dst.size() == len_src);
16,236✔
103
    if (updated && update_out) {
16,236✔
104
        *update_out = updated;
1,650✔
105
    }
1,650✔
106
}
16,236✔
107

108
void InterRealmValueConverter::copy_set(const SetBase& src, SetBase& dst, bool* update_out) const
109
{
10,932✔
110
    std::vector<size_t> sorted_src, sorted_dst, to_insert, to_delete;
10,932✔
111
    constexpr bool ascending = true;
10,932✔
112
    // the implementation could be storing elements in sorted order, but
5,466✔
113
    // we don't assume that here.
5,466✔
114
    src.sort(sorted_src, ascending);
10,932✔
115
    dst.sort(sorted_dst, ascending);
10,932✔
116

5,466✔
117
    size_t dst_ndx = 0;
10,932✔
118
    size_t src_ndx = 0;
10,932✔
119
    while (src_ndx < sorted_src.size()) {
19,722✔
120
        if (dst_ndx == sorted_dst.size()) {
10,200✔
121
            // if we have reached the end of the dst items, all remaining
705✔
122
            // src items should be added
705✔
123
            while (src_ndx < sorted_src.size()) {
3,900✔
124
                to_insert.push_back(sorted_src[src_ndx++]);
2,490✔
125
            }
2,490✔
126
            break;
1,410✔
127
        }
1,410✔
128
        size_t ndx_in_src = sorted_src[src_ndx];
8,790✔
129
        Mixed src_val = src.get_any(ndx_in_src);
8,790✔
130
        while (dst_ndx < sorted_dst.size()) {
9,540✔
131
            size_t ndx_in_dst = sorted_dst[dst_ndx];
9,114✔
132

4,557✔
133
            int cmp = cmp_src_to_dst(src_val, dst.get_any(ndx_in_dst), nullptr, update_out);
9,114✔
134
            if (cmp == 0) {
9,114✔
135
                // equal: advance both src and dst
3,852✔
136
                ++dst_ndx;
7,704✔
137
                ++src_ndx;
7,704✔
138
                break;
7,704✔
139
            }
7,704✔
140
            else if (cmp < 0) {
1,410✔
141
                // src < dst: insert src, advance src only
330✔
142
                to_insert.push_back(ndx_in_src);
660✔
143
                ++src_ndx;
660✔
144
                break;
660✔
145
            }
660✔
146
            else {
750✔
147
                // src > dst: delete dst, advance only dst
375✔
148
                to_delete.push_back(ndx_in_dst);
750✔
149
                ++dst_ndx;
750✔
150
                continue;
750✔
151
            }
750✔
152
        }
9,114✔
153
    }
8,790✔
154
    while (dst_ndx < sorted_dst.size()) {
12,162✔
155
        to_delete.push_back(sorted_dst[dst_ndx++]);
1,230✔
156
    }
1,230✔
157

5,466✔
158
    std::sort(to_delete.begin(), to_delete.end());
10,932✔
159
    for (auto it = to_delete.rbegin(); it != to_delete.rend(); ++it) {
12,912✔
160
        dst.erase_any(dst.get_any(*it));
1,980✔
161
    }
1,980✔
162
    for (auto ndx : to_insert) {
7,041✔
163
        InterRealmValueConverter::ConversionResult converted_src;
3,150✔
164
        cmp_src_to_dst(src.get_any(ndx), Mixed{}, &converted_src, update_out);
3,150✔
165
        // we do not support a set of embedded objects
1,575✔
166
        REALM_ASSERT(!converted_src.requires_new_embedded_object);
3,150✔
167
        dst.insert_any(converted_src.converted_value);
3,150✔
168
    }
3,150✔
169

5,466✔
170
    if (update_out && (to_delete.size() || to_insert.size())) {
10,932✔
171
        *update_out = true;
1,782✔
172
    }
1,782✔
173
}
10,932✔
174

175
void InterRealmValueConverter::copy_dictionary(const Dictionary& src, Dictionary& dst, bool* update_out) const
176
{
11,364✔
177
    std::vector<size_t> to_insert, to_delete;
11,364✔
178

5,682✔
179
    size_t dst_ndx = 0;
11,364✔
180
    size_t src_ndx = 0;
11,364✔
181
    while (src_ndx < src.size()) {
22,746✔
182
        if (dst_ndx == dst.size()) {
12,504✔
183
            // if we have reached the end of the dst items, all remaining
561✔
184
            // src items should be added
561✔
185
            while (src_ndx < src.size()) {
2,988✔
186
                to_insert.push_back(src_ndx++);
1,866✔
187
            }
1,866✔
188
            break;
1,122✔
189
        }
1,122✔
190

5,691✔
191
        auto src_val = src.get_pair(src_ndx);
11,382✔
192
        while (dst_ndx < dst.size()) {
11,574✔
193
            auto dst_val = dst.get_pair(dst_ndx);
11,562✔
194
            int cmp = src_val.first.compare(dst_val.first);
11,562✔
195
            if (cmp == 0) {
11,562✔
196
                // Check if the values differ
5,280✔
197
                if (cmp_src_to_dst(src_val.second, dst_val.second, nullptr, update_out)) {
10,560✔
198
                    // values are different - modify destination, advance both
687✔
199
                    to_insert.push_back(src_ndx);
1,374✔
200
                }
1,374✔
201
                // keys and values equal: advance both src and dst
5,280✔
202
                ++dst_ndx;
10,560✔
203
                ++src_ndx;
10,560✔
204
                break;
10,560✔
205
            }
10,560✔
206
            else if (cmp < 0) {
1,002✔
207
                // src < dst: insert src, advance src only
405✔
208
                to_insert.push_back(src_ndx++);
810✔
209
                break;
810✔
210
            }
810✔
211
            else {
192✔
212
                // src > dst: delete dst, advance only dst
96✔
213
                to_delete.push_back(dst_ndx++);
192✔
214
                continue;
192✔
215
            }
192✔
216
        }
11,562✔
217
    }
11,382✔
218
    // at this point, we've gone through all src items but still have dst items
5,682✔
219
    // oustanding; these should all be deleted because they are not in src
5,682✔
220
    while (dst_ndx < dst.size()) {
11,772✔
221
        to_delete.push_back(dst_ndx++);
408✔
222
    }
408✔
223

5,682✔
224
    for (auto it = to_delete.rbegin(); it != to_delete.rend(); ++it) {
11,964✔
225
        dst.erase(dst.begin() + *it);
600✔
226
    }
600✔
227
    for (auto ndx : to_insert) {
7,707✔
228
        auto pair = src.get_pair(ndx);
4,050✔
229
        InterRealmValueConverter::ConversionResult converted_val;
4,050✔
230
        cmp_src_to_dst(pair.second, Mixed{}, &converted_val, update_out);
4,050✔
231
        if (converted_val.requires_new_embedded_object) {
4,050✔
232
            Obj new_embedded = dst.create_and_insert_linked_object(pair.first);
120✔
233
            track_new_embedded(converted_val.src_embedded_to_check, new_embedded);
120✔
234
        }
120✔
235
        else {
3,930✔
236
            dst.insert(pair.first, converted_val.converted_value);
3,930✔
237
        }
3,930✔
238
    }
4,050✔
239
    if (update_out && (to_delete.size() || to_insert.size())) {
11,364✔
240
        *update_out = true;
1,500✔
241
    }
1,500✔
242
}
11,364✔
243

244
void InterRealmValueConverter::copy_value(const Obj& src_obj, Obj& dst_obj, bool* update_out)
245
{
153,000✔
246
    if (m_src_col.is_list()) {
153,000✔
247
        LstBasePtr src = src_obj.get_listbase_ptr(m_src_col);
16,236✔
248
        LstBasePtr dst = dst_obj.get_listbase_ptr(m_dst_col);
16,236✔
249
        copy_list(*src, *dst, update_out);
16,236✔
250
    }
16,236✔
251
    else if (m_src_col.is_dictionary()) {
136,764✔
252
        Dictionary src = src_obj.get_dictionary(m_src_col);
11,364✔
253
        Dictionary dst = dst_obj.get_dictionary(m_dst_col);
11,364✔
254
        copy_dictionary(src, dst, update_out);
11,364✔
255
    }
11,364✔
256
    else if (m_src_col.is_set()) {
125,400✔
257
        SetBasePtr src = src_obj.get_setbase_ptr(m_src_col);
10,902✔
258
        SetBasePtr dst = dst_obj.get_setbase_ptr(m_dst_col);
10,902✔
259
        copy_set(*src, *dst, update_out);
10,902✔
260
    }
10,902✔
261
    else {
114,498✔
262
        REALM_ASSERT(!m_src_col.is_collection());
114,498✔
263
        // nested collections
57,249✔
264
        auto src_mixed = src_obj.get_any(m_src_col);
114,498✔
265
        if (src_mixed.is_type(type_List)) {
114,498✔
266
            dst_obj.set_collection(m_dst_col, CollectionType::List);
192✔
267
            Lst<Mixed> src_list{src_obj, m_src_col};
192✔
268
            Lst<Mixed> dst_list{dst_obj, m_dst_col};
192✔
269
            handle_list_in_mixed(src_list, dst_list);
192✔
270
        }
192✔
271
        else if (src_mixed.is_type(type_Set)) {
114,306✔
272
            dst_obj.set_collection(m_dst_col, CollectionType::Set);
6✔
273
            realm::Set<Mixed> src_set{src_obj, m_src_col};
6✔
274
            realm::Set<Mixed> dst_set{dst_obj, m_dst_col};
6✔
275
            // sets cannot be nested, so we just need to copy the values
3✔
276
            copy_set(src_set, dst_set, nullptr);
6✔
277
        }
6✔
278
        else if (src_mixed.is_type(type_Dictionary)) {
114,300✔
279
            dst_obj.set_collection(m_dst_col, CollectionType::Dictionary);
60✔
280
            Dictionary src_dict{src_obj, m_src_col};
60✔
281
            Dictionary dst_dict{dst_obj, m_dst_col};
60✔
282
            handle_dictionary_in_mixed(src_dict, dst_dict);
60✔
283
        }
60✔
284
        else {
114,240✔
285
            InterRealmValueConverter::ConversionResult converted_src;
114,240✔
286
            auto dst_mixed = dst_obj.get_any(m_dst_col);
114,240✔
287
            if (cmp_src_to_dst(src_mixed, dst_mixed, &converted_src, update_out)) {
114,240✔
288
                if (converted_src.requires_new_embedded_object) {
70,428✔
289
                    Obj new_embedded = dst_obj.create_and_set_linked_object(m_dst_col);
498✔
290
                    track_new_embedded(converted_src.src_embedded_to_check, new_embedded);
498✔
291
                }
498✔
292
                else {
69,930✔
293
                    dst_obj.set_any(m_dst_col, converted_src.converted_value);
69,930✔
294
                }
69,930✔
295
            }
70,428✔
296
        }
114,240✔
297
    }
114,498✔
298
}
153,000✔
299

300
//
301
// Handle collections in mixed. A collection can have N nested levels (expect for Sets). And these levels can be
302
// nested in arbitrary way (eg a List within a Dictionary or viceversa). In order to try to merge server changes with
303
// client changes, the algorithm needs to go throw each single element in the collection, check its type and perform
304
// the most appropriate action in order to miminize the number of notificiations triggered.
305
//
306
void InterRealmValueConverter::handle_list_in_mixed(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list) const
307
{
354✔
308
    int sz = (int)std::min(src_list.size(), dst_list.size());
354✔
309
    int left = 0;
354✔
310
    int right = (int)sz - 1;
354✔
311

177✔
312
    // find fist not matching element from beginning
177✔
313
    while (left < sz) {
492✔
314
        auto src_any = src_list.get_any(left);
294✔
315
        auto dst_any = dst_list.get_any(left);
294✔
316
        if (src_any != dst_any)
294✔
317
            break;
78✔
318
        if (is_collection(src_any) && !check_matching_list(src_list, dst_list, left, to_collection_type(src_any)))
216✔
319
            break;
78✔
320
        left += 1;
138✔
321
    }
138✔
322

177✔
323
    // find first not matching element from end
177✔
324
    while (right >= 0) {
462✔
325
        auto src_any = src_list.get_any(right);
264✔
326
        auto dst_any = dst_list.get_any(right);
264✔
327
        if (src_any != dst_any)
264✔
328
            break;
96✔
329
        if (is_collection(src_any) && !check_matching_list(src_list, dst_list, right, to_collection_type(src_any)))
168✔
330
            break;
60✔
331
        right -= 1;
108✔
332
    }
108✔
333

177✔
334
    // Replace all different elements in [left, right]
177✔
335
    while (left <= right) {
612✔
336
        auto src_any = src_list.get_any(left);
258✔
337
        auto dst_any = dst_list.get_any(left);
258✔
338

129✔
339
        if (is_collection(src_any)) {
258✔
340
            auto coll_type = to_collection_type(src_any);
150✔
341

75✔
342
            if (!dst_any.is_type(src_any.get_type())) {
150✔
343
                // Mixed vs Collection
12✔
344
                dst_list.set_collection(left, coll_type);
24✔
345
                copy_list_in_mixed(src_list, dst_list, left, coll_type);
24✔
346
            }
24✔
347
            else if (!check_matching_list(src_list, dst_list, left, coll_type)) {
126✔
348
                // Collection vs Collection
57✔
349
                dst_list.set_any(left, src_any);
114✔
350
                copy_list_in_mixed(src_list, dst_list, left, coll_type);
114✔
351
            }
114✔
352
        }
150✔
353
        else if (dst_any != src_any) {
108✔
354
            // Mixed vs Mixed
48✔
355
            dst_list.set_any(left, src_any);
96✔
356
        }
96✔
357
        left += 1;
258✔
358
    }
258✔
359

177✔
360
    // remove dst elements not present in src
177✔
361
    if (dst_list.size() > src_list.size()) {
354✔
362
        auto dst_size = dst_list.size();
48✔
363
        auto src_size = src_list.size();
48✔
364
        while (dst_size > src_size)
120✔
365
            dst_list.remove(--dst_size);
72✔
366
    }
48✔
367

177✔
368
    // append remainig src into dst
177✔
369
    for (size_t i = dst_list.size(); i < src_list.size(); ++i) {
612✔
370
        auto src_any = src_list.get(i);
258✔
371
        if (is_collection(src_any)) {
258✔
372
            auto coll_type = to_collection_type(src_any);
84✔
373
            dst_list.insert_collection(i, coll_type);
84✔
374
            copy_list_in_mixed(src_list, dst_list, i, coll_type);
84✔
375
        }
84✔
376
        else {
174✔
377
            dst_list.insert_any(i, src_any);
174✔
378
        }
174✔
379
    }
258✔
380
}
354✔
381

382
void InterRealmValueConverter::handle_dictionary_in_mixed(Dictionary& src_dictionary,
383
                                                          Dictionary& dst_dictionary) const
384
{
168✔
385
    std::vector<size_t> to_insert, to_delete;
168✔
386
    size_t src_ndx = 0, dst_ndx = 0;
168✔
387
    while (src_ndx < src_dictionary.size() && dst_ndx < dst_dictionary.size()) {
384✔
388
        const auto [key_src, src_any] = src_dictionary.get_pair(src_ndx);
216✔
389
        const auto [key_dst, dst_any] = dst_dictionary.get_pair(dst_ndx);
216✔
390

108✔
391
        auto cmp = key_src.compare(key_dst);
216✔
392
        if (cmp == 0) {
216✔
393
            if (src_any != dst_any) {
114✔
394
                to_insert.push_back(src_ndx);
12✔
395
            }
12✔
396
            else if (is_collection(src_any) &&
102✔
397
                     !check_matching_dictionary(src_dictionary, dst_dictionary, key_src.get_string(),
69✔
398
                                                to_collection_type(src_any))) {
18✔
NEW
399
                to_insert.push_back(src_ndx);
×
NEW
400
            }
×
401
            src_ndx += 1;
114✔
402
            dst_ndx += 1;
114✔
403
        }
114✔
404
        else if (cmp < 0) {
102✔
405
            to_insert.push_back(src_ndx);
60✔
406
            src_ndx += 1;
60✔
407
        }
60✔
408
        else {
42✔
409
            to_delete.push_back(dst_ndx);
42✔
410
            dst_ndx += 1;
42✔
411
        }
42✔
412
    }
216✔
413

84✔
414
    // append src to dst
84✔
415
    while (src_ndx < src_dictionary.size()) {
348✔
416
        to_insert.push_back(src_ndx);
180✔
417
        src_ndx += 1;
180✔
418
    }
180✔
419

84✔
420
    // delete everything that did not match passed src.size()
84✔
421
    while (dst_ndx < dst_dictionary.size()) {
168✔
NEW
422
        to_delete.push_back(dst_ndx);
×
NEW
423
        dst_ndx += 1;
×
NEW
424
    }
×
425

84✔
426
    // delete all the non matching keys
84✔
427
    while (!to_delete.empty()) {
210✔
428
        dst_dictionary.erase(dst_dictionary.begin() + to_delete.back());
42✔
429
        to_delete.pop_back();
42✔
430
    }
42✔
431

84✔
432
    // insert into dst
84✔
433
    for (const auto pos : to_insert) {
252✔
434
        const auto [key, any] = src_dictionary.get_pair(pos);
252✔
435
        if (is_collection(any)) {
252✔
436
            auto coll_type = to_collection_type(any);
72✔
437
            dst_dictionary.insert_collection(key.get_string(), coll_type);
72✔
438
            copy_dictionary_in_mixed(src_dictionary, dst_dictionary, key.get_string(), coll_type);
72✔
439
        }
72✔
440
        else {
180✔
441
            dst_dictionary.insert(key, any);
180✔
442
        }
180✔
443
    }
252✔
444
}
168✔
445

446
bool InterRealmValueConverter::check_matching_list(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list, size_t ndx,
447
                                                   CollectionType type) const
448
{
408✔
449

204✔
450
    if (type == CollectionType::List) {
408✔
451
        auto nested_src_list = src_list.get_list(ndx);
288✔
452
        auto nested_dst_list = dst_list.get_list(ndx);
288✔
453
        auto size_src = nested_src_list->size();
288✔
454
        auto size_dst = nested_dst_list->size();
288✔
455
        if (size_src != size_dst)
288✔
456
            return false;
84✔
457
        for (size_t i = 0; i < size_src; ++i) {
378✔
458
            auto src_mixed = nested_src_list->get_any(i);
270✔
459
            auto dst_mixed = nested_dst_list->get_any(i);
270✔
460
            if (src_mixed != dst_mixed)
270✔
461
                return false;
96✔
462
        }
270✔
463
    }
204✔
464
    else if (type == CollectionType::Dictionary) {
120✔
465
        auto nested_src_dictionary = src_list.get_dictionary(ndx);
120✔
466
        auto nested_dst_dictionary = dst_list.get_dictionary(ndx);
120✔
467
        auto size_src = nested_src_dictionary->size();
120✔
468
        auto size_dst = nested_dst_dictionary->size();
120✔
469
        if (size_src != size_dst)
120✔
470
            return false;
12✔
471
        for (size_t i = 0; i < size_src; ++i) {
168✔
472
            auto [src_key, src_mixed] = nested_src_dictionary->get_pair(i);
120✔
473
            auto [dst_key, dst_mixed] = nested_dst_dictionary->get_pair(i);
120✔
474
            if (src_key != dst_key)
120✔
475
                return false;
36✔
476
            if (src_mixed != dst_mixed)
84✔
477
                return false;
24✔
478
        }
84✔
479
    }
108✔
480
    return true;
282✔
481
}
408✔
482

483
bool InterRealmValueConverter::check_matching_dictionary(const Dictionary& src_dictionary,
484
                                                         const Dictionary& dst_dictionary, StringData key,
485
                                                         CollectionType type) const
486
{
36✔
487
    if (type == CollectionType::List) {
36✔
488
        auto n_src_list = src_dictionary.get_list(key);
18✔
489
        auto n_dst_list = dst_dictionary.get_list(key);
18✔
490
        auto size_src = n_src_list->size();
18✔
491
        auto size_dst = n_dst_list->size();
18✔
492
        if (size_src != size_dst)
18✔
NEW
493
            return false;
×
494
        for (size_t i = 0; i < size_src; ++i) {
36✔
495
            auto src_mixed = n_src_list->get_any(i);
18✔
496
            auto dst_mixed = n_dst_list->get_any(i);
18✔
497
            if (src_mixed != dst_mixed)
18✔
NEW
498
                return false;
×
499
        }
18✔
500
    }
18✔
501
    else if (type == CollectionType::Dictionary) {
18✔
502
        auto n_src_dictionary = src_dictionary.get_dictionary(key);
18✔
503
        auto n_dst_dictionary = dst_dictionary.get_dictionary(key);
18✔
504
        auto size_src = n_src_dictionary->size();
18✔
505
        auto size_dst = n_dst_dictionary->size();
18✔
506
        if (size_src != size_dst)
18✔
NEW
507
            return false;
×
508
        for (size_t i = 0; i < size_src; ++i) {
36✔
509
            auto [src_key, src_mixed] = n_src_dictionary->get_pair(i);
18✔
510
            auto [dst_key, dst_mixed] = n_dst_dictionary->get_pair(i);
18✔
511
            if (src_key != dst_key)
18✔
NEW
512
                return false;
×
513
            if (src_mixed != dst_mixed)
18✔
NEW
514
                return false;
×
515
        }
18✔
516
    }
18✔
517
    return true;
36✔
518
}
36✔
519

520
void InterRealmValueConverter::copy_list_in_mixed(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list, size_t ndx,
521
                                                  CollectionType type) const
522
{
222✔
523
    if (type == CollectionType::List) {
222✔
524
        auto n_src_list = src_list.get_list(ndx);
132✔
525
        auto n_dst_list = dst_list.get_list(ndx);
132✔
526
        handle_list_in_mixed(*n_src_list, *n_dst_list);
132✔
527
    }
132✔
528
    else if (type == CollectionType::Set) {
90✔
529
        auto n_src_set = src_list.get_set(ndx);
12✔
530
        auto n_dst_set = dst_list.get_set(ndx);
12✔
531
        copy_set(*n_src_set, *n_dst_set, nullptr);
12✔
532
    }
12✔
533
    else if (type == CollectionType::Dictionary) {
78✔
534
        auto n_src_dict = src_list.get_dictionary(ndx);
78✔
535
        auto n_dst_dict = dst_list.get_dictionary(ndx);
78✔
536
        handle_dictionary_in_mixed(*n_src_dict, *n_dst_dict);
78✔
537
    }
78✔
538
}
222✔
539

540
void InterRealmValueConverter::copy_dictionary_in_mixed(const Dictionary& src_dictionary, Dictionary& dst_dictionary,
541
                                                        StringData key, CollectionType type) const
542
{
72✔
543
    if (type == CollectionType::List) {
72✔
544
        auto n_src_list = src_dictionary.get_list(key);
30✔
545
        auto n_dst_list = dst_dictionary.get_list(key);
30✔
546
        handle_list_in_mixed(*n_src_list, *n_dst_list);
30✔
547
    }
30✔
548
    else if (type == CollectionType::Set) {
42✔
549
        auto n_src_set = src_dictionary.get_set(key);
12✔
550
        auto n_dst_set = dst_dictionary.get_set(key);
12✔
551
        copy_set(*n_src_set, *n_dst_set, nullptr);
12✔
552
    }
12✔
553
    else if (type == CollectionType::Dictionary) {
30✔
554
        auto n_src_dictionary = src_dictionary.get_dictionary(key);
30✔
555
        auto n_dst_dictionary = dst_dictionary.get_dictionary(key);
30✔
556
        handle_dictionary_in_mixed(*n_src_dictionary, *n_dst_dictionary);
30✔
557
    }
30✔
558
}
72✔
559

560
bool InterRealmValueConverter::is_collection(Mixed mixed) const
561
{
1,254✔
562
    return mixed.is_type(type_List, type_Set, type_Dictionary);
1,254✔
563
}
1,254✔
564

565
CollectionType InterRealmValueConverter::to_collection_type(Mixed mixed) const
566
{
624✔
567
    const auto mixed_type = mixed.get_type();
624✔
568
    if (mixed_type == type_List)
624✔
569
        return CollectionType::List;
390✔
570
    if (mixed_type == type_Set)
234✔
571
        return CollectionType::Set;
24✔
572
    if (mixed_type == type_Dictionary)
210✔
573
        return CollectionType::Dictionary;
210✔
NEW
574
    REALM_UNREACHABLE();
×
NEW
575
}
×
576

577
// If an embedded object is encountered, add it to a list of embedded objects to process.
578
// This relies on the property that embedded objects only have one incoming link
579
// otherwise there could be an infinite loop while discovering embedded objects.
580
void EmbeddedObjectConverter::track(const Obj& e_src, const Obj& e_dst)
581
{
67,158✔
582
    embedded_pending.push_back({e_src, e_dst});
67,158✔
583
}
67,158✔
584

585
void EmbeddedObjectConverter::process_pending()
586
{
30,078✔
587
    util::FlatMap<TableKey, InterRealmObjectConverter> converters;
30,078✔
588

15,039✔
589
    while (!embedded_pending.empty()) {
97,236✔
590
        EmbeddedToCheck pending = embedded_pending.back();
67,158✔
591
        embedded_pending.pop_back();
67,158✔
592

33,579✔
593
        TableRef dst_table = pending.embedded_in_dst.get_table();
67,158✔
594
        TableKey dst_table_key = dst_table->get_key();
67,158✔
595
        auto it = converters.find(dst_table_key);
67,158✔
596
        if (it == converters.end()) {
67,158✔
597
            TableRef src_table = pending.embedded_in_src.get_table();
1,074✔
598
            it = converters.insert({dst_table_key, InterRealmObjectConverter{src_table, dst_table, this}}).first;
1,074✔
599
        }
1,074✔
600
        InterRealmObjectConverter& converter = it->second;
67,158✔
601
        converter.copy(pending.embedded_in_src, pending.embedded_in_dst, nullptr);
67,158✔
602
    }
67,158✔
603
}
30,078✔
604

605
InterRealmValueConverter::InterRealmValueConverter(ConstTableRef src_table, ColKey src_col, ConstTableRef dst_table,
606
                                                   ColKey dst_col, EmbeddedObjectConverter* ec)
607
    : m_src_table(src_table)
608
    , m_dst_table(dst_table)
609
    , m_src_col(src_col)
610
    , m_dst_col(dst_col)
611
    , m_embedded_converter(ec)
612
    , m_is_embedded_link(false)
613
    , m_primitive_types_only(!(src_col.get_type() == col_type_TypedLink || src_col.get_type() == col_type_Link ||
614
                               src_col.get_type() == col_type_LinkList || src_col.get_type() == col_type_Mixed))
615
{
57,582✔
616
    if (!m_primitive_types_only) {
57,582✔
617
        REALM_ASSERT(src_table);
9,042✔
618
        m_opposite_of_src = src_table->get_opposite_table(src_col);
9,042✔
619
        m_opposite_of_dst = dst_table->get_opposite_table(dst_col);
9,042✔
620
        REALM_ASSERT(bool(m_opposite_of_src) == bool(m_opposite_of_dst));
9,042✔
621
        if (m_opposite_of_src) {
9,042✔
622
            m_is_embedded_link = m_opposite_of_src->is_embedded();
4,968✔
623
        }
4,968✔
624
    }
9,042✔
625
}
57,582✔
626

627
void InterRealmValueConverter::track_new_embedded(const Obj& src, const Obj& dst) const
628
{
6,840✔
629
    m_embedded_converter->track(src, dst);
6,840✔
630
}
6,840✔
631

632
// convert `src` to the destination Realm and compare that value with `dst`
633
// If `converted_src_out` is provided, it will be set to the converted src value
634
int InterRealmValueConverter::cmp_src_to_dst(Mixed src, Mixed dst, ConversionResult* converted_src_out,
635
                                             bool* did_update_out) const
636
{
163,584✔
637
    int cmp = 0;
163,584✔
638
    Mixed converted_src;
163,584✔
639
    if (m_primitive_types_only || !src.is_type(type_Link, type_TypedLink)) {
163,584✔
640
        converted_src = src;
150,156✔
641
        cmp = src.compare(dst);
150,156✔
642
    }
150,156✔
643
    else if (m_opposite_of_src) {
13,428✔
644
        ObjKey src_link_key = src.get<ObjKey>();
10,320✔
645
        if (m_is_embedded_link) {
10,320✔
646
            Obj src_embedded = m_opposite_of_src->get_object(src_link_key);
6,858✔
647
            REALM_ASSERT_DEBUG(src_embedded.is_valid());
6,858✔
648
            if (dst.is_type(type_Link, type_TypedLink)) {
6,858✔
649
                cmp = 0; // no need to set this link, there is already an embedded object here
5,790✔
650
                Obj dst_embedded = m_opposite_of_dst->get_object(dst.get<ObjKey>());
5,790✔
651
                REALM_ASSERT_DEBUG(dst_embedded.is_valid());
5,790✔
652
                converted_src = dst_embedded.get_key();
5,790✔
653
                track_new_embedded(src_embedded, dst_embedded);
5,790✔
654
            }
5,790✔
655
            else {
1,068✔
656
                cmp = src.compare(dst);
1,068✔
657
                if (converted_src_out) {
1,068✔
658
                    converted_src_out->requires_new_embedded_object = true;
1,050✔
659
                    converted_src_out->src_embedded_to_check = src_embedded;
1,050✔
660
                }
1,050✔
661
            }
1,068✔
662
        }
6,858✔
663
        else {
3,462✔
664
            Obj dst_link;
3,462✔
665
            if (m_opposite_of_dst == m_opposite_of_src) {
3,462✔
666
                // if this is the same Realm, we can use the ObjKey
96✔
667
                dst_link = m_opposite_of_dst->get_object(src_link_key);
192✔
668
            }
192✔
669
            else {
3,270✔
670
                // in different Realms we create a new object
1,635✔
671
                if (m_opposite_of_src->get_primary_key_column()) {
3,270✔
672
                    Mixed src_link_pk = m_opposite_of_src->get_primary_key(src_link_key);
3,270✔
673
                    dst_link = m_opposite_of_dst->create_object_with_primary_key(src_link_pk, did_update_out);
3,270✔
674
                }
3,270✔
675
                else {
×
676
                    dst_link = m_opposite_of_dst->create_object();
×
677
                }
×
678
            }
3,270✔
679
            converted_src = dst_link.get_key();
3,462✔
680
            if (dst.is_type(type_TypedLink)) {
3,462✔
681
                cmp = converted_src.compare(dst.get<ObjKey>());
2,724✔
682
            }
2,724✔
683
            else {
738✔
684
                cmp = converted_src.compare(dst);
738✔
685
            }
738✔
686
        }
3,462✔
687
    }
10,320✔
688
    else {
3,108✔
689
        ObjLink src_link = src.get<ObjLink>();
3,108✔
690
        if (src_link.is_unresolved()) {
3,108✔
691
            converted_src = Mixed{}; // no need to transfer over unresolved links
×
692
            cmp = converted_src.compare(dst);
×
693
        }
×
694
        else {
3,108✔
695
            TableRef src_link_table = m_src_table->get_parent_group()->get_table(src_link.get_table_key());
3,108✔
696
            REALM_ASSERT_EX(src_link_table, src_link.get_table_key());
3,108✔
697
            TableRef dst_link_table = m_dst_table->get_parent_group()->get_table(src_link_table->get_name());
3,108✔
698
            REALM_ASSERT_EX(dst_link_table, src_link_table->get_name());
3,108✔
699
            // embedded tables should always be covered by the m_opposite_of_src case above.
1,554✔
700
            REALM_ASSERT_EX(!src_link_table->is_embedded(), src_link_table->get_name());
3,108✔
701
            // regular table, convert by pk
1,554✔
702
            if (src_link_table->get_primary_key_column()) {
3,108✔
703
                Mixed src_pk = src_link_table->get_primary_key(src_link.get_obj_key());
3,108✔
704
                Obj dst_link = dst_link_table->create_object_with_primary_key(src_pk, did_update_out);
3,108✔
705
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
3,108✔
706
            }
3,108✔
707
            else if (src_link_table == dst_link_table) {
×
708
                // no pk, but this is the same Realm, so convert by ObjKey
709
                Obj dst_link = dst_link_table->get_object(src_link.get_obj_key());
×
710
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
711
            }
×
712
            else {
×
713
                // no pk, and different Realm, create an object
714
                Obj dst_link = dst_link_table->create_object();
×
715
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
716
            }
×
717
            cmp = converted_src.compare(dst);
3,108✔
718
        }
3,108✔
719
    }
3,108✔
720
    if (converted_src_out) {
163,584✔
721
        converted_src_out->converted_value = converted_src;
127,524✔
722
    }
127,524✔
723
    if (did_update_out && cmp) {
163,584✔
724
        *did_update_out = true;
15,096✔
725
    }
15,096✔
726
    return cmp;
163,584✔
727
}
163,584✔
728

729
InterRealmObjectConverter::InterRealmObjectConverter(ConstTableRef table_src, TableRef table_dst,
730
                                                     EmbeddedObjectConverter* embedded_tracker)
731
    : m_embedded_tracker(embedded_tracker)
732
{
24,402✔
733
    m_columns_cache.reserve(table_src->get_column_count());
24,402✔
734
    ColKey pk_col = table_src->get_primary_key_column();
24,402✔
735
    for (ColKey col_key_src : table_src->get_column_keys()) {
79,614✔
736
        if (col_key_src == pk_col)
79,614✔
737
            continue;
23,328✔
738
        StringData col_name = table_src->get_column_name(col_key_src);
56,286✔
739
        ColKey col_key_dst = table_dst->get_column_key(col_name);
56,286✔
740
        REALM_ASSERT(col_key_dst);
56,286✔
741
        m_columns_cache.emplace_back(table_src, col_key_src, table_dst, col_key_dst, m_embedded_tracker);
56,286✔
742
    }
56,286✔
743
}
24,402✔
744

745
void InterRealmObjectConverter::copy(const Obj& src, Obj& dst, bool* update_out)
746
{
99,762✔
747
    for (auto& column : m_columns_cache) {
151,704✔
748
        column.copy_value(src, dst, update_out);
151,704✔
749
    }
151,704✔
750
}
99,762✔
751

752
//
753

754
} // namespace realm::converters
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

© 2026 Coveralls, Inc