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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

88.48
/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
#include <iostream>
27
namespace realm::converters {
28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

165✔
313
    // find fist not matching element from beginning
165✔
314
    while (left < sz) {
462✔
315
        auto src_any = src_list.get_any(left);
276✔
316
        auto dst_any = dst_list.get_any(left);
276✔
317
        if (src_any != dst_any)
276✔
318
            break;
78✔
319
        if (is_collection(src_any) && !check_matching_list(src_list, dst_list, left, to_collection_type(src_any)))
198✔
320
            break;
66✔
321
        left += 1;
132✔
322
    }
132✔
323

165✔
324
    // find first not matching element from end
165✔
325
    while (right >= 0) {
432✔
326
        auto src_any = src_list.get_any(right);
246✔
327
        auto dst_any = dst_list.get_any(right);
246✔
328
        if (src_any != dst_any)
246✔
329
            break;
84✔
330
        if (is_collection(src_any) && !check_matching_list(src_list, dst_list, right, to_collection_type(src_any)))
162✔
331
            break;
60✔
332
        right -= 1;
102✔
333
    }
102✔
334

165✔
335
    // Replace all different elements in [left, right]
165✔
336
    while (left <= right) {
552✔
337
        auto src_any = src_list.get_any(left);
222✔
338
        auto dst_any = dst_list.get_any(left);
222✔
339

111✔
340
        if (is_collection(src_any)) {
222✔
341
            auto coll_type = to_collection_type(src_any);
126✔
342

63✔
343
            if (!dst_any.is_type(src_any.get_type())) {
126✔
344
                // Mixed vs Collection
9✔
345
                dst_list.set_collection(left, coll_type);
18✔
346
                copy_list_in_mixed(src_list, dst_list, left, coll_type);
18✔
347
            }
18✔
348
            else if (!check_matching_list(src_list, dst_list, left, coll_type)) {
108✔
349
                // Collection vs Collection
51✔
350
                dst_list.set_any(left, src_any);
102✔
351
                copy_list_in_mixed(src_list, dst_list, left, coll_type);
102✔
352
            }
102✔
353
        }
126✔
354
        else if (dst_any != src_any) {
96✔
355
            // Mixed vs Mixed
45✔
356
            dst_list.set_any(left, src_any);
90✔
357
        }
90✔
358
        left += 1;
222✔
359
    }
222✔
360

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

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

383
void InterRealmValueConverter::handle_dictionary_in_mixed(Dictionary& src_dictionary,
384
                                                          Dictionary& dst_dictionary) const
385
{
114✔
386
    int sz = (int)std::min(src_dictionary.size(), dst_dictionary.size());
114✔
387
    int left = 0;
114✔
388
    int right = (int)sz - 1;
114✔
389

57✔
390
    // find fist not matching element from beginning
57✔
391
    while (left < sz) {
132✔
392
        const auto [key_src, src_any] = src_dictionary.get_pair(left);
42✔
393
        const auto [key_dst, dst_any] = dst_dictionary.get_pair(left);
42✔
394
        if (src_any != dst_any || key_src != key_dst)
42✔
395
            break;
24✔
396
        if (is_collection(src_any) && !check_matching_dictionary(src_dictionary, dst_dictionary, key_src.get_string(),
18!
NEW
397
                                                                 to_collection_type(src_any)))
×
NEW
398
            break;
×
399
        left += 1;
18✔
400
    }
18✔
401

57✔
402
    // find first not matching element from end
57✔
403
    while (right >= 0) {
120✔
404
        const auto [key_src, src_any] = src_dictionary.get_pair(right);
30✔
405
        const auto [key_dst, dst_any] = dst_dictionary.get_pair(right);
30✔
406
        if (src_any != dst_any || key_src != key_dst)
30✔
407
            break;
24✔
408
        if (is_collection(src_any) && !check_matching_dictionary(src_dictionary, dst_dictionary, key_src.get_string(),
6!
NEW
409
                                                                 to_collection_type(src_any)))
×
NEW
410
            break;
×
411
        right -= 1;
6✔
412
    }
6✔
413

57✔
414
    // Replace all different elements in [left, right]
57✔
415
    while (left <= right) {
138✔
416
        const auto [key_src, src_any] = src_dictionary.get_pair(left);
24✔
417
        const auto [key_dst, dst_any] = dst_dictionary.get_pair(left);
24✔
418

12✔
419
        // handle possible key mismatches
12✔
420
        if (key_src != key_dst) {
24✔
421
            dst_dictionary.erase(key_dst);
24✔
422
            dst_dictionary.insert(key_src, src_any);
24✔
423
        }
24✔
424

12✔
425
        if (is_collection(src_any)) {
24✔
NEW
426
            auto coll_type = to_collection_type(src_any);
×
NEW
427
            const auto key = key_src.get_string();
×
428

NEW
429
            if (!dst_any.is_type(src_any.get_type())) {
×
430
                // Mixed vs Collection
NEW
431
                const auto key = key_src.get_string();
×
NEW
432
                dst_dictionary.set_collection(key, coll_type);
×
NEW
433
                copy_dictionary_in_mixed(src_dictionary, dst_dictionary, key, coll_type);
×
NEW
434
            }
×
NEW
435
            else if (!check_matching_dictionary(src_dictionary, dst_dictionary, key, coll_type)) {
×
436
                // Collection vs Collection
NEW
437
                dst_dictionary.insert(key_src, src_any);
×
NEW
438
                copy_dictionary_in_mixed(src_dictionary, dst_dictionary, key, coll_type);
×
UNCOV
439
            }
×
UNCOV
440
        }
×
441
        else if (dst_any != src_any) {
24✔
442
            // Mixed vs Mixed
12✔
443
            dst_dictionary.insert(key_src, src_any);
24✔
444
        }
24✔
445
        left += 1;
24✔
446
    }
24✔
447

57✔
448
    // remove dst elements not present in src
57✔
449
    if (dst_dictionary.size() > src_dictionary.size()) {
114✔
NEW
450
        auto dst_size = dst_dictionary.size();
×
NEW
451
        auto src_size = src_dictionary.size();
×
NEW
452
        while (dst_size > src_size) {
×
NEW
453
            const auto [dst_key, dst_any] = dst_dictionary.get_pair(--dst_size);
×
NEW
454
            dst_dictionary.erase(dst_key);
×
NEW
455
        }
×
NEW
456
    }
×
457

57✔
458
    // append remainig src into dst
57✔
459
    for (size_t i = dst_dictionary.size(); i < src_dictionary.size(); ++i) {
270✔
460
        const auto [src_key, src_any] = src_dictionary.get_pair(i);
156✔
461
        if (is_collection(src_any)) {
156✔
462
            const auto coll_type = to_collection_type(src_any);
48✔
463
            const auto key = src_key.get_string();
48✔
464
            dst_dictionary.insert_collection(key, coll_type);
48✔
465
            copy_dictionary_in_mixed(src_dictionary, dst_dictionary, key, coll_type);
48✔
466
        }
48✔
467
        else {
108✔
468
            dst_dictionary.insert(src_key, src_any);
108✔
469
        }
108✔
470
    }
156✔
471
}
114✔
472

473
bool InterRealmValueConverter::check_matching_list(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list, size_t ndx,
474
                                                   CollectionType type) const
475
{
372✔
476

186✔
477
    if (type == CollectionType::List) {
372✔
478
        auto nested_src_list = src_list.get_list(ndx);
288✔
479
        auto nested_dst_list = dst_list.get_list(ndx);
288✔
480
        auto size_src = nested_src_list->size();
288✔
481
        auto size_dst = nested_dst_list->size();
288✔
482
        if (size_src != size_dst)
288✔
483
            return false;
84✔
484
        for (size_t i = 0; i < size_src; ++i) {
378✔
485
            auto src_mixed = nested_src_list->get_any(i);
270✔
486
            auto dst_mixed = nested_dst_list->get_any(i);
270✔
487
            if (src_mixed != dst_mixed)
270✔
488
                return false;
96✔
489
        }
270✔
490
    }
204✔
491
    else if (type == CollectionType::Dictionary) {
84✔
492
        auto nested_src_dictionary = src_list.get_dictionary(ndx);
84✔
493
        auto nested_dst_dictionary = dst_list.get_dictionary(ndx);
84✔
494
        auto size_src = nested_src_dictionary->size();
84✔
495
        auto size_dst = nested_dst_dictionary->size();
84✔
496
        if (size_src != size_dst)
84✔
497
            return false;
12✔
498
        for (size_t i = 0; i < size_src; ++i) {
120✔
499
            auto [src_key, src_mixed] = nested_src_dictionary->get_pair(i);
84✔
500
            auto [dst_key, dst_mixed] = nested_dst_dictionary->get_pair(i);
84✔
501
            if (src_key != dst_key)
84✔
502
                return false;
36✔
503
            if (src_mixed != dst_mixed)
48✔
NEW
504
                return false;
×
505
        }
48✔
506
    }
72✔
507
    return true;
258✔
508
}
372✔
509

510
bool InterRealmValueConverter::check_matching_dictionary(const Dictionary& src_dictionary,
511
                                                         const Dictionary& dst_dictionary, StringData key,
512
                                                         CollectionType type) const
NEW
513
{
×
NEW
514
    if (type == CollectionType::List) {
×
NEW
515
        auto n_src_list = src_dictionary.get_list(key);
×
NEW
516
        auto n_dst_list = dst_dictionary.get_list(key);
×
NEW
517
        auto size_src = n_src_list->size();
×
NEW
518
        auto size_dst = n_dst_list->size();
×
NEW
519
        if (size_src != size_dst)
×
NEW
520
            return false;
×
NEW
521
        for (size_t i = 0; i < size_src; ++i) {
×
NEW
522
            auto src_mixed = n_src_list->get_any(i);
×
NEW
523
            auto dst_mixed = n_dst_list->get_any(i);
×
NEW
524
            if (src_mixed != dst_mixed)
×
NEW
525
                return false;
×
NEW
526
        }
×
NEW
527
    }
×
NEW
528
    else if (type == CollectionType::Dictionary) {
×
NEW
529
        auto n_src_dictionary = src_dictionary.get_dictionary(key);
×
NEW
530
        auto n_dst_dictionary = dst_dictionary.get_dictionary(key);
×
NEW
531
        auto size_src = n_src_dictionary->size();
×
NEW
532
        auto size_dst = n_dst_dictionary->size();
×
NEW
533
        if (size_src != size_dst)
×
NEW
534
            return false;
×
NEW
535
        for (size_t i = 0; i < size_src; ++i) {
×
NEW
536
            auto [src_key, src_mixed] = n_src_dictionary->get_pair(i);
×
NEW
537
            auto [dst_key, dst_mixed] = n_dst_dictionary->get_pair(i);
×
NEW
538
            if (src_key != dst_key)
×
NEW
539
                return false;
×
NEW
540
            if (src_mixed != dst_mixed)
×
NEW
541
                return false;
×
NEW
542
        }
×
NEW
543
    }
×
NEW
544
    return true;
×
NEW
545
}
×
546

547
void InterRealmValueConverter::copy_list_in_mixed(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list, size_t ndx,
548
                                                  CollectionType type) const
549
{
204✔
550
    if (type == CollectionType::List) {
204✔
551
        auto n_src_list = src_list.get_list(ndx);
132✔
552
        auto n_dst_list = dst_list.get_list(ndx);
132✔
553
        handle_list_in_mixed(*n_src_list, *n_dst_list);
132✔
554
    }
132✔
555
    else if (type == CollectionType::Set) {
72✔
556
        auto n_src_set = src_list.get_set(ndx);
12✔
557
        auto n_dst_set = dst_list.get_set(ndx);
12✔
558
        copy_set(*n_src_set, *n_dst_set, nullptr);
12✔
559
    }
12✔
560
    else if (type == CollectionType::Dictionary) {
60✔
561
        auto n_src_dict = src_list.get_dictionary(ndx);
60✔
562
        auto n_dst_dict = dst_list.get_dictionary(ndx);
60✔
563
        handle_dictionary_in_mixed(*n_src_dict, *n_dst_dict);
60✔
564
    }
60✔
565
}
204✔
566

567
void InterRealmValueConverter::copy_dictionary_in_mixed(const Dictionary& src_dictionary, Dictionary& dst_dictionary,
568
                                                        StringData key, CollectionType type) const
569
{
48✔
570
    if (type == CollectionType::List) {
48✔
571
        auto n_src_list = src_dictionary.get_list(key);
18✔
572
        auto n_dst_list = dst_dictionary.get_list(key);
18✔
573
        handle_list_in_mixed(*n_src_list, *n_dst_list);
18✔
574
    }
18✔
575
    else if (type == CollectionType::Set) {
30✔
576
        auto n_src_set = src_dictionary.get_set(key);
12✔
577
        auto n_dst_set = dst_dictionary.get_set(key);
12✔
578
        copy_set(*n_src_set, *n_dst_set, nullptr);
12✔
579
    }
12✔
580
    else if (type == CollectionType::Dictionary) {
18✔
581
        auto n_src_dictionary = src_dictionary.get_dictionary(key);
18✔
582
        auto n_dst_dictionary = dst_dictionary.get_dictionary(key);
18✔
583
        handle_dictionary_in_mixed(*n_src_dictionary, *n_dst_dictionary);
18✔
584
    }
18✔
585
}
48✔
586

587
bool InterRealmValueConverter::is_collection(Mixed mixed) const
588
{
1,020✔
589
    return mixed.is_type(type_List, type_Set, type_Dictionary);
1,020✔
590
}
1,020✔
591

592
CollectionType InterRealmValueConverter::to_collection_type(Mixed mixed) const
593
{
522✔
594
    const auto mixed_type = mixed.get_type();
522✔
595
    if (mixed_type == type_List)
522✔
596
        return CollectionType::List;
360✔
597
    if (mixed_type == type_Set)
162✔
598
        return CollectionType::Set;
24✔
599
    if (mixed_type == type_Dictionary)
138✔
600
        return CollectionType::Dictionary;
138✔
NEW
601
    REALM_UNREACHABLE();
×
NEW
602
}
×
603

604
// If an embedded object is encountered, add it to a list of embedded objects to process.
605
// This relies on the property that embedded objects only have one incoming link
606
// otherwise there could be an infinite loop while discovering embedded objects.
607
void EmbeddedObjectConverter::track(const Obj& e_src, const Obj& e_dst)
608
{
67,158✔
609
    embedded_pending.push_back({e_src, e_dst});
67,158✔
610
}
67,158✔
611

612
void EmbeddedObjectConverter::process_pending()
613
{
29,988✔
614
    util::FlatMap<TableKey, InterRealmObjectConverter> converters;
29,988✔
615

14,994✔
616
    while (!embedded_pending.empty()) {
97,146✔
617
        EmbeddedToCheck pending = embedded_pending.back();
67,158✔
618
        embedded_pending.pop_back();
67,158✔
619

33,579✔
620
        TableRef dst_table = pending.embedded_in_dst.get_table();
67,158✔
621
        TableKey dst_table_key = dst_table->get_key();
67,158✔
622
        auto it = converters.find(dst_table_key);
67,158✔
623
        if (it == converters.end()) {
67,158✔
624
            TableRef src_table = pending.embedded_in_src.get_table();
1,074✔
625
            it = converters.insert({dst_table_key, InterRealmObjectConverter{src_table, dst_table, this}}).first;
1,074✔
626
        }
1,074✔
627
        InterRealmObjectConverter& converter = it->second;
67,158✔
628
        converter.copy(pending.embedded_in_src, pending.embedded_in_dst, nullptr);
67,158✔
629
    }
67,158✔
630
}
29,988✔
631

632
InterRealmValueConverter::InterRealmValueConverter(ConstTableRef src_table, ColKey src_col, ConstTableRef dst_table,
633
                                                   ColKey dst_col, EmbeddedObjectConverter* ec)
634
    : m_src_table(src_table)
635
    , m_dst_table(dst_table)
636
    , m_src_col(src_col)
637
    , m_dst_col(dst_col)
638
    , m_embedded_converter(ec)
639
    , m_is_embedded_link(false)
640
    , m_primitive_types_only(!(src_col.get_type() == col_type_TypedLink || src_col.get_type() == col_type_Link ||
641
                               src_col.get_type() == col_type_LinkList || src_col.get_type() == col_type_Mixed))
642
{
57,510✔
643
    if (!m_primitive_types_only) {
57,510✔
644
        REALM_ASSERT(src_table);
9,006✔
645
        m_opposite_of_src = src_table->get_opposite_table(src_col);
9,006✔
646
        m_opposite_of_dst = dst_table->get_opposite_table(dst_col);
9,006✔
647
        REALM_ASSERT(bool(m_opposite_of_src) == bool(m_opposite_of_dst));
9,006✔
648
        if (m_opposite_of_src) {
9,006✔
649
            m_is_embedded_link = m_opposite_of_src->is_embedded();
4,968✔
650
        }
4,968✔
651
    }
9,006✔
652
}
57,510✔
653

654
void InterRealmValueConverter::track_new_embedded(const Obj& src, const Obj& dst) const
655
{
6,840✔
656
    m_embedded_converter->track(src, dst);
6,840✔
657
}
6,840✔
658

659
// convert `src` to the destination Realm and compare that value with `dst`
660
// If `converted_src_out` is provided, it will be set to the converted src value
661
int InterRealmValueConverter::cmp_src_to_dst(Mixed src, Mixed dst, ConversionResult* converted_src_out,
662
                                             bool* did_update_out) const
663
{
163,548✔
664
    int cmp = 0;
163,548✔
665
    Mixed converted_src;
163,548✔
666
    if (m_primitive_types_only || !src.is_type(type_Link, type_TypedLink)) {
163,548✔
667
        converted_src = src;
150,120✔
668
        cmp = src.compare(dst);
150,120✔
669
    }
150,120✔
670
    else if (m_opposite_of_src) {
13,428✔
671
        ObjKey src_link_key = src.get<ObjKey>();
10,320✔
672
        if (m_is_embedded_link) {
10,320✔
673
            Obj src_embedded = m_opposite_of_src->get_object(src_link_key);
6,858✔
674
            REALM_ASSERT_DEBUG(src_embedded.is_valid());
6,858✔
675
            if (dst.is_type(type_Link, type_TypedLink)) {
6,858✔
676
                cmp = 0; // no need to set this link, there is already an embedded object here
5,790✔
677
                Obj dst_embedded = m_opposite_of_dst->get_object(dst.get<ObjKey>());
5,790✔
678
                REALM_ASSERT_DEBUG(dst_embedded.is_valid());
5,790✔
679
                converted_src = dst_embedded.get_key();
5,790✔
680
                track_new_embedded(src_embedded, dst_embedded);
5,790✔
681
            }
5,790✔
682
            else {
1,068✔
683
                cmp = src.compare(dst);
1,068✔
684
                if (converted_src_out) {
1,068✔
685
                    converted_src_out->requires_new_embedded_object = true;
1,050✔
686
                    converted_src_out->src_embedded_to_check = src_embedded;
1,050✔
687
                }
1,050✔
688
            }
1,068✔
689
        }
6,858✔
690
        else {
3,462✔
691
            Obj dst_link;
3,462✔
692
            if (m_opposite_of_dst == m_opposite_of_src) {
3,462✔
693
                // if this is the same Realm, we can use the ObjKey
96✔
694
                dst_link = m_opposite_of_dst->get_object(src_link_key);
192✔
695
            }
192✔
696
            else {
3,270✔
697
                // in different Realms we create a new object
1,635✔
698
                if (m_opposite_of_src->get_primary_key_column()) {
3,270✔
699
                    Mixed src_link_pk = m_opposite_of_src->get_primary_key(src_link_key);
3,270✔
700
                    dst_link = m_opposite_of_dst->create_object_with_primary_key(src_link_pk, did_update_out);
3,270✔
701
                }
3,270✔
702
                else {
×
703
                    dst_link = m_opposite_of_dst->create_object();
×
704
                }
×
705
            }
3,270✔
706
            converted_src = dst_link.get_key();
3,462✔
707
            if (dst.is_type(type_TypedLink)) {
3,462✔
708
                cmp = converted_src.compare(dst.get<ObjKey>());
2,724✔
709
            }
2,724✔
710
            else {
738✔
711
                cmp = converted_src.compare(dst);
738✔
712
            }
738✔
713
        }
3,462✔
714
    }
10,320✔
715
    else {
3,108✔
716
        ObjLink src_link = src.get<ObjLink>();
3,108✔
717
        if (src_link.is_unresolved()) {
3,108✔
718
            converted_src = Mixed{}; // no need to transfer over unresolved links
×
719
            cmp = converted_src.compare(dst);
×
720
        }
×
721
        else {
3,108✔
722
            TableRef src_link_table = m_src_table->get_parent_group()->get_table(src_link.get_table_key());
3,108✔
723
            REALM_ASSERT_EX(src_link_table, src_link.get_table_key());
3,108✔
724
            TableRef dst_link_table = m_dst_table->get_parent_group()->get_table(src_link_table->get_name());
3,108✔
725
            REALM_ASSERT_EX(dst_link_table, src_link_table->get_name());
3,108✔
726
            // embedded tables should always be covered by the m_opposite_of_src case above.
1,554✔
727
            REALM_ASSERT_EX(!src_link_table->is_embedded(), src_link_table->get_name());
3,108✔
728
            // regular table, convert by pk
1,554✔
729
            if (src_link_table->get_primary_key_column()) {
3,108✔
730
                Mixed src_pk = src_link_table->get_primary_key(src_link.get_obj_key());
3,108✔
731
                Obj dst_link = dst_link_table->create_object_with_primary_key(src_pk, did_update_out);
3,108✔
732
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
3,108✔
733
            }
3,108✔
734
            else if (src_link_table == dst_link_table) {
×
735
                // no pk, but this is the same Realm, so convert by ObjKey
736
                Obj dst_link = dst_link_table->get_object(src_link.get_obj_key());
×
737
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
738
            }
×
739
            else {
×
740
                // no pk, and different Realm, create an object
741
                Obj dst_link = dst_link_table->create_object();
×
742
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
743
            }
×
744
            cmp = converted_src.compare(dst);
3,108✔
745
        }
3,108✔
746
    }
3,108✔
747
    if (converted_src_out) {
163,548✔
748
        converted_src_out->converted_value = converted_src;
127,488✔
749
    }
127,488✔
750
    if (did_update_out && cmp) {
163,548✔
751
        *did_update_out = true;
15,078✔
752
    }
15,078✔
753
    return cmp;
163,548✔
754
}
163,548✔
755

756
InterRealmObjectConverter::InterRealmObjectConverter(ConstTableRef table_src, TableRef table_dst,
757
                                                     EmbeddedObjectConverter* embedded_tracker)
758
    : m_embedded_tracker(embedded_tracker)
759
{
24,330✔
760
    m_columns_cache.reserve(table_src->get_column_count());
24,330✔
761
    ColKey pk_col = table_src->get_primary_key_column();
24,330✔
762
    for (ColKey col_key_src : table_src->get_column_keys()) {
79,470✔
763
        if (col_key_src == pk_col)
79,470✔
764
            continue;
23,256✔
765
        StringData col_name = table_src->get_column_name(col_key_src);
56,214✔
766
        ColKey col_key_dst = table_dst->get_column_key(col_name);
56,214✔
767
        REALM_ASSERT(col_key_dst);
56,214✔
768
        m_columns_cache.emplace_back(table_src, col_key_src, table_dst, col_key_dst, m_embedded_tracker);
56,214✔
769
    }
56,214✔
770
}
24,330✔
771

772
void InterRealmObjectConverter::copy(const Obj& src, Obj& dst, bool* update_out)
773
{
99,690✔
774
    for (auto& column : m_columns_cache) {
151,632✔
775
        column.copy_value(src, dst, update_out);
151,632✔
776
    }
151,632✔
777
}
99,690✔
778

779
//
780

781
} // 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