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

realm / realm-core / 2306

10 May 2024 05:50PM UTC coverage: 90.837% (-0.2%) from 91.065%
2306

push

Evergreen

danieltabacaru
Add back ability to format Objective-C code

102110 of 181070 branches covered (56.39%)

214623 of 236272 relevant lines covered (90.84%)

5666944.3 hits per line

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

96.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
{
15,804✔
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;
15,804✔
42
    const size_t len_src = src.size();
15,804✔
43
    const size_t len_dst_orig = dst.size();
15,804✔
44
    size_t len_min = std::min(len_src, len_dst_orig);
15,804✔
45

46
    size_t ndx = 0;
15,804✔
47
    size_t suffix_len = 0;
15,804✔
48

49
    while (ndx < len_min && cmp_src_to_dst(src.get_any(ndx), dst.get_any(ndx), nullptr, update_out) == 0) {
29,022✔
50
        ndx++;
13,218✔
51
    }
13,218✔
52

53
    if (ndx == len_src && len_src == len_dst_orig) {
15,804✔
54
        // all are equal, early out
55
        if (update_out) {
12,441✔
56
            *update_out = false;
6,237✔
57
        }
6,237✔
58
        return;
12,441✔
59
    }
12,441✔
60

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

63
    while (suffix_len < suffix_len_max &&
3,837✔
64
           cmp_src_to_dst(src.get_any(len_src - 1 - suffix_len), dst.get_any(len_dst_orig - 1 - suffix_len), nullptr,
3,837✔
65
                          update_out) == 0) {
1,710✔
66
        suffix_len++;
474✔
67
    }
474✔
68

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

71
    auto dst_as_link_list = dynamic_cast<LnkLst*>(&dst);
3,363✔
72
    auto dst_as_lst_mixed = dynamic_cast<Lst<Mixed>*>(&dst);
3,363✔
73
    REALM_ASSERT(!dst_as_lst_mixed);
3,363✔
74
    auto is_link_to_deleted_object = [&](const Mixed& converted_value) -> bool {
5,277✔
75
        return dst_as_link_list && converted_value.is_null();
5,277✔
76
    };
5,277✔
77

78
    std::vector<size_t> dst_to_erase;
3,363✔
79
    for (size_t i = 0; i < len_min; i++) {
5,925✔
80
        InterRealmValueConverter::ConversionResult converted_src;
2,562✔
81
        const Mixed src_value = src.get_any(ndx);
2,562✔
82
        if (cmp_src_to_dst(src_value, dst.get_any(ndx), &converted_src, update_out)) {
2,562✔
83
            if (converted_src.requires_new_embedded_object) {
2,286✔
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);
×
87
            }
×
88
            else if (is_link_to_deleted_object(converted_src.converted_value)) {
2,286✔
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);
24✔
93
            }
24✔
94
            else {
2,262✔
95
                dst.set_any(ndx, converted_src.converted_value);
2,262✔
96
            }
2,262✔
97
            updated = true;
2,286✔
98
        }
2,286✔
99
        ndx++;
2,562✔
100
    }
2,562✔
101

102
    // New elements must be inserted in dst.
103
    while (ndx < len_src - suffix_len) {
6,786✔
104
        InterRealmValueConverter::ConversionResult converted_src;
3,423✔
105
        const Mixed src_value = src.get_any(ndx);
3,423✔
106
        cmp_src_to_dst(src_value, Mixed{}, &converted_src, update_out);
3,423✔
107
        size_t dst_ndx_to_insert = dst.size() - suffix_len;
3,423✔
108
        if (converted_src.requires_new_embedded_object) {
3,423✔
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(converted_src.converted_value)) {
2,991✔
114
            // ignore trying to insert a link to a object which no longer exists
115
        }
36✔
116
        else {
2,955✔
117
            dst.insert_any(dst_ndx_to_insert, converted_src.converted_value);
2,955✔
118
        }
2,955✔
119
        ndx++;
3,423✔
120
        updated = true;
3,423✔
121
    }
3,423✔
122
    // Excess elements must be removed from ll_dst.
123
    if (dst.size() > len_src) {
3,363✔
124
        dst.remove(len_src - suffix_len, dst.size() - suffix_len);
810✔
125
        updated = true;
810✔
126
    }
810✔
127

128
    while (dst_to_erase.size()) {
3,387✔
129
        size_t ndx_to_remove = dst_to_erase.back();
24✔
130
        dst_as_link_list->remove(ndx_to_remove);
24✔
131
        dst_to_erase.pop_back();
24✔
132
    }
24✔
133
    if (updated && update_out) {
3,363✔
134
        *update_out = updated;
1,449✔
135
    }
