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

realm / realm-core / jorgen.edelbo_138

13 Mar 2024 08:41AM UTC coverage: 91.77% (-0.3%) from 92.078%
jorgen.edelbo_138

Pull #7356

Evergreen

jedelbo
Add ability to get path to modified collections in object notifications
Pull Request #7356: Add ability to get path to modified collections in object notifications

94532 of 174642 branches covered (54.13%)

118 of 163 new or added lines in 16 files covered. (72.39%)

765 existing lines in 41 files now uncovered.

242808 of 264584 relevant lines covered (91.77%)

5878961.32 hits per line

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

91.24
/src/realm/obj.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 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/obj.hpp"
20
#include "realm/array_basic.hpp"
21
#include "realm/array_integer.hpp"
22
#include "realm/array_bool.hpp"
23
#include "realm/array_string.hpp"
24
#include "realm/array_binary.hpp"
25
#include "realm/array_mixed.hpp"
26
#include "realm/array_timestamp.hpp"
27
#include "realm/array_decimal128.hpp"
28
#include "realm/array_key.hpp"
29
#include "realm/array_fixed_bytes.hpp"
30
#include "realm/array_backlink.hpp"
31
#include "realm/array_typed_link.hpp"
32
#include "realm/cluster_tree.hpp"
33
#include "realm/list.hpp"
34
#include "realm/set.hpp"
35
#include "realm/dictionary.hpp"
36
#if REALM_ENABLE_GEOSPATIAL
37
#include "realm/geospatial.hpp"
38
#endif
39
#include "realm/link_translator.hpp"
40
#include "realm/index_string.hpp"
41
#include "realm/object_converter.hpp"
42
#include "realm/replication.hpp"
43
#include "realm/spec.hpp"
44
#include "realm/table_view.hpp"
45
#include "realm/util/base64.hpp"
46
#include "realm/util/overload.hpp"
47

48
#include <ostream>
49

50
namespace realm {
51
namespace {
52

53
template <class T, class U>
54
size_t find_link_value_in_collection(T& coll, Obj& obj, ColKey origin_col_key, U link)
55
{
108,858✔
56
    coll.set_owner(obj, origin_col_key);
108,858✔
57
    return coll.find_first(link);
108,858✔
58
}
108,858✔
59

60
template <class T>
61
inline void nullify_linklist(Obj& obj, ColKey origin_col_key, T target)
62
{
2,310✔
63
    Lst<T> link_list(origin_col_key);
2,310✔
64
    size_t ndx = find_link_value_in_collection(link_list, obj, origin_col_key, target);
2,310✔
65

1,155✔
66
    REALM_ASSERT(ndx != realm::npos); // There has to be one
2,310✔
67

1,155✔
68
    if (Replication* repl = obj.get_replication()) {
2,310✔
69
        if constexpr (std::is_same_v<T, ObjKey>) {
2,136✔
70
            repl->link_list_nullify(link_list, ndx); // Throws
2,136✔
71
        }
2,136✔
72
        else {
2,136✔
73
            repl->list_erase(link_list, ndx); // Throws
2,136✔
74
        }
2,136✔
75
    }
2,136✔
76

1,155✔
77
    // We cannot just call 'remove' on link_list as it would produce the wrong
1,155✔
78
    // replication instruction and also attempt an update on the backlinks from
1,155✔
79
    // the object that we in the process of removing.
1,155✔
80
    BPlusTree<T>& tree = const_cast<BPlusTree<T>&>(link_list.get_tree());
2,310✔
81
    tree.erase(ndx);
2,310✔
82
}
2,310✔
83

84
template <class T>
85
inline void nullify_set(Obj& obj, ColKey origin_col_key, T target)
86
{
2,562✔
87
    Set<T> link_set(origin_col_key);
2,562✔
88
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
2,562✔
89

1,281✔
90
    REALM_ASSERT(ndx != realm::npos); // There has to be one
2,562✔
91

1,281✔
92
    if (Replication* repl = obj.get_replication()) {
2,562✔
93
        repl->set_erase(link_set, ndx, target); // Throws
2,550✔
94
    }
2,550✔
95

1,281✔
96
    // We cannot just call 'remove' on set as it would produce the wrong
1,281✔
97
    // replication instruction and also attempt an update on the backlinks from
1,281✔
98
    // the object that we in the process of removing.
1,281✔
99
    BPlusTree<T>& tree = const_cast<BPlusTree<T>&>(link_set.get_tree());
2,562✔
100
    tree.erase(ndx);
2,562✔
101
}
2,562✔
102

103
} // namespace
104

105
/*********************************** Obj *************************************/
106

107
Obj::Obj(TableRef table, MemRef mem, ObjKey key, size_t row_ndx)
108
    : m_table(table)
109
    , m_key(key)
110
    , m_mem(mem)
111
    , m_row_ndx(row_ndx)
112
    , m_valid(true)
113
{
171,973,431✔
114
    m_storage_version = get_alloc().get_storage_version();
171,973,431✔
115
}
171,973,431✔
116

117
GlobalKey Obj::get_object_id() const
118
{
108✔
119
    return m_table->get_object_id(m_key);
108✔
120
}
108✔
121

122
ObjLink Obj::get_link() const
123
{
50,814✔
124
    return ObjLink(m_table->get_key(), m_key);
50,814✔
125
}
50,814✔
126

127
const ClusterTree* Obj::get_tree_top() const
128
{
38,763,090✔
129
    if (m_key.is_unresolved()) {
38,763,090✔
130
        return m_table.unchecked_ptr()->m_tombstones.get();
44,766✔
131
    }
44,766✔
132
    else {
38,718,324✔
133
        return &m_table.unchecked_ptr()->m_clusters;
38,718,324✔
134
    }
38,718,324✔
135
}
38,763,090✔
136

137
Allocator& Obj::get_alloc() const
138
{
224,242,386✔
139
    // Do a "checked" deref to table to ensure the instance_version is correct.
112,092,924✔
140
    // Even if removed from the public API, this should *not* be optimized away,
112,092,924✔
141
    // because it is used internally in situations, where we want stale table refs
112,092,924✔
142
    // to be detected.
112,092,924✔
143
    return m_table->m_alloc;
224,242,386✔
144
}
224,242,386✔
145

146
Allocator& Obj::_get_alloc() const noexcept
147
{
362,812,581✔
148
    // Bypass check of table instance version. To be used only in contexts,
180,297,042✔
149
    // where instance version match has already been established (e.g _get<>)
180,297,042✔
150
    return m_table.unchecked_ptr()->m_alloc;
362,812,581✔
151
}
362,812,581✔
152

153
const Spec& Obj::get_spec() const
154
{
7,975,764✔
155
    return m_table.unchecked_ptr()->m_spec;
7,975,764✔
156
}
7,975,764✔
157

158
StableIndex Obj::build_index(ColKey col_key) const
159
{
2,424,294✔
160
    if (col_key.is_collection()) {
2,424,294✔
161
        return {col_key, 0};
2,416,827✔
162
    }
2,416,827✔
163
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
7,467✔
164
    _update_if_needed();
7,467✔
165
    ArrayMixed values(_get_alloc());
7,467✔
166
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
7,467✔
167
    values.init_from_ref(ref);
7,467✔
168
    auto key = values.get_key(m_row_ndx);
7,467✔
169
    return {col_key, key};
7,467✔
170
}
7,467✔
171

172
bool Obj::check_index(StableIndex index) const
173
{
17,835✔
174
    if (index.is_collection()) {
17,835✔
175
        return true;
×
176
    }
×
177
    _update_if_needed();
17,835✔
178
    ArrayMixed values(_get_alloc());
17,835✔
179
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), index.get_index().val + 1));
17,835✔
180
    values.init_from_ref(ref);
17,835✔
181
    auto key = values.get_key(m_row_ndx);
17,835✔
182
    return key == index.get_salt();
17,835✔
183
}
17,835✔
184

185
Replication* Obj::get_replication() const
186
{
29,546,970✔
187
    return m_table->get_repl();
29,546,970✔
188
}
29,546,970✔
189

190
bool Obj::compare_values(Mixed val1, Mixed val2, ColKey ck, Obj other, StringData col_name) const
191
{
4,884✔
192
    if (val1.is_null()) {
4,884✔
193
        if (!val2.is_null())
60✔
194
            return false;
×
195
    }
4,824✔
196
    else {
4,824✔
197
        if (val1.get_type() != val2.get_type())
4,824✔
198
            return false;
×
199
        if (val1.is_type(type_Link, type_TypedLink)) {
4,824✔
200
            auto o1 = _get_linked_object(ck, val1);
228✔
201
            auto o2 = other._get_linked_object(col_name, val2);
228✔
202
            if (o1.m_table->is_embedded()) {
228✔
203
                return o1 == o2;
144✔
204
            }
144✔
205
            else {
84✔
206
                return o1.get_primary_key() == o2.get_primary_key();
84✔
207
            }
84✔
208
        }
4,596✔
209
        else {
4,596✔
210
            const auto type = val1.get_type();
4,596✔
211
            if (type == type_List) {
4,596✔
212
                Lst<Mixed> lst1(*this, ck);
6✔
213
                Lst<Mixed> lst2(other, other.get_column_key(col_name));
6✔
214
                return compare_list_in_mixed(lst1, lst2, ck, other, col_name);
6✔
215
            }
6✔
216
            else if (type == type_Set) {
4,590✔
217
                Set<Mixed> set1(*this, ck);
×
218
                Set<Mixed> set2(other, other.get_column_key(col_name));
×
219
                return set1 == set2;
×
220
            }
×
221
            else if (type == type_Dictionary) {
4,590✔
222
                Dictionary dict1(*this, ck);
6✔
223
                Dictionary dict2(other, other.get_column_key(col_name));
6✔
224
                return compare_dict_in_mixed(dict1, dict2, ck, other, col_name);
6✔
225
            }
6✔
226
            return val1 == val2;
4,584✔
227
        }
4,584✔
228
    }
4,824✔
229
    return true;
60✔
230
}
60✔
231

232
bool Obj::compare_list_in_mixed(Lst<Mixed>& val1, Lst<Mixed>& val2, ColKey ck, Obj other, StringData col_name) const
233
{
12✔
234
    if (val1.size() != val2.size())
12✔
235
        return false;
×
236

6✔
237
    for (size_t i = 0; i < val1.size(); ++i) {
30✔
238

12✔
239
        auto m1 = val1.get_any(i);
24✔
240
        auto m2 = val2.get_any(i);
24✔
241

12✔
242
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
24✔
243
            DummyParent parent(get_table(), m2.get_ref());
6✔
244
            Lst<Mixed> list(parent, 0);
6✔
245
            return compare_list_in_mixed(*val1.get_list(i), list, ck, other, col_name);
6✔
246
        }
6✔
247
        else if (m1.is_type(type_Dictionary) && m2.is_type(type_Dictionary)) {
18!
248
            DummyParent parent(get_table(), m2.get_ref());
×
249
            Dictionary dict(parent, 0);
×
250
            return compare_dict_in_mixed(*val1.get_dictionary(i), dict, ck, other, col_name);
×
251
        }
×
252
        else if (!compare_values(m1, m2, ck, other, col_name)) {
18✔
253
            return false;
×
254
        }
×
255
    }
24✔
256
    return true;
9✔
257
}
12✔
258

259
bool Obj::compare_dict_in_mixed(Dictionary& val1, Dictionary& val2, ColKey ck, Obj other, StringData col_name) const
260
{
12✔
261
    if (val1.size() != val2.size())
12✔
262
        return false;
×
263

6✔
264
    for (size_t i = 0; i < val1.size(); ++i) {
18✔
265

6✔
266
        auto [k1, m1] = val1.get_pair(i);
12✔
267
        auto [k2, m2] = val2.get_pair(i);
12✔
268
        if (k1 != k2)
12✔
269
            return false;
×
270

6✔
271
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
12!
272
            DummyParent parent(get_table(), m2.get_ref());
×
273
            Lst<Mixed> list(parent, 0);
×
274
            return compare_list_in_mixed(*val1.get_list(k1.get_string()), list, ck, other, col_name);
×
275
        }
×
276
        else if (m1.is_type(type_Dictionary) && m2.is_type(type_Dictionary)) {
12✔
277
            DummyParent parent(get_table(), m2.get_ref());
6✔
278
            Dictionary dict(parent, 0);
6✔
279
            return compare_dict_in_mixed(*val1.get_dictionary(k1.get_string()), dict, ck, other, col_name);
6✔
280
        }
6✔
281
        else if (!compare_values(m1, m2, ck, other, col_name)) {
6✔
282
            return false;
×
283
        }
×
284
    }
12✔
285
    return true;
9✔
286
}
12✔
287

288
bool Obj::operator==(const Obj& other) const
289
{
3,054✔
290
    for (auto ck : m_table->get_column_keys()) {
4,878✔
291
        StringData col_name = m_table->get_column_name(ck);
4,878✔
292
        auto compare = [&](Mixed m1, Mixed m2) {
4,869✔
293
            return compare_values(m1, m2, ck, other, col_name);
4,860✔
294
        };
4,860✔
295

2,439✔
296
        if (!ck.is_collection()) {
4,878✔
297
            if (!compare(get_any(ck), other.get_any(col_name)))
4,482✔
298
                return false;
24✔
299
        }
396✔
300
        else {
396✔
301
            auto coll1 = get_collection_ptr(ck);
396✔
302
            auto coll2 = other.get_collection_ptr(col_name);
396✔
303
            size_t sz = coll1->size();
396✔
304
            if (coll2->size() != sz)
396✔
305
                return false;
×
306
            if (ck.is_list() || ck.is_set()) {
396✔
307
                for (size_t i = 0; i < sz; i++) {
450✔
308
                    if (!compare(coll1->get_any(i), coll2->get_any(i)))
234✔
309
                        return false;
×
310
                }
234✔
311
            }
216✔
312
            if (ck.is_dictionary()) {
396✔
313
                auto dict1 = dynamic_cast<Dictionary*>(coll1.get());
180✔
314
                auto dict2 = dynamic_cast<Dictionary*>(coll2.get());
180✔
315
                for (size_t i = 0; i < sz; i++) {
318✔
316
                    auto [key, value] = dict1->get_pair(i);
144✔
317
                    auto val2 = dict2->try_get(key);
144✔
318
                    if (!val2)
144✔
319
                        return false;
×
320
                    if (!compare(value, *val2))
144✔
321
                        return false;
6✔
322
                }
144✔
323
            }
180✔
324
        }
396✔
325
    }
4,878✔
326
    return true;
3,039✔
327
}
3,054✔
328

