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

realm / realm-core / 2297

06 May 2024 10:50PM UTC coverage: 90.77% (+0.03%) from 90.739%
2297

push

Evergreen

web-flow
Fix test (#7679)

101978 of 180634 branches covered (56.46%)

3 of 3 new or added lines in 1 file covered. (100.0%)

72 existing lines in 13 files now uncovered.

213135 of 234807 relevant lines covered (90.77%)

5612765.27 hits per line

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

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

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
{
17,028✔
32
    // The two arrays are compared by finding the longest common prefix and
33
    // suffix.  The middle section differs between them and is made equal by
34
    // updating the middle section of dst.
35
    //
36
    // Example:
37
    // src = abcdefghi
38
    // dst = abcxyhi
39
    // The common prefix is abc. The common suffix is hi. xy is replaced by defg.
40

41
    bool updated = false;
17,028✔
42
    const size_t len_src = src.size();
17,028✔
43
    const size_t len_dst_orig = dst.size();
17,028✔
44
    size_t len_min = std::min(len_src, len_dst_orig);
17,028✔
45

46
    size_t ndx = 0;
17,028✔
47
    size_t suffix_len = 0;
17,028✔
48

49
    while (ndx < len_min && cmp_src_to_dst(src.get_any(ndx), dst.get_any(ndx), nullptr, update_out) == 0) {
31,248✔
50
        ndx++;
14,220✔
51
    }
14,220✔
52

53
    if (ndx == len_src && len_src == len_dst_orig) {
17,028✔
54
        // all are equal, early out
55
        if (update_out) {
13,119✔
56
            *update_out = false;
6,897✔
57
        }
6,897✔
58
        return;
13,119✔
59
    }
13,119✔
60

61
    size_t suffix_len_max = len_min - ndx;
3,909✔
62

63
    while (suffix_len < suffix_len_max &&
4,647✔
64
           cmp_src_to_dst(src.get_any(len_src - 1 - suffix_len), dst.get_any(len_dst_orig - 1 - suffix_len), nullptr,
4,647✔
65
                          update_out) == 0) {
2,184✔
66
        suffix_len++;
738✔
67
    }
738✔
68

69
    len_min -= (ndx + suffix_len);
3,909✔
70

71
    auto dst_as_link_list = dynamic_cast<LnkLst*>(&dst);
3,909✔
72
    auto dst_as_lst_mixed = dynamic_cast<Lst<Mixed>*>(&dst);
3,909✔
73
    auto is_link_to_deleted_object = [&](const Mixed& src_value, const Mixed& converted_value) -> bool {
5,895✔
74
        return (dst_as_link_list && converted_value.is_null()) ||
5,895✔
75
               (dst_as_lst_mixed && converted_value.is_null() && src_value.is_type(type_TypedLink));
5,895✔
76
    };
5,895✔
77

78
    std::vector<size_t> dst_to_erase;
3,909✔
79
    for (size_t i = 0; i < len_min; i++) {
6,873✔
80
        InterRealmValueConverter::ConversionResult converted_src;
2,964✔
81
        const Mixed src_value = src.get_any(ndx);
2,964✔
82
        if (cmp_src_to_dst(src_value, dst.get_any(ndx), &converted_src, update_out)) {
2,964✔
83
            if (converted_src.requires_new_embedded_object) {
2,640✔
84
                REALM_ASSERT(dst_as_link_list); // this is the only type of list that supports embedded objects
×
85
                Obj embedded = dst_as_link_list->create_and_set_linked_object(ndx);
×
86
                track_new_embedded(converted_src.src_embedded_to_check, embedded);
×
UNCOV
87
            }
×
88
            else if (is_link_to_deleted_object(src_value, converted_src.converted_value)) {
2,640✔
89
                // this can happen when the source linked list points to an object
90
                // which has been deleted in the dest Realm. Lists do not support
91
                // setting an element to null, so it must be deleted later.
92
                dst_to_erase.push_back(ndx);
42✔
93
            }
42✔
94
            else {
2,598✔
95
                dst.set_any(ndx, converted_src.converted_value);
2,598✔
96
            }
2,598✔
97
            updated = true;
2,640✔
98
        }
2,640✔
99
        ndx++;
2,964✔
100
    }
2,964✔
101

102
    // New elements must be inserted in dst.
103
    while (ndx < len_src - suffix_len) {
7,596✔
104
        InterRealmValueConverter::ConversionResult converted_src;
3,687✔
105
        const Mixed src_value = src.get_any(ndx);
3,687✔
106
        cmp_src_to_dst(src_value, Mixed{}, &converted_src, update_out);
3,687✔
107
        size_t dst_ndx_to_insert = dst.size() - suffix_len;
3,687✔
108
        if (converted_src.requires_new_embedded_object) {
3,687✔
109
            REALM_ASSERT(dst_as_link_list); // this is the only type of list that supports embedded objects
432✔
110
            Obj embedded = dst_as_link_list->create_and_insert_linked_object(dst_ndx_to_insert);
432✔
111
            track_new_embedded(converted_src.src_embedded_to_check, embedded);
432✔
112
        }
432✔
113
        else if (is_link_to_deleted_object(src_value, converted_src.converted_value)) {
3,255✔
114
            // ignore trying to insert a link to a object which no longer exists
115
        }
60✔
116
        else {
3,195✔
117
            dst.insert_any(dst_ndx_to_insert, converted_src.converted_value);
3,195✔
118
        }
3,195✔
119
        ndx++;
3,687✔
120
        updated = true;
3,687✔
121
    }
3,687✔
122
    // Excess elements must be removed from ll_dst.
123
    if (dst.size() > len_src) {
3,909✔
124
        dst.remove(len_src - suffix_len, dst.size() - suffix_len);
1,056✔
125
        updated = true;
1,056✔
126
    }
1,056✔
127

128
    while (dst_to_erase.size()) {
3,951✔
129
        size_t ndx_to_remove = dst_to_erase.back();
42✔
130
        dst_as_link_list ? dst_as_link_list->remove(ndx_to_remove) : dst_as_lst_mixed->remove(ndx_to_remove);
42✔
131
        dst_to_erase.pop_back();
42✔
132
    }
42✔
133
    if (updated && update_out) {
3,909✔
134
        *update_out = updated;
1,881✔
135
    }
1,881✔
136
}
3,909✔
137

138
void InterRealmValueConverter::copy_set(const SetBase& src, SetBase& dst, bool* update_out) const
139
{
11,214✔
140
    std::vector<size_t> sorted_src, sorted_dst, to_insert, to_delete;
11,214✔
141
    constexpr bool ascending = true;
11,214✔
142
    // the implementation could be storing elements in sorted order, but
143
    // we don't assume that here.
144
    src.sort(sorted_src, ascending);
11,214✔
145
    dst.sort(sorted_dst, ascending);
11,214✔
146

147
    size_t dst_ndx = 0;
11,214✔
148
    size_t src_ndx = 0;
11,214✔
149
    while (src_ndx < sorted_src.size()) {
20,598✔
150
        if (dst_ndx == sorted_dst.size()) {
10,854✔
151
            // if we have reached the end of the dst items, all remaining
152
            // src items should be added
153
            while (src_ndx < sorted_src.size()) {
4,050✔
154
                to_insert.push_back(sorted_src[src_ndx++]);
2,580✔
155
            }
2,580✔
156
            break;
1,470✔
157
        }
1,470✔
158
        size_t ndx_in_src = sorted_src[src_ndx];
9,384✔
159
        Mixed src_val = src.get_any(ndx_in_src);
9,384✔
160
        while (dst_ndx < sorted_dst.size()) {
10,314✔
161
            size_t ndx_in_dst = sorted_dst[dst_ndx];
9,834✔
162

163
            int cmp = cmp_src_to_dst(src_val, dst.get_any(ndx_in_dst), nullptr, update_out);
9,834✔
164
            if (cmp == 0) {
9,834✔
165
                // equal: advance both src and dst
166
                ++dst_ndx;
8,232✔
167
                ++src_ndx;
8,232✔
168
                break;
8,232✔
169
            }
8,232✔
170
            else if (cmp < 0) {
1,602✔
171
                // src < dst: insert src, advance src only
172
                to_insert.push_back(ndx_in_src);
672✔
173
                ++src_ndx;
672✔
174
                break;
672✔
175
            }
672✔
176
            else {
930✔
177
                // src > dst: delete dst, advance only dst
178
                to_delete.push_back(ndx_in_dst);
930✔
179
                ++dst_ndx;
930✔
180
                continue;
930✔
181
            }
930✔
182
        }
9,834✔
183
    }
9,384✔
184
    while (dst_ndx < sorted_dst.size()) {
12,480✔
185
        to_delete.push_back(sorted_dst[dst_ndx++]);
1,266✔
186
    }
1,266✔
187

188
    std::sort(to_delete.begin(), to_delete.end());
11,214✔
189
    for (auto it = to_delete.rbegin(); it != to_delete.rend(); ++it) {
13,410✔
190
        dst.erase_any(dst.get_any(*it));
2,196✔
191
    }
2,196✔
192
    for (auto ndx : to_insert) {
11,214✔
193
        InterRealmValueConverter::ConversionResult converted_src;
3,252✔
194
        cmp_src_to_dst(src.get_any(ndx), Mixed{}, &converted_src, update_out);
3,252✔
195
        // we do not support a set of embedded objects
196
        REALM_ASSERT(!converted_src.requires_new_embedded_object);
3,252✔
197
        dst.insert_any(converted_src.converted_value);
3,252✔
198
    }
3,252✔
199

200
    if (update_out && (to_delete.size() || to_insert.size())) {
11,214✔
201
        *update_out = true;
1,938✔
202
    }
1,938✔
203
}
11,214✔
204

205
void InterRealmValueConverter::copy_dictionary(const Dictionary& src, Dictionary& dst, bool* update_out) const
206
{
11,664✔
207
    std::vector<size_t> to_insert, to_delete;
11,664✔
208

209
    size_t dst_ndx = 0;
11,664✔
210
    size_t src_ndx = 0;
11,664✔
211
    while (src_ndx < src.size()) {
23,934✔
212
        if (dst_ndx == dst.size()) {
13,440✔
213
            // if we have reached the end of the dst items, all remaining
214
            // src items should be added
215
            while (src_ndx < src.size()) {
3,084✔
216
                to_insert.push_back(src_ndx++);
1,914✔
217
            }
1,914✔
218
            break;
1,170✔
219
        }
1,170✔
220

221
        auto src_val = src.get_pair(src_ndx);
12,270✔
222
        while (dst_ndx < dst.size()) {
12,510✔
223
            auto dst_val = dst.get_pair(dst_ndx);
12,474✔
224
            int cmp = src_val.first.compare(dst_val.first);
12,474✔
225
            if (cmp == 0) {
12,474✔
226
                // Check if the values differ
227
                if (cmp_src_to_dst(src_val.second, dst_val.second, nullptr, update_out)) {
11,424✔
228
                    // values are different - modify destination, advance both
229
                    to_insert.push_back(src_ndx);
1,410✔
230
                }
1,410✔
231
                // keys and values equal: advance both src and dst
232
                ++dst_ndx;
11,424✔
233
                ++src_ndx;
11,424✔
234
                break;
11,424✔
235
            }
11,424✔
236
            else if (cmp < 0) {
1,050✔
237
                // src < dst: insert src, advance src only
238
                to_insert.push_back(src_ndx++);
810✔
239
                break;
810✔
240
            }
810✔
241
            else {
240✔
242
                // src > dst: delete dst, advance only dst
243
                to_delete.push_back(dst_ndx++);
240✔
244
                continue;
240✔
245
            }
240✔
246
        }
12,474✔
247
    }
12,270✔
248
    // at this point, we've gone through all src items but still have dst items
249
    // oustanding; these should all be deleted because they are not in src
250
    while (dst_ndx < dst.size()) {
12,168✔
251
        to_delete.push_back(dst_ndx++);
504✔
252
    }
504✔
253

254
    for (auto it = to_delete.rbegin(); it != to_delete.rend(); ++it) {
12,408✔
255
        dst.erase(dst.begin() + *it);
744✔
256
    }
744✔
257
    for (auto ndx : to_insert) {
11,664✔
258
        auto pair = src.get_pair(ndx);
4,134✔
259
        InterRealmValueConverter::ConversionResult converted_val;
4,134✔
260
        cmp_src_to_dst(pair.second, Mixed{}, &converted_val, update_out);
4,134✔
261
        if (converted_val.requires_new_embedded_object) {
4,134✔
262
            Obj new_embedded = dst.create_and_insert_linked_object(pair.first);
120✔
263
            track_new_embedded(converted_val.src_embedded_to_check, new_embedded);
120✔
264
        }
120✔
265
        else {
4,014✔
266
            dst.insert(pair.first, converted_val.converted_value);
4,014✔
267
        }
4,014✔
268
    }
4,134✔
269
    if (update_out && (to_delete.size() || to_insert.size())) {
11,664✔
270
        *update_out = true;
1,632✔
271
    }
1,632✔
272
}
11,664✔
273

274
void InterRealmValueConverter::copy_value(const Obj& src_obj, Obj& dst_obj, bool* update_out)
275
{
160,794✔
276
    if (m_src_col.is_list()) {
160,794✔
277
        LstBasePtr src = src_obj.get_listbase_ptr(m_src_col);
17,028✔
278
        LstBasePtr dst = dst_obj.get_listbase_ptr(m_dst_col);
17,028✔
279
        copy_list(*src, *dst, update_out);
17,028✔
280
    }
17,028✔
281
    else if (m_src_col.is_dictionary()) {
143,766✔
282
        Dictionary src = src_obj.get_dictionary(m_src_col);
11,664✔
283
        Dictionary dst = dst_obj.get_dictionary(m_dst_col);
11,664✔
284
        copy_dictionary(src, dst, update_out);
11,664✔
285
    }
11,664✔
286
    else if (m_src_col.is_set()) {
132,102✔
287
        SetBasePtr src = src_obj.get_setbase_ptr(m_src_col);
11,214✔
288
        SetBasePtr dst = dst_obj.get_setbase_ptr(m_dst_col);
11,214✔
289
        copy_set(*src, *dst, update_out);
11,214✔
290
    }
11,214✔
291
    else {
120,888✔
292
        REALM_ASSERT(!m_src_col.is_collection());
120,888✔
293
        // nested collections
294
        auto src_mixed = src_obj.get_any(m_src_col);
120,888✔
295
        if (src_mixed.is_type(type_List)) {
120,888✔
296
            dst_obj.set_collection(m_dst_col, CollectionType::List);
246✔
297
            Lst<Mixed> src_list{src_obj, m_src_col};
246✔
298
            Lst<Mixed> dst_list{dst_obj, m_dst_col};
246✔
299
            handle_list_in_mixed(src_list, dst_list);
246✔
300
        }
246✔
301
        else if (src_mixed.is_type(type_Dictionary)) {
120,642✔
302
            dst_obj.set_collection(m_dst_col, CollectionType::Dictionary);
96✔
303
            Dictionary src_dict{src_obj, m_src_col};
96✔
304
            Dictionary dst_dict{dst_obj, m_dst_col};
96✔
305
            handle_dictionary_in_mixed(src_dict, dst_dict);
96✔
306
        }
96✔
307
        else if (src_mixed.is_type(type_Set)) {
120,546✔
UNCOV
308
            REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
309
        }
×
310
        else {
120,546✔
311
            InterRealmValueConverter::ConversionResult converted_src;
120,546✔
312
            auto dst_mixed = dst_obj.get_any(m_dst_col);
120,546✔
313
            if (cmp_src_to_dst(src_mixed, dst_mixed, &converted_src, update_out)) {
120,546✔
314
                if (converted_src.requires_new_embedded_object) {
71,070✔
315
                    Obj new_embedded = dst_obj.create_and_set_linked_object(m_dst_col);
498✔
316
                    track_new_embedded(converted_src.src_embedded_to_check, new_embedded);
498✔
317
                }
498✔
318
                else {
70,572✔
319
                    dst_obj.set_any(m_dst_col, converted_src.converted_value);
70,572✔
320
                }
70,572✔
321
            }
71,070✔
322
        }
120,546✔
323
    }
120,888✔
324
}
160,794✔
325

326
//
327
// Handle collections in mixed. A collection can have N nested levels (except for Sets). And these levels can be
328
// nested in arbitrary way (eg a List within a Dictionary or viceversa). In order to try to merge server changes with
329
// client changes, the algorithm needs to go through each single element in the collection, check its type and perform
330
// the most appropriate action in order to miminize the number of notifications triggered.
331
//
332
void InterRealmValueConverter::handle_list_in_mixed(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list) const
333
{
408✔
334
    const auto sz = (int)std::min(src_list.size(), dst_list.size());
408✔
335
    int left = 0;
408✔
336

337
    // find fist not matching element from beginning
338
    while (left < sz) {
660✔
339
        auto src_any = src_list.get_any(left);
426✔
340
        auto dst_any = dst_list.get_any(left);
426✔
341
        if (src_any != dst_any)
426✔
342
            break;
84✔
343
        if (is_collection(src_any) &&
342✔
344
            !check_matching_list(src_list, dst_list, left, left, to_collection_type(src_any)))
342✔
345
            break;
90✔
346
        left += 1;
252✔
347
    }
252✔
348

349
    // find first not matching element from end
350
    int right_src = (int)src_list.size() - 1;
408✔
351
    int right_dst = (int)dst_list.size() - 1;
408✔
352
    while (right_src >= left && right_dst >= left) {
432✔
353
        auto src_any = src_list.get_any(right_src);
180✔
354
        auto dst_any = dst_list.get_any(right_dst);
180✔
355
        if (src_any != dst_any)
180✔
356
            break;
96✔
357
        if (is_collection(src_any) &&
84✔
358
            !check_matching_list(src_list, dst_list, right_src, right_dst, to_collection_type(src_any)))
84✔
359
            break;
60✔
360
        right_src -= 1;
24✔
361
        right_dst -= 1;
24✔
362
    }
24✔
363

364
    // Replace all different elements in [left, right]
365
    auto left_src = left;
408✔
366
    auto left_dst = left;
408✔
367
    while (left_src <= right_src && left_dst <= right_dst) {
678✔
368
        auto src_any = src_list.get_any(left_src);
270✔
369
        auto dst_any = dst_list.get_any(left_dst);
270✔
370

371
        if (is_collection(src_any)) {
270✔
372
            auto coll_type = to_collection_type(src_any);
150✔
373

374
            if (!dst_any.is_type(src_any.get_type())) {
150✔
375
                // Mixed vs Collection
376
                dst_list.set_collection(left_dst, coll_type);
24✔
377
                copy_list_in_mixed(src_list, dst_list, left_src, left_dst, coll_type);
24✔
378
            }
24✔
379
            else if (!check_matching_list(src_list, dst_list, left_src, left_dst, coll_type)) {
126✔
380
                // Collection vs Collection
381
                dst_list.set_any(left_dst, src_any);
114✔
382
                copy_list_in_mixed(src_list, dst_list, left_src, left_dst, coll_type);
114✔
383
            }
114✔
384
        }
150✔
385
        else if (dst_any != src_any) {
120✔
386
            // Mixed vs Mixed
387
            InterRealmValueConverter::ConversionResult converted_src;
96✔
388
            bool update_out = false;
96✔
389
            cmp_src_to_dst(src_any, dst_any, &converted_src, &update_out);
96✔
390
            if (update_out) {
96✔
391
                // we do not support embedded objects
392
                REALM_ASSERT(!converted_src.requires_new_embedded_object);
96✔
393
                dst_list.set_any(left_dst, converted_src.converted_value);
96✔
394
            }
96✔
UNCOV
395
            else {
×
UNCOV
396
                dst_list.set_any(left_dst, src_any);
×
UNCOV
397
            }
×
398
        }
96✔
399
        left_src += 1;
270✔
400
        left_dst += 1;
270✔
401
    }
270✔
402

403
    // remove dst elements not present in src
404
    if (right_dst > right_src) {
408✔
405
        while (right_dst > right_src)
90✔
406
            dst_list.remove(right_dst--);
54✔
407
    }
36✔
408

409
    // append remainig src into dst
410
    for (int i = left_src; i <= right_src; ++i) {
798✔
411
        auto src_any = src_list.get(i);
390✔
412
        if (is_collection(src_any)) {
390✔
413
            auto coll_type = to_collection_type(src_any);
72✔
414
            dst_list.insert_collection(i, coll_type);
72✔
415
            copy_list_in_mixed(src_list, dst_list, i, i, coll_type);
72✔
416
        }
72✔
417
        else {
318✔
418
            InterRealmValueConverter::ConversionResult converted_src;
318✔
419
            bool update_out = false;
318✔
420
            cmp_src_to_dst(src_any, Mixed{}, &converted_src, &update_out);
318✔
421
            if (update_out) {
318✔
422
                // we do not support embedded objects
423
                REALM_ASSERT(!converted_src.requires_new_embedded_object);
318✔
424
                dst_list.insert_any(i, converted_src.converted_value);
318✔
425
            }
318✔
UNCOV
426
            else {
×
UNCOV
427
                dst_list.insert_any(i, src_any);
×
428
            }
×
429
        }
318✔
430
    }
390✔
431
}
408✔
432

433
void InterRealmValueConverter::handle_dictionary_in_mixed(Dictionary& src_dictionary,
434
                                                          Dictionary& dst_dictionary) const
435
{
222✔
436
    std::vector<size_t> to_insert, to_delete;
222✔
437
    size_t src_ndx = 0, dst_ndx = 0;
222✔
438
    while (src_ndx < src_dictionary.size() && dst_ndx < dst_dictionary.size()) {
504✔
439
        const auto [key_src, src_any] = src_dictionary.get_pair(src_ndx);
282✔
440
        const auto [key_dst, dst_any] = dst_dictionary.get_pair(dst_ndx);
282✔
441

442
        auto cmp = key_src.compare(key_dst);
282✔
443
        if (cmp == 0) {
282✔
444
            if (src_any != dst_any) {
174✔
445
                to_insert.push_back(src_ndx);
18✔
446
            }
18✔
447
            else if (is_collection(src_any) &&
156✔
448
                     !check_matching_dictionary(src_dictionary, dst_dictionary, key_src.get_string(),
156✔
449
                                                to_collection_type(src_any))) {
72✔
450
                to_insert.push_back(src_ndx);
18✔
451
            }
18✔
452
            src_ndx += 1;
174✔
453
            dst_ndx += 1;
174✔
454
        }
174✔
455
        else if (cmp < 0) {
108✔
456
            to_insert.push_back(src_ndx);
60✔
457
            src_ndx += 1;
60✔
458
        }
60✔
459
        else {
48✔
460
            to_delete.push_back(dst_ndx);
48✔
461
            dst_ndx += 1;
48✔
462
        }
48✔
463
    }
282✔
464

465
    // append src to dst
466
    while (src_ndx < src_dictionary.size()) {
402✔
467
        to_insert.push_back(src_ndx);
180✔
468
        src_ndx += 1;
180✔
469
    }
180✔
470

471
    // delete everything that did not match passed src.size()
472
    while (dst_ndx < dst_dictionary.size()) {
222✔
UNCOV
473
        to_delete.push_back(dst_ndx);
×
UNCOV
474
        dst_ndx += 1;
×
UNCOV
475
    }
×
476

477
    // delete all the non matching keys
478
    while (!to_delete.empty()) {
270✔
479
        dst_dictionary.erase(dst_dictionary.begin() + to_delete.back());
48✔
480
        to_delete.pop_back();
48✔
481
    }
48✔
482

483
    // insert into dst
484
    for (const auto pos : to_insert) {
276✔
485
        const auto [key, any] = src_dictionary.get_pair(pos);
276✔
486
        if (is_collection(any)) {
276✔
487
            auto coll_type = to_collection_type(any);
78✔
488
            dst_dictionary.insert_collection(key.get_string(), coll_type);
78✔
489
            copy_dictionary_in_mixed(src_dictionary, dst_dictionary, key.get_string(), coll_type);
78✔
490
        }
78✔
491
        else {
198✔
492

493
            InterRealmValueConverter::ConversionResult converted_src;
198✔
494
            bool update_out = false;
198✔
495
            cmp_src_to_dst(any, Mixed{}, &converted_src, &update_out);
198✔
496
            if (update_out) {
198✔
497
                // we do not support embedded objects
498
                REALM_ASSERT(!converted_src.requires_new_embedded_object);
198✔
499
                dst_dictionary.insert(key, converted_src.converted_value);
198✔
500
            }
198✔
UNCOV
501
            else
×
UNCOV
502
                dst_dictionary.insert(key, any);
×
503
        }
198✔
504
    }
276✔
505
}
222✔
506

507
bool InterRealmValueConverter::check_matching_list(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list, size_t ndx_src,
508
                                                   size_t ndx_dst, CollectionType type) const
509
{
372✔
510

511
    if (type == CollectionType::List) {
372✔
512
        auto nested_src_list = src_list.get_list(ndx_src);
270✔
513
        auto nested_dst_list = dst_list.get_list(ndx_dst);
270✔
514
        auto size_src = nested_src_list->size();
270✔
515
        auto size_dst = nested_dst_list->size();
270✔
516
        if (size_src != size_dst)
270✔
517
            return false;
84✔
518
        for (size_t i = 0; i < size_src; ++i) {
312✔
519
            auto src_mixed = nested_src_list->get_any(i);
234✔
520
            auto dst_mixed = nested_dst_list->get_any(i);
234✔
521
            if (src_mixed != dst_mixed)
234✔
522
                return false;
108✔
523
        }
234✔
524
    }
186✔
525
    else if (type == CollectionType::Dictionary) {
102✔
526
        auto nested_src_dictionary = src_list.get_dictionary(ndx_src);
102✔
527
        auto nested_dst_dictionary = dst_list.get_dictionary(ndx_dst);
102✔
528
        auto size_src = nested_src_dictionary->size();
102✔
529
        auto size_dst = nested_dst_dictionary->size();
102✔
530
        if (size_src != size_dst)
102✔
531
            return false;
12✔
532
        for (size_t i = 0; i < size_src; ++i) {
132✔
533
            auto [src_key, src_mixed] = nested_src_dictionary->get_pair(i);
102✔
534
            auto [dst_key, dst_mixed] = nested_dst_dictionary->get_pair(i);
102✔
535
            if (src_key != dst_key)
102✔
536
                return false;
36✔
537
            if (src_mixed != dst_mixed)
66✔
538
                return false;
24✔
539
        }
66✔
540
    }
90✔
541
    return true;
108✔
542
}
372✔
543

544
bool InterRealmValueConverter::check_matching_dictionary(const Dictionary& src_dictionary,
545
                                                         const Dictionary& dst_dictionary, StringData key,
546
                                                         CollectionType type) const
547
{
72✔
548
    if (type == CollectionType::List) {
72✔
549
        auto n_src_list = src_dictionary.get_list(key);
18✔
550
        auto n_dst_list = dst_dictionary.get_list(key);
18✔
551
        auto size_src = n_src_list->size();
18✔
552
        auto size_dst = n_dst_list->size();
18✔
553
        if (size_src != size_dst)
18✔
UNCOV
554
            return false;
×
555
        for (size_t i = 0; i < size_src; ++i) {
36✔
556
            auto src_mixed = n_src_list->get_any(i);
18✔
557
            auto dst_mixed = n_dst_list->get_any(i);
18✔
558
            if (src_mixed != dst_mixed)
18✔
UNCOV
559
                return false;
×
560
        }
18✔
561
    }
18✔
562
    else if (type == CollectionType::Dictionary) {
54✔
563
        auto n_src_dictionary = src_dictionary.get_dictionary(key);
54✔
564
        auto n_dst_dictionary = dst_dictionary.get_dictionary(key);
54✔
565
        auto size_src = n_src_dictionary->size();
54✔
566
        auto size_dst = n_dst_dictionary->size();
54✔
567
        if (size_src != size_dst)
54✔
568
            return false;
6✔
569
        for (size_t i = 0; i < size_src; ++i) {
84✔
570
            auto [src_key, src_mixed] = n_src_dictionary->get_pair(i);
48✔
571
            auto [dst_key, dst_mixed] = n_dst_dictionary->get_pair(i);
48✔
572
            if (src_key != dst_key)
48✔
573
                return false;
6✔
574
            if (src_mixed != dst_mixed)
42✔
575
                return false;
6✔
576
        }
42✔
577
    }
48✔
578
    return true;
54✔
579
}
72✔
580

581
void InterRealmValueConverter::copy_list_in_mixed(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list, size_t ndx_src,
582
                                                  size_t ndx_dst, CollectionType type) const
583
{
210✔
584
    if (type == CollectionType::List) {
210✔
585
        auto n_src_list = src_list.get_list(ndx_src);
132✔
586
        auto n_dst_list = dst_list.get_list(ndx_dst);
132✔
587
        handle_list_in_mixed(*n_src_list, *n_dst_list);
132✔
588
    }
132✔
589
    else if (type == CollectionType::Dictionary) {
78✔
590
        auto n_src_dict = src_list.get_dictionary(ndx_src);
78✔
591
        auto n_dst_dict = dst_list.get_dictionary(ndx_dst);
78✔
592
        handle_dictionary_in_mixed(*n_src_dict, *n_dst_dict);
78✔
593
    }
78✔
594
}
210✔
595

596
void InterRealmValueConverter::copy_dictionary_in_mixed(const Dictionary& src_dictionary, Dictionary& dst_dictionary,
597
                                                        StringData key, CollectionType type) const
598
{
78✔
599
    if (type == CollectionType::List) {
78✔
600
        auto n_src_list = src_dictionary.get_list(key);
30✔
601
        auto n_dst_list = dst_dictionary.get_list(key);
30✔
602
        handle_list_in_mixed(*n_src_list, *n_dst_list);
30✔
603
    }
30✔
604
    else if (type == CollectionType::Dictionary) {
48✔
605
        auto n_src_dictionary = src_dictionary.get_dictionary(key);
48✔
606
        auto n_dst_dictionary = dst_dictionary.get_dictionary(key);
48✔
607
        handle_dictionary_in_mixed(*n_src_dictionary, *n_dst_dictionary);
48✔
608
    }
48✔
609
}
78✔
610

611
bool InterRealmValueConverter::is_collection(Mixed mixed) const
612
{
1,518✔
613
    REALM_ASSERT_DEBUG(!mixed.is_type(type_Set));
1,518✔
614
    return mixed.is_type(type_List, type_Dictionary);
1,518✔
615
}
1,518✔
616

617
CollectionType InterRealmValueConverter::to_collection_type(Mixed mixed) const
618
{
618✔
619
    const auto mixed_type = mixed.get_type();
618✔
620
    if (mixed_type == type_List)
618✔
621
        return CollectionType::List;
372✔
622
    else if (mixed_type == type_Dictionary)
246✔
623
        return CollectionType::Dictionary;
246✔
624
    REALM_UNREACHABLE();
UNCOV
625
}
×
626

627
// If an embedded object is encountered, add it to a list of embedded objects to process.
628
// This relies on the property that embedded objects only have one incoming link
629
// otherwise there could be an infinite loop while discovering embedded objects.
630
void EmbeddedObjectConverter::track(const Obj& e_src, const Obj& e_dst)
631
{
67,350✔
632
    embedded_pending.push_back({e_src, e_dst});
67,350✔
633
}
67,350✔
634

635
void EmbeddedObjectConverter::process_pending()
636
{
38,895✔
637
    util::FlatMap<TableKey, InterRealmObjectConverter> converters;
38,895✔
638

639
    while (!embedded_pending.empty()) {
106,245✔
640
        EmbeddedToCheck pending = embedded_pending.back();
67,350✔
641
        embedded_pending.pop_back();
67,350✔
642

643
        TableRef dst_table = pending.embedded_in_dst.get_table();
67,350✔
644
        TableKey dst_table_key = dst_table->get_key();
67,350✔
645
        auto it = converters.find(dst_table_key);
67,350✔
646
        if (it == converters.end()) {
67,350✔
647
            TableRef src_table = pending.embedded_in_src.get_table();
1,098✔
648
            it = converters.insert({dst_table_key, InterRealmObjectConverter{src_table, dst_table, this}}).first;
1,098✔
649
        }
1,098✔
650
        InterRealmObjectConverter& converter = it->second;
67,350✔
651
        converter.copy(pending.embedded_in_src, pending.embedded_in_dst, nullptr);
67,350✔
652
    }
67,350✔
653
}
38,895✔
654

655
InterRealmValueConverter::InterRealmValueConverter(ConstTableRef src_table, ColKey src_col, ConstTableRef dst_table,
656
                                                   ColKey dst_col, EmbeddedObjectConverter* ec)
657
    : m_src_table(src_table)
31,017✔
658
    , m_dst_table(dst_table)
31,017✔
659
    , m_src_col(src_col)
31,017✔
660
    , m_dst_col(dst_col)
31,017✔
661
    , m_embedded_converter(ec)
31,017✔
662
    , m_is_embedded_link(false)
31,017✔
663
    , m_primitive_types_only(!(src_col.get_type() == col_type_TypedLink || src_col.get_type() == col_type_Link ||
31,017✔
664
                               src_col.get_type() == col_type_Mixed))
31,017✔
665
{
62,034✔
666
    if (!m_primitive_types_only) {
62,034✔
667
        REALM_ASSERT(src_table);
10,086✔
668
        m_opposite_of_src = src_table->get_opposite_table(src_col);
10,086✔
669
        m_opposite_of_dst = dst_table->get_opposite_table(dst_col);
10,086✔
670
        REALM_ASSERT(bool(m_opposite_of_src) == bool(m_opposite_of_dst));
10,086✔
671
        if (m_opposite_of_src) {
10,086✔
672
            m_is_embedded_link = m_opposite_of_src->is_embedded();
5,502✔
673
        }
5,502✔
674
    }
10,086✔
675
}
62,034✔
676

677
void InterRealmValueConverter::track_new_embedded(const Obj& src, const Obj& dst) const
678
{
7,032✔
679
    m_embedded_converter->track(src, dst);
7,032✔
680
}
7,032✔
681

682
// convert `src` to the destination Realm and compare that value with `dst`
683
// If `converted_src_out` is provided, it will be set to the converted src value
684
int InterRealmValueConverter::cmp_src_to_dst(Mixed src, Mixed dst, ConversionResult* converted_src_out,
685
                                             bool* did_update_out) const
686
{
174,789✔
687
    int cmp = 0;
174,789✔
688
    Mixed converted_src;
174,789✔
689
    if (m_primitive_types_only || !src.is_type(type_Link, type_TypedLink)) {
174,789✔
690
        converted_src = src;
158,505✔
691
        cmp = src.compare(dst);
158,505✔
692
    }
158,505✔
693
    else if (m_opposite_of_src) {
16,284✔
694
        ObjKey src_link_key = src.get<ObjKey>();
11,796✔
695
        if (m_is_embedded_link) {
11,796✔
696
            Obj src_embedded = m_opposite_of_src->get_object(src_link_key);
7,050✔
697
            REALM_ASSERT_DEBUG(src_embedded.is_valid());
7,050✔
698
            if (dst.is_type(type_Link, type_TypedLink)) {
7,050✔
699
                cmp = 0; // no need to set this link, there is already an embedded object here
5,982✔
700
                Obj dst_embedded = m_opposite_of_dst->get_object(dst.get<ObjKey>());
5,982✔
701
                REALM_ASSERT_DEBUG(dst_embedded.is_valid());
5,982✔
702
                converted_src = dst_embedded.get_key();
5,982✔
703
                track_new_embedded(src_embedded, dst_embedded);
5,982✔
704
            }
5,982✔
705
            else {
1,068✔
706
                cmp = src.compare(dst);
1,068✔
707
                if (converted_src_out) {
1,068✔
708
                    converted_src_out->requires_new_embedded_object = true;
1,050✔
709
                    converted_src_out->src_embedded_to_check = src_embedded;
1,050✔
710
                }
1,050✔
711
            }
1,068✔
712
        }
7,050✔
713
        else {
4,746✔
714
            Obj dst_link;
4,746✔
715
            if (m_opposite_of_dst == m_opposite_of_src) {
4,746✔
716
                // if this is the same Realm, we can use the ObjKey
717
                dst_link = m_opposite_of_dst->get_object(src_link_key);
192✔
718
            }
192✔
719
            else {
4,554✔
720
                // in different Realms we create a new object
721
                if (m_opposite_of_src->get_primary_key_column()) {
4,554✔
722
                    Mixed src_link_pk = m_opposite_of_src->get_primary_key(src_link_key);
4,554✔
723
                    dst_link =
4,554✔
724
                        m_opposite_of_dst->get_object_with_primary_key(src_link_pk); // returns ObjKey{} if not found
4,554✔
725
                }
4,554✔
UNCOV
726
                else {
×
UNCOV
727
                    dst_link = m_opposite_of_dst->create_object();
×
728
                }
×
729
            }
4,554✔
730
            converted_src = dst_link.get_key();
4,746✔
731
            if (dst.is_type(type_TypedLink)) {
4,746✔
732
                cmp = converted_src.compare(dst.get<ObjKey>());
3,780✔
733
            }
3,780✔
734
            else {
966✔
735
                cmp = converted_src.compare(dst);
966✔
736
            }
966✔
737
        }
4,746✔
738
    }
11,796✔
739
    else {
4,488✔
740
        ObjLink src_link = src.get<ObjLink>();
4,488✔
741
        if (src_link.is_unresolved()) {
4,488✔
UNCOV
742
            converted_src = Mixed{}; // no need to transfer over unresolved links
×
UNCOV
743
            cmp = converted_src.compare(dst);
×
UNCOV
744
        }
×
745
        else {
4,488✔
746
            TableRef src_link_table = m_src_table->get_parent_group()->get_table(src_link.get_table_key());
4,488✔
747
            REALM_ASSERT_EX(src_link_table, src_link.get_table_key());
4,488✔
748
            TableRef dst_link_table = m_dst_table->get_parent_group()->get_table(src_link_table->get_name());
4,488✔
749
            REALM_ASSERT_EX(dst_link_table, src_link_table->get_name());
4,488✔
750
            // embedded tables should always be covered by the m_opposite_of_src case above.
751
            REALM_ASSERT_EX(!src_link_table->is_embedded(), src_link_table->get_name());
4,488✔
752
            // regular table, convert by pk
753
            if (src_link_table->get_primary_key_column()) {
4,488✔
754
                Mixed src_pk = src_link_table->get_primary_key(src_link.get_obj_key());
4,488✔
755
                if (Obj dst_link = dst_link_table->get_object_with_primary_key(src_pk)) {
4,488✔
756
                    converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
4,416✔
757
                }
4,416✔
758
            }
4,488✔
UNCOV
759
            else if (src_link_table == dst_link_table) {
×
760
                // no pk, but this is the same Realm, so convert by ObjKey
UNCOV
761
                Obj dst_link = dst_link_table->get_object(src_link.get_obj_key());
×
UNCOV
762
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
UNCOV
763
            }
×
UNCOV
764
            else {
×
765
                // no pk, and different Realm, create an object
UNCOV
766
                Obj dst_link = dst_link_table->create_object();
×
UNCOV
767
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
UNCOV
768
            }
×
769
            cmp = converted_src.compare(dst);
4,488✔
770
        }
4,488✔
771
    }
4,488✔
772
    if (converted_src_out) {
174,789✔
773
        converted_src_out->converted_value = converted_src;
135,195✔
774
    }
135,195✔
775
    if (did_update_out && cmp) {
174,789✔
776
        *did_update_out = true;
17,313✔
777
    }
17,313✔
778
    return cmp;
174,789✔
779
}
174,789✔
780

781
InterRealmObjectConverter::InterRealmObjectConverter(ConstTableRef table_src, TableRef table_dst,
782
                                                     EmbeddedObjectConverter* embedded_tracker)
783
    : m_embedded_tracker(embedded_tracker)
13,524✔
784
{
27,048✔
785
    m_columns_cache.reserve(table_src->get_column_count());
27,048✔
786
    ColKey pk_col = table_src->get_primary_key_column();
27,048✔
787
    for (ColKey col_key_src : table_src->get_column_keys()) {
86,610✔
788
        if (col_key_src == pk_col)
86,610✔
789
            continue;
25,950✔
790
        StringData col_name = table_src->get_column_name(col_key_src);
60,660✔
791
        ColKey col_key_dst = table_dst->get_column_key(col_name);
60,660✔
792
        REALM_ASSERT(col_key_dst);
60,660✔
793
        m_columns_cache.emplace_back(table_src, col_key_src, table_dst, col_key_dst, m_embedded_tracker);
60,660✔
794
    }
60,660✔
795
}
27,048✔
796

797
void InterRealmObjectConverter::copy(const Obj& src, Obj& dst, bool* update_out)
798
{
104,958✔
799
    for (auto& column : m_columns_cache) {
159,420✔
800
        column.copy_value(src, dst, update_out);
159,420✔
801
    }
159,420✔
802
}
104,958✔
803

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

© 2025 Coveralls, Inc