1,449✔
136
}
3,363✔
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
{
10,722✔
207
    std::vector<size_t> to_insert, to_delete;
10,722✔
208

209
    size_t dst_ndx = 0;
10,722✔
210
    size_t src_ndx = 0;
10,722✔
211
    while (src_ndx < src.size()) {
21,822✔
212
        if (dst_ndx == dst.size()) {
12,138✔
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()) {
2,772✔
216
                to_insert.push_back(src_ndx++);
1,734✔
217
            }
1,734✔
218
            break;
1,038✔
219
        }
1,038✔
220

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

253
    for (auto it = to_delete.rbegin(); it != to_delete.rend(); ++it) {
11,232✔
254
        dst.erase(dst.begin() + *it);
510✔
255
    }
510✔
256
    for (auto ndx : to_insert) {
10,722✔
257
        auto pair = src.get_pair(ndx);
3,840✔
258
        InterRealmValueConverter::ConversionResult converted_val;
3,840✔
259
        cmp_src_to_dst(pair.second, Mixed{}, &converted_val, update_out);
3,840✔
260
        if (converted_val.requires_new_embedded_object) {
3,840✔
261
            Obj new_embedded = dst.create_and_insert_linked_object(pair.first);
120✔
262
            track_new_embedded(converted_val.src_embedded_to_check, new_embedded);
120✔
263
        }
120✔
264
        else {
3,720✔
265
            dst.insert(pair.first, converted_val.converted_value);
3,720✔
266
        }
3,720✔
267
    }
3,840✔
268
    if (update_out && (to_delete.size() || to_insert.size())) {
10,722✔
269
        *update_out = true;
1,314✔
270
    }
1,314✔
271
}
10,722✔
272

273
void InterRealmValueConverter::copy_value(const Obj& src_obj, Obj& dst_obj, bool* update_out)
274
{
161,628✔
275
    if (m_src_col.is_list()) {
161,628✔
276
        LstBasePtr src = src_obj.get_listbase_ptr(m_src_col);
16,158✔
277
        LstBasePtr dst = dst_obj.get_listbase_ptr(m_dst_col);
16,158✔
278
        if (src->get_data_type() == type_Mixed) {
16,158✔
279
            Lst<Mixed> src_list{src_obj, m_src_col};
1,584✔
280
            Lst<Mixed> dst_list{dst_obj, m_dst_col};
1,584✔
281
            return handle_list_in_mixed(src_list, dst_list);
1,584✔
282
        }
1,584✔
283
        copy_list(*src, *dst, update_out);
14,574✔
284
    }
14,574✔
285
    else if (m_src_col.is_dictionary()) {
145,470✔
286
        Dictionary src = src_obj.get_dictionary(m_src_col);
12,402✔
287
        Dictionary dst = dst_obj.get_dictionary(m_dst_col);
12,402✔
288
        if (src.get_value_data_type() == type_Mixed) {
12,402✔
289
            return handle_dictionary_in_mixed(src, dst);
1,680✔
290
        }
1,680✔
291
        copy_dictionary(src, dst, update_out);
10,722✔
292
    }
10,722✔
293
    else if (m_src_col.is_set()) {
133,068✔
294
        SetBasePtr src = src_obj.get_setbase_ptr(m_src_col);
11,214✔
295
        SetBasePtr dst = dst_obj.get_setbase_ptr(m_dst_col);
11,214✔
296
        copy_set(*src, *dst, update_out);
11,214✔
297
    }
11,214✔
298
    else {
121,854✔
299
        REALM_ASSERT(!m_src_col.is_collection());
121,854✔
300
        // nested collections
301
        auto src_mixed = src_obj.get_any(m_src_col);
121,854✔
302
        if (src_mixed.is_type(type_List)) {
121,854✔
303
            dst_obj.set_collection(m_dst_col, CollectionType::List);
276✔
304
            Lst<Mixed> src_list{src_obj, m_src_col};
276✔
305
            Lst<Mixed> dst_list{dst_obj, m_dst_col};
276✔
306
            handle_list_in_mixed(src_list, dst_list);
276✔
307
        }
276✔
308
        else if (src_mixed.is_type(type_Dictionary)) {
121,578✔
309
            dst_obj.set_collection(m_dst_col, CollectionType::Dictionary);
246✔
310
            Dictionary src_dict{src_obj, m_src_col};
246✔
311
            Dictionary dst_dict{dst_obj, m_dst_col};
246✔
312
            handle_dictionary_in_mixed(src_dict, dst_dict);
246✔
313
        }
246✔
314
        else if (src_mixed.is_type(type_Set)) {
121,332✔
315
            REALM_COMPILER_HINT_UNREACHABLE();
×
316
        }
×
317
        else {
121,332✔
318
            InterRealmValueConverter::ConversionResult converted_src;
121,332✔
319
            auto dst_mixed = dst_obj.get_any(m_dst_col);
121,332✔
320
            if (cmp_src_to_dst(src_mixed, dst_mixed, &converted_src, update_out)) {
121,332✔
321
                if (converted_src.requires_new_embedded_object) {
71,226✔
322
                    Obj new_embedded = dst_obj.create_and_set_linked_object(m_dst_col);
498✔
323
                    track_new_embedded(converted_src.src_embedded_to_check, new_embedded);
498✔
324
                }
498✔
325
                else {
70,728✔
326
                    dst_obj.set_any(m_dst_col, converted_src.converted_value);
70,728✔
327
                }
70,728✔
328
            }
71,226✔
329
        }
121,332✔
330
    }
121,854✔
331
}
161,628✔
332