329
bool Obj::is_valid() const noexcept
330
{
1,454,880✔
331
    // Cache valid state. If once invalid, it can never become valid again
779,409✔
332
    if (m_valid)
1,454,880✔
333
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,453,488✔
334
                                    m_table.unchecked_ptr()->is_valid(m_key));
795,243✔
335

779,409✔
336
    return m_valid;
1,454,880✔
337
}
1,454,880✔
338

339
void Obj::remove()
340
{
129,573✔
341
    m_table.cast_away_const()->remove_object(m_key);
129,573✔
342
}
129,573✔
343

344
void Obj::invalidate()
345
{
3,456✔
346
    m_key = m_table.cast_away_const()->invalidate_object(m_key);
3,456✔
347
}
3,456✔
348

349
ColKey Obj::get_column_key(StringData col_name) const
350
{
3,775,023✔
351
    return get_table()->get_column_key(col_name);
3,775,023✔
352
}
3,775,023✔
353

354
TableKey Obj::get_table_key() const
UNCOV
355
{
×
UNCOV
356
    return get_table()->get_key();
×
UNCOV
357
}
×
358

359
TableRef Obj::get_target_table(ColKey col_key) const
360
{
7,094,505✔
361
    if (m_table) {
7,094,505✔
362
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,093,845✔
363
    }
7,093,845✔
364
    else {
660✔
365
        return TableRef();
660✔
366
    }
660✔
367
}
7,094,505✔
368

369
TableRef Obj::get_target_table(ObjLink link) const
370
{
×
371
    if (m_table) {
×
372
        return m_table.unchecked_ptr()->get_parent_group()->get_table(link.get_table_key());
×
373
    }
×
374
    else {
×
375
        return TableRef();
×
376
    }
×
377
}
×
378

379
bool Obj::update() const
380
{
162,411✔
381
    // Get a new object from key
82,692✔
382
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
162,411✔
383

82,692✔
384
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
162,411✔
385
    if (changes) {
162,411✔
386
        m_mem = new_obj.m_mem;
26,025✔
387
        m_row_ndx = new_obj.m_row_ndx;
26,025✔
388
        ++m_version_counter;
26,025✔
389
    }
26,025✔
390
    // Always update versions
82,692✔
391
    m_storage_version = new_obj.m_storage_version;
162,411✔
392
    m_table = new_obj.m_table;
162,411✔
393
    return changes;
162,411✔
394
}
162,411✔
395

396
inline bool Obj::_update_if_needed() const
397
{
78,610,362✔
398
    auto current_version = _get_alloc().get_storage_version();
78,610,362✔
399
    if (current_version != m_storage_version) {
78,610,362✔
400
        return update();
16,335✔
401
    }
16,335✔
402
    return false;
78,594,027✔
403
}
78,594,027✔
404

405
UpdateStatus Obj::update_if_needed() const
406
{
80,014,380✔
407
    if (!m_table) {
80,014,380✔
408
        // Table deleted
177✔
409
        return UpdateStatus::Detached;
354✔
410
    }
354✔
411

39,198,033✔
412
    auto current_version = _get_alloc().get_storage_version();
80,014,026✔
413
    if (current_version != m_storage_version) {
80,014,026✔
414
        ClusterNode::State state = get_tree_top()->try_get(m_key);
1,343,658✔
415

670,359✔
416
        if (!state) {
1,343,658✔
417
            // Object deleted
2,610✔
418
            return UpdateStatus::Detached;
5,220✔
419
        }
5,220✔
420

667,749✔
421
        // Always update versions
667,749✔
422
        m_storage_version = current_version;
1,338,438✔
423
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
1,338,438✔
424
            m_mem = state.mem;
209,724✔
425
            m_row_ndx = state.index;
209,724✔
426
            ++m_version_counter;
209,724✔
427
            return UpdateStatus::Updated;
209,724✔
428
        }
209,724✔
429
    }
79,799,082✔
430
    return UpdateStatus::NoChange;
79,799,082✔
431
}
79,799,082✔
432

433
void Obj::checked_update_if_needed() const
434
{
36,031,716✔
435
    if (update_if_needed() == UpdateStatus::Detached) {
36,031,716✔
436
        m_table.check();
18✔
437
        get_tree_top()->get(m_key); // should always throw
18✔
438
    }
18✔
439
}
36,031,716✔
440

441
template <class T>
442
T Obj::get(ColKey col_key) const
443
{
84,486,828✔
444
    m_table->check_column(col_key);
84,486,828✔
445
    ColumnType type = col_key.get_type();
84,486,828✔
446
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
84,486,828!
447

42,298,107✔
448
    return _get<T>(col_key.get_index());
84,486,828✔
449
}
84,486,828✔
450

451
template UUID Obj::_get(ColKey::Idx col_ndx) const;
452
template util::Optional<UUID> Obj::_get(ColKey::Idx col_ndx) const;
453

454
#if REALM_ENABLE_GEOSPATIAL
455

456
template <>
457
Geospatial Obj::get(ColKey col_key) const
458
{
84✔
459
    m_table->check_column(col_key);
84✔
460
    ColumnType type = col_key.get_type();
84✔
461
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
84✔
462
    return Geospatial::from_link(get_linked_object(col_key));
84✔
463
}
84✔
464

465
template <>
466
std::optional<Geospatial> Obj::get(ColKey col_key) const
467
{
12✔
468
    m_table->check_column(col_key);
12✔
469
    ColumnType type = col_key.get_type();
12✔
470
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
12✔
471

6✔
472
    auto geo = get_linked_object(col_key);
12✔
473
    if (!geo) {
12✔
474
        return {};
12✔
475
    }
12✔
UNCOV
476
    return Geospatial::from_link(geo);
×
UNCOV
477
}
×
478

479
#endif
480

481
template <class T>
482
T Obj::_get(ColKey::Idx col_ndx) const
483
{
77,989,398✔
484
    _update_if_needed();
77,989,398✔
485

39,080,256✔
486
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
77,989,398✔
487
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
77,989,398✔
488
    values.init_from_ref(ref);
77,989,398✔
489

39,080,256✔
490
    return values.get(m_row_ndx);
77,989,398✔
491
}
77,989,398✔
492

493
Mixed Obj::get_unfiltered_mixed(ColKey::Idx col_ndx) const
494
{
80,106✔
495
    ArrayMixed values(get_alloc());
80,106✔
496
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
80,106✔
497
    values.init_from_ref(ref);
80,106✔
498

40,050✔
499
    return values.get(m_row_ndx);
80,106✔
500
}
80,106✔
501

502
template <>
503
Mixed Obj::_get<Mixed>(ColKey::Idx col_ndx) const
504
{
70,236✔
505
    _update_if_needed();
70,236✔
506
    Mixed m = get_unfiltered_mixed(col_ndx);
70,236✔
507
    return m.is_unresolved_link() ? Mixed{} : m;
70,197✔
508
}
70,236✔
509

510
template <>
511
ObjKey Obj::_get<ObjKey>(ColKey::Idx col_ndx) const
512
{
323,595✔
513
    _update_if_needed();
323,595✔
514

161,799✔
515
    ArrayKey values(_get_alloc());
323,595✔
516
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
323,595✔
517
    values.init_from_ref(ref);
323,595✔
518

161,799✔
519
    ObjKey k = values.get(m_row_ndx);
323,595✔
520
    return k.is_unresolved() ? ObjKey{} : k;
320,484✔
521
}
323,595✔
522

523
bool Obj::is_unresolved(ColKey col_key) const
524
{
30✔
525
    m_table->check_column(col_key);
30✔
526
    ColumnType type = col_key.get_type();
30✔
527
    REALM_ASSERT(type == col_type_Link);
30✔
528

15✔
529
    _update_if_needed();
30✔
530

15✔
531
    return get_unfiltered_link(col_key).is_unresolved();
30✔
532
}
30✔
533

534
ObjKey Obj::get_unfiltered_link(ColKey col_key) const
535
{
328,473✔
536
    ArrayKey values(get_alloc());
328,473✔
537
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
328,473✔
538
    values.init_from_ref(ref);
328,473✔
539

164,214✔
540
    return values.get(m_row_ndx);
328,473✔
541
}
328,473✔
542

543
template <>
544
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
545
{
118,504,788✔
546
    // manual inline of _update_if_needed():
59,665,341✔
547
    auto& alloc = _get_alloc();
118,504,788✔
548
    auto current_version = alloc.get_storage_version();
118,504,788✔
549
    if (current_version != m_storage_version) {
118,504,788✔
550
        update();
140,145✔
551
    }
140,145✔
552

59,665,341✔
553
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
118,504,788✔
554
    char* header = alloc.translate(ref);
118,504,788✔
555
    int width = Array::get_width_from_header(header);
118,504,788✔
556
    char* data = Array::get_data_from_header(header);
118,504,788✔
557
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
118,504,788✔
UNCOV
558
}
×
559

560
template <>
561
int64_t Obj::get<int64_t>(ColKey col_key) const
562
{
105,663,018✔
563
    m_table->check_column(col_key);
105,663,018✔
564
    ColumnType type = col_key.get_type();
105,663,018✔
565
    REALM_ASSERT(type == col_type_Int);
105,663,018✔
566

54,252,690✔
567
    if (col_key.get_attrs().test(col_attr_Nullable)) {
105,663,018✔
568
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
7,788✔
569
        if (!val) {
7,788✔
570
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
6✔
571
        }
6✔
572
        return *val;
7,782✔
573
    }
7,782✔
574
    else {
105,655,230✔
575
        return _get<int64_t>(col_key.get_index());
105,655,230✔
576
    }
105,655,230✔
577
}
105,663,018✔
578

579
template <>
580
bool Obj::get<bool>(ColKey col_key) const
581
{
563,205✔
582
    m_table->check_column(col_key);
563,205✔
583
    ColumnType type = col_key.get_type();
563,205✔
584
    REALM_ASSERT(type == col_type_Bool);
563,205✔
585

301,086✔
586
    if (col_key.get_attrs().test(col_attr_Nullable)) {
563,205✔
587
        auto val = _get<util::Optional<bool>>(col_key.get_index());
30✔
588
        if (!val) {
30✔
UNCOV
589
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
×
UNCOV
590
        }
×
591
        return *val;
30✔
592
    }
30✔
593
    else {
563,175✔
594
        return _get<bool>(col_key.get_index());
563,175✔
595
    }
563,175✔
596
}
563,205✔
597

598
template <>
599
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
600
{
5,317,755✔
601
    // manual inline of _update_if_needed():
2,660,328✔
602
    auto& alloc = _get_alloc();
5,317,755✔
603
    auto current_version = alloc.get_storage_version();
5,317,755✔
604
    if (current_version != m_storage_version) {
5,317,755✔
605
        update();
5,799✔
606
    }
5,799✔
607

2,660,328✔
608
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
5,317,755✔
609
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
5,317,755✔
610
    auto& spec = get_spec();
5,317,755✔
611
    if (spec.is_string_enum_type(spec_ndx)) {
5,317,755✔
612
        ArrayString values(get_alloc());
1,060,533✔
613
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
1,060,533✔
614
        values.init_from_ref(ref);
1,060,533✔
615

530,529✔
616
        return values.get(m_row_ndx);
1,060,533✔
617
    }
1,060,533✔
618
    else {
4,257,222✔
619
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
4,257,222✔
620
    }
4,257,222✔
621
}
5,317,755✔
622

623
template <>
624
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
625
{
6,879,912✔
626
    // manual inline of _update_if_needed():
3,439,854✔
627
    auto& alloc = _get_alloc();
6,879,912✔
628
    auto current_version = alloc.get_storage_version();
6,879,912✔
629
    if (current_version != m_storage_version) {
6,879,912✔
630
        update();
132✔
631
    }
132✔
632

3,439,854✔
633
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
6,879,912✔
634
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
6,879,912✔
635
}
6,879,912✔
636

637
Mixed Obj::get_any(ColKey col_key) const
638
{
15,159,027✔
639
    m_table->check_column(col_key);
15,159,027✔
640
    auto col_ndx = col_key.get_index();
15,159,027✔
641
    if (col_key.is_collection()) {
15,159,027✔
642
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
6,924✔
643
        return Mixed(ref, get_table()->get_collection_type(col_key));
6,924✔
644
    }
6,924✔
645
    switch (col_key.get_type()) {
15,152,103✔
646
        case col_type_Int:
10,183,563✔
647
            if (col_key.get_attrs().test(col_attr_Nullable)) {
10,183,563✔
648
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
509,142✔
649
            }
509,142✔
650
            else {
9,674,421✔
651
                return Mixed{_get<int64_t>(col_ndx)};
9,674,421✔
652
            }
9,674,421✔
653
        case col_type_Bool:
203,274✔
654
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
203,274✔
655
        case col_type_Float:
8,007✔
656
            return Mixed{_get<util::Optional<float>>(col_ndx)};
8,007✔
657
        case col_type_Double:
19,614✔
658
            return Mixed{_get<util::Optional<double>>(col_ndx)};
19,614✔
659
        case col_type_String:
4,127,457✔
660
            return Mixed{_get<String>(col_ndx)};
4,127,457✔
661
        case col_type_Binary:
1,605✔
662
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
663
        case col_type_Mixed:
14,271✔
664
            return _get<Mixed>(col_ndx);
14,271✔
665
        case col_type_Timestamp:
162,744✔
666
            return Mixed{_get<Timestamp>(col_ndx)};
162,744✔
667
        case col_type_Decimal:
7,860✔
668
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
669
        case col_type_ObjectId:
361,593✔
670
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
361,593✔
671
        case col_type_UUID:
52,317✔
672
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
52,317✔
673
        case col_type_Link:
42,261✔
674
            return Mixed{_get<ObjKey>(col_ndx)};
42,261✔
UNCOV
675
        default:
✔
676
            REALM_UNREACHABLE();
UNCOV
677
            break;
×
UNCOV
678
    }
×
UNCOV
679
    return {};
×
UNCOV
680
}
×
681

682
Mixed Obj::get_primary_key() const
683
{
154,386✔
684
    auto col = m_table->get_primary_key_column();
154,386✔
685
    return col ? get_any(col) : Mixed{get_key()};
154,260✔
686
}
154,386✔
687

688
/* FIXME: Make this one fast too!
689
template <>
690
ObjKey Obj::_get(size_t col_ndx) const
691
{
692
    return ObjKey(_get<int64_t>(col_ndx));
693
}
694
*/
695

696
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
697
{
26,742✔
698
    Obj obj;
26,742✔
699
    if (!link.is_null()) {
26,742✔
700
        TableRef target_table;
18,360✔
701
        if (link.is_type(type_TypedLink)) {
18,360✔
702
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
324✔
703
        }
324✔
704
        else {
18,036✔
705
            target_table = get_target_table(link_col_key);
18,036✔
706
        }
18,036✔
707
        obj = target_table->get_object(link.get<ObjKey>());
18,360✔
708
    }
18,360✔
709
    return obj;
26,742✔
710
}
26,742✔
711

712
Obj Obj::get_parent_object() const
713
{
12✔
714
    Obj obj;
12✔
715
    checked_update_if_needed();
12✔
716

6✔
717
    if (!m_table->is_embedded()) {
12✔
UNCOV
718
        throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded");
×
UNCOV
719
    }
×
720
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
18✔
721
        if (get_backlink_cnt(backlink_col_key) == 1) {
18✔
722
            auto obj_key = get_backlink(backlink_col_key, 0);
12✔
723
            obj = m_table->get_opposite_table(backlink_col_key)->get_object(obj_key);
12✔
724
            return IteratorControl::Stop;
12✔
725
        }
12✔
726
        return IteratorControl::AdvanceToNext;
6✔
727
    });
6✔
728

6✔
729
    return obj;
12✔
730
}
12✔
731

732
template <class T>
733
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
734
{
1,201,773✔
735
    T values(get_alloc());
1,201,773✔
736
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,201,773✔
737
    values.init_from_ref(ref);
1,201,773✔
738
    return values.is_null(m_row_ndx);
1,201,773✔
739
}
1,201,773✔
740

741
template <>
742
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
743
{
262,395✔
744
    ArrayString values(get_alloc());
262,395✔
745
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
262,395✔
746
    values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
262,395✔
747
    values.init_from_ref(ref);
262,395✔
748
    return values.is_null(m_row_ndx);
262,395✔
749
}
262,395✔
750

751
size_t Obj::get_link_count(ColKey col_key) const
752
{
108✔
753
    return get_list<ObjKey>(col_key).size();
108✔
754
}
108✔
755

756
bool Obj::is_null(ColKey col_key) const
757
{
5,369,541✔
758
    checked_update_if_needed();
5,369,541✔
759
    ColumnAttrMask attr = col_key.get_attrs();
5,369,541✔
760
    ColKey::Idx col_ndx = col_key.get_index();
5,369,541✔
761
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,369,541✔
762
        switch (col_key.get_type()) {
1,463,820✔
763
            case col_type_Int:
416,325✔
764
                return do_is_null<ArrayIntNull>(col_ndx);
416,325✔
765
            case col_type_Bool:
448,032✔
766
                return do_is_null<ArrayBoolNull>(col_ndx);
448,032✔
767
            case col_type_Float:
8,865✔
768
                return do_is_null<ArrayFloatNull>(col_ndx);
8,865✔
769
            case col_type_Double:
3,267✔
770
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
771
            case col_type_String:
262,395✔
772
                return do_is_null<ArrayString>(col_ndx);
262,395✔
773
            case col_type_Binary:
267✔
774
                return do_is_null<ArrayBinary>(col_ndx);
267✔
775
            case col_type_Mixed:
1,377✔
776
                return do_is_null<ArrayMixed>(col_ndx);
1,377✔
777
            case col_type_Timestamp:
299,181✔
778
                return do_is_null<ArrayTimestamp>(col_ndx);
299,181✔
779
            case col_type_Link:
21,987✔
780
                return do_is_null<ArrayKey>(col_ndx);
21,987✔
781
            case col_type_ObjectId:
231✔
782
                return do_is_null<ArrayObjectIdNull>(col_ndx);
231✔
783
            case col_type_Decimal:
2,013✔
784
                return do_is_null<ArrayDecimal128>(col_ndx);
2,013✔
785
            case col_type_UUID:
231✔
786
                return do_is_null<ArrayUUIDNull>(col_ndx);
231✔
UNCOV
787
            default:
✔
788
                REALM_UNREACHABLE();
789
        }
1,463,820✔
790
    }
1,463,820✔
791
    return false;
4,717,095✔
792
}
5,369,541✔
793

794

795
// Figure out if this object has any remaining backlinkss
796
bool Obj::has_backlinks(bool only_strong_links) const
797
{
12,654✔
798
    const Table& target_table = *m_table;
12,654✔
799

6,240✔
800
    // If we only look for strong links and the table is not embedded,
6,240✔
801
    // then there is no relevant backlinks to find.
6,240✔
802
    if (only_strong_links && !target_table.is_embedded()) {
12,654✔
UNCOV
803
        return false;
×
UNCOV
804
    }
×
805

6,240✔
806
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
16,206✔
807
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
15,714✔
808
    });
16,206✔
809
}
12,654✔
810

811
size_t Obj::get_backlink_count() const
812
{
174,060✔
813
    checked_update_if_needed();
174,060✔
814

86,907✔
815
    size_t cnt = 0;
174,060✔
816
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
457,464✔
817
        cnt += get_backlink_cnt(backlink_col_key);
457,464✔
818
        return IteratorControl::AdvanceToNext;
457,464✔
819
    });
457,464✔
820
    return cnt;
174,060✔
821
}
174,060✔
822

823
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
824
{
32,010✔
825
    checked_update_if_needed();
32,010✔
826

16,011✔
827
    size_t cnt = 0;
32,010✔
828
    if (TableKey origin_table_key = origin.get_key()) {
32,010✔
829
        ColKey backlink_col_key;
32,010✔
830
        auto type = origin_col_key.get_type();
32,010✔
831
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
32,010✔
832
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
12✔
833
        }
12✔
834
        else {
31,998✔
835
            backlink_col_key = origin.get_opposite_column(origin_col_key);
31,998✔
836
        }
31,998✔
837

16,011✔
838
        cnt = get_backlink_cnt(backlink_col_key);
32,010✔
839
    }
32,010✔
840
    return cnt;
32,010✔
841
}
32,010✔
842

843
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
844
{
9,023,535✔
845
    ColKey backlink_col_key;
9,023,535✔
846
    auto type = origin_col_key.get_type();
9,023,535✔
847
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
9,023,541✔
848
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
849
    }
36✔
850
    else {
9,023,499✔
851
        backlink_col_key = origin.get_opposite_column(origin_col_key);
9,023,499✔
852
    }
9,023,499✔
853
    return get_backlink(backlink_col_key, backlink_ndx);
9,023,535✔
854
}
9,023,535✔
855

856
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key) const
857
{
696✔
858
    TableView tv(src_table, src_col_key, *this);
696✔
859
    tv.do_sync();
696✔
860
    return tv;
696✔
861
}
696✔
862

863
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
864
{
9,023,553✔
865
    get_table()->check_column(backlink_col);
9,023,553✔
866
    Allocator& alloc = get_alloc();
9,023,553✔
867
    Array fields(alloc);
9,023,553✔
868
    fields.init_from_mem(m_mem);
9,023,553✔
869

4,511,781✔
870
    ArrayBacklink backlinks(alloc);
9,023,553✔
871
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
9,023,553✔
872
    backlinks.init_from_parent();
9,023,553✔
873
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
9,023,553✔
874
}
9,023,553✔
875

876
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
877
{
289,002✔
878
    checked_update_if_needed();
289,002✔
879

144,270✔
880
    get_table()->check_column(backlink_col);
289,002✔
881
    Allocator& alloc = get_alloc();
289,002✔
882
    Array fields(alloc);
289,002✔
883
    fields.init_from_mem(m_mem);
289,002✔
884

144,270✔
885
    ArrayBacklink backlinks(alloc);
289,002✔
886
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
289,002✔
887
    backlinks.init_from_parent();
289,002✔
888

144,270✔
889
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
289,002✔
890
    std::vector<ObjKey> vec;
289,002✔
891
    vec.reserve(cnt);
289,002✔
892
    for (size_t i = 0; i < cnt; i++) {
553,800✔
893
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
264,798✔
894
    }
264,798✔
895
    return vec;
289,002✔
896
}
289,002✔
897

898
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
899
{
505,698✔
900
    Allocator& alloc = get_alloc();
505,698✔
901
    Array fields(alloc);
505,698✔
902
    fields.init_from_mem(m_mem);
505,698✔
903

252,459✔
904
    ArrayBacklink backlinks(alloc);
505,698✔
905
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
505,698✔
906
    backlinks.init_from_parent();
505,698✔
907

252,459✔
908
    return backlinks.get_backlink_count(m_row_ndx);
505,698✔
909
}
505,698✔
910

911
void Obj::verify_backlink(const Table& origin, ColKey origin_col_key, ObjKey origin_key) const
912
{
126,666✔
913
#ifdef REALM_DEBUG
126,666✔
914
    ColKey backlink_col_key;
126,666✔
915
    auto type = origin_col_key.get_type();
126,666✔
916
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
126,666✔
UNCOV
917
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
×
UNCOV
918
    }
×
919
    else {
126,666✔
920
        backlink_col_key = origin.get_opposite_column(origin_col_key);
126,666✔
921
    }
126,666✔
922

63,333✔
923
    Allocator& alloc = get_alloc();
126,666✔
924
    Array fields(alloc);
126,666✔
925
    fields.init_from_mem(m_mem);
126,666✔
926

63,333✔
927
    ArrayBacklink backlinks(alloc);
126,666✔
928
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
126,666✔
929
    backlinks.init_from_parent();
126,666✔
930

63,333✔
931
    REALM_ASSERT(backlinks.verify_backlink(m_row_ndx, origin_key.value));
126,666✔
932
#else
933
    static_cast<void>(origin);
934
    static_cast<void>(origin_col_key);
935
    static_cast<void>(origin_key);
936
#endif
937
}
126,666✔
938

939
void Obj::traverse_path(Visitor v, PathSizer ps, size_t path_length) const
940
{
90✔
941
    struct BacklinkTraverser : public LinkTranslator {
90✔
942
        BacklinkTraverser(Obj origin, ColKey origin_col_key, Obj dest)
90✔
943
            : LinkTranslator(origin, origin_col_key)
90✔
944
            , m_dest_obj(dest)
90✔
945
        {
69✔
946
        }
48✔
947
        void on_list_of_links(LnkLst& ll) final
90✔
948
        {
60✔
949
            auto i = ll.find_first(m_dest_obj.get_key());
30✔
950
            REALM_ASSERT(i != realm::npos);
30✔
951
            m_index = Mixed(int64_t(i));
30✔
952
        }
30✔
953
        void on_dictionary(Dictionary& dict) final
90✔
954
        {
48✔
955
            for (auto it : dict) {
12✔
956
                if (it.second.is_type(type_TypedLink) && it.second.get_link() == m_dest_obj.get_link()) {
12✔
957
                    m_index = it.first;
6✔
958
                    break;
6✔
959
                }
6✔
960
            }
12✔
961
            REALM_ASSERT(!m_index.is_null());
6✔
962
        }
6✔
963
        void on_list_of_mixed(Lst<Mixed>&) final
90✔
964
        {
45✔
965
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
966
        }
×
967
        void on_set_of_links(LnkSet&) final
90✔
968
        {
45✔
969
            REALM_UNREACHABLE(); // sets of embedded objects are not allowed at the schema level
UNCOV
970
        }
×
971
        void on_set_of_mixed(Set<Mixed>&) final
90✔
972
        {
45✔
973
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
UNCOV
974
        }
×
975
        void on_link_property(ColKey) final {}
51✔
976
        void on_mixed_property(ColKey) final {}
45✔
977
        Mixed result()
90✔
978
        {
69✔
979
            return m_index;
48✔
980
        }
48✔
981

45✔
982
    private:
90✔
983
        Mixed m_index;
90✔
984
        Obj m_dest_obj;
90✔
985
    };
90✔
986

45✔
987
    if (m_table->is_embedded()) {
90✔
988
        REALM_ASSERT(get_backlink_count() == 1);
48✔
989
        m_table->for_each_backlink_column([&](ColKey col_key) {
84✔
990
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
84✔
991
            if (backlinks.size() == 1) {
84✔
992
                TableRef tr = m_table->get_opposite_table(col_key);
48✔
993
                Obj obj = tr->get_object(backlinks[0]); // always the first (and only)
48✔
994
                auto next_col_key = m_table->get_opposite_column(col_key);
48✔
995
                BacklinkTraverser traverser{obj, next_col_key, *this};
48✔
996
                traverser.run();
48✔
997
                Mixed index = traverser.result();
48✔
998
                obj.traverse_path(v, ps, path_length + 1);
48✔
999
                v(obj, next_col_key, index);
48✔
1000
                return IteratorControl::Stop; // early out
48✔
1001
            }
48✔
1002
            return IteratorControl::AdvanceToNext; // try next column
36✔
1003
        });
36✔
1004
    }
48✔
1005
    else {
42✔
1006
        ps(path_length);
42✔
1007
    }
42✔
1008
}
90✔
1009

1010
Obj::FatPath Obj::get_fat_path() const
1011
{
30✔
1012
    FatPath result;
30✔
1013
    auto sizer = [&](size_t size) {
30✔
1014
        result.reserve(size);
30✔
1015
    };
30✔
1016
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
30✔
1017
        result.push_back({o2, col, idx});
30✔
1018
    };
30✔
1019
    traverse_path(step, sizer);
30✔
1020
    return result;
30✔
1021
}
30✔
1022