333
void InterRealmValueConverter::copy_list(const LstBase& src_list, LstBase& dst_list) const
334
{
1,422✔
335
    auto src_as_lst_mixed = dynamic_cast<const Lst<Mixed>*>(&src_list);
1,422✔
336
    auto dst_as_lst_mixed = dynamic_cast<Lst<Mixed>*>(&dst_list);
1,422✔
337
    REALM_ASSERT(bool(src_as_lst_mixed) == bool(dst_as_lst_mixed));
1,422✔
338
    if (src_as_lst_mixed) {
1,422✔
339
        return handle_list_in_mixed(*src_as_lst_mixed, *dst_as_lst_mixed);
192✔
340
    }
192✔
341
    copy_list(src_list, dst_list, nullptr);
1,230✔
342
}
1,230✔
343

344
//
345
// Handle collections in mixed. A collection can have N nested levels (except for Sets). And these levels can be
346
// nested in arbitrary way (eg a List within a Dictionary or viceversa). In order to try to merge server changes with
347
// client changes, the algorithm needs to go through each single element in the collection, check its type and perform
348
// the most appropriate action in order to minimize the number of notifications triggered.
349
//
350
void InterRealmValueConverter::handle_list_in_mixed(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list) const
351
{
2,334✔
352
    const auto sz = (int)std::min(src_list.size(), dst_list.size());
2,334✔
353
    int left = 0;
2,334✔
354

355
    // find fist not matching element from beginning
356
    while (left < sz && check_if_list_elements_match(src_list, dst_list, left, left)) {
3,648✔
357
        left += 1;
1,314✔
358
    }
1,314✔
359

360
    // find first not matching element from end
361
    int right_src = (int)src_list.size() - 1;
2,334✔
362
    int right_dst = (int)dst_list.size() - 1;
2,334✔
363
    while (right_src >= left && right_dst >= left &&
2,688✔
364
           check_if_list_elements_match(src_list, dst_list, right_src, right_dst)) {
2,688✔
365
        right_src -= 1;
354✔
366
        right_dst -= 1;
354✔
367
    }
354✔
368

369
    auto is_link_to_deleted_object = [&](const Mixed& src_value, const Mixed& converted_value) -> bool {
2,334✔
370
        return converted_value.is_null() && src_value.is_type(type_TypedLink);
1,188✔
371
    };
1,188✔
372

373
    // Replace all different elements in [left, right]
374
    auto left_src = left;
2,334✔
375
    auto left_dst = left;
2,334✔
376
    std::vector<size_t> dst_to_erase;
2,334✔
377
    while (left_src <= right_src && left_dst <= right_dst) {
3,222✔
378
        auto src_any = src_list.get_any(left_src);
888✔
379
        auto dst_any = dst_list.get_any(left_dst);
888✔
380

381
        if (is_collection(src_any)) {
888✔
382
            auto coll_type = to_collection_type(src_any);
216✔
383

384
            if (!dst_any.is_type(src_any.get_type())) {
216✔
385
                // Primitive vs Collection or different collection types
386
                dst_list.set_collection(left_dst, coll_type);
24✔
387
                copy_list_in_mixed(src_list, dst_list, left_src, left_dst, coll_type);
24✔
388
            }
24✔
389
            else if (!check_if_list_elements_match(src_list, dst_list, left_src, left_dst)) {
192✔
390
                // Same collection type but different contents
391
                copy_list_in_mixed(src_list, dst_list, left_src, left_dst, coll_type);
180✔
392
            }
180✔
393
        }
216✔
394
        else if (dst_any != src_any) {
672✔
395
            // Mixed vs Mixed
396
            InterRealmValueConverter::ConversionResult converted_src;
594✔
397
            bool update_out = false;
594✔
398
            cmp_src_to_dst(src_any, dst_any, &converted_src, &update_out);
594✔
399
            REALM_ASSERT(!converted_src.requires_new_embedded_object);
594✔
400
            if (is_link_to_deleted_object(src_any, converted_src.converted_value)) {
594✔
401
                dst_to_erase.push_back(left_dst);
18✔
402
            }
18✔
403
            else {
576✔
404
                dst_list.set_any(left_dst, converted_src.converted_value);
576✔
405
            }
576✔
406
        }
594✔
407
        left_src += 1;
888✔
408
        left_dst += 1;
888✔
409
    }
888✔
410

411
    // remove dst elements not present in src
412
    while (right_dst > right_src)
2,910✔
413
        dst_list.remove(right_dst--);
576✔
414

415
    // append remainig src into dst
416
    for (int i = left_src; i <= right_src; ++i) {
3,006✔
417
        auto src_any = src_list.get(i);
672✔
418
        if (is_collection(src_any)) {
672✔
419
            auto coll_type = to_collection_type(src_any);
78✔
420
            dst_list.insert_collection(i, coll_type);
78✔
421
            copy_list_in_mixed(src_list, dst_list, i, i, coll_type);
78✔
422
        }
78✔
423
        else {
594✔
424
            InterRealmValueConverter::ConversionResult converted_src;
594✔
425
            bool update_out = false;
594✔
426
            cmp_src_to_dst(src_any, Mixed{}, &converted_src, &update_out);
594✔
427
            REALM_ASSERT(!converted_src.requires_new_embedded_object);
594✔
428
            if (is_link_to_deleted_object(src_any, converted_src.converted_value)) {
594✔
429
                // ignore trying to insert a link to a object which no longer exists
430
            }
24✔
431
            else {
570✔
432
                dst_list.insert_any(i, converted_src.converted_value);
570✔
433
            }
570✔
434
        }
594✔
435
    }
672✔
436

437
    while (dst_to_erase.size()) {
2,352✔
438
        size_t ndx_to_remove = dst_to_erase.back();
18✔
439
        dst_list.remove(ndx_to_remove);
18✔
440
        dst_to_erase.pop_back();
18✔
441
    }
18✔
442
}
2,334✔
443