1023
FullPath Obj::get_path() const
1024
{
293,772✔
1025
    FullPath result;
293,772✔
1026
    if (m_table->is_embedded()) {
293,772✔
1027
        REALM_ASSERT(get_backlink_count() == 1);
168,762✔
1028
        m_table->for_each_backlink_column([&](ColKey col_key) {
276,990✔
1029
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
276,990✔
1030
            if (backlinks.size() == 1) {
276,990✔
1031
                TableRef origin_table = m_table->get_opposite_table(col_key);
168,762✔
1032
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
168,762✔
1033
                auto next_col_key = m_table->get_opposite_column(col_key);
168,762✔
1034

84,258✔
1035
                ColumnAttrMask attr = next_col_key.get_attrs();
168,762✔
1036
                Mixed index;
168,762✔
1037
                if (attr.test(col_attr_List)) {
168,762✔
1038
                    REALM_ASSERT(next_col_key.get_type() == col_type_Link);
58,104✔
1039
                    Lst<ObjKey> link_list(next_col_key);
58,104✔
1040
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
58,104✔
1041
                    REALM_ASSERT(i != realm::not_found);
58,104✔
1042
                    result = link_list.get_path();
58,104✔
1043
                    result.path_from_top.emplace_back(i);
58,104✔
1044
                }
58,104✔
1045
                else if (attr.test(col_attr_Dictionary)) {
110,658✔
1046
                    Dictionary dict(next_col_key);
45,594✔
1047
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
45,594✔
1048
                    REALM_ASSERT(ndx != realm::not_found);
45,594✔
1049
                    result = dict.get_path();
45,594✔
1050
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
45,594✔
1051
                }
45,594✔
1052
                else {
65,064✔
1053
                    result = obj.get_path();
65,064✔
1054
                    if (result.path_from_top.empty()) {
65,064✔
1055
                        result.path_from_top.push_back(next_col_key);
14,562✔
1056
                    }
14,562✔
1057
                    else {
50,502✔
1058
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
50,502✔
1059
                    }
50,502✔
1060
                }
65,064✔
1061

84,258✔
1062
                return IteratorControl::Stop; // early out
168,762✔
1063
            }
168,762✔
1064
            return IteratorControl::AdvanceToNext; // try next column
108,228✔
1065
        });
108,228✔
1066
    }
168,762✔
1067
    else {
125,010✔
1068
        result.top_objkey = get_key();
125,010✔
1069
        result.top_table = get_table()->get_key();
125,010✔
1070
    }
125,010✔
1071
    return result;
293,772✔
1072
}
293,772✔
1073

1074
std::string Obj::get_id() const
1075
{
29,421✔
1076
    std::ostringstream ostr;
29,421✔
1077
    auto path = get_path();
29,421✔
1078
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
29,421✔
1079
    ostr << top_table->get_class_name() << '[';
29,421✔
1080
    if (top_table->get_primary_key_column()) {
29,421✔
1081
        ostr << top_table->get_primary_key(path.top_objkey);
29,151✔
1082
    }
29,151✔
1083
    else {
270✔
1084
        ostr << path.top_objkey;
270✔
1085
    }
270✔
1086
    ostr << ']';
29,421✔
1087
    if (!path.path_from_top.empty()) {
29,421✔
1088
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
19,443✔
1089
        path.path_from_top[0] = PathElement(prop_name);
19,443✔
1090
        ostr << path.path_from_top;
19,443✔
1091
    }
19,443✔
1092
    return ostr.str();
29,421✔
1093
}
29,421✔
1094

1095
Path Obj::get_short_path() const noexcept
1096
{
367,020✔
1097
    return {};
367,020✔
1098
}
367,020✔
1099

1100
ColKey Obj::get_col_key() const noexcept
UNCOV
1101
{
×
UNCOV
1102
    return {};
×
UNCOV
1103
}
×
1104

1105
StablePath Obj::get_stable_path() const noexcept
1106
{
1,913,811✔
1107
    return {};
1,913,811✔
1108
}
1,913,811✔
1109

1110
void Obj::add_index(Path& path, const CollectionParent::Index& index) const
1111
{
470,736✔
1112
    if (path.empty()) {
470,736✔
1113
        path.emplace_back(get_table()->get_column_key(index));
467,490✔
1114
    }
467,490✔
1115
    else {
3,246✔
1116
        StringData col_name = get_table()->get_column_name(index);
3,246✔
1117
        path.emplace_back(col_name);
3,246✔
1118
    }
3,246✔
1119
}
470,736✔
1120

1121
std::string Obj::to_string() const
1122
{
12✔
1123
    std::ostringstream ostr;
12✔
1124
    to_json(ostr);
12✔
1125
    return ostr.str();
12✔
1126
}
12✔
1127

1128
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
1129
{
×
1130
    obj.to_json(ostr);
×
1131
    return ostr;
×
1132
}
×
1133

1134
/*********************************** Obj *************************************/
1135

1136
bool Obj::ensure_writeable()
1137
{
×
UNCOV
1138
    Allocator& alloc = get_alloc();
×
UNCOV
1139
    if (alloc.is_read_only(m_mem.get_ref())) {
×
UNCOV
1140
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
×
UNCOV
1141
        m_storage_version = alloc.get_storage_version();
×
UNCOV
1142
        return true;
×
UNCOV
1143
    }
×
UNCOV
1144
    return false;
×
UNCOV
1145
}
×
1146

1147
REALM_FORCEINLINE void Obj::sync(Node& arr)
1148
{
36,917,298✔
1149
    auto ref = arr.get_ref();
36,917,298✔
1150
    if (arr.has_missing_parent_update()) {
36,917,298✔
1151
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
270,690✔
1152
    }
270,690✔
1153
    if (m_mem.get_ref() != ref) {
36,917,298✔
1154
        m_mem = arr.get_mem();
532,875✔
1155
        m_storage_version = arr.get_alloc().get_storage_version();
532,875✔
1156
    }
532,875✔
1157
}
36,917,298✔
1158

1159
template <>
1160
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
1161
{
9,876✔
1162
    checked_update_if_needed();
9,876✔
1163
    get_table()->check_column(col_key);
9,876✔
1164
    auto type = col_key.get_type();
9,876✔
1165
    auto col_ndx = col_key.get_index();
9,876✔
1166
    bool recurse = false;
9,876✔
1167
    CascadeState state;
9,876✔
1168

4,932✔
1169
    if (type != col_type_Mixed)
9,876✔
UNCOV
1170
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
×
1171
    if (value_is_null(value) && !col_key.is_nullable()) {
9,876✔
UNCOV
1172
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
×
UNCOV
1173
    }
×
1174
    if (value.is_type(type_Link)) {
9,876✔
UNCOV
1175
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
×
UNCOV
1176
    }
×
1177

4,932✔
1178
    Mixed old_value = get_unfiltered_mixed(col_ndx);
9,876✔
1179
    if (old_value.is_type(type_TypedLink)) {
9,876✔
1180
        if (old_value == value) {
132✔
1181
            return *this;
24✔
1182
        }
24✔
1183
        auto old_link = old_value.get<ObjLink>();
108✔
1184
        recurse = remove_backlink(col_key, old_link, state);
108✔
1185
    }
108✔
1186
    else if (old_value.is_type(type_Dictionary)) {
9,744✔
1187
        Dictionary dict(*this, col_key);
36✔
1188
        recurse = dict.remove_backlinks(state);
36✔
1189
    }
36✔
1190
    else if (old_value.is_type(type_List)) {
9,708✔
1191
        Lst<Mixed> list(*this, col_key);
60✔
1192
        recurse = list.remove_backlinks(state);
60✔
1193
    }
60✔
1194

4,932✔
1195
    if (value.is_type(type_TypedLink)) {
9,864✔
1196
        if (m_table->is_asymmetric()) {
936✔
1197
            throw IllegalOperation("Links not allowed in asymmetric tables");
12✔
1198
        }
12✔
1199
        auto new_link = value.get<ObjLink>();
924✔
1200
        m_table->get_parent_group()->validate(new_link);
924✔
1201
        set_backlink(col_key, new_link);
924✔
1202
    }
924✔
1203

4,920✔
1204
    SearchIndex* index = m_table->get_search_index(col_key);
9,846✔
1205
    // The following check on unresolved is just a precaution as it should not
4,914✔
1206
    // be possible to hit that while Mixed is not a supported primary key type.
4,914✔
1207
    if (index && !m_key.is_unresolved()) {
9,840✔
1208
        index->set(m_key, value.is_unresolved_link() ? Mixed() : value);
1,137✔
1209
    }
1,140✔
1210

4,914✔
1211
    Allocator& alloc = get_alloc();
9,840✔
1212
    alloc.bump_content_version();
9,840✔
1213
    Array fallback(alloc);
9,840✔
1214
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,840✔
1215
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,840✔
1216
    ArrayMixed values(alloc);
9,840✔
1217
    values.set_parent(&fields, col_ndx.val + 1);
9,840✔
1218
    values.init_from_parent();
9,840✔
1219
    values.set(m_row_ndx, value);
9,840✔
1220

4,914✔
1221
    sync(fields);
9,840✔
1222

4,914✔
1223
    if (Replication* repl = get_replication())
9,840✔
1224
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
5,688✔
1225
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
5,688✔
1226

4,914✔
1227
    if (recurse)
9,840✔
UNCOV
1228
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1229

4,914✔
1230
    return *this;
9,840✔
1231
}
9,852✔
1232

1233
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1234
{
779,880✔
1235
    if (value.is_null()) {
779,880✔
1236
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
312✔
1237
        set_null(col_key);
312✔
1238
    }
312✔
1239
    else {
779,568✔
1240
        switch (col_key.get_type()) {
779,568✔
1241
            case col_type_Int:
722,694✔
1242
                if (col_key.get_attrs().test(col_attr_Nullable)) {
722,694✔
1243
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
1,770✔
1244
                }
1,770✔
1245
                else {
720,924✔
1246
                    set(col_key, value.get_int(), is_default);
720,924✔
1247
                }
720,924✔
1248
                break;
722,694✔
1249
            case col_type_Bool:
108✔
1250
                set(col_key, value.get_bool(), is_default);
108✔
1251
                break;
108✔
1252
            case col_type_Float:
246✔
1253
                set(col_key, value.get_float(), is_default);
246✔
1254
                break;
246✔
1255
            case col_type_Double:
1,416✔
1256
                set(col_key, value.get_double(), is_default);
1,416✔
1257
                break;
1,416✔
1258
            case col_type_String:
30,294✔
1259
                set(col_key, value.get_string(), is_default);
30,294✔
1260
                break;
30,294✔
1261
            case col_type_Binary:
17,442✔
1262
                set(col_key, value.get<Binary>(), is_default);
17,442✔
1263
                break;
17,442✔
1264
            case col_type_Mixed:
438✔
1265
                set(col_key, value, is_default);
438✔
1266
                break;
438✔
1267
            case col_type_Timestamp:
5,319✔
1268
                set(col_key, value.get<Timestamp>(), is_default);
5,319✔
1269
                break;
5,319✔
1270
            case col_type_ObjectId:
1,320✔
1271
                set(col_key, value.get<ObjectId>(), is_default);
1,320✔
1272
                break;
1,320✔
1273
            case col_type_Decimal:
102✔
1274
                set(col_key, value.get<Decimal128>(), is_default);
102✔
1275
                break;
102✔
1276
            case col_type_UUID:
108✔
1277
                set(col_key, value.get<UUID>(), is_default);
108✔
1278
                break;
108✔
1279
            case col_type_Link:
72✔
1280
                set(col_key, value.get<ObjKey>(), is_default);
72✔
1281
                break;
72✔
UNCOV
1282
            case col_type_TypedLink:
✔
UNCOV
1283
                set(col_key, value.get<ObjLink>(), is_default);
×
UNCOV
1284
                break;
×
UNCOV
1285
            default:
✔
UNCOV
1286
                break;
×
1287
        }
779,817✔
1288
    }
779,817✔
1289
    return *this;
779,817✔
1290
}
779,817✔
1291

1292
template <>
1293
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1294
{
19,594,029✔
1295
    checked_update_if_needed();
19,594,029✔
1296
    get_table()->check_column(col_key);
19,594,029✔
1297
    auto col_ndx = col_key.get_index();
19,594,029✔
1298

9,771,690✔
1299
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
19,594,029✔
UNCOV
1300
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
UNCOV
1301
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1302

9,771,690✔
1303
    SearchIndex* index = m_table->get_search_index(col_key);
19,594,029✔
1304
    if (index && !m_key.is_unresolved()) {
19,594,029✔
1305
        index->set(m_key, value);
183,114✔
1306
    }
183,114✔
1307

9,771,690✔
1308
    Allocator& alloc = get_alloc();
19,594,029✔
1309
    alloc.bump_content_version();
19,594,029✔
1310
    Array fallback(alloc);
19,594,029✔
1311
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
19,594,029✔
1312
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
19,594,029✔
1313
    auto attr = col_key.get_attrs();
19,594,029✔
1314
    if (attr.test(col_attr_Nullable)) {
19,594,029✔
1315
        ArrayIntNull values(alloc);
2,524,083✔
1316
        values.set_parent(&fields, col_ndx.val + 1);
2,524,083✔
1317
        values.init_from_parent();
2,524,083✔
1318
        values.set(m_row_ndx, value);
2,524,083✔
1319
    }
2,524,083✔
1320
    else {
17,069,946✔
1321
        ArrayInteger values(alloc);
17,069,946✔
1322
        values.set_parent(&fields, col_ndx.val + 1);
17,069,946✔
1323
        values.init_from_parent();
17,069,946✔
1324
        values.set(m_row_ndx, value);
17,069,946✔
1325
    }
17,069,946✔
1326

9,771,690✔
1327
    sync(fields);
19,594,029✔
1328

9,771,690✔
1329
    if (Replication* repl = get_replication()) {
19,594,029✔
1330
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
10,238,013✔
1331
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
10,237,827✔
1332
    }
10,238,013✔
1333

9,771,690✔
1334
    return *this;
19,594,029✔
1335
}
19,594,029✔
1336

1337
Obj& Obj::add_int(ColKey col_key, int64_t value)
1338
{
20,466✔
1339
    checked_update_if_needed();
20,466✔
1340
    get_table()->check_column(col_key);
20,466✔
1341
    auto col_ndx = col_key.get_index();
20,466✔
1342

10,257✔
1343
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
20,460✔
1344
        uint64_t ua = uint64_t(a);
20,454✔
1345
        uint64_t ub = uint64_t(b);
20,454✔
1346
        return int64_t(ua + ub);
20,454✔
1347
    };
20,454✔
1348

10,257✔
1349
    Allocator& alloc = get_alloc();
20,466✔
1350
    alloc.bump_content_version();
20,466✔
1351
    Array fallback(alloc);
20,466✔
1352
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
20,466✔
1353
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
20,466✔
1354

10,257✔
1355
    if (col_key.get_type() == col_type_Mixed) {
20,466✔
1356
        ArrayMixed values(alloc);
72✔
1357
        values.set_parent(&fields, col_ndx.val + 1);
72✔
1358
        values.init_from_parent();
72✔
1359
        Mixed old = values.get(m_row_ndx);
72✔
1360
        if (old.is_type(type_Int)) {
72✔
1361
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
66✔
1362
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
66✔
UNCOV
1363
                index->set(m_key, new_val);
×
UNCOV
1364
            }
×
1365
            values.set(m_row_ndx, Mixed(new_val));
66✔
1366
        }
66✔
1367
        else {
6✔
1368
            throw IllegalOperation("Value not an int");
6✔
1369
        }
6✔
1370
    }
20,394✔
1371
    else {
20,394✔
1372
        if (col_key.get_type() != col_type_Int)
20,394✔
UNCOV
1373
            throw IllegalOperation("Property not an int");
×
1374

10,221✔
1375
        auto attr = col_key.get_attrs();
20,394✔
1376
        if (attr.test(col_attr_Nullable)) {
20,394✔
1377
            ArrayIntNull values(alloc);
126✔
1378
            values.set_parent(&fields, col_ndx.val + 1);
126✔
1379
            values.init_from_parent();
126✔
1380
            util::Optional<int64_t> old = values.get(m_row_ndx);
126✔
1381
            if (old) {
126✔
1382
                auto new_val = add_wrap(*old, value);
120✔
1383
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
120✔
UNCOV
1384
                    index->set(m_key, new_val);
×
UNCOV
1385
                }
×
1386
                values.set(m_row_ndx, new_val);
120✔
1387
            }
120✔
1388
            else {
6✔
1389
                throw IllegalOperation("No prior value");
6✔
1390
            }
6✔
1391
        }
20,268✔
1392
        else {
20,268✔
1393
            ArrayInteger values(alloc);
20,268✔
1394
            values.set_parent(&fields, col_ndx.val + 1);
20,268✔
1395
            values.init_from_parent();
20,268✔
1396
            int64_t old = values.get(m_row_ndx);
20,268✔
1397
            auto new_val = add_wrap(old, value);
20,268✔
1398
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
20,268✔
1399
                index->set(m_key, new_val);
6✔
1400
            }
6✔
1401
            values.set(m_row_ndx, new_val);
20,268✔
1402
        }
20,268✔
1403
    }
20,394✔
1404

10,257✔
1405
    sync(fields);
20,460✔
1406

10,251✔
1407
    if (Replication* repl = get_replication()) {
20,454✔
1408
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
8,460✔
1409
    }
8,460✔
1410

10,251✔
1411
    return *this;
20,454✔
1412
}
20,466✔
1413

1414
template <>
1415
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
1416
{
261,789✔
1417
    checked_update_if_needed();
261,789✔
1418
    get_table()->check_column(col_key);
261,789✔
1419
    ColKey::Idx col_ndx = col_key.get_index();
261,789✔
1420
    ColumnType type = col_key.get_type();
261,789✔
1421
    if (type != col_type_Link)
261,789✔
UNCOV
1422
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
×
1423
    TableRef target_table = get_target_table(col_key);
261,789✔
1424
    TableKey target_table_key = target_table->get_key();
261,789✔
1425
    if (target_key) {
261,789✔
1426
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
258,192✔
1427
        if (!ct->is_valid(target_key)) {
261,228✔
1428
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
12✔
1429
        }
12✔
1430
        if (target_table->is_embedded()) {
261,228✔
UNCOV
1431
            throw IllegalOperation(
×
UNCOV
1432
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
×
UNCOV
1433
        }
×
1434
    }
261,789✔
1435
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
261,789✔
1436

130,878✔
1437
    if (target_key != old_key) {
261,789✔
1438
        CascadeState state(CascadeState::Mode::Strong);
260,856✔
1439

130,389✔
1440
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
260,856✔
1441
        _update_if_needed();
260,856✔
1442

130,389✔
1443
        Allocator& alloc = get_alloc();
260,856✔
1444
        alloc.bump_content_version();
260,856✔
1445
        Array fallback(alloc);
260,856✔
1446
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
260,856✔
1447
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
260,856✔
1448
        ArrayKey values(alloc);
260,856✔
1449
        values.set_parent(&fields, col_ndx.val + 1);
260,856✔
1450
        values.init_from_parent();
260,856✔
1451

130,389✔
1452
        values.set(m_row_ndx, target_key);
260,856✔
1453

130,389✔
1454
        sync(fields);
260,856✔
1455

130,389✔
1456
        if (Replication* repl = get_replication()) {
260,856✔
1457
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
37,800✔
1458
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
37,800✔
1459
        }
37,800✔
1460

130,389✔
1461
        if (recurse)
260,856✔
1462
            target_table->remove_recursive(state);
258✔
1463
    }
260,856✔
1464

130,878✔
1465
    return *this;
261,789✔
1466
}
261,789✔
1467

1468
template <>
1469
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
UNCOV
1470
{
×
1471
    checked_update_if_needed();
×
UNCOV
1472
    get_table()->check_column(col_key);
×
1473
    ColKey::Idx col_ndx = col_key.get_index();
×
1474
    ColumnType type = col_key.get_type();
×
1475
    if (type != col_type_TypedLink)
×
UNCOV
1476
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
×
1477
    m_table->get_parent_group()->validate(target_link);
×
1478

UNCOV
1479
    ObjLink old_link = get<ObjLink>(col_key); // Will update if needed
×
1480

1481
    if (target_link != old_link) {
×
1482
        CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All
×
1483
                                                                  : CascadeState::Mode::Strong);
×
1484

1485
        bool recurse = replace_backlink(col_key, old_link, target_link, state);
×
1486
        _update_if_needed();
×
1487

UNCOV
1488
        Allocator& alloc = get_alloc();
×
1489
        alloc.bump_content_version();
×
UNCOV
1490
        Array fallback(alloc);
×
1491
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
×
UNCOV
1492
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
1493
        ArrayTypedLink values(alloc);
×
1494
        values.set_parent(&fields, col_ndx.val + 1);
×
1495
        values.init_from_parent();
×
1496

UNCOV
1497
        values.set(m_row_ndx, target_link);
×
1498

1499
        sync(fields);
×
1500

UNCOV
1501
        if (Replication* repl = get_replication()) {
×
1502
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
×
1503
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
×
UNCOV
1504
        }
×
1505

UNCOV
1506
        if (recurse)
×
UNCOV
1507
            const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
UNCOV
1508
    }
×
1509

UNCOV
1510
    return *this;
×
UNCOV
1511
}
×
1512

1513
Obj Obj::create_and_set_linked_object(ColKey col_key, bool is_default)
1514
{
13,671✔
1515
    checked_update_if_needed();
13,671✔
1516
    get_table()->check_column(col_key);
13,671✔
1517
    ColKey::Idx col_ndx = col_key.get_index();
13,671✔
1518
    ColumnType type = col_key.get_type();
13,671✔
1519
    if (type != col_type_Link)
13,671✔
UNCOV
1520
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link type");
×
1521
    TableRef target_table = get_target_table(col_key);
13,671✔
1522
    Table& t = *target_table;
13,671✔
1523
    // Only links to embedded objects are allowed.
6,810✔
1524
    REALM_ASSERT(t.is_embedded() || !get_table()->is_asymmetric());
13,671!
1525
    // Incoming links to asymmetric objects are disallowed.
6,810✔
1526
    REALM_ASSERT(!t.is_asymmetric());
13,671✔
1527
    TableKey target_table_key = t.get_key();
13,671✔
1528
    auto result = t.is_embedded() ? t.create_linked_object() : t.create_object();
13,671✔
1529
    auto target_key = result.get_key();
13,671✔
1530
    ObjKey old_key = get<ObjKey>(col_key); // Will update if needed
13,671✔
1531
    if (old_key != ObjKey()) {
13,671✔
1532
        if (t.is_embedded()) {
48✔
1533
            // If this is an embedded object and there was already an embedded object here, then we need to
24✔
1534
            // emit an instruction to set the old embedded object to null to clear the old object on other
24✔
1535
            // sync clients. Without this, you'll only see the Set ObjectValue instruction, which is idempotent,
24✔
1536
            // and then array operations will have a corrupted prior_size.
24✔
1537
            if (Replication* repl = get_replication()) {
48✔
1538
                repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
24✔
1539
                          is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
24✔
1540
            }
24✔
1541
        }
48✔
1542
    }
48✔
1543

6,810✔
1544
    if (target_key != old_key) {
13,671✔
1545
        CascadeState state;
13,671✔
1546

6,810✔
1547
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
13,671✔
1548
        _update_if_needed();
13,671✔
1549

6,810✔
1550
        Allocator& alloc = get_alloc();
13,671✔
1551
        alloc.bump_content_version();
13,671✔
1552
        Array fallback(alloc);
13,671✔
1553
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
13,671✔
1554
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
13,671✔
1555
        ArrayKey values(alloc);
13,671✔
1556
        values.set_parent(&fields, col_ndx.val + 1);
13,671✔
1557
        values.init_from_parent();
13,671✔
1558

6,810✔
1559
        values.set(m_row_ndx, target_key);
13,671✔
1560

6,810✔
1561
        sync(fields);
13,671✔
1562

6,810✔
1563
        if (Replication* repl = get_replication()) {
13,671✔
1564
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
13,023✔
1565
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
13,023✔
1566
        }
13,023✔
1567

6,810✔
1568
        if (recurse)
13,671✔
1569
            target_table->remove_recursive(state);
48✔
1570
    }
13,671✔
1571

6,810✔
1572
    return result;
13,671✔
1573
}
13,671✔
1574

1575
namespace {
1576
template <class T>
1577
inline void check_range(const T&)
1578
{
2,141,370✔
1579
}
2,141,370✔
1580
template <>
1581
inline void check_range(const StringData& val)
1582
{
2,411,934✔
1583
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
2,411,934✔
1584
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
1,197,228✔
1585
}
2,411,934✔
1586
template <>
1587
inline void check_range(const BinaryData& val)
1588
{
5,162,826✔
1589
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
5,162,826✔
1590
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
2,581,524✔
1591
}
5,162,826✔
1592
} // namespace
1593

1594
// helper functions for filtering out calls to set_spec()
1595
template <class T>
1596
inline void Obj::set_spec(T&, ColKey)
1597
{
7,302,453✔
1598
}
7,302,453✔
1599
template <>
1600
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1601
{
2,411,817✔
1602
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
2,411,817✔
1603
    Spec* spec = const_cast<Spec*>(&get_spec());
2,411,817✔
1604
    values.set_spec(spec, spec_ndx);
2,411,817✔
1605
}
2,411,817✔
1606

1607
#if REALM_ENABLE_GEOSPATIAL
1608

1609
template <>
1610
Obj& Obj::set(ColKey col_key, Geospatial value, bool)
1611
{
246✔
1612
    checked_update_if_needed();
246✔
1613
    get_table()->check_column(col_key);
246✔
1614
    auto type = col_key.get_type();
246✔
1615

123✔
1616
    if (type != ColumnTypeTraits<Link>::column_id)
246✔
1617
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1618
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1619
                                           get_table()->get_column_name(col_key)));
6✔
1620

120✔
1621
    Obj geo = get_linked_object(col_key);
240✔
1622
    if (!geo) {
240✔
1623
        geo = create_and_set_linked_object(col_key);
216✔
1624
    }
216✔
1625
    value.assign_to(geo);
240✔
1626
    return *this;
240✔
1627
}
240✔
1628

1629
template <>
1630
Obj& Obj::set(ColKey col_key, std::optional<Geospatial> value, bool)
1631
{
12✔
1632
    checked_update_if_needed();
12✔
1633
    auto table = get_table();
12✔
1634
    table->check_column(col_key);
12✔
1635
    auto type = col_key.get_type();
12✔
1636
    auto attrs = col_key.get_attrs();
12✔
1637

6✔
1638
    if (type != ColumnTypeTraits<Link>::column_id)
12✔
1639
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1640
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1641
                                           get_table()->get_column_name(col_key)));
6✔
1642
    if (!value && !attrs.test(col_attr_Nullable))
6✔
1643
        throw NotNullable(Group::table_name_to_class_name(table->get_name()), table->get_column_name(col_key));
×
1644

3✔
1645
    if (!value) {
6✔
1646
        set_null(col_key);
6✔
1647
    }
6✔
UNCOV
1648
    else {
×
UNCOV
1649
        Obj geo = get_linked_object(col_key);
×
UNCOV
1650
        if (!geo) {
×
UNCOV
1651
            geo = create_and_set_linked_object(col_key);
×
UNCOV
1652
        }
×
UNCOV
1653
        value->assign_to(geo);
×
UNCOV
1654
    }
×
1655
    return *this;
6✔
1656
}
6✔
1657

1658
#endif
1659

1660
template <class T>
1661
Obj& Obj::set(ColKey col_key, T value, bool is_default)
1662
{
9,719,967✔
1663
    checked_update_if_needed();
9,719,967✔
1664
    get_table()->check_column(col_key);
9,719,967✔
1665
    auto type = col_key.get_type();
9,719,967✔
1666
    auto attrs = col_key.get_attrs();
9,719,967✔
1667
    auto col_ndx = col_key.get_index();
9,719,967✔
1668

4,852,035✔
1669
    if (type != ColumnTypeTraits<T>::column_id)
9,719,967✔
UNCOV
1670
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
UNCOV
1671
                              util::format("Property not a %1", ColumnTypeTraits<T>::column_id));
×
1672
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
9,719,967!
1673
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
6✔
1674

4,852,032✔
1675
    check_range(value);
9,719,961✔
1676