444
void InterRealmValueConverter::handle_dictionary_in_mixed(Dictionary& src_dictionary,
445
                                                          Dictionary& dst_dictionary) const
446
{
2,106✔
447
    std::vector<size_t> to_insert, to_delete;
2,106✔
448
    size_t src_ndx = 0, dst_ndx = 0;
2,106✔
449
    while (src_ndx < src_dictionary.size() && dst_ndx < dst_dictionary.size()) {
3,834✔
450
        const auto [key_src, src_any] = src_dictionary.get_pair(src_ndx);
1,728✔
451
        const auto [key_dst, dst_any] = dst_dictionary.get_pair(dst_ndx);
1,728✔
452

453
        auto cmp = key_src.compare(key_dst);
1,728✔
454
        if (cmp == 0) {
1,728✔
455
            if (src_any != dst_any) {
1,482✔
456
                to_insert.push_back(src_ndx);
132✔
457
            }
132✔
458
            else if (is_collection(src_any) &&
1,350✔
459
                     !check_if_dictionary_elements_match(src_dictionary, dst_dictionary, key_src.get_string())) {
1,350✔
460
                to_insert.push_back(src_ndx);
108✔
461
            }
108✔
462
            src_ndx += 1;
1,482✔
463
            dst_ndx += 1;
1,482✔
464
        }
1,482✔
465
        else if (cmp < 0) {
246✔
466
            to_insert.push_back(src_ndx);
114✔
467
            src_ndx += 1;
114✔
468
        }
114✔
469
        else {
132✔
470
            to_delete.push_back(dst_ndx);
132✔
471
            dst_ndx += 1;
132✔
472
        }
132✔
473
    }
1,728✔
474

475
    // append src to dst
476
    while (src_ndx < src_dictionary.size()) {
2,514✔
477
        to_insert.push_back(src_ndx);
408✔
478
        src_ndx += 1;
408✔
479
    }
408✔
480

481
    // delete everything that did not match passed src.size()
482
    while (dst_ndx < dst_dictionary.size()) {
2,292✔
483
        to_delete.push_back(dst_ndx);
186✔
484
        dst_ndx += 1;
186✔
485
    }
186✔
486

487
    // delete all the non matching keys
488
    while (!to_delete.empty()) {
2,424✔
489
        dst_dictionary.erase(dst_dictionary.begin() + to_delete.back());
318✔
490
        to_delete.pop_back();
318✔
491
    }
318✔
492

493
    // insert into dst
494
    for (const auto pos : to_insert) {
2,106✔
495
        const auto [key, any] = src_dictionary.get_pair(pos);
762✔
496
        if (is_collection(any)) {
762✔
497
            auto coll_type = to_collection_type(any);
180✔
498
            dst_dictionary.insert_collection(key.get_string(), coll_type);
180✔
499
            copy_dictionary_in_mixed(src_dictionary, dst_dictionary, key.get_string(), coll_type);
180✔
500
        }
180✔
501
        else {
582✔
502

503
            InterRealmValueConverter::ConversionResult converted_src;
582✔
504
            bool update_out = false;
582✔
505
            cmp_src_to_dst(any, Mixed{}, &converted_src, &update_out);
582✔
506
            if (update_out) {
582✔
507
                // we do not support embedded objects
508
                REALM_ASSERT(!converted_src.requires_new_embedded_object);
528✔
509
                dst_dictionary.insert(key, converted_src.converted_value);
528✔
510
            }
528✔
511
            else
54✔
512
                dst_dictionary.insert(key, any);
54✔
513
        }
582✔
514
    }
762✔
515
}
2,106✔
516