4,852,032✔
1677
    SearchIndex* index = m_table->get_search_index(col_key);
9,719,961✔
1678
    if (index && !m_key.is_unresolved()) {
9,719,961!
1679
        index->set(m_key, value);
472,494✔
1680
    }
472,494✔
1681

4,852,032✔
1682
    Allocator& alloc = get_alloc();
9,719,961✔
1683
    alloc.bump_content_version();
9,719,961✔
1684
    Array fallback(alloc);
9,719,961✔
1685
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,719,961✔
1686
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,719,961✔
1687
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
9,719,961✔
1688
    LeafType values(alloc);
9,719,961✔
1689
    values.set_parent(&fields, col_ndx.val + 1);
9,719,961✔
1690
    set_spec<LeafType>(values, col_key);
9,719,961✔
1691
    values.init_from_parent();
9,719,961✔
1692
    values.set(m_row_ndx, value);
9,719,961✔
1693

4,852,032✔
1694
    sync(fields);
9,719,961✔
1695

4,852,032✔
1696
    if (Replication* repl = get_replication())
9,719,961✔
1697
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
5,961,477✔
1698
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
5,961,450✔
1699

4,852,032✔
1700
    return *this;
9,719,961✔
1701
}
9,719,961✔
1702

1703
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
1704
INSTANTIATE_OBJ_SET(bool);
1705
INSTANTIATE_OBJ_SET(StringData);
1706
INSTANTIATE_OBJ_SET(float);
1707
INSTANTIATE_OBJ_SET(double);
1708
INSTANTIATE_OBJ_SET(Decimal128);
1709
INSTANTIATE_OBJ_SET(Timestamp);
1710
INSTANTIATE_OBJ_SET(BinaryData);
1711
INSTANTIATE_OBJ_SET(ObjectId);
1712
INSTANTIATE_OBJ_SET(UUID);
1713

1714
void Obj::set_int(ColKey::Idx col_ndx, int64_t value)
1715
{
534,195✔
1716
    checked_update_if_needed();
534,195✔
1717

267,192✔
1718
    Allocator& alloc = get_alloc();
534,195✔
1719
    alloc.bump_content_version();
534,195✔
1720
    Array fallback(alloc);
534,195✔
1721
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
534,195✔
1722
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
534,195✔
1723
    Array values(alloc);
534,195✔
1724
    values.set_parent(&fields, col_ndx.val + 1);
534,195✔
1725
    values.init_from_parent();
534,195✔
1726
    values.set(m_row_ndx, value);
534,195✔
1727

267,192✔
1728
    sync(fields);
534,195✔
1729
}
534,195✔
1730

1731
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
1732
{
3,162✔
1733
    checked_update_if_needed();
3,162✔
1734

1,581✔
1735
    Allocator& alloc = get_alloc();
3,162✔
1736
    alloc.bump_content_version();
3,162✔
1737
    Array fallback(alloc);
3,162✔
1738
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
3,162✔
1739
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
3,162✔
1740
    ArrayMixed values(alloc);
3,162✔
1741
    values.set_parent(&fields, col_ndx.val + 1);
3,162✔
1742
    values.init_from_parent();
3,162✔
1743
    values.set(m_row_ndx, Mixed(value, type));
3,162✔
1744

1,581✔
1745
    sync(fields);
3,162✔
1746
}
3,162✔
1747

1748
void Obj::add_backlink(ColKey backlink_col_key, ObjKey origin_key)
1749
{
6,755,745✔
1750
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
6,755,745✔
1751
    Allocator& alloc = get_alloc();
6,755,745✔
1752
    alloc.bump_content_version();
6,755,745✔
1753
    Array fallback(alloc);
6,755,745✔
1754
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
6,755,745✔
1755

3,377,469✔
1756
    ArrayBacklink backlinks(alloc);
6,755,745✔
1757
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
6,755,745✔
1758
    backlinks.init_from_parent();
6,755,745✔
1759

3,377,469✔
1760
    backlinks.add(m_row_ndx, origin_key);
6,755,745✔
1761

3,377,469✔
1762
    sync(fields);
6,755,745✔
1763
}
6,755,745✔
1764

1765
bool Obj::remove_one_backlink(ColKey backlink_col_key, ObjKey origin_key)
1766
{
196,863✔
1767
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
196,863✔
1768
    Allocator& alloc = get_alloc();
196,863✔
1769
    alloc.bump_content_version();
196,863✔
1770
    Array fallback(alloc);
196,863✔
1771
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
196,863✔
1772

98,391✔
1773
    ArrayBacklink backlinks(alloc);
196,863✔
1774
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
196,863✔
1775
    backlinks.init_from_parent();
196,863✔
1776

98,391✔
1777
    bool ret = backlinks.remove(m_row_ndx, origin_key);
196,863✔
1778

98,391✔
1779
    sync(fields);
196,863✔
1780

98,391✔
1781
    return ret;
196,863✔
1782
}
196,863✔
1783

1784
template <class ValueType>
1785
inline void Obj::nullify_single_link(ColKey col, ValueType target)
1786
{
891✔
1787
    ColKey::Idx origin_col_ndx = col.get_index();
891✔
1788
    Allocator& alloc = get_alloc();
891✔
1789
    Array fallback(alloc);
891✔
1790
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
891✔
1791
    using ArrayType = typename ColumnTypeTraits<ValueType>::cluster_leaf_type;
891✔
1792
    ArrayType links(alloc);
891✔
1793
    links.set_parent(&fields, origin_col_ndx.val + 1);
891✔
1794
    links.init_from_parent();
891✔
1795
    // Ensure we are nullifying correct link
444✔
1796
    REALM_ASSERT(links.get(m_row_ndx) == target);
891✔
1797
    links.set(m_row_ndx, ValueType{});
891✔
1798
    sync(fields);
891✔
1799

444✔
1800
    if (Replication* repl = get_replication())
891✔
1801
        repl->nullify_link(m_table.unchecked_ptr(), col,
759✔
1802
                           m_key); // Throws
759✔
1803
}
891✔
1804

1805
template <>
1806
inline void Obj::nullify_single_link<Mixed>(ColKey col, Mixed target)
1807
{
18✔
1808
    ColKey::Idx origin_col_ndx = col.get_index();
18✔
1809
    Allocator& alloc = get_alloc();
18✔
1810
    Array fallback(alloc);
18✔
1811
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
18✔
1812
    ArrayMixed mixed(alloc);
18✔
1813
    mixed.set_parent(&fields, origin_col_ndx.val + 1);
18✔
1814
    mixed.init_from_parent();
18✔
1815
    auto val = mixed.get(m_row_ndx);
18✔
1816
    bool result = false;
18✔
1817
    if (val.is_type(type_TypedLink)) {
18✔
1818
        // Ensure we are nullifying correct link
6✔
1819
        result = (val == target);
12✔
1820
        mixed.set(m_row_ndx, Mixed{});
12✔
1821
        sync(fields);
12✔
1822

6✔
1823
        if (Replication* repl = get_replication())
12✔
1824
            repl->nullify_link(m_table.unchecked_ptr(), col,
×
1825
                               m_key); // Throws
×
1826
    }
12✔
1827
    else if (val.is_type(type_Dictionary)) {
6✔
1828
        Dictionary dict(*this, col);
6✔
1829
        result = dict.nullify(target.get_link());
6✔
1830
    }
6✔
UNCOV
1831
    else if (val.is_type(type_List)) {
×
UNCOV
1832
        Lst<Mixed> list(*this, col);
×
UNCOV
1833
        result = list.nullify(target.get_link());
×
UNCOV
1834
    }
×
1835
    REALM_ASSERT(result);
18✔
1836
    static_cast<void>(result);
18✔
1837
}
18✔
1838