517
bool InterRealmValueConverter::check_if_list_elements_match(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list,
518
                                                            size_t ndx_src, size_t ndx_dst) const
519
{
3,576✔
520
    REALM_ASSERT(ndx_src < src_list.size() && ndx_dst < dst_list.size());
3,576✔
521
    auto src_any = src_list.get_any(ndx_src);
3,576✔
522
    auto dst_any = dst_list.get_any(ndx_dst);
3,576✔
523
    if (src_any != dst_any)
3,576✔
524
        return false;
1,182✔
525
    if (!is_collection(src_any))
2,394✔
526
        return true;
1,788✔
527
    // Check collections are equal
528
    if (src_any.is_type(type_List)) {
606✔
529
        auto src_element = src_list.get_list(ndx_src);
480✔
530
        auto dst_element = dst_list.get_list(ndx_dst);
480✔
531
        auto src_element_size = src_element->size();
480✔
532
        auto dst_element_size = dst_element->size();
480✔
533
        if (src_element_size != dst_element_size)
480✔
534
            return false;
96✔
535
        for (size_t i = 0; i < src_element_size; ++i) {
612✔
536
            if (!(check_if_list_elements_match(*src_element, *dst_element, i, i)))
462✔
537
                return false;
234✔
538
        }
462✔
539
    }
384✔
540
    else if (src_any.is_type(type_Dictionary)) {
126✔
541
        auto src_element = src_list.get_dictionary(ndx_src);
126✔
542
        auto dst_element = dst_list.get_dictionary(ndx_dst);
126✔
543
        auto src_element_size = src_element->size();
126✔
544
        auto dst_element_size = dst_element->size();
126✔
545
        if (src_element_size != dst_element_size)
126✔
546
            return false;
×
547
        for (size_t i = 0; i < src_element_size; ++i) {
234✔
548
            auto src_key = src_element->get_key(i);
192✔
549
            auto dst_key = dst_element->get_key(i);
192✔
550
            if (src_key != dst_key)
192✔
551
                return false;
24✔
552
            if (!check_if_dictionary_elements_match(*src_element, *dst_element, src_key.get_string()))
168✔
553
                return false;
60✔
554
        }
168✔
555
    }
126✔
556
    return true;
192✔
557
}
606✔
558

559
bool InterRealmValueConverter::check_if_dictionary_elements_match(const Dictionary& src_dictionary,
560
                                                                  const Dictionary& dst_dictionary,
561
                                                                  StringData key) const
562
{
456✔
563
    REALM_ASSERT(src_dictionary.contains(key) && dst_dictionary.contains(key));
456✔
564
    auto src_any = src_dictionary.get(key);
456✔
565
    auto dst_any = dst_dictionary.get(key);
456✔
566
    if (src_any != dst_any)
456✔
567
        return false;
54✔
568
    if (!is_collection(src_any))
402✔
569
        return true;
150✔
570
    // Check collections are equal
571
    if (src_any.is_type(type_List)) {
252✔
572
        auto src_element = src_dictionary.get_list(key);
120✔
573
        auto dst_element = dst_dictionary.get_list(key);
120✔
574
        auto src_element_size = src_element->size();
120✔
575
        auto dst_element_size = dst_element->size();
120✔
576
        if (src_element_size != dst_element_size)
120✔
577
            return false;
36✔
578
        for (size_t i = 0; i < src_element_size; ++i) {
156✔
579
            if (!(check_if_list_elements_match(*src_element, *dst_element, i, i)))
102✔
580
                return false;
30✔
581
        }
102✔
582
    }
84✔
583
    else if (src_any.is_type(type_Dictionary)) {
132✔
584
        auto src_element = src_dictionary.get_dictionary(key);
132✔
585
        auto dst_element = dst_dictionary.get_dictionary(key);
132✔
586
        auto src_element_size = src_element->size();
132✔
587
        auto dst_element_size = dst_element->size();
132✔
588
        if (src_element_size != dst_element_size)
132✔
589
            return false;
6✔
590
        for (size_t i = 0; i < src_element_size; ++i) {
180✔
591
            auto left_key = src_element->get_key(i);
132✔
592
            auto right_key = dst_element->get_key(i);
132✔
593
            if (left_key != right_key)
132✔
594
                return false;
42✔
595
            if (!check_if_dictionary_elements_match(*src_element, *dst_element, left_key.get_string()))
90✔
596
                return false;
36✔
597
        }
90✔
598
    }
126✔
599
    return true;
102✔
600
}
252✔
601