1839
void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) &&
1840
{
6,975✔
1841
    REALM_ASSERT(get_alloc().get_storage_version() == m_storage_version);
6,975✔
1842

3,486✔
1843
    struct LinkNullifier : public LinkTranslator {
6,975✔
1844
        LinkNullifier(Obj origin_obj, ColKey origin_col, ObjLink target)
6,975✔
1845
            : LinkTranslator(origin_obj, origin_col)
6,975✔
1846
            , m_target_link(target)
6,975✔
1847
        {
6,975✔
1848
        }
6,975✔
1849
        void on_list_of_links(LnkLst&) final
6,975✔
1850
        {
4,641✔
1851
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,310✔
1852
        }
2,310✔
1853
        void on_list_of_mixed(Lst<Mixed>& list) final
6,975✔
1854
        {
3,723✔
1855
            list.nullify(m_target_link);
474✔
1856
        }
474✔
1857
        void on_set_of_links(LnkSet&) final
6,975✔
1858
        {
4,617✔
1859
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,262✔
1860
        }
2,262✔
1861
        void on_set_of_mixed(Set<Mixed>&) final
6,975✔
1862
        {
3,636✔
1863
            nullify_set(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
300✔
1864
        }
300✔
1865
        void on_dictionary(Dictionary& dict) final
6,975✔
1866
        {
3,846✔
1867
            dict.nullify(m_target_link);
720✔
1868
        }
720✔
1869
        void on_link_property(ColKey origin_col_key) final
6,975✔
1870
        {
3,933✔
1871
            m_origin_obj.nullify_single_link<ObjKey>(origin_col_key, m_target_link.get_obj_key());
891✔
1872
        }
891✔
1873
        void on_mixed_property(ColKey origin_col_key) final
6,975✔
1874
        {
3,495✔
1875
            m_origin_obj.nullify_single_link<Mixed>(origin_col_key, Mixed{m_target_link});
18✔
1876
        }
18✔
1877

3,486✔
1878
    private:
6,975✔
1879
        ObjLink m_target_link;
6,975✔
1880
    } nullifier{*this, origin_col_key, target_link};
6,975✔
1881

3,486✔
1882
    nullifier.run();
6,975✔
1883

3,486✔
1884
    get_alloc().bump_content_version();
6,975✔
1885
}
6,975✔
1886

1887

1888
struct EmbeddedObjectLinkMigrator : public LinkTranslator {
1889
    EmbeddedObjectLinkMigrator(Obj origin, ColKey origin_col, Obj dest_orig, Obj dest_replace)
1890
        : LinkTranslator(origin, origin_col)
1891
        , m_dest_orig(dest_orig)
1892
        , m_dest_replace(dest_replace)
1893
    {
60,318✔
1894
    }
60,318✔
1895
    void on_list_of_links(LnkLst& list) final
1896
    {
168✔
1897
        auto n = list.find_first(m_dest_orig.get_key());
168✔
1898
        REALM_ASSERT(n != realm::npos);
168✔
1899
        list.set(n, m_dest_replace.get_key());
168✔
1900
    }
168✔
1901
    void on_dictionary(Dictionary& dict) final
1902
    {
60✔
1903
        auto pos = dict.find_any(m_dest_orig.get_link());
60✔
1904
        REALM_ASSERT(pos != realm::npos);
60✔
1905
        Mixed key = dict.get_key(pos);
60✔
1906
        dict.insert(key, m_dest_replace.get_link());
60✔
1907
    }
60✔
1908
    void on_link_property(ColKey col) final
1909
    {
60,090✔
1910
        REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
60,090✔
1911
        m_origin_obj.set(col, m_dest_replace.get_key());
60,090✔
1912
    }
60,090✔
1913
    void on_set_of_links(LnkSet&) final
1914
    {
×
1915
        // this should never happen because sets of embedded objects are not allowed at the schema level
1916
        REALM_UNREACHABLE();
1917
    }
×
1918
    // The following cases have support here but are expected to fail later on in the
1919
    // migration due to core not yet supporting untyped Mixed links to embedded objects.
1920
    void on_set_of_mixed(Set<Mixed>& set) final
1921
    {
×
1922
        auto did_erase_pair = set.erase(m_dest_orig.get_link());
×
1923
        REALM_ASSERT(did_erase_pair.second);
×
UNCOV
1924
        set.insert(m_dest_replace.get_link());
×
1925
    }
×
1926
    void on_list_of_mixed(Lst<Mixed>& list) final
1927
    {
×
1928
        auto n = list.find_any(m_dest_orig.get_link());
×
1929
        REALM_ASSERT(n != realm::npos);
×
UNCOV
1930
        list.insert_any(n, m_dest_replace.get_link());
×
UNCOV
1931
    }
×
1932
    void on_mixed_property(ColKey col) final
UNCOV
1933
    {
×
UNCOV
1934
        REALM_ASSERT(m_origin_obj.get<Mixed>(col).is_null() ||
×
UNCOV
1935
                     m_origin_obj.get<Mixed>(col) == m_dest_orig.get_link());
×
UNCOV
1936
        m_origin_obj.set_any(col, m_dest_replace.get_link());
×
UNCOV
1937
    }
×
1938

1939
private:
1940
    Obj m_dest_orig;
1941
    Obj m_dest_replace;
1942
};
1943

1944
void Obj::handle_multiple_backlinks_during_schema_migration()
1945
{
54✔
1946
    REALM_ASSERT(!m_table->get_primary_key_column());
54✔
1947
    converters::EmbeddedObjectConverter embedded_obj_tracker;
54✔
1948
    auto copy_links = [&](ColKey col) {
108✔
1949
        auto opposite_table = m_table->get_opposite_table(col);
108✔
1950
        auto opposite_column = m_table->get_opposite_column(col);
108✔
1951
        auto backlinks = get_all_backlinks(col);
108✔
1952
        for (auto backlink : backlinks) {
60,318✔
1953
            // create a new obj
30,159✔
1954
            auto obj = m_table->create_object();
60,318✔
1955
            embedded_obj_tracker.track(*this, obj);
60,318✔
1956
            auto linking_obj = opposite_table->get_object(backlink);
60,318✔
1957
            // change incoming links to point to the newly created object
30,159✔
1958
            EmbeddedObjectLinkMigrator{linking_obj, opposite_column, *this, obj}.run();
60,318✔
1959
        }
60,318✔
1960
        embedded_obj_tracker.process_pending();
108✔
1961
        return IteratorControl::AdvanceToNext;
108✔
1962
    };
108✔
1963
    m_table->for_each_backlink_column(copy_links);
54✔
1964
}
54✔
1965

1966
LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const
1967
{
299,322✔
1968
    auto list = CollectionParent::get_listbase_ptr(col_key, 0);
299,322✔
1969
    list->set_owner(*this, col_key);
299,322✔
1970
    return list;
299,322✔
1971
}
299,322✔
1972

1973
SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const
1974
{
43,518✔
1975
    auto set = CollectionParent::get_setbase_ptr(col_key, 0);
43,518✔
1976
    set->set_owner(*this, col_key);
43,518✔
1977
    return set;
43,518✔
1978
}
43,518✔
1979

1980
Dictionary Obj::get_dictionary(ColKey col_key) const
1981
{
91,569✔
1982
    REALM_ASSERT(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed);
91,569✔
1983
    checked_update_if_needed();
91,569✔
1984
    return Dictionary(Obj(*this), col_key);
91,569✔
1985
}
91,569✔
1986

1987
Obj& Obj::set_collection(ColKey col_key, CollectionType type)
1988
{
2,274✔
1989
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
2,274✔
1990
    if ((col_key.is_dictionary() && type == CollectionType::Dictionary) ||
2,274✔
1991
        (col_key.is_list() && type == CollectionType::List)) {
2,265✔
1992
        return *this;
30✔
1993
    }
30✔
1994
    checked_update_if_needed();
2,244✔
1995
    Mixed new_val(0, type);
2,244✔
1996

1,122✔
1997
    if (type == CollectionType::Set) {
2,244✔
UNCOV
1998
        throw IllegalOperation("Set nested in Mixed is not supported");
×
UNCOV
1999
    }
×
2000

1,122✔
2001
    ArrayMixed arr(_get_alloc());
2,244✔
2002
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
2,244✔
2003
    arr.init_from_ref(ref);
2,244✔
2004
    auto old_val = arr.get(m_row_ndx);
2,244✔
2005

1,122✔
2006
    if (old_val != new_val) {
2,244✔
2007
        CascadeState state;
1,896✔
2008
        if (old_val.is_type(type_TypedLink)) {
1,896✔
UNCOV
2009
            remove_backlink(col_key, old_val.get<ObjLink>(), state);
×
UNCOV
2010
        }
×
2011
        else if (old_val.is_type(type_Dictionary)) {
1,896✔
2012
            Dictionary dict(*this, col_key);
114✔
2013
            dict.remove_backlinks(state);
114✔
2014
        }
114✔
2015
        else if (old_val.is_type(type_List)) {
1,782✔
2016
            Lst<Mixed> list(*this, col_key);
138✔
2017
            list.remove_backlinks(state);
138✔
2018
        }
138✔
2019

948✔
2020
        if (SearchIndex* index = m_table->get_search_index(col_key)) {
1,896✔
2021
            index->set(m_key, new_val);
6✔
2022
        }
6✔
2023

948✔
2024
        Allocator& alloc = _get_alloc();
1,896✔
2025
        alloc.bump_content_version();
1,896✔
2026

948✔
2027
        Array fallback(alloc);
1,896✔
2028
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
1,896✔
2029
        ArrayMixed values(alloc);
1,896✔
2030
        values.set_parent(&fields, col_key.get_index().val + 1);
1,896✔
2031
        values.init_from_parent();
1,896✔
2032

948✔
2033
        values.set(m_row_ndx, new_val);
1,896✔
2034
        values.set_key(m_row_ndx, CollectionParent::generate_key(0x10));
1,896✔
2035

948✔
2036
        sync(fields);
1,896✔
2037

948✔
2038
        if (Replication* repl = get_replication())
1,896✔
2039
            repl->set(m_table.unchecked_ptr(), col_key, m_key, new_val); // Throws
1,794✔
2040
    }
1,896✔
2041

1,122✔
2042
    return *this;
2,244✔
2043
}
2,244✔
2044

2045
DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const
2046
{
462✔
2047
    return std::make_shared<Dictionary>(get_dictionary(col_key));
462✔
2048
}
462✔
2049

2050
DictionaryPtr Obj::get_dictionary_ptr(const Path& path) const
2051
{
216✔
2052
    return std::dynamic_pointer_cast<Dictionary>(get_collection_ptr(path));
216✔
2053
}
216✔
2054

2055
Dictionary Obj::get_dictionary(StringData col_name) const
2056
{
16,578✔
2057
    return get_dictionary(get_column_key(col_name));
16,578✔
2058
}
16,578✔
2059

2060
CollectionPtr Obj::get_collection_ptr(const Path& path) const
2061
{
11,532✔
2062
    REALM_ASSERT(path.size() > 0);
11,532✔
2063
    // First element in path must be column name
5,766✔
2064
    auto col_key = path[0].is_col_key() ? path[0].get_col_key() : m_table->get_column_key(path[0].get_key());
11,448✔
2065
    REALM_ASSERT(col_key);
11,532✔
2066
    size_t level = 1;
11,532✔
2067
    CollectionBasePtr collection = get_collection_ptr(col_key);
11,532✔
2068

5,766✔
2069
    while (level < path.size()) {
12,354✔
2070
        auto& path_elem = path[level];
822✔
2071
        Mixed ref;
822✔
2072
        if (collection->get_collection_type() == CollectionType::List) {
822✔
2073
            ref = collection->get_any(path_elem.get_ndx());
402✔
2074
        }
402✔
2075
        else {
420✔
2076
            ref = dynamic_cast<Dictionary*>(collection.get())->get(path_elem.get_key());
420✔
2077
        }
420✔
2078
        if (ref.is_type(type_List)) {
822✔
2079
            collection = collection->get_list(path_elem);
486✔
2080
        }
486✔
2081
        else if (ref.is_type(type_Dictionary)) {
336✔
2082
            collection = collection->get_dictionary(path_elem);
336✔
2083
        }
336✔
UNCOV
2084
        else {
×
UNCOV
2085
            throw InvalidArgument("Wrong path");
×
UNCOV
2086
        }
×
2087
        level++;
822✔
2088
    }
822✔
2089

5,766✔
2090
    return collection;
11,532✔
2091
}
11,532✔
2092

2093
void Obj::translate_path(const StablePath& stable_path, Path& path) const
2094
{
36✔
2095
    ColKey col_key = m_table->get_column_key(stable_path[0]);
36✔
2096
    path.emplace_back(m_table->get_column_name(col_key));
36✔
2097
    if (stable_path.size() > 1) {
36✔
2098
        CollectionBasePtr collection = get_collection_ptr(col_key);
36✔
2099
        dynamic_cast<CollectionParent*>(collection.get())->translate_path(stable_path, path);
36✔
2100
    }
36✔
2101
}
36✔
2102

2103
CollectionPtr Obj::get_collection_by_stable_path(const StablePath& path) const
2104
{
10,536✔
2105
    // First element in path is phony column key
5,268✔
2106
    ColKey col_key = m_table->get_column_key(path[0]);
10,536✔
2107
    size_t level = 1;
10,536✔
2108
    CollectionBasePtr collection = get_collection_ptr(col_key);
10,536✔
2109

5,268✔
2110
    while (level < path.size()) {
11,154✔
2111
        auto& index = path[level];
630✔
2112
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
630✔
2113
            Mixed ref;
630✔
2114
            PathElement path_elem;
630✔
2115
            if (collection->get_collection_type() == CollectionType::List) {
630✔
2116
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
378✔
2117
                size_t ndx = list_of_mixed->find_index(index);
378✔
2118
                if (ndx != realm::not_found) {
378✔
2119
                    ref = list_of_mixed->get(ndx);
378✔
2120
                    path_elem = ndx;
378✔
2121
                }
378✔
2122
            }
378✔
2123
            else {
252✔
2124
                auto dict = dynamic_cast<Dictionary*>(collection.get());
252✔
2125
                size_t ndx = dict->find_index(index);
252✔
2126
                if (ndx != realm::not_found) {
252✔
2127
                    ref = dict->get_any(ndx);
240✔
2128
                    path_elem = dict->get_key(ndx).get_string();
240✔
2129
                }
240✔
2130
            }
252✔
2131
            return {ref, path_elem};
630✔
2132
        };
630✔
2133
        auto [ref, path_elem] = get_ref();
630✔
2134
        if (ref.is_type(type_List)) {
630✔
2135
            collection = collection->get_list(path_elem);
462✔
2136
        }
462✔
2137
        else if (ref.is_type(type_Dictionary)) {
168✔
2138
            collection = collection->get_dictionary(path_elem);
156✔
2139
        }
156✔
2140
        else {
12✔
2141
            return nullptr;
12✔
2142
        }
12✔
2143
        level++;
618✔
2144
    }
618✔
2145

5,268✔
2146
    return collection;
10,530✔
2147
}
10,536✔
2148

2149
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2150
{
129,777✔
2151
    if (col_key.is_collection()) {
129,777✔
2152
        auto collection = CollectionParent::get_collection_ptr(col_key, 0);
127,215✔
2153
        collection->set_owner(*this, col_key);
127,215✔
2154
        return collection;
127,215✔
2155
    }
127,215✔
2156
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
2,562✔
2157
    auto val = get<Mixed>(col_key);
2,562✔
2158
    if (val.is_type(type_List)) {
2,562✔
2159
        return std::make_shared<Lst<Mixed>>(*this, col_key);
1,428✔
2160
    }
1,428✔
2161
    REALM_ASSERT(val.is_type(type_Dictionary));
1,134✔
2162
    return std::make_shared<Dictionary>(*this, col_key);
1,134✔
2163
}
1,134✔
2164

2165
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
2166
{
396✔
2167
    return get_collection_ptr(get_column_key(col_name));
396✔
2168
}
396✔
2169

2170
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
2171
{
3,114✔
2172
    if (col_key.is_list()) {
3,114✔
2173
        return get_linklist_ptr(col_key);
2,994✔
2174
    }
2,994✔
2175
    else if (col_key.is_set()) {
120✔
2176
        return get_linkset_ptr(col_key);
78✔
2177
    }
78✔
2178
    else if (col_key.is_dictionary()) {
42✔
2179
        auto dict = get_dictionary(col_key);
42✔
2180
        return std::make_unique<DictionaryLinkValues>(dict);
42✔
2181
    }
42✔
UNCOV
2182
    return {};
×
UNCOV
2183
}
×
2184

2185
template <class T>
2186
inline void replace_in_linkset(Obj& obj, ColKey origin_col_key, T target, T replacement)
2187
{
288✔
2188
    Set<T> link_set(origin_col_key);
288✔
2189
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
288✔
2190

144✔
2191
    REALM_ASSERT(ndx != realm::npos); // There has to be one
288✔
2192

144✔
2193
    link_set.erase(target);
288✔
2194
    link_set.insert(replacement);
288✔
2195
}
288✔
2196

2197
inline void replace_in_dictionary(Obj& obj, ColKey origin_col_key, Mixed target, Mixed replacement)
2198
{
×
UNCOV
2199
    Dictionary dict(origin_col_key);
×
UNCOV
2200
    size_t ndx = find_link_value_in_collection(dict, obj, origin_col_key, target);
×
UNCOV
2201

×
UNCOV
2202
    REALM_ASSERT(ndx != realm::npos); // There has to be one
×
UNCOV
2203

×
UNCOV
2204
    auto key = dict.get_key(ndx);
×
UNCOV
2205
    dict.insert(key, replacement);
×
UNCOV
2206
}
×
2207