602
void InterRealmValueConverter::copy_list_in_mixed(const Lst<Mixed>& src_list, Lst<Mixed>& dst_list, size_t ndx_src,
603
                                                  size_t ndx_dst, CollectionType type) const
604
{
282✔
605
    if (type == CollectionType::List) {
282✔
606
        auto n_src_list = src_list.get_list(ndx_src);
192✔
607
        auto n_dst_list = dst_list.get_list(ndx_dst);
192✔
608
        handle_list_in_mixed(*n_src_list, *n_dst_list);
192✔
609
    }
192✔
610
    else if (type == CollectionType::Dictionary) {
90✔
611
        auto n_src_dict = src_list.get_dictionary(ndx_src);
90✔
612
        auto n_dst_dict = dst_list.get_dictionary(ndx_dst);
90✔
613
        handle_dictionary_in_mixed(*n_src_dict, *n_dst_dict);
90✔
614
    }
90✔
615
}
282✔
616

617
void InterRealmValueConverter::copy_dictionary_in_mixed(const Dictionary& src_dictionary, Dictionary& dst_dictionary,
618
                                                        StringData key, CollectionType type) const
619
{
180✔
620
    if (type == CollectionType::List) {
180✔
621
        auto n_src_list = src_dictionary.get_list(key);
90✔
622
        auto n_dst_list = dst_dictionary.get_list(key);
90✔
623
        handle_list_in_mixed(*n_src_list, *n_dst_list);
90✔
624
    }
90✔
625
    else if (type == CollectionType::Dictionary) {
90✔
626
        auto n_src_dictionary = src_dictionary.get_dictionary(key);
90✔
627
        auto n_dst_dictionary = dst_dictionary.get_dictionary(key);
90✔
628
        handle_dictionary_in_mixed(*n_src_dictionary, *n_dst_dictionary);
90✔
629
    }
90✔
630
}
180✔
631

632
bool InterRealmValueConverter::is_collection(Mixed mixed) const
633
{
6,468✔
634
    REALM_ASSERT_DEBUG(!mixed.is_type(type_Set));
6,468✔
635
    return mixed.is_type(type_List, type_Dictionary);
6,468✔
636
}
6,468✔
637

638
CollectionType InterRealmValueConverter::to_collection_type(Mixed mixed) const
639
{
474✔
640
    const auto mixed_type = mixed.get_type();
474✔
641
    if (mixed_type == type_List)
474✔
642
        return CollectionType::List;
288✔
643
    else if (mixed_type == type_Dictionary)
186✔
644
        return CollectionType::Dictionary;
186✔
645
    REALM_UNREACHABLE();
646
}
×
647

648
// If an embedded object is encountered, add it to a list of embedded objects to process.
649
// This relies on the property that embedded objects only have one incoming link
650
// otherwise there could be an infinite loop while discovering embedded objects.
651
void EmbeddedObjectConverter::track(const Obj& e_src, const Obj& e_dst)
652
{
67,350✔
653
    embedded_pending.push_back({e_src, e_dst});
67,350✔
654
}
67,350✔
655

656
void EmbeddedObjectConverter::process_pending()
657
{
39,717✔
658
    util::FlatMap<TableKey, InterRealmObjectConverter> converters;
39,717✔
659

660
    while (!embedded_pending.empty()) {
107,067✔
661
        EmbeddedToCheck pending = embedded_pending.back();
67,350✔
662
        embedded_pending.pop_back();
67,350✔
663

664
        TableRef dst_table = pending.embedded_in_dst.get_table();
67,350✔
665
        TableKey dst_table_key = dst_table->get_key();
67,350✔
666
        auto it = converters.find(dst_table_key);
67,350✔
667
        if (it == converters.end()) {
67,350✔
668
            TableRef src_table = pending.embedded_in_src.get_table();
1,098✔
669
            it = converters.insert({dst_table_key, InterRealmObjectConverter{src_table, dst_table, this}}).first;
1,098✔
670
        }
1,098✔
671
        InterRealmObjectConverter& converter = it->second;
67,350✔
672
        converter.copy(pending.embedded_in_src, pending.embedded_in_dst, nullptr);
67,350✔
673
    }
67,350✔
674
}
39,717✔
675

676
InterRealmValueConverter::InterRealmValueConverter(ConstTableRef src_table, ColKey src_col, ConstTableRef dst_table,
677
                                                   ColKey dst_col, EmbeddedObjectConverter* ec)
678
    : m_src_table(src_table)
32,013✔
679
    , m_dst_table(dst_table)
32,013✔
680
    , m_src_col(src_col)
32,013✔
681
    , m_dst_col(dst_col)
32,013✔
682
    , m_embedded_converter(ec)
32,013✔
683
    , m_is_embedded_link(false)
32,013✔
684
    , m_primitive_types_only(!(src_col.get_type() == col_type_TypedLink || src_col.get_type() == col_type_Link ||
32,013✔
685
                               src_col.get_type() == col_type_Mixed))
32,013✔
686
{
64,026✔
687
    if (!m_primitive_types_only) {
64,026✔
688
        REALM_ASSERT(src_table);
11,760✔
689
        m_opposite_of_src = src_table->get_opposite_table(src_col);
11,760✔
690
        m_opposite_of_dst = dst_table->get_opposite_table(dst_col);
11,760✔
691
        REALM_ASSERT(bool(m_opposite_of_src) == bool(m_opposite_of_dst));
11,760✔
692
        if (m_opposite_of_src) {
11,760✔
693
            m_is_embedded_link = m_opposite_of_src->is_embedded();
5,526✔
694
        }
5,526✔
695
    }
11,760✔
696
}
64,026✔
697

698
void InterRealmValueConverter::track_new_embedded(const Obj& src, const Obj& dst) const
699
{
7,032✔
700
    m_embedded_converter->track(src, dst);
7,032✔
701
}
7,032✔
702

703
// convert `src` to the destination Realm and compare that value with `dst`
704
// If `converted_src_out` is provided, it will be set to the converted src value
705
int InterRealmValueConverter::cmp_src_to_dst(Mixed src, Mixed dst, ConversionResult* converted_src_out,
706
                                             bool* did_update_out) const