2208
void Obj::assign_pk_and_backlinks(Obj& other)
2209
{
11,679✔
2210
    struct LinkReplacer : LinkTranslator {
11,679✔
2211
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
11,679✔
2212
            : LinkTranslator(origin, origin_col_key)
11,679✔
2213
            , m_dest_orig(dest_orig)
11,679✔
2214
            , m_dest_replace(dest_replace)
11,679✔
2215
        {
34,314✔
2216
        }
34,314✔
2217
        void on_list_of_links(LnkLst&) final
11,679✔
2218
        {
27,414✔
2219
            auto linklist = m_origin_obj.get_linklist(m_origin_col_key);
27,414✔
2220
            linklist.replace_link(m_dest_orig.get_key(), m_dest_replace.get_key());
27,414✔
2221
        }
27,414✔
2222
        void on_list_of_mixed(Lst<Mixed>& list) final
11,679✔
2223
        {
5,892✔
2224
            list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
114✔
2225
        }
114✔
2226
        void on_set_of_links(LnkSet&) final
11,679✔
2227
        {
5,904✔
2228
            replace_in_linkset(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
138✔
2229
        }
138✔
2230
        void on_set_of_mixed(Set<Mixed>&) final
11,679✔
2231
        {
5,910✔
2232
            replace_in_linkset<Mixed>(m_origin_obj, m_origin_col_key, m_dest_orig.get_link(),
150✔
2233
                                      m_dest_replace.get_link());
150✔
2234
        }
150✔
2235
        void on_dictionary(Dictionary& dict) final
11,679✔
2236
        {
5,961✔
2237
            dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
252✔
2238
        }
252✔
2239
        void on_link_property(ColKey col) final
11,679✔
2240
        {
8,925✔
2241
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
6,180✔
2242
            // Handle links as plain integers. Backlinks has been taken care of.
3,090✔
2243
            // Be careful here - links are stored as value + 1 so that null link (-1) will be 0
3,090✔
2244
            auto new_key = m_dest_replace.get_key();
6,180✔
2245
            m_origin_obj.set_int(col.get_index(), new_key.value + 1);
6,180✔
2246
            if (Replication* repl = m_origin_obj.get_replication())
6,180✔
2247
                repl->set(m_origin_obj.get_table().unchecked_ptr(), col, m_origin_obj.get_key(), new_key);
6,132✔
2248
        }
6,180✔
2249
        void on_mixed_property(ColKey col) final
11,679✔
2250
        {
5,868✔
2251
            auto val = m_origin_obj.get_any(col);
66✔
2252
            if (val.is_type(type_Dictionary)) {
66✔
2253
                Dictionary dict(m_origin_obj, m_origin_col_key);
12✔
2254
                dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2255
            }
12✔
2256
            else if (val.is_type(type_List)) {
54✔
2257
                Lst<Mixed> list(m_origin_obj, m_origin_col_key);
12✔
2258
                list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2259
            }
12✔
2260
            else {
42✔
2261
                REALM_ASSERT(val.is_null() || val.get_link().get_obj_key() == m_dest_orig.get_key());
42✔
2262
                m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
42✔
2263
            }
42✔
2264
        }
66✔
2265

5,835✔
2266
    private:
11,679✔
2267
        const Obj& m_dest_orig;
11,679✔
2268
        const Obj& m_dest_replace;
11,679✔
2269
    };
11,679✔
2270

5,835✔
2271
    REALM_ASSERT(get_table() == other.get_table());
11,679✔
2272
    if (auto col_pk = m_table->get_primary_key_column()) {
11,679✔
2273
        Mixed val = other.get_any(col_pk);
11,517✔
2274
        this->set_any(col_pk, val);
11,517✔
2275
    }
11,517✔
2276
    auto nb_tombstones = m_table->m_tombstones->size();
11,679✔
2277

5,835✔
2278
    auto copy_links = [this, &other, nb_tombstones](ColKey col) {
11,049✔
2279
        if (nb_tombstones != m_table->m_tombstones->size()) {
10,428✔
2280
            // Object has been deleted - we are done
UNCOV
2281
            return IteratorControl::Stop;
×
UNCOV
2282
        }
×
2283

5,214✔
2284
        auto t = m_table->get_opposite_table(col);
10,428✔
2285
        auto c = m_table->get_opposite_column(col);
10,428✔
2286
        auto backlinks = other.get_all_backlinks(col);
10,428✔
2287

5,214✔
2288
        if (c.get_type() == col_type_Link && !(c.is_dictionary() || c.is_set())) {
10,428✔
2289
            auto idx = col.get_index();
9,762✔
2290
            // Transfer the backlinks from tombstone to live object
4,881✔
2291
            REALM_ASSERT(_get<int64_t>(idx) == 0);
9,762✔
2292
            auto other_val = other._get<int64_t>(idx);
9,762✔
2293
            set_int(idx, other_val);
9,762✔
2294
            other.set_int(idx, 0);
9,762✔
2295
        }
9,762✔
2296

5,214✔
2297
        for (auto bl : backlinks) {
34,314✔
2298
            auto linking_obj = t->get_object(bl);
34,314✔
2299
            LinkReplacer replacer{linking_obj, c, other, *this};
34,314✔
2300
            replacer.run();
34,314✔
2301
        }
34,314✔
2302
        return IteratorControl::AdvanceToNext;
10,428✔
2303
    };
10,428✔
2304
    m_table->for_each_backlink_column(copy_links);
11,679✔
2305
}
11,679✔
2306

2307
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
2308
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
2309
template float Obj::get<float>(ColKey col_key) const;
2310
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
2311
template double Obj::get<double>(ColKey col_key) const;
2312
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2313
template StringData Obj::get<StringData>(ColKey col_key) const;
2314
template BinaryData Obj::get<BinaryData>(ColKey col_key) const;
2315
template Timestamp Obj::get<Timestamp>(ColKey col_key) const;
2316
template ObjectId Obj::get<ObjectId>(ColKey col_key) const;
2317
template util::Optional<ObjectId> Obj::get<util::Optional<ObjectId>>(ColKey col_key) const;
2318
template ObjKey Obj::get<ObjKey>(ColKey col_key) const;
2319
template Decimal128 Obj::get<Decimal128>(ColKey col_key) const;
2320
template ObjLink Obj::get<ObjLink>(ColKey col_key) const;
2321
template Mixed Obj::get<Mixed>(realm::ColKey) const;
2322
template UUID Obj::get<UUID>(realm::ColKey) const;
2323
template util::Optional<UUID> Obj::get<util::Optional<UUID>>(ColKey col_key) const;
2324

2325
template <class T>
2326
inline void Obj::do_set_null(ColKey col_key)
2327
{
39,234✔
2328
    ColKey::Idx col_ndx = col_key.get_index();
39,234✔
2329
    Allocator& alloc = get_alloc();
39,234✔
2330
    alloc.bump_content_version();
39,234✔
2331
    Array fallback(alloc);
39,234✔
2332
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
39,234✔
2333

19,533✔
2334
    T values(alloc);
39,234✔
2335
    values.set_parent(&fields, col_ndx.val + 1);
39,234✔
2336
    values.init_from_parent();
39,234✔
2337
    values.set_null(m_row_ndx);
39,234✔
2338

19,533✔
2339
    sync(fields);
39,234✔
2340
}
39,234✔
2341

2342
template <>
2343
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2344
{
2,541✔
2345
    ColKey::Idx col_ndx = col_key.get_index();
2,541✔
2346
    size_t spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
2,541✔
2347
    Allocator& alloc = get_alloc();
2,541✔
2348
    alloc.bump_content_version();
2,541✔
2349
    Array fallback(alloc);
2,541✔
2350
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
2,541✔
2351

1,299✔
2352
    ArrayString values(alloc);
2,541✔
2353
    values.set_parent(&fields, col_ndx.val + 1);
2,541✔
2354
    values.set_spec(const_cast<Spec*>(&get_spec()), spec_ndx);
2,541✔
2355
    values.init_from_parent();
2,541✔
2356
    values.set_null(m_row_ndx);
2,541✔
2357

1,299✔
2358
    sync(fields);
2,541✔
2359
}
2,541✔
2360

2361
Obj& Obj::set_null(ColKey col_key, bool is_default)
2362
{
42,369✔
2363
    ColumnType col_type = col_key.get_type();
42,369✔
2364
    // Links need special handling
21,129✔
2365
    if (col_type == col_type_Link) {
42,369✔
2366
        set(col_key, null_key);
462✔
2367
    }
462✔
2368
    else if (col_type == col_type_Mixed) {
41,907✔
2369
        set(col_key, Mixed{});
66✔
2370
    }
66✔
2371
    else {
41,841✔
2372
        auto attrs = col_key.get_attrs();
41,841✔
2373
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
41,841✔
2374
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
66✔
2375
                              m_table->get_column_name(col_key));
66✔
2376
        }
66✔
2377

20,832✔
2378
        checked_update_if_needed();
41,775✔
2379

20,832✔
2380
        SearchIndex* index = m_table->get_search_index(col_key);
41,775✔
2381
        if (index && !m_key.is_unresolved()) {
41,775✔
2382
            index->set(m_key, null{});
4,605✔
2383
        }
4,605✔
2384

20,832✔
2385
        switch (col_type) {
41,775✔
2386
            case col_type_Int:
5,871✔
2387
                do_set_null<ArrayIntNull>(col_key);
5,871✔
2388
                break;
5,871✔
2389
            case col_type_Bool:
5,613✔
2390
                do_set_null<ArrayBoolNull>(col_key);
5,613✔
2391
                break;
5,613✔
2392
            case col_type_Float:
6,408✔
2393
                do_set_null<ArrayFloatNull>(col_key);
6,408✔
2394
                break;
6,408✔
2395
            case col_type_Double:
6,321✔
2396
                do_set_null<ArrayDoubleNull>(col_key);
6,321✔
2397
                break;
6,321✔
2398
            case col_type_ObjectId:
3,165✔
2399
                do_set_null<ArrayObjectIdNull>(col_key);
3,165✔
2400
                break;
3,165✔
2401
            case col_type_String:
2,541✔
2402
                do_set_null<ArrayString>(col_key);
2,541✔
2403
                break;
2,541✔
2404
            case col_type_Binary:
1,248✔
2405
                do_set_null<ArrayBinary>(col_key);
1,248✔
2406
                break;
1,248✔
2407
            case col_type_Timestamp:
2,496✔
2408
                do_set_null<ArrayTimestamp>(col_key);
2,496✔
2409
                break;
2,496✔
2410
            case col_type_Decimal:
1,830✔
2411
                do_set_null<ArrayDecimal128>(col_key);
1,830✔
2412
                break;
1,830✔
2413
            case col_type_UUID:
6,282✔
2414
                do_set_null<ArrayUUIDNull>(col_key);
6,282✔
2415
                break;
6,282✔
UNCOV
2416
            case col_type_Mixed:
✔
UNCOV
2417
            case col_type_Link:
✔
UNCOV
2418
            case col_type_BackLink:
✔
UNCOV
2419
            case col_type_TypedLink:
✔
2420
                REALM_UNREACHABLE();
2421
        }
41,775✔
2422
    }
41,775✔
2423

21,129✔
2424
    if (Replication* repl = get_replication())
42,327✔
2425
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
21,903✔
2426
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
21,894✔
2427

21,096✔
2428
    return *this;
42,294✔
2429
}
42,369✔
2430

2431

2432
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2433
{
8,563,902✔
2434
    return get_table()->spec_ndx2colkey(col_ndx);
8,563,902✔
2435
}
8,563,902✔
2436

2437
size_t Obj::colkey2spec_ndx(ColKey key)
2438
{
1,011✔
2439
    return get_table()->colkey2spec_ndx(key);
1,011✔
2440
}
1,011✔
2441

2442
ColKey Obj::get_primary_key_column() const
2443
{
5,258,004✔
2444
    return m_table->get_primary_key_column();
5,258,004✔
2445
}
5,258,004✔
2446

2447
ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key)
2448
{
12,786✔
2449
    return to_ref(obj._get<int64_t>(col_key.get_index()));
12,786✔
2450
}
12,786✔
2451

2452
ref_type Obj::get_collection_ref(StableIndex index, CollectionType type) const
2453
{
3,249,468✔
2454
    if (index.is_collection()) {
3,249,468✔
2455
        return to_ref(_get<int64_t>(index.get_index()));
3,234,033✔
2456
    }
3,234,033✔
2457
    if (check_index(index)) {
15,435✔
2458
        auto val = _get<Mixed>(index.get_index());
15,240✔
2459
        if (val.is_type(DataType(int(type)))) {
15,240✔
2460
            return val.get_ref();
15,231✔
2461
        }
15,231✔
2462
        throw realm::IllegalOperation(util::format("Not a %1", type));
9✔
2463
    }
9✔
2464
    throw StaleAccessor("This collection is no more");
195✔
2465
}
195✔
2466

2467
bool Obj::check_collection_ref(StableIndex index, CollectionType type) const noexcept
2468
{
852,963✔
2469
    if (index.is_collection()) {
852,963✔
2470
        return true;
850,419✔
2471
    }
850,419✔
2472
    if (check_index(index)) {
2,544✔
2473
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
2,538✔
2474
    }
2,538✔
2475
    return false;
6✔
2476
}
6✔
2477

2478
void Obj::set_collection_ref(StableIndex index, ref_type ref, CollectionType type)
2479
{
511,677✔
2480
    if (index.is_collection()) {
511,677✔
2481
        set_int(index.get_index(), from_ref(ref));
508,509✔
2482
        return;
508,509✔
2483
    }
508,509✔
2484
    set_ref(index.get_index(), ref, type);
3,168✔
2485
}
3,168✔
2486

2487
void Obj::set_backlink(ColKey col_key, ObjLink new_link) const
2488
{
6,797,721✔
2489
    if (!new_link) {
6,797,721✔
2490
        return;
41,850✔
2491
    }
41,850✔
2492

3,377,436✔
2493
    auto target_table = m_table->get_parent_group()->get_table(new_link.get_table_key());
6,755,871✔
2494
    ColKey backlink_col_key;
6,755,871✔
2495
    auto type = col_key.get_type();
6,755,871✔
2496
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
6,755,871✔
2497
        // This may modify the target table
17,154✔
2498
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
34,524✔
2499
        // it is possible that this was a link to the same table and that adding a backlink column has
17,154✔
2500
        // caused the need to update this object as well.
17,154✔
2501
        update_if_needed();
34,524✔
2502
    }
34,524✔
2503
    else {
6,721,347✔
2504
        backlink_col_key = m_table->get_opposite_column(col_key);
6,721,347✔
2505
    }
6,721,347✔
2506
    auto obj_key = new_link.get_obj_key();
6,755,871✔
2507
    auto target_obj =
6,755,871✔
2508
        obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key) : target_table->try_get_object(obj_key);
6,738,939✔
2509
    if (!target_obj) {
6,755,871✔
2510
        throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
12✔
2511
    }
12✔
2512
    target_obj.add_backlink(backlink_col_key, m_key);
6,755,859✔
2513
}
6,755,859✔
2514

2515
bool Obj::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
2516
{
338,400✔
2517
    bool recurse = remove_backlink(col_key, old_link, state);
338,400✔
2518
    set_backlink(col_key, new_link);
338,400✔
2519
    return recurse;
338,400✔
2520
}
338,400✔
2521

2522
bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
2523
{
357,954✔
2524
    if (!old_link) {
357,954✔
2525
        return false;
232,752✔
2526
    }
232,752✔
2527

62,574✔
2528
    REALM_ASSERT(m_table->valid_column(col_key));
125,202✔
2529
    ObjKey old_key = old_link.get_obj_key();
125,202✔
2530
    auto target_obj = m_table->get_parent_group()->get_object(old_link);
125,202✔
2531
    TableRef target_table = target_obj.get_table();
125,202✔
2532
    ColKey backlink_col_key;
125,202✔
2533
    auto type = col_key.get_type();
125,202✔
2534
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
125,202✔
2535
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
12,336✔
2536
    }
12,336✔
2537
    else {
112,866✔
2538
        backlink_col_key = m_table->get_opposite_column(col_key);
112,866✔
2539
    }
112,866✔
2540

62,574✔
2541
    bool strong_links = target_table->is_embedded();
125,202✔
2542
    bool is_unres = old_key.is_unresolved();
125,202✔
2543

62,574✔
2544
    bool last_removed = target_obj.remove_one_backlink(backlink_col_key, m_key); // Throws
125,202✔
2545
    if (is_unres) {
125,202✔
2546
        if (last_removed) {
264✔
2547
            // Check is there are more backlinks
126✔
2548
            if (!target_obj.has_backlinks(false)) {
252✔
2549
                // Tombstones can be erased right away - there is no cascading effect
126✔
2550
                target_table->m_tombstones->erase(old_key, state);
252✔
2551
            }
252✔
2552
        }
252✔
2553
    }
264✔
2554
    else {
124,938✔
2555
        return state.enqueue_for_cascade(target_obj, strong_links, last_removed);
124,938✔
2556
    }
124,938✔
2557

132✔
2558
    return false;
264✔
2559
}
264✔
2560

2561
} // namespace realm
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