707
{
172,863✔
708
    int cmp = 0;
172,863✔
709
    Mixed converted_src;
172,863✔
710
    if (m_primitive_types_only || !src.is_type(type_Link, type_TypedLink)) {
172,863✔
711
        converted_src = src;
159,057✔
712
        cmp = src.compare(dst);
159,057✔
713
    }
159,057✔
714
    else if (m_opposite_of_src) {
13,806✔
715
        ObjKey src_link_key = src.get<ObjKey>();
11,796✔
716
        if (m_is_embedded_link) {
11,796✔
717
            Obj src_embedded = m_opposite_of_src->get_object(src_link_key);
7,050✔
718
            REALM_ASSERT_DEBUG(src_embedded.is_valid());
7,050✔
719
            if (dst.is_type(type_Link, type_TypedLink)) {
7,050✔
720
                cmp = 0; // no need to set this link, there is already an embedded object here
5,982✔
721
                Obj dst_embedded = m_opposite_of_dst->get_object(dst.get<ObjKey>());
5,982✔
722
                REALM_ASSERT_DEBUG(dst_embedded.is_valid());
5,982✔
723
                converted_src = dst_embedded.get_key();
5,982✔
724
                track_new_embedded(src_embedded, dst_embedded);
5,982✔
725
            }
5,982✔
726
            else {
1,068✔
727
                cmp = src.compare(dst);
1,068✔
728
                if (converted_src_out) {
1,068✔
729
                    converted_src_out->requires_new_embedded_object = true;
1,050✔
730
                    converted_src_out->src_embedded_to_check = src_embedded;
1,050✔
731
                }
1,050✔
732
            }
1,068✔
733
        }
7,050✔
734
        else {
4,746✔
735
            Obj dst_link;
4,746✔
736
            if (m_opposite_of_dst == m_opposite_of_src) {
4,746✔
737
                // if this is the same Realm, we can use the ObjKey
738
                dst_link = m_opposite_of_dst->get_object(src_link_key);
192✔
739
            }
192✔
740
            else {
4,554✔
741
                // in different Realms we create a new object
742
                if (m_opposite_of_src->get_primary_key_column()) {
4,554✔
743
                    Mixed src_link_pk = m_opposite_of_src->get_primary_key(src_link_key);
4,554✔
744
                    dst_link =
4,554✔
745
                        m_opposite_of_dst->get_object_with_primary_key(src_link_pk); // returns ObjKey{} if not found
4,554✔
746
                }
4,554✔
747
                else {
×
748
                    dst_link = m_opposite_of_dst->create_object();
×
749
                }
×
750
            }
4,554✔
751
            converted_src = dst_link.get_key();
4,746✔
752
            if (dst.is_type(type_TypedLink)) {
4,746✔
753
                cmp = converted_src.compare(dst.get<ObjKey>());
3,780✔
754
            }
3,780✔
755
            else {
966✔
756
                cmp = converted_src.compare(dst);
966✔
757
            }
966✔
758
        }
4,746✔
759
    }
11,796✔
760
    else {
2,010✔
761
        ObjLink src_link = src.get<ObjLink>();
2,010✔
762
        if (src_link.is_unresolved()) {
2,010✔
763
            converted_src = Mixed{}; // no need to transfer over unresolved links
×
764
            cmp = converted_src.compare(dst);
×
765
        }
×
766
        else {
2,010✔
767
            TableRef src_link_table = m_src_table->get_parent_group()->get_table(src_link.get_table_key());
2,010✔
768
            REALM_ASSERT_EX(src_link_table, src_link.get_table_key());
2,010✔
769
            TableRef dst_link_table = m_dst_table->get_parent_group()->get_table(src_link_table->get_name());
2,010✔
770
            REALM_ASSERT_EX(dst_link_table, src_link_table->get_name());
2,010✔
771
            // embedded tables should always be covered by the m_opposite_of_src case above.
772
            REALM_ASSERT_EX(!src_link_table->is_embedded(), src_link_table->get_name());
2,010✔
773
            // regular table, convert by pk
774
            if (src_link_table->get_primary_key_column()) {
2,010✔
775
                Mixed src_pk = src_link_table->get_primary_key(src_link.get_obj_key());
2,010✔
776
                if (Obj dst_link = dst_link_table->get_object_with_primary_key(src_pk)) {
2,010✔
777
                    converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
1,968✔
778
                }
1,968✔
779
            }
2,010✔
780
            else if (src_link_table == dst_link_table) {
×
781
                // no pk, but this is the same Realm, so convert by ObjKey
782
                Obj dst_link = dst_link_table->get_object(src_link.get_obj_key());
×
783
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
784
            }
×
785
            else {
×
786
                // no pk, and different Realm, create an object
787
                Obj dst_link = dst_link_table->create_object();
×
788
                converted_src = ObjLink{dst_link_table->get_key(), dst_link.get_key()};
×
789
            }
×
790
            cmp = converted_src.compare(dst);
2,010✔
791
        }
2,010✔
792
    }
2,010✔
793
    if (converted_src_out) {
172,863✔
794
        converted_src_out->converted_value = converted_src;
136,179✔
795
    }
136,179✔
796
    if (did_update_out && cmp) {
172,863✔
797
        *did_update_out = true;
17,373✔
798
    }
17,373✔
799
    return cmp;
172,863✔
800
}
172,863✔
801

802
InterRealmObjectConverter::InterRealmObjectConverter(ConstTableRef table_src, TableRef table_dst,
803
                                                     EmbeddedObjectConverter* embedded_tracker)
804
    : m_embedded_tracker(embedded_tracker)
13,782✔
805
{
27,564✔
806
    m_columns_cache.reserve(table_src->get_column_count());
27,564✔
807
    ColKey pk_col = table_src->get_primary_key_column();
27,564✔
808
    for (ColKey col_key_src : table_src->get_column_keys()) {
89,070✔
809
        if (col_key_src == pk_col)
89,070✔
810
            continue;
26,466✔
811
        StringData col_name = table_src->get_column_name(col_key_src);
62,604✔
812
        ColKey col_key_dst = table_dst->get_column_key(col_name);
62,604✔
813
        REALM_ASSERT(col_key_dst);
62,604✔
814
        m_columns_cache.emplace_back(table_src, col_key_src, table_dst, col_key_dst, m_embedded_tracker);
62,604✔
815
    }
62,604✔
816
}
27,564✔
817

818
void InterRealmObjectConverter::copy(const Obj& src, Obj& dst, bool* update_out)
819
{
105,456✔
820
    for (auto& column : m_columns_cache) {
161,628✔
821
        column.copy_value(src, dst, update_out);
161,628✔
822
    }
161,628✔
823
}
105,456✔
824

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