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

realm / realm-core / finn.schiermer-andersen_89

04 Jun 2024 02:04PM UTC coverage: 90.651% (-0.03%) from 90.685%
finn.schiermer-andersen_89

Pull #7654

Evergreen

finnschiermer
optimized string cache gc
Pull Request #7654: Fsa/string interning

102644 of 180648 branches covered (56.82%)

1005 of 1125 new or added lines in 15 files covered. (89.33%)

154 existing lines in 21 files now uncovered.

217953 of 240431 relevant lines covered (90.65%)

7671710.15 hits per line

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

90.03
/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,738✔
56
    coll.set_owner(obj, origin_col_key);
108,738✔
57
    return coll.find_first(link);
108,738✔
58
}
108,738✔
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

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

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
        }
1,068✔
72
        else {
1,068✔
73
            repl->list_erase(link_list, ndx); // Throws
1,068✔
74
        }
1,068✔
75
    }
2,136✔
76

77
    // We cannot just call 'remove' on link_list as it would produce the wrong
78
    // replication instruction and also attempt an update on the backlinks from
79
    // the object that we in the process of removing.
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

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

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

96
    // We cannot just call 'remove' on set as it would produce the wrong
97
    // replication instruction and also attempt an update on the backlinks from
98
    // the object that we in the process of removing.
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)
118,853,637✔
109
    , m_key(key)
118,853,637✔
110
    , m_mem(mem)
118,853,637✔
111
    , m_row_ndx(row_ndx)
118,853,637✔
112
    , m_valid(true)
118,853,637✔
113
{
242,037,780✔
114
    m_storage_version = get_alloc().get_storage_version();
242,037,780✔
115
}
242,037,780✔
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
{
51,006✔
124
    return ObjLink(m_table->get_key(), m_key);
51,006✔
125
}
51,006✔
126

127
const ClusterTree* Obj::get_tree_top() const
128
{
41,290,875✔
129
    if (m_key.is_unresolved()) {
41,290,875✔
130
        return m_table.unchecked_ptr()->m_tombstones.get();
38,886✔
131
    }
38,886✔
132
    else {
41,251,989✔
133
        return &m_table.unchecked_ptr()->m_clusters;
41,251,989✔
134
    }
41,251,989✔
135
}
41,290,875✔
136

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

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

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

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

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

185
Replication* Obj::get_replication() const
186
{
32,041,038✔
187
    return m_table->get_repl();
32,041,038✔
188
}
32,041,038✔
189

190
bool Obj::compare_values(Mixed val1, Mixed val2, ColKey ck, Obj other, StringData col_name) const
191
{
11,052✔
192
    if (val1.is_null()) {
11,052✔
193
        if (!val2.is_null())
60✔
194
            return false;
×
195
    }
60✔
196
    else {
10,992✔
197
        if (val1.get_type() != val2.get_type())
10,992✔
198
            return false;
×
199
        if (val1.is_type(type_Link, type_TypedLink)) {
10,992✔
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
        }
228✔
209
        else {
10,764✔
210
            const auto type = val1.get_type();
10,764✔
211
            if (type == type_List) {
10,764✔
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) {
10,758✔
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) {
10,758✔
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;
10,752✔
227
        }
10,764✔
228
    }
10,992✔
229
    return true;
60✔
230
}
11,052✔
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

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

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

242
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
24✔
243
            DummyParent parent(other.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(other.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;
6✔
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

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

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

271
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
12!
272
            DummyParent parent(other.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(other.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;
6✔
286
}
12✔
287

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

296
        if (!ck.is_collection()) {
4,902✔
297
            if (!compare(get_any(ck), other.get_any(col_name)))
4,482✔
298
                return false;
24✔
299
        }
4,482✔
300
        else {
420✔
301
            auto coll1 = get_collection_ptr(ck);
420✔
302
            auto coll2 = other.get_collection_ptr(col_name);
420✔
303
            size_t sz = coll1->size();
420✔
304
            if (coll2->size() != sz)
420✔
305
                return false;
×
306
            if (ck.is_list() || ck.is_set()) {
420✔
307
                for (size_t i = 0; i < sz; i++) {
6,642✔
308
                    if (!compare(coll1->get_any(i), coll2->get_any(i)))
6,402✔
309
                        return false;
×
310
                }
6,402✔
311
            }
240✔
312
            if (ck.is_dictionary()) {
420✔
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
        }
420✔
325
    }
4,902✔
326
    return true;
3,048✔
327
}
3,078✔
328

329
bool Obj::is_valid() const noexcept
330
{
1,457,661✔
331
    // Cache valid state. If once invalid, it can never become valid again
332
    if (m_valid)
1,457,661✔
333
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,456,272✔
334
                                    m_table.unchecked_ptr()->is_valid(m_key));
1,456,242✔
335

336
    return m_valid;
1,457,661✔
337
}
1,457,661✔
338

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

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

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

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

359
TableRef Obj::get_target_table(ColKey col_key) const
360
{
7,100,997✔
361
    if (m_table) {
7,100,997✔
362
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,100,469✔
363
    }
7,100,469✔
364
    else {
528✔
365
        return TableRef();
528✔
366
    }
528✔
367
}
7,100,997✔
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
{
163,167✔
381
    // Get a new object from key
382
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
163,167✔
383

384
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
163,167✔
385
    if (changes) {
163,167✔
386
        m_mem = new_obj.m_mem;
22,515✔
387
        m_row_ndx = new_obj.m_row_ndx;
22,515✔
388
        ++m_version_counter;
22,515✔
389
    }
22,515✔
390
    // Always update versions
391
    m_storage_version = new_obj.m_storage_version;
163,167✔
392
    m_table = new_obj.m_table;
163,167✔
393
    return changes;
163,167✔
394
}
163,167✔
395

396
inline bool Obj::_update_if_needed() const
397
{
78,827,175✔
398
    auto current_version = _get_alloc().get_storage_version();
78,827,175✔
399
    if (current_version != m_storage_version) {
78,827,175✔
400
        return update();
18,060✔
401
    }
18,060✔
402
    return false;
78,809,115✔
403
}
78,827,175✔
404

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

412
    auto current_version = _get_alloc().get_storage_version();
83,284,032✔
413
    if (current_version != m_storage_version) {
83,284,032✔
414
        ClusterNode::State state = get_tree_top()->try_get(m_key);
1,361,421✔
415

416
        if (!state) {
1,361,421✔
417
            // Object deleted
418
            return UpdateStatus::Detached;
5,220✔
419
        }
5,220✔
420

421
        // Always update versions
422
        m_storage_version = current_version;
1,356,201✔
423
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
1,356,201✔
424
            m_mem = state.mem;
198,156✔
425
            m_row_ndx = state.index;
198,156✔
426
            ++m_version_counter;
198,156✔
427
            return UpdateStatus::Updated;
198,156✔
428
        }
198,156✔
429
    }
1,356,201✔
430
    return UpdateStatus::NoChange;
83,080,656✔
431
}
83,284,032✔
432

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

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

448
    return _get<T>(col_key.get_index());
84,714,537✔
449
}
84,714,537✔
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

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

479
#endif
480

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

486
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
78,125,064✔
487
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
78,125,064✔
488
    values.init_from_ref(ref);
78,125,064✔
489

490
    return values.get(m_row_ndx);
78,125,064✔
491
}
78,125,064✔
492

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

499
    return values.get(m_row_ndx);
104,595✔
500
}
104,595✔
501

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

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

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

519
    ObjKey k = values.get(m_row_ndx);
318,036✔
520
    return k.is_unresolved() ? ObjKey{} : k;
318,036✔
521
}
318,036✔
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

529
    _update_if_needed();
30✔
530

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

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

540
    return values.get(m_row_ndx);
316,464✔
541
}
316,464✔
542

543
template <>
544
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
545
{
126,513,489✔
546
    // manual inline of _update_if_needed():
547
    auto& alloc = _get_alloc();
126,513,489✔
548
    auto current_version = alloc.get_storage_version();
126,513,489✔
549
    if (current_version != m_storage_version) {
126,513,489✔
550
        update();
142,551✔
551
    }
142,551✔
552

553
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
126,513,489✔
554
    // Possible future optimization: replace the full construction of an Array,
555
    // with whatever is needed to get at the value (like we once did in the
556
    // commented out section below)
557
    Array a(alloc);
126,513,489✔
558
    a.init_from_ref(ref);
126,513,489✔
559
    return a.get(m_row_ndx);
126,513,489✔
560
    //
561
    // char* header = alloc.translate(ref);
562
    // int width = Array::get_width_from_header(header);
563
    // char* data = Array::get_data_from_header(header);
564
    // REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
565
}
126,513,489✔
566

567
template <>
568
int64_t Obj::get<int64_t>(ColKey col_key) const
569
{
113,542,752✔
570
    m_table->check_column(col_key);
113,542,752✔
571
    ColumnType type = col_key.get_type();
113,542,752✔
572
    REALM_ASSERT(type == col_type_Int);
113,542,752✔
573

574
    if (col_key.get_attrs().test(col_attr_Nullable)) {
113,542,752✔
575
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
7,788✔
576
        if (!val) {
7,788✔
577
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
6✔
578
        }
6✔
579
        return *val;
7,782✔
580
    }
7,788✔
581
    else {
113,534,964✔
582
        return _get<int64_t>(col_key.get_index());
113,534,964✔
583
    }
113,534,964✔
584
}
113,542,752✔
585

586
template <>
587
bool Obj::get<bool>(ColKey col_key) const
588
{
550,980✔
589
    m_table->check_column(col_key);
550,980✔
590
    ColumnType type = col_key.get_type();
550,980✔
591
    REALM_ASSERT(type == col_type_Bool);
550,980✔
592

593
    if (col_key.get_attrs().test(col_attr_Nullable)) {
550,980✔
594
        auto val = _get<util::Optional<bool>>(col_key.get_index());
30✔
595
        if (!val) {
30✔
596
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
×
597
        }
×
598
        return *val;
30✔
599
    }
30✔
600
    else {
550,950✔
601
        return _get<bool>(col_key.get_index());
550,950✔
602
    }
550,950✔
603
}
550,980✔
604

605
template <>
606
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
607
{
65,360,298✔
608
    // manual inline of _update_if_needed():
609
    auto& alloc = _get_alloc();
65,360,298✔
610
    auto current_version = alloc.get_storage_version();
65,360,298✔
611
    if (current_version != m_storage_version) {
65,360,298✔
612
        update();
2,424✔
613
    }
2,424✔
614

615
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
65,360,298✔
616
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
65,360,298✔
617
    auto& spec = get_spec();
65,360,298✔
618
    if (spec.is_string_enum_type(spec_ndx)) {
65,360,298✔
619
        ArrayString values(get_alloc());
1,071,171✔
620
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
1,071,171✔
621
        values.init_from_ref(ref);
1,071,171✔
622

623
        return values.get(m_row_ndx);
1,071,171✔
624
    }
1,071,171✔
625
    else {
64,289,127✔
626
        ArrayString values(get_alloc());
64,289,127✔
627
        auto col_key = m_table->leaf_ndx2colkey(col_ndx);
64,289,127✔
628
        values.set_string_interner(m_table->get_string_interner(col_key));
64,289,127✔
629
        values.init_from_ref(ref);
64,289,127✔
630
        return values.get(m_row_ndx);
64,289,127✔
631
    }
64,289,127✔
632
}
65,360,298✔
633

634
template <>
635
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
636
{
6,880,200✔
637
    // manual inline of _update_if_needed():
638
    auto& alloc = _get_alloc();
6,880,200✔
639
    auto current_version = alloc.get_storage_version();
6,880,200✔
640
    if (current_version != m_storage_version) {
6,880,200✔
641
        update();
132✔
642
    }
132✔
643

644
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
6,880,200✔
645
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
6,880,200✔
646
}
6,880,200✔
647

648
Mixed Obj::get_any(ColKey col_key) const
649
{
75,060,375✔
650
    m_table->check_column(col_key);
75,060,375✔
651
    auto col_ndx = col_key.get_index();
75,060,375✔
652
    if (col_key.is_collection()) {
75,060,375✔
653
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
6,924✔
654
        return Mixed(ref, get_table()->get_collection_type(col_key));
6,924✔
655
    }
6,924✔
656
    switch (col_key.get_type()) {
75,053,451✔
657
        case col_type_Int:
10,213,962✔
658
            if (col_key.get_attrs().test(col_attr_Nullable)) {
10,213,962✔
659
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
513,702✔
660
            }
513,702✔
661
            else {
9,700,260✔
662
                return Mixed{_get<int64_t>(col_ndx)};
9,700,260✔
663
            }
9,700,260✔
664
        case col_type_Bool:
204,759✔
665
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
204,759✔
666
        case col_type_Float:
8,007✔
667
            return Mixed{_get<util::Optional<float>>(col_ndx)};
8,007✔
668
        case col_type_Double:
19,617✔
669
            return Mixed{_get<util::Optional<double>>(col_ndx)};
19,617✔
670
        case col_type_String:
64,070,079✔
671
            return Mixed{_get<String>(col_ndx)};
64,070,079✔
672
        case col_type_Binary:
1,605✔
673
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
674
        case col_type_Mixed:
22,557✔
675
            return _get<Mixed>(col_ndx);
22,557✔
676
        case col_type_Timestamp:
167,013✔
677
            return Mixed{_get<Timestamp>(col_ndx)};
167,013✔
678
        case col_type_Decimal:
7,860✔
679
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
680
        case col_type_ObjectId:
376,113✔
681
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
376,113✔
682
        case col_type_UUID:
54,717✔
683
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
54,717✔
684
        case col_type_Link:
42,591✔
685
            return Mixed{_get<ObjKey>(col_ndx)};
42,591✔
686
        default:
✔
687
            REALM_UNREACHABLE();
688
            break;
×
689
    }
75,053,451✔
690
    return {};
×
691
}
75,053,451✔
692

693
Mixed Obj::get_primary_key() const
694
{
156,288✔
695
    auto col = m_table->get_primary_key_column();
156,288✔
696
    return col ? get_any(col) : Mixed{get_key()};
156,288✔
697
}
156,288✔
698

699
/* FIXME: Make this one fast too!
700
template <>
701
ObjKey Obj::_get(size_t col_ndx) const
702
{
703
    return ObjKey(_get<int64_t>(col_ndx));
704
}
705
*/
706

707
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
708
{
27,024✔
709
    Obj obj;
27,024✔
710
    if (!link.is_null()) {
27,024✔
711
        TableRef target_table;
18,642✔
712
        if (link.is_type(type_TypedLink)) {
18,642✔
713
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
324✔
714
        }
324✔
715
        else {
18,318✔
716
            target_table = get_target_table(link_col_key);
18,318✔
717
        }
18,318✔
718
        obj = target_table->get_object(link.get<ObjKey>());
18,642✔
719
    }
18,642✔
720
    return obj;
27,024✔
721
}
27,024✔
722

723
Obj Obj::get_parent_object() const
724
{
12✔
725
    Obj obj;
12✔
726
    checked_update_if_needed();
12✔
727

728
    if (!m_table->is_embedded()) {
12✔
729
        throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded");
×
730
    }
×
731
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
18✔
732
        if (get_backlink_cnt(backlink_col_key) == 1) {
18✔
733
            auto obj_key = get_backlink(backlink_col_key, 0);
12✔
734
            obj = m_table->get_opposite_table(backlink_col_key)->get_object(obj_key);
12✔
735
            return IteratorControl::Stop;
12✔
736
        }
12✔
737
        return IteratorControl::AdvanceToNext;
6✔
738
    });
18✔
739

740
    return obj;
12✔
741
}
12✔
742

743
template <class T>
744
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
745
{
1,384,422✔
746
    T values(get_alloc());
1,384,422✔
747
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,384,422✔
748
    values.init_from_ref(ref);
1,384,422✔
749
    return values.is_null(m_row_ndx);
1,384,422✔
750
}
1,384,422✔
751

752
template <>
753
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
UNCOV
754
{
×
NEW
755
    REALM_ASSERT(false); // Don't come here, you're falling from a cliff....
×
UNCOV
756
    ArrayString values(get_alloc());
×
UNCOV
757
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
×
UNCOV
758
    values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
×
NEW
759
    // TODO: Set string interner if needed
×
NEW
760
    // values.set_string_interner(m_table->get_string_interner(col_key));
×
UNCOV
761
    values.init_from_ref(ref);
×
UNCOV
762
    return values.is_null(m_row_ndx);
×
UNCOV
763
}
×
764

765
size_t Obj::get_link_count(ColKey col_key) const
766
{
108✔
767
    return get_list<ObjKey>(col_key).size();
108✔
768
}
108✔
769

770
bool Obj::is_null(ColKey col_key) const
771
{
5,476,278✔
772
    checked_update_if_needed();
5,476,278✔
773
    ColumnAttrMask attr = col_key.get_attrs();
5,476,278✔
774
    ColKey::Idx col_ndx = col_key.get_index();
5,476,278✔
775
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,476,278✔
776
        switch (col_key.get_type()) {
1,676,742✔
777
            case col_type_Int:
536,049✔
778
                return do_is_null<ArrayIntNull>(col_ndx);
536,049✔
779
            case col_type_Bool:
450,816✔
780
                return do_is_null<ArrayBoolNull>(col_ndx);
450,816✔
781
            case col_type_Float:
9,357✔
782
                return do_is_null<ArrayFloatNull>(col_ndx);
9,357✔
783
            case col_type_Double:
3,267✔
784
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
785
            case col_type_String: {
292,329✔
786
                ArrayString values(get_alloc());
292,329✔
787
                ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
292,329✔
788
                values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
292,329✔
789
                // TODO: Set string interner if needed
790
                values.set_string_interner(m_table->get_string_interner(col_key));
292,329✔
791
                values.init_from_ref(ref);
292,329✔
792
                return values.is_null(m_row_ndx);
292,329✔
NEW
793
            }
×
794
                // return do_is_null<ArrayString>(col_ndx);
795
            case col_type_Binary:
267✔
796
                return do_is_null<ArrayBinary>(col_ndx);
267✔
797
            case col_type_Mixed:
1,971✔
798
                return do_is_null<ArrayMixed>(col_ndx);
1,971✔
799
            case col_type_Timestamp:
357,951✔
800
                return do_is_null<ArrayTimestamp>(col_ndx);
357,951✔
801
            case col_type_Link:
22,269✔
802
                return do_is_null<ArrayKey>(col_ndx);
22,269✔
803
            case col_type_ObjectId:
231✔
804
                return do_is_null<ArrayObjectIdNull>(col_ndx);
231✔
805
            case col_type_Decimal:
2,013✔
806
                return do_is_null<ArrayDecimal128>(col_ndx);
2,013✔
807
            case col_type_UUID:
231✔
808
                return do_is_null<ArrayUUIDNull>(col_ndx);
231✔
809
            default:
✔
810
                REALM_UNREACHABLE();
811
        }
1,676,742✔
812
    }
1,676,742✔
813
    return false;
3,799,536✔
814
}
5,476,278✔
815

816

817
// Figure out if this object has any remaining backlinkss
818
bool Obj::has_backlinks(bool only_strong_links) const
819
{
13,059✔
820
    const Table& target_table = *m_table;
13,059✔
821

822
    // If we only look for strong links and the table is not embedded,
823
    // then there is no relevant backlinks to find.
824
    if (only_strong_links && !target_table.is_embedded()) {
13,059✔
825
        return false;
×
826
    }
×
827

828
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
16,500✔
829
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
16,500✔
830
    });
16,500✔
831
}
13,059✔
832

833
size_t Obj::get_backlink_count() const
834
{
174,504✔
835
    checked_update_if_needed();
174,504✔
836

837
    size_t cnt = 0;
174,504✔
838
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
458,124✔
839
        cnt += get_backlink_cnt(backlink_col_key);
458,124✔
840
        return IteratorControl::AdvanceToNext;
458,124✔
841
    });
458,124✔
842
    return cnt;
174,504✔
843
}
174,504✔
844

845
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
846
{
32,010✔
847
    checked_update_if_needed();
32,010✔
848

849
    size_t cnt = 0;
32,010✔
850
    if (TableKey origin_table_key = origin.get_key()) {
32,010✔
851
        ColKey backlink_col_key;
32,010✔
852
        auto type = origin_col_key.get_type();
32,010✔
853
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
32,010✔
854
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
12✔
855
        }
12✔
856
        else {
31,998✔
857
            backlink_col_key = origin.get_opposite_column(origin_col_key);
31,998✔
858
        }
31,998✔
859

860
        cnt = get_backlink_cnt(backlink_col_key);
32,010✔
861
    }
32,010✔
862
    return cnt;
32,010✔
863
}
32,010✔
864

865
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
866
{
9,023,523✔
867
    ColKey backlink_col_key;
9,023,523✔
868
    auto type = origin_col_key.get_type();
9,023,523✔
869
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
9,023,526✔
870
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
871
    }
36✔
872
    else {
9,023,487✔
873
        backlink_col_key = origin.get_opposite_column(origin_col_key);
9,023,487✔
874
    }
9,023,487✔
875
    return get_backlink(backlink_col_key, backlink_ndx);
9,023,523✔
876
}
9,023,523✔
877

878
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key) const
879
{
696✔
880
    TableView tv(src_table, src_col_key, *this);
696✔
881
    tv.do_sync();
696✔
882
    return tv;
696✔
883
}
696✔
884

885
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
886
{
9,023,493✔
887
    get_table()->check_column(backlink_col);
9,023,493✔
888
    Allocator& alloc = get_alloc();
9,023,493✔
889
    Array fields(alloc);
9,023,493✔
890
    fields.init_from_mem(m_mem);
9,023,493✔
891

892
    ArrayBacklink backlinks(alloc);
9,023,493✔
893
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
9,023,493✔
894
    backlinks.init_from_parent();
9,023,493✔
895
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
9,023,493✔
896
}
9,023,493✔
897

898
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
899
{
289,002✔
900
    checked_update_if_needed();
289,002✔
901

902
    get_table()->check_column(backlink_col);
289,002✔
903
    Allocator& alloc = get_alloc();
289,002✔
904
    Array fields(alloc);
289,002✔
905
    fields.init_from_mem(m_mem);
289,002✔
906

907
    ArrayBacklink backlinks(alloc);
289,002✔
908
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
289,002✔
909
    backlinks.init_from_parent();
289,002✔
910

911
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
289,002✔
912
    std::vector<ObjKey> vec;
289,002✔
913
    vec.reserve(cnt);
289,002✔
914
    for (size_t i = 0; i < cnt; i++) {
547,680✔
915
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
258,678✔
916
    }
258,678✔
917
    return vec;
289,002✔
918
}
289,002✔
919

920
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
921
{
506,652✔
922
    Allocator& alloc = get_alloc();
506,652✔
923
    Array fields(alloc);
506,652✔
924
    fields.init_from_mem(m_mem);
506,652✔
925

926
    ArrayBacklink backlinks(alloc);
506,652✔
927
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
506,652✔
928
    backlinks.init_from_parent();
506,652✔
929

930
    return backlinks.get_backlink_count(m_row_ndx);
506,652✔
931
}
506,652✔
932

933
void Obj::verify_backlink(const Table& origin, ColKey origin_col_key, ObjKey origin_key) const
934
{
120,714✔
935
#ifdef REALM_DEBUG
120,714✔
936
    ColKey backlink_col_key;
120,714✔
937
    auto type = origin_col_key.get_type();
120,714✔
938
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
120,714✔
939
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
×
940
    }
×
941
    else {
120,714✔
942
        backlink_col_key = origin.get_opposite_column(origin_col_key);
120,714✔
943
    }
120,714✔
944

945
    Allocator& alloc = get_alloc();
120,714✔
946
    Array fields(alloc);
120,714✔
947
    fields.init_from_mem(m_mem);
120,714✔
948

949
    ArrayBacklink backlinks(alloc);
120,714✔
950
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
120,714✔
951
    backlinks.init_from_parent();
120,714✔
952

953
    REALM_ASSERT(backlinks.verify_backlink(m_row_ndx, origin_key.value));
120,714✔
954
#else
955
    static_cast<void>(origin);
956
    static_cast<void>(origin_col_key);
957
    static_cast<void>(origin_key);
958
#endif
959
}
120,714✔
960

961
void Obj::traverse_path(Visitor v, PathSizer ps, size_t path_length) const
962
{
90✔
963
    struct BacklinkTraverser : public LinkTranslator {
90✔
964
        BacklinkTraverser(Obj origin, ColKey origin_col_key, Obj dest)
90✔
965
            : LinkTranslator(origin, origin_col_key)
90✔
966
            , m_dest_obj(dest)
90✔
967
        {
90✔
968
        }
48✔
969
        void on_list_of_links(LnkLst& ll) final
90✔
970
        {
90✔
971
            auto i = ll.find_first(m_dest_obj.get_key());
30✔
972
            REALM_ASSERT(i != realm::npos);
30✔
973
            m_index = Mixed(int64_t(i));
30✔
974
        }
30✔
975
        void on_dictionary(Dictionary& dict) final
90✔
976
        {
90✔
977
            for (auto it : dict) {
12✔
978
                if (it.second.is_type(type_TypedLink) && it.second.get_link() == m_dest_obj.get_link()) {
12✔
979
                    m_index = it.first;
6✔
980
                    break;
6✔
981
                }
6✔
982
            }
12✔
983
            REALM_ASSERT(!m_index.is_null());
6✔
984
        }
6✔
985
        void on_list_of_mixed(Lst<Mixed>&) final
90✔
986
        {
90✔
987
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
988
        }
×
989
        void on_set_of_links(LnkSet&) final
90✔
990
        {
90✔
991
            REALM_UNREACHABLE(); // sets of embedded objects are not allowed at the schema level
992
        }
×
993
        void on_set_of_mixed(Set<Mixed>&) final
90✔
994
        {
90✔
995
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
996
        }
×
997
        void on_link_property(ColKey) final {}
90✔
998
        void on_mixed_property(ColKey) final {}
90✔
999
        Mixed result()
90✔
1000
        {
90✔
1001
            return m_index;
48✔
1002
        }
48✔
1003

1004
    private:
90✔
1005
        Mixed m_index;
90✔
1006
        Obj m_dest_obj;
90✔
1007
    };
90✔
1008

1009
    if (m_table->is_embedded()) {
90✔
1010
        REALM_ASSERT(get_backlink_count() == 1);
48✔
1011
        m_table->for_each_backlink_column([&](ColKey col_key) {
84✔
1012
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
84✔
1013
            if (backlinks.size() == 1) {
84✔
1014
                TableRef tr = m_table->get_opposite_table(col_key);
48✔
1015
                Obj obj = tr->get_object(backlinks[0]); // always the first (and only)
48✔
1016
                auto next_col_key = m_table->get_opposite_column(col_key);
48✔
1017
                BacklinkTraverser traverser{obj, next_col_key, *this};
48✔
1018
                traverser.run();
48✔
1019
                Mixed index = traverser.result();
48✔
1020
                obj.traverse_path(v, ps, path_length + 1);
48✔
1021
                v(obj, next_col_key, index);
48✔
1022
                return IteratorControl::Stop; // early out
48✔
1023
            }
48✔
1024
            return IteratorControl::AdvanceToNext; // try next column
36✔
1025
        });
84✔
1026
    }
48✔
1027
    else {
42✔
1028
        ps(path_length);
42✔
1029
    }
42✔
1030
}
90✔
1031

1032
Obj::FatPath Obj::get_fat_path() const
1033
{
30✔
1034
    FatPath result;
30✔
1035
    auto sizer = [&](size_t size) {
30✔
1036
        result.reserve(size);
30✔
1037
    };
30✔
1038
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
30✔
1039
        result.push_back({o2, col, idx});
30✔
1040
    };
30✔
1041
    traverse_path(step, sizer);
30✔
1042
    return result;
30✔
1043
}
30✔
1044

1045
FullPath Obj::get_path() const
1046
{
293,532✔
1047
    FullPath result;
293,532✔
1048
    if (m_table->is_embedded()) {
293,532✔
1049
        REALM_ASSERT(get_backlink_count() == 1);
168,642✔
1050
        m_table->for_each_backlink_column([&](ColKey col_key) {
276,870✔
1051
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
276,870✔
1052
            if (backlinks.size() == 1) {
276,870✔
1053
                TableRef origin_table = m_table->get_opposite_table(col_key);
168,642✔
1054
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
168,642✔
1055
                auto next_col_key = m_table->get_opposite_column(col_key);
168,642✔
1056

1057
                ColumnAttrMask attr = next_col_key.get_attrs();
168,642✔
1058
                Mixed index;
168,642✔
1059
                if (attr.test(col_attr_List)) {
168,642✔
1060
                    REALM_ASSERT(next_col_key.get_type() == col_type_Link);
57,984✔
1061
                    Lst<ObjKey> link_list(next_col_key);
57,984✔
1062
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
57,984✔
1063
                    REALM_ASSERT(i != realm::not_found);
57,984✔
1064
                    result = link_list.get_path();
57,984✔
1065
                    result.path_from_top.emplace_back(i);
57,984✔
1066
                }
57,984✔
1067
                else if (attr.test(col_attr_Dictionary)) {
110,658✔
1068
                    Dictionary dict(next_col_key);
45,594✔
1069
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
45,594✔
1070
                    REALM_ASSERT(ndx != realm::not_found);
45,594✔
1071
                    result = dict.get_path();
45,594✔
1072
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
45,594✔
1073
                }
45,594✔
1074
                else {
65,064✔
1075
                    result = obj.get_path();
65,064✔
1076
                    if (result.path_from_top.empty()) {
65,064✔
1077
                        result.path_from_top.push_back(next_col_key);
14,562✔
1078
                    }
14,562✔
1079
                    else {
50,502✔
1080
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
50,502✔
1081
                    }
50,502✔
1082
                }
65,064✔
1083

1084
                return IteratorControl::Stop; // early out
168,642✔
1085
            }
168,642✔
1086
            return IteratorControl::AdvanceToNext; // try next column
108,228✔
1087
        });
276,870✔
1088
    }
168,642✔
1089
    else {
124,890✔
1090
        result.top_objkey = get_key();
124,890✔
1091
        result.top_table = get_table()->get_key();
124,890✔
1092
    }
124,890✔
1093
    return result;
293,532✔
1094
}
293,532✔
1095

1096
std::string Obj::get_id() const
1097
{
29,301✔
1098
    std::ostringstream ostr;
29,301✔
1099
    auto path = get_path();
29,301✔
1100
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
29,301✔
1101
    ostr << top_table->get_class_name() << '[';
29,301✔
1102
    if (top_table->get_primary_key_column()) {
29,301✔
1103
        ostr << top_table->get_primary_key(path.top_objkey);
29,151✔
1104
    }
29,151✔
1105
    else {
150✔
1106
        ostr << path.top_objkey;
150✔
1107
    }
150✔
1108
    ostr << ']';
29,301✔
1109
    if (!path.path_from_top.empty()) {
29,301✔
1110
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
19,323✔
1111
        path.path_from_top[0] = PathElement(prop_name);
19,323✔
1112
        ostr << path.path_from_top;
19,323✔
1113
    }
19,323✔
1114
    return ostr.str();
29,301✔
1115
}
29,301✔
1116

1117
Path Obj::get_short_path() const noexcept
1118
{
371,514✔
1119
    return {};
371,514✔
1120
}
371,514✔
1121

1122
ColKey Obj::get_col_key() const noexcept
1123
{
×
1124
    return {};
×
1125
}
×
1126

1127
StablePath Obj::get_stable_path() const noexcept
1128
{
1,938,858✔
1129
    return {};
1,938,858✔
1130
}
1,938,858✔
1131

1132
void Obj::add_index(Path& path, const CollectionParent::Index& index) const
1133
{
475,107✔
1134
    if (path.empty()) {
475,107✔
1135
        path.emplace_back(get_table()->get_column_key(index));
471,861✔
1136
    }
471,861✔
1137
    else {
3,246✔
1138
        StringData col_name = get_table()->get_column_name(index);
3,246✔
1139
        path.emplace_back(col_name);
3,246✔
1140
    }
3,246✔
1141
}
475,107✔
1142

1143
std::string Obj::to_string() const
1144
{
12✔
1145
    std::ostringstream ostr;
12✔
1146
    to_json(ostr);
12✔
1147
    return ostr.str();
12✔
1148
}
12✔
1149

1150
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
1151
{
×
1152
    obj.to_json(ostr);
×
1153
    return ostr;
×
1154
}
×
1155

1156
/*********************************** Obj *************************************/
1157

1158
bool Obj::ensure_writeable()
1159
{
×
1160
    Allocator& alloc = get_alloc();
×
1161
    if (alloc.is_read_only(m_mem.get_ref())) {
×
1162
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
×
1163
        m_storage_version = alloc.get_storage_version();
×
1164
        return true;
×
1165
    }
×
1166
    return false;
×
1167
}
×
1168

1169
REALM_FORCEINLINE void Obj::sync(Node& arr)
1170
{
39,455,700✔
1171
    auto ref = arr.get_ref();
39,455,700✔
1172
    if (arr.has_missing_parent_update()) {
39,455,700✔
1173
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
270,303✔
1174
    }
270,303✔
1175
    if (m_mem.get_ref() != ref) {
39,455,700✔
1176
        m_mem = arr.get_mem();
529,077✔
1177
        m_storage_version = arr.get_alloc().get_storage_version();
529,077✔
1178
    }
529,077✔
1179
}
39,455,700✔
1180

1181
template <>
1182
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
1183
{
16,950✔
1184
    checked_update_if_needed();
16,950✔
1185
    get_table()->check_column(col_key);
16,950✔
1186
    auto type = col_key.get_type();
16,950✔
1187
    auto col_ndx = col_key.get_index();
16,950✔
1188
    bool recurse = false;
16,950✔
1189
    CascadeState state;
16,950✔
1190

1191
    if (type != col_type_Mixed)
16,950✔
1192
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
×
1193
    if (value_is_null(value) && !col_key.is_nullable()) {
16,950✔
1194
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
×
1195
    }
×
1196
    if (value.is_type(type_Link)) {
16,950✔
1197
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
×
1198
    }
×
1199

1200
    Mixed old_value = get_unfiltered_mixed(col_ndx);
16,950✔
1201
    if (!value.is_same_type(old_value) || value != old_value) {
16,950✔
1202
        if (old_value.is_type(type_TypedLink)) {
15,402✔
1203
            auto old_link = old_value.get<ObjLink>();
114✔
1204
            recurse = remove_backlink(col_key, old_link, state);
114✔
1205
        }
114✔
1206
        else if (old_value.is_type(type_Dictionary)) {
15,288✔
1207
            Dictionary dict(*this, col_key);
264✔
1208
            recurse = dict.remove_backlinks(state);
264✔
1209
        }
264✔
1210
        else if (old_value.is_type(type_List)) {
15,024✔
1211
            Lst<Mixed> list(*this, col_key);
426✔
1212
            recurse = list.remove_backlinks(state);
426✔
1213
        }
426✔
1214

1215
        if (value.is_type(type_TypedLink)) {
15,402✔
1216
            if (m_table->is_asymmetric()) {
960✔
1217
                throw IllegalOperation("Links not allowed in asymmetric tables");
12✔
1218
            }
12✔
1219
            auto new_link = value.get<ObjLink>();
948✔
1220
            m_table->get_parent_group()->validate(new_link);
948✔
1221
            set_backlink(col_key, new_link);
948✔
1222
        }
948✔
1223

1224
        SearchIndex* index = m_table->get_search_index(col_key);
15,390✔
1225
        // The following check on unresolved is just a precaution as it should not
1226
        // be possible to hit that while Mixed is not a supported primary key type.
1227
        if (index && !m_key.is_unresolved()) {
15,390✔
1228
            index->set(m_key, value.is_unresolved_link() ? Mixed() : value);
2,316✔
1229
        }
2,316✔
1230

1231
        Allocator& alloc = get_alloc();
15,390✔
1232
        alloc.bump_content_version();
15,390✔
1233
        Array fallback(alloc);
15,390✔
1234
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
15,390✔
1235
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
15,390✔
1236
        ArrayMixed values(alloc);
15,390✔
1237
        values.set_parent(&fields, col_ndx.val + 1);
15,390✔
1238
        values.init_from_parent();
15,390✔
1239
        values.set(m_row_ndx, value);
15,390✔
1240
        if (value.is_type(type_Dictionary, type_List)) {
15,390✔
1241
            values.set_key(m_row_ndx, CollectionParent::generate_key(0x10));
3,348✔
1242
        }
3,348✔
1243

1244
        sync(fields);
15,390✔
1245
    }
15,390✔
1246

1247
    if (Replication* repl = get_replication())
16,938✔
1248
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
10,272✔
1249
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
10,272✔
1250

1251
    if (recurse)
16,938✔
1252
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1253

1254
    return *this;
16,938✔
1255
}
16,950✔
1256

1257
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1258
{
813,501✔
1259
    if (value.is_null()) {
813,501✔
1260
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
312✔
1261
        set_null(col_key, is_default);
312✔
1262
    }
312✔
1263
    else {
813,189✔
1264
        switch (col_key.get_type()) {
813,189✔
1265
            case col_type_Int:
729,618✔
1266
                if (col_key.get_attrs().test(col_attr_Nullable)) {
729,618✔
1267
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
3,090✔
1268
                }
3,090✔
1269
                else {
726,528✔
1270
                    set(col_key, value.get_int(), is_default);
726,528✔
1271
                }
726,528✔
1272
                break;
729,618✔
1273
            case col_type_Bool:
108✔
1274
                set(col_key, value.get_bool(), is_default);
108✔
1275
                break;
108✔
1276
            case col_type_Float:
2,646✔
1277
                set(col_key, value.get_float(), is_default);
2,646✔
1278
                break;
2,646✔
1279
            case col_type_Double:
3,816✔
1280
                set(col_key, value.get_double(), is_default);
3,816✔
1281
                break;
3,816✔
1282
            case col_type_String:
33,909✔
1283
                set(col_key, value.get_string(), is_default);
33,909✔
1284
                break;
33,909✔
1285
            case col_type_Binary:
19,842✔
1286
                set(col_key, value.get<Binary>(), is_default);
19,842✔
1287
                break;
19,842✔
1288
            case col_type_Mixed:
3,066✔
1289
                set(col_key, value, is_default);
3,066✔
1290
                break;
3,066✔
1291
            case col_type_Timestamp:
8,919✔
1292
                set(col_key, value.get<Timestamp>(), is_default);
8,919✔
1293
                break;
8,919✔
1294
            case col_type_ObjectId:
4,986✔
1295
                set(col_key, value.get<ObjectId>(), is_default);
4,986✔
1296
                break;
4,986✔
1297
            case col_type_Decimal:
2,502✔
1298
                set(col_key, value.get<Decimal128>(), is_default);
2,502✔
1299
                break;
2,502✔
1300
            case col_type_UUID:
3,708✔
1301
                set(col_key, value.get<UUID>(), is_default);
3,708✔
1302
                break;
3,708✔
1303
            case col_type_Link:
72✔
1304
                set(col_key, value.get<ObjKey>(), is_default);
72✔
1305
                break;
72✔
1306
            case col_type_TypedLink:
✔
1307
                set(col_key, value.get<ObjLink>(), is_default);
×
1308
                break;
×
1309
            default:
✔
1310
                break;
×
1311
        }
813,189✔
1312
    }
813,189✔
1313
    return *this;
813,516✔
1314
}
813,501✔
1315

1316
template <>
1317
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1318
{
20,706,714✔
1319
    checked_update_if_needed();
20,706,714✔
1320
    get_table()->check_column(col_key);
20,706,714✔
1321
    auto col_ndx = col_key.get_index();
20,706,714✔
1322

1323
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
20,706,714✔
1324
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1325
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1326

1327
    SearchIndex* index = m_table->get_search_index(col_key);
20,706,714✔
1328
    if (index && !m_key.is_unresolved()) {
20,706,714✔
1329
        index->set(m_key, value);
184,578✔
1330
    }
184,578✔
1331

1332
    Allocator& alloc = get_alloc();
20,706,714✔
1333
    alloc.bump_content_version();
20,706,714✔
1334
    Array fallback(alloc);
20,706,714✔
1335
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
20,706,714✔
1336
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
20,706,714✔
1337
    auto attr = col_key.get_attrs();
20,706,714✔
1338
    if (attr.test(col_attr_Nullable)) {
20,706,714✔
1339
        ArrayIntNull values(alloc);
3,132,096✔
1340
        values.set_parent(&fields, col_ndx.val + 1);
3,132,096✔
1341
        values.init_from_parent();
3,132,096✔
1342
        values.set(m_row_ndx, value);
3,132,096✔
1343
    }
3,132,096✔
1344
    else {
17,574,618✔
1345
        ArrayInteger values(alloc);
17,574,618✔
1346
        values.set_parent(&fields, col_ndx.val + 1);
17,574,618✔
1347
        values.init_from_parent();
17,574,618✔
1348
        values.set(m_row_ndx, value);
17,574,618✔
1349
    }
17,574,618✔
1350

1351
    sync(fields);
20,706,714✔
1352

1353
    if (Replication* repl = get_replication()) {
20,706,714✔
1354
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
11,449,284✔
1355
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
11,449,284✔
1356
    }
11,449,284✔
1357

1358
    return *this;
20,706,714✔
1359
}
20,706,714✔
1360

1361
Obj& Obj::add_int(ColKey col_key, int64_t value)
1362
{
20,514✔
1363
    checked_update_if_needed();
20,514✔
1364
    get_table()->check_column(col_key);
20,514✔
1365
    auto col_ndx = col_key.get_index();
20,514✔
1366

1367
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
20,514✔
1368
        uint64_t ua = uint64_t(a);
20,502✔
1369
        uint64_t ub = uint64_t(b);
20,502✔
1370
        return int64_t(ua + ub);
20,502✔
1371
    };
20,502✔
1372

1373
    Allocator& alloc = get_alloc();
20,514✔
1374
    alloc.bump_content_version();
20,514✔
1375
    Array fallback(alloc);
20,514✔
1376
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
20,514✔
1377
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
20,514✔
1378

1379
    if (col_key.get_type() == col_type_Mixed) {
20,514✔
1380
        ArrayMixed values(alloc);
150✔
1381
        values.set_parent(&fields, col_ndx.val + 1);
150✔
1382
        values.init_from_parent();
150✔
1383
        Mixed old = values.get(m_row_ndx);
150✔
1384
        if (old.is_type(type_Int)) {
150✔
1385
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
144✔
1386
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
144✔
1387
                index->set(m_key, new_val);
×
1388
            }
×
1389
            values.set(m_row_ndx, Mixed(new_val));
144✔
1390
        }
144✔
1391
        else {
6✔
1392
            throw IllegalOperation("Value not an int");
6✔
1393
        }
6✔
1394
    }
150✔
1395
    else {
20,364✔
1396
        if (col_key.get_type() != col_type_Int)
20,364✔
1397
            throw IllegalOperation("Property not an int");
×
1398

1399
        auto attr = col_key.get_attrs();
20,364✔
1400
        if (attr.test(col_attr_Nullable)) {
20,364✔
1401
            ArrayIntNull values(alloc);
96✔
1402
            values.set_parent(&fields, col_ndx.val + 1);
96✔
1403
            values.init_from_parent();
96✔
1404
            util::Optional<int64_t> old = values.get(m_row_ndx);
96✔
1405
            if (old) {
96✔
1406
                auto new_val = add_wrap(*old, value);
90✔
1407
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
90✔
1408
                    index->set(m_key, new_val);
×
1409
                }
×
1410
                values.set(m_row_ndx, new_val);
90✔
1411
            }
90✔
1412
            else {
6✔
1413
                throw IllegalOperation("No prior value");
6✔
1414
            }
6✔
1415
        }
96✔
1416
        else {
20,268✔
1417
            ArrayInteger values(alloc);
20,268✔
1418
            values.set_parent(&fields, col_ndx.val + 1);
20,268✔
1419
            values.init_from_parent();
20,268✔
1420
            int64_t old = values.get(m_row_ndx);
20,268✔
1421
            auto new_val = add_wrap(old, value);
20,268✔
1422
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
20,268✔
1423
                index->set(m_key, new_val);
6✔
1424
            }
6✔
1425
            values.set(m_row_ndx, new_val);
20,268✔
1426
        }
20,268✔
1427
    }
20,364✔
1428

1429
    sync(fields);
20,502✔
1430

1431
    if (Replication* repl = get_replication()) {
20,502✔
1432
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
8,508✔
1433
    }
8,508✔
1434

1435
    return *this;
20,502✔
1436
}
20,514✔
1437

1438
template <>
1439
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
1440
{
255,744✔
1441
    checked_update_if_needed();
255,744✔
1442
    get_table()->check_column(col_key);
255,744✔
1443
    ColKey::Idx col_ndx = col_key.get_index();
255,744✔
1444
    ColumnType type = col_key.get_type();
255,744✔
1445
    if (type != col_type_Link)
255,744✔
1446
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
×
1447
    TableRef target_table = get_target_table(col_key);
255,744✔
1448
    TableKey target_table_key = target_table->get_key();
255,744✔
1449
    if (target_key) {
255,744✔
1450
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
255,243✔
1451
        if (!ct->is_valid(target_key)) {
255,243✔
1452
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
12✔
1453
        }
12✔
1454
        if (target_table->is_embedded()) {
255,243✔
1455
            throw IllegalOperation(
×
1456
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
×
1457
        }
×
1458
    }
255,243✔
1459
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
255,744✔
1460
    CascadeState state(CascadeState::Mode::Strong);
255,744✔
1461
    bool recurse = false;
255,744✔
1462

1463
    if (target_key != old_key) {
255,744✔
1464
        recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
254,961✔
1465
        _update_if_needed();
254,961✔
1466

1467
        Allocator& alloc = get_alloc();
254,961✔
1468
        alloc.bump_content_version();
254,961✔
1469
        Array fallback(alloc);
254,961✔
1470
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
254,961✔
1471
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
254,961✔
1472
        ArrayKey values(alloc);
254,961✔
1473
        values.set_parent(&fields, col_ndx.val + 1);
254,961✔
1474
        values.init_from_parent();
254,961✔
1475

1476
        values.set(m_row_ndx, target_key);
254,961✔
1477

1478
        sync(fields);
254,961✔
1479
    }
254,961✔
1480

1481
    if (Replication* repl = get_replication()) {
255,744✔
1482
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
32,592✔
1483
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
32,592✔
1484
    }
32,592✔
1485

1486
    if (recurse)
255,744✔
1487
        target_table->remove_recursive(state);
258✔
1488

1489
    return *this;
255,744✔
1490
}
255,744✔
1491

1492
template <>
1493
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
1494
{
×
1495
    checked_update_if_needed();
×
1496
    get_table()->check_column(col_key);
×
1497
    ColKey::Idx col_ndx = col_key.get_index();
×
1498
    ColumnType type = col_key.get_type();
×
1499
    if (type != col_type_TypedLink)
×
1500
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
×
1501
    m_table->get_parent_group()->validate(target_link);
×
1502

1503
    ObjLink old_link = get<ObjLink>(col_key); // Will update if needed
×
1504
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
1505
    bool recurse = false;
×
1506

1507
    if (target_link != old_link) {
×
1508
        recurse = replace_backlink(col_key, old_link, target_link, state);
×
1509
        _update_if_needed();
×
1510

1511
        Allocator& alloc = get_alloc();
×
1512
        alloc.bump_content_version();
×
1513
        Array fallback(alloc);
×
1514
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
×
1515
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
1516
        ArrayTypedLink values(alloc);
×
1517
        values.set_parent(&fields, col_ndx.val + 1);
×
1518
        values.init_from_parent();
×
1519

1520
        values.set(m_row_ndx, target_link);
×
1521

1522
        sync(fields);
×
1523
    }
×
1524

1525
    if (Replication* repl = get_replication()) {
×
1526
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
×
1527
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
×
1528
    }
×
1529

1530
    if (recurse)
×
1531
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1532

1533
    return *this;
×
1534
}
×
1535

1536
Obj Obj::create_and_set_linked_object(ColKey col_key, bool is_default)
1537
{
13,923✔
1538
    checked_update_if_needed();
13,923✔
1539
    get_table()->check_column(col_key);
13,923✔
1540
    ColKey::Idx col_ndx = col_key.get_index();
13,923✔
1541
    ColumnType type = col_key.get_type();
13,923✔
1542
    if (type != col_type_Link)
13,923✔
1543
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link type");
×
1544
    TableRef target_table = get_target_table(col_key);
13,923✔
1545
    Table& t = *target_table;
13,923✔
1546
    // Only links to embedded objects are allowed.
1547
    REALM_ASSERT(t.is_embedded() || !get_table()->is_asymmetric());
13,923!
1548
    // Incoming links to asymmetric objects are disallowed.
1549
    REALM_ASSERT(!t.is_asymmetric());
13,923✔
1550
    TableKey target_table_key = t.get_key();
13,923✔
1551
    auto result = t.is_embedded() ? t.create_linked_object() : t.create_object();
13,923✔
1552
    auto target_key = result.get_key();
13,923✔
1553
    ObjKey old_key = get<ObjKey>(col_key); // Will update if needed
13,923✔
1554
    if (old_key != ObjKey()) {
13,923✔
1555
        if (t.is_embedded()) {
48✔
1556
            // If this is an embedded object and there was already an embedded object here, then we need to
1557
            // emit an instruction to set the old embedded object to null to clear the old object on other
1558
            // sync clients. Without this, you'll only see the Set ObjectValue instruction, which is idempotent,
1559
            // and then array operations will have a corrupted prior_size.
1560
            if (Replication* repl = get_replication()) {
48✔
1561
                repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
24✔
1562
                          is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
24✔
1563
            }
24✔
1564
        }
48✔
1565
    }
48✔
1566

1567
    REALM_ASSERT(target_key != old_key); // We will always create a new object
13,923✔
1568
    CascadeState state;
13,923✔
1569

1570
    bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
13,923✔
1571
    _update_if_needed();
13,923✔
1572

1573
    Allocator& alloc = get_alloc();
13,923✔
1574
    alloc.bump_content_version();
13,923✔
1575
    Array fallback(alloc);
13,923✔
1576
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
13,923✔
1577
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
13,923✔
1578
    ArrayKey values(alloc);
13,923✔
1579
    values.set_parent(&fields, col_ndx.val + 1);
13,923✔
1580
    values.init_from_parent();
13,923✔
1581

1582
    values.set(m_row_ndx, target_key);
13,923✔
1583

1584
    sync(fields);
13,923✔
1585

1586
    if (Replication* repl = get_replication()) {
13,923✔
1587
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
13,275✔
1588
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
13,275✔
1589
    }
13,275✔
1590

1591
    if (recurse)
13,923✔
1592
        target_table->remove_recursive(state);
48✔
1593

1594
    return result;
13,923✔
1595
}
13,923✔
1596

1597
namespace {
1598
template <class T>
1599
inline void check_range(const T&)
1600
{
2,761,134✔
1601
}
2,761,134✔
1602
template <>
1603
inline void check_range(const StringData& val)
1604
{
3,086,838✔
1605
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
3,086,838✔
1606
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
6✔
1607
}
3,086,838✔
1608
template <>
1609
inline void check_range(const BinaryData& val)
1610
{
5,166,549✔
1611
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
5,166,549✔
1612
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
6✔
1613
}
5,166,549✔
1614
} // namespace
1615

1616
// helper functions for filtering out calls to set_string_interner()
1617
template <class T>
1618
inline void Obj::set_string_interner(T&, ColKey)
1619
{
7,927,866✔
1620
}
7,927,866✔
1621
template <>
1622
inline void Obj::set_string_interner<ArrayString>(ArrayString& values, ColKey col_key)
1623
{
3,086,853✔
1624
    values.set_string_interner(m_table->get_string_interner(col_key));
3,086,853✔
1625
}
3,086,853✔
1626

1627
// helper functions for filtering out calls to set_spec()
1628
template <class T>
1629
inline void Obj::set_spec(T&, ColKey)
1630
{
7,927,800✔
1631
}
7,927,800✔
1632
template <>
1633
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1634
{
3,087,045✔
1635
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
3,087,045✔
1636
    Spec* spec = const_cast<Spec*>(&get_spec());
3,087,045✔
1637
    values.set_spec(spec, spec_ndx);
3,087,045✔
1638
}
3,087,045✔
1639

1640
#if REALM_ENABLE_GEOSPATIAL
1641

1642
template <>
1643
Obj& Obj::set(ColKey col_key, Geospatial value, bool is_default)
1644
{
246✔
1645
    checked_update_if_needed();
246✔
1646
    get_table()->check_column(col_key);
246✔
1647
    auto type = col_key.get_type();
246✔
1648

1649
    if (type != ColumnTypeTraits<Link>::column_id)
246✔
1650
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1651
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1652
                                           get_table()->get_column_name(col_key)));
6✔
1653

1654
    Obj geo = get_linked_object(col_key);
240✔
1655
    if (!geo) {
240✔
1656
        geo = create_and_set_linked_object(col_key, is_default);
216✔
1657
    }
216✔
1658
    value.assign_to(geo);
240✔
1659
    return *this;
240✔
1660
}
246✔
1661

1662
template <>
1663
Obj& Obj::set(ColKey col_key, std::optional<Geospatial> value, bool is_default)
1664
{
12✔
1665
    checked_update_if_needed();
12✔
1666
    auto table = get_table();
12✔
1667
    table->check_column(col_key);
12✔
1668
    auto type = col_key.get_type();
12✔
1669
    auto attrs = col_key.get_attrs();
12✔
1670

1671
    if (type != ColumnTypeTraits<Link>::column_id)
12✔
1672
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1673
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1674
                                           get_table()->get_column_name(col_key)));
6✔
1675
    if (!value && !attrs.test(col_attr_Nullable))
6✔
1676
        throw NotNullable(Group::table_name_to_class_name(table->get_name()), table->get_column_name(col_key));
×
1677

1678
    if (!value) {
6✔
1679
        set_null(col_key, is_default);
6✔
1680
    }
6✔
1681
    else {
×
1682
        Obj geo = get_linked_object(col_key);
×
1683
        if (!geo) {
×
1684
            geo = create_and_set_linked_object(col_key, is_default);
×
1685
        }
×
1686
        value->assign_to(geo);
×
1687
    }
×
1688
    return *this;
6✔
1689
}
6✔
1690

1691
#endif
1692

1693
template <class T>
1694
Obj& Obj::set(ColKey col_key, T value, bool is_default)
1695
{
11,015,742✔
1696
    checked_update_if_needed();
11,015,742✔
1697
    get_table()->check_column(col_key);
11,015,742✔
1698
    auto type = col_key.get_type();
11,015,742✔
1699
    auto attrs = col_key.get_attrs();
11,015,742✔
1700
    auto col_ndx = col_key.get_index();
11,015,742✔
1701

1702
    if (type != ColumnTypeTraits<T>::column_id)
11,015,742✔
1703
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1704
                              util::format("Property not a %1", ColumnTypeTraits<T>::column_id));
×
1705
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
11,015,742!
1706
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
6✔
1707

1708
    check_range(value);
11,015,736✔
1709

1710
    SearchIndex* index = m_table->get_search_index(col_key);
11,015,736✔
1711
    if (index && !m_key.is_unresolved()) {
11,015,736✔
1712
        index->set(m_key, value);
477,471✔
1713
    }
477,471✔
1714

1715
    Allocator& alloc = get_alloc();
11,015,736✔
1716
    alloc.bump_content_version();
11,015,736✔
1717
    Array fallback(alloc);
11,015,736✔
1718
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
11,015,736✔
1719
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
11,015,736✔
1720
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
11,015,736✔
1721
    LeafType values(alloc);
11,015,736✔
1722
    values.set_parent(&fields, col_ndx.val + 1);
11,015,736✔
1723
    set_spec<LeafType>(values, col_key);
11,015,736✔
1724
    set_string_interner<LeafType>(values, col_key);
11,015,736✔
1725
    values.init_from_parent();
11,015,736✔
1726
    values.set(m_row_ndx, value);
11,015,736✔
1727

1728
    sync(fields);
11,015,736✔
1729

1730
    if (Replication* repl = get_replication())
11,015,736✔
1731
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
7,174,884✔
1732
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
7,174,884✔
1733

1734
    return *this;
11,015,736✔
1735
}
11,015,742✔
1736

1737
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
1738
INSTANTIATE_OBJ_SET(bool);
1739
INSTANTIATE_OBJ_SET(StringData);
1740
INSTANTIATE_OBJ_SET(float);
1741
INSTANTIATE_OBJ_SET(double);
1742
INSTANTIATE_OBJ_SET(Decimal128);
1743
INSTANTIATE_OBJ_SET(Timestamp);
1744
INSTANTIATE_OBJ_SET(BinaryData);
1745
INSTANTIATE_OBJ_SET(ObjectId);
1746
INSTANTIATE_OBJ_SET(UUID);
1747

1748
void Obj::set_int(ColKey::Idx col_ndx, int64_t value)
1749
{
529,377✔
1750
    checked_update_if_needed();
529,377✔
1751

1752
    Allocator& alloc = get_alloc();
529,377✔
1753
    alloc.bump_content_version();
529,377✔
1754
    Array fallback(alloc);
529,377✔
1755
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
529,377✔
1756
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
529,377✔
1757
    Array values(alloc);
529,377✔
1758
    values.set_parent(&fields, col_ndx.val + 1);
529,377✔
1759
    values.init_from_parent();
529,377✔
1760
    values.set(m_row_ndx, value);
529,377✔
1761

1762
    sync(fields);
529,377✔
1763
}
529,377✔
1764

1765
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
1766
{
5,508✔
1767
    checked_update_if_needed();
5,508✔
1768

1769
    Allocator& alloc = get_alloc();
5,508✔
1770
    alloc.bump_content_version();
5,508✔
1771
    Array fallback(alloc);
5,508✔
1772
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
5,508✔
1773
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
5,508✔
1774
    ArrayMixed values(alloc);
5,508✔
1775
    values.set_parent(&fields, col_ndx.val + 1);
5,508✔
1776
    values.init_from_parent();
5,508✔
1777
    values.set(m_row_ndx, Mixed(value, type));
5,508✔
1778

1779
    sync(fields);
5,508✔
1780
}
5,508✔
1781

1782
void Obj::add_backlink(ColKey backlink_col_key, ObjKey origin_key)
1783
{
6,750,855✔
1784
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
6,750,855✔
1785
    Allocator& alloc = get_alloc();
6,750,855✔
1786
    alloc.bump_content_version();
6,750,855✔
1787
    Array fallback(alloc);
6,750,855✔
1788
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
6,750,855✔
1789

1790
    ArrayBacklink backlinks(alloc);
6,750,855✔
1791
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
6,750,855✔
1792
    backlinks.init_from_parent();
6,750,855✔
1793

1794
    backlinks.add(m_row_ndx, origin_key);
6,750,855✔
1795

1796
    sync(fields);
6,750,855✔
1797
}
6,750,855✔
1798

1799
bool Obj::remove_one_backlink(ColKey backlink_col_key, ObjKey origin_key)
1800
{
197,670✔
1801
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
197,670✔
1802
    Allocator& alloc = get_alloc();
197,670✔
1803
    alloc.bump_content_version();
197,670✔
1804
    Array fallback(alloc);
197,670✔
1805
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
197,670✔
1806

1807
    ArrayBacklink backlinks(alloc);
197,670✔
1808
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
197,670✔
1809
    backlinks.init_from_parent();
197,670✔
1810

1811
    bool ret = backlinks.remove(m_row_ndx, origin_key);
197,670✔
1812

1813
    sync(fields);
197,670✔
1814

1815
    return ret;
197,670✔
1816
}
197,670✔
1817

1818
template <class ValueType>
1819
inline void Obj::nullify_single_link(ColKey col, ValueType target)
1820
{
891✔
1821
    ColKey::Idx origin_col_ndx = col.get_index();
891✔
1822
    Allocator& alloc = get_alloc();
891✔
1823
    Array fallback(alloc);
891✔
1824
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
891✔
1825
    using ArrayType = typename ColumnTypeTraits<ValueType>::cluster_leaf_type;
891✔
1826
    ArrayType links(alloc);
891✔
1827
    links.set_parent(&fields, origin_col_ndx.val + 1);
891✔
1828
    links.init_from_parent();
891✔
1829
    // Ensure we are nullifying correct link
1830
    REALM_ASSERT(links.get(m_row_ndx) == target);
891✔
1831
    links.set(m_row_ndx, ValueType{});
891✔
1832
    sync(fields);
891✔
1833

1834
    if (Replication* repl = get_replication())
891✔
1835
        repl->nullify_link(m_table.unchecked_ptr(), col,
759✔
1836
                           m_key); // Throws
759✔
1837
}
891✔
1838

1839
template <>
1840
inline void Obj::nullify_single_link<Mixed>(ColKey col, Mixed target)
1841
{
30✔
1842
    ColKey::Idx origin_col_ndx = col.get_index();
30✔
1843
    Allocator& alloc = get_alloc();
30✔
1844
    Array fallback(alloc);
30✔
1845
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
30✔
1846
    ArrayMixed mixed(alloc);
30✔
1847
    mixed.set_parent(&fields, origin_col_ndx.val + 1);
30✔
1848
    mixed.init_from_parent();
30✔
1849
    auto val = mixed.get(m_row_ndx);
30✔
1850
    bool result = false;
30✔
1851
    if (val.is_type(type_TypedLink)) {
30✔
1852
        // Ensure we are nullifying correct link
1853
        result = (val == target);
12✔
1854
        mixed.set(m_row_ndx, Mixed{});
12✔
1855
        sync(fields);
12✔
1856

1857
        if (Replication* repl = get_replication())
12✔
1858
            repl->nullify_link(m_table.unchecked_ptr(), col,
×
1859
                               m_key); // Throws
×
1860
    }
12✔
1861
    else if (val.is_type(type_Dictionary)) {
18✔
1862
        Dictionary dict(*this, col);
18✔
1863
        result = dict.nullify(target.get_link());
18✔
1864
    }
18✔
1865
    else if (val.is_type(type_List)) {
×
1866
        Lst<Mixed> list(*this, col);
×
1867
        result = list.nullify(target.get_link());
×
1868
    }
×
1869
    REALM_ASSERT(result);
30✔
1870
    static_cast<void>(result);
30✔
1871
}
30✔
1872

1873
void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) &&
1874
{
6,987✔
1875
    REALM_ASSERT(get_alloc().get_storage_version() == m_storage_version);
6,987✔
1876

1877
    struct LinkNullifier : public LinkTranslator {
6,987✔
1878
        LinkNullifier(Obj origin_obj, ColKey origin_col, ObjLink target)
6,987✔
1879
            : LinkTranslator(origin_obj, origin_col)
6,987✔
1880
            , m_target_link(target)
6,987✔
1881
        {
6,987✔
1882
        }
6,987✔
1883
        void on_list_of_links(LnkLst&) final
6,987✔
1884
        {
6,987✔
1885
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,310✔
1886
        }
2,310✔
1887
        void on_list_of_mixed(Lst<Mixed>& list) final
6,987✔
1888
        {
6,987✔
1889
            list.nullify(m_target_link);
474✔
1890
        }
474✔
1891
        void on_set_of_links(LnkSet&) final
6,987✔
1892
        {
6,987✔
1893
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,262✔
1894
        }
2,262✔
1895
        void on_set_of_mixed(Set<Mixed>&) final
6,987✔
1896
        {
6,987✔
1897
            nullify_set(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
300✔
1898
        }
300✔
1899
        void on_dictionary(Dictionary& dict) final
6,987✔
1900
        {
6,987✔
1901
            dict.nullify(m_target_link);
720✔
1902
        }
720✔
1903
        void on_link_property(ColKey origin_col_key) final
6,987✔
1904
        {
6,987✔
1905
            m_origin_obj.nullify_single_link<ObjKey>(origin_col_key, m_target_link.get_obj_key());
891✔
1906
        }
891✔
1907
        void on_mixed_property(ColKey origin_col_key) final
6,987✔
1908
        {
6,987✔
1909
            m_origin_obj.nullify_single_link<Mixed>(origin_col_key, Mixed{m_target_link});
30✔
1910
        }
30✔
1911

1912
    private:
6,987✔
1913
        ObjLink m_target_link;
6,987✔
1914
    } nullifier{*this, origin_col_key, target_link};
6,987✔
1915

1916
    nullifier.run();
6,987✔
1917

1918
    get_alloc().bump_content_version();
6,987✔
1919
}
6,987✔
1920

1921

1922
struct EmbeddedObjectLinkMigrator : public LinkTranslator {
1923
    EmbeddedObjectLinkMigrator(Obj origin, ColKey origin_col, Obj dest_orig, Obj dest_replace)
1924
        : LinkTranslator(origin, origin_col)
30,159✔
1925
        , m_dest_orig(dest_orig)
30,159✔
1926
        , m_dest_replace(dest_replace)
30,159✔
1927
    {
60,318✔
1928
    }
60,318✔
1929
    void on_list_of_links(LnkLst& list) final
1930
    {
168✔
1931
        auto n = list.find_first(m_dest_orig.get_key());
168✔
1932
        REALM_ASSERT(n != realm::npos);
168✔
1933
        list.set(n, m_dest_replace.get_key());
168✔
1934
    }
168✔
1935
    void on_dictionary(Dictionary& dict) final
1936
    {
60✔
1937
        auto pos = dict.find_any(m_dest_orig.get_link());
60✔
1938
        REALM_ASSERT(pos != realm::npos);
60✔
1939
        Mixed key = dict.get_key(pos);
60✔
1940
        dict.insert(key, m_dest_replace.get_link());
60✔
1941
    }
60✔
1942
    void on_link_property(ColKey col) final
1943
    {
60,090✔
1944
        REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
60,090✔
1945
        m_origin_obj.set(col, m_dest_replace.get_key());
60,090✔
1946
    }
60,090✔
1947
    void on_set_of_links(LnkSet&) final
1948
    {
×
1949
        // this should never happen because sets of embedded objects are not allowed at the schema level
1950
        REALM_UNREACHABLE();
1951
    }
×
1952
    // The following cases have support here but are expected to fail later on in the
1953
    // migration due to core not yet supporting untyped Mixed links to embedded objects.
1954
    void on_set_of_mixed(Set<Mixed>& set) final
1955
    {
×
1956
        auto did_erase_pair = set.erase(m_dest_orig.get_link());
×
1957
        REALM_ASSERT(did_erase_pair.second);
×
1958
        set.insert(m_dest_replace.get_link());
×
1959
    }
×
1960
    void on_list_of_mixed(Lst<Mixed>& list) final
1961
    {
×
1962
        auto n = list.find_any(m_dest_orig.get_link());
×
1963
        REALM_ASSERT(n != realm::npos);
×
1964
        list.insert_any(n, m_dest_replace.get_link());
×
1965
    }
×
1966
    void on_mixed_property(ColKey col) final
1967
    {
×
1968
        REALM_ASSERT(m_origin_obj.get<Mixed>(col).is_null() ||
×
1969
                     m_origin_obj.get<Mixed>(col) == m_dest_orig.get_link());
×
1970
        m_origin_obj.set_any(col, m_dest_replace.get_link());
×
1971
    }
×
1972

1973
private:
1974
    Obj m_dest_orig;
1975
    Obj m_dest_replace;
1976
};
1977

1978
void Obj::handle_multiple_backlinks_during_schema_migration()
1979
{
54✔
1980
    REALM_ASSERT(!m_table->get_primary_key_column());
54✔
1981
    converters::EmbeddedObjectConverter embedded_obj_tracker;
54✔
1982
    auto copy_links = [&](ColKey col) {
108✔
1983
        auto opposite_table = m_table->get_opposite_table(col);
108✔
1984
        auto opposite_column = m_table->get_opposite_column(col);
108✔
1985
        auto backlinks = get_all_backlinks(col);
108✔
1986
        for (auto backlink : backlinks) {
60,318✔
1987
            // create a new obj
1988
            auto obj = m_table->create_object();
60,318✔
1989
            embedded_obj_tracker.track(*this, obj);
60,318✔
1990
            auto linking_obj = opposite_table->get_object(backlink);
60,318✔
1991
            // change incoming links to point to the newly created object
1992
            EmbeddedObjectLinkMigrator{linking_obj, opposite_column, *this, obj}.run();
60,318✔
1993
        }
60,318✔
1994
        embedded_obj_tracker.process_pending();
108✔
1995
        return IteratorControl::AdvanceToNext;
108✔
1996
    };
108✔
1997
    m_table->for_each_backlink_column(copy_links);
54✔
1998
}
54✔
1999

2000
LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const
2001
{
298,254✔
2002
    auto list = CollectionParent::get_listbase_ptr(col_key, 0);
298,254✔
2003
    list->set_owner(*this, col_key);
298,254✔
2004
    return list;
298,254✔
2005
}
298,254✔
2006

2007
SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const
2008
{
43,548✔
2009
    auto set = CollectionParent::get_setbase_ptr(col_key, 0);
43,548✔
2010
    set->set_owner(*this, col_key);
43,548✔
2011
    return set;
43,548✔
2012
}
43,548✔
2013

2014
Dictionary Obj::get_dictionary(ColKey col_key) const
2015
{
93,330✔
2016
    REALM_ASSERT(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed);
93,330✔
2017
    checked_update_if_needed();
93,330✔
2018
    return Dictionary(Obj(*this), col_key);
93,330✔
2019
}
93,330✔
2020

2021
Obj& Obj::set_collection(ColKey col_key, CollectionType type)
2022
{
4,416✔
2023
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
4,416✔
2024
    if ((col_key.is_dictionary() && type == CollectionType::Dictionary) ||
4,416✔
2025
        (col_key.is_list() && type == CollectionType::List)) {
4,416✔
2026
        return *this;
30✔
2027
    }
30✔
2028
    if (type == CollectionType::Set) {
4,386✔
2029
        throw IllegalOperation("Set nested in Mixed is not supported");
×
2030
    }
×
2031
    set(col_key, Mixed(0, type));
4,386✔
2032

2033
    return *this;
4,386✔
2034
}
4,386✔
2035

2036
DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const
2037
{
570✔
2038
    return std::make_shared<Dictionary>(get_dictionary(col_key));
570✔
2039
}
570✔
2040

2041
DictionaryPtr Obj::get_dictionary_ptr(const Path& path) const
2042
{
216✔
2043
    return std::dynamic_pointer_cast<Dictionary>(get_collection_ptr(path));
216✔
2044
}
216✔
2045

2046
Dictionary Obj::get_dictionary(StringData col_name) const
2047
{
16,608✔
2048
    return get_dictionary(get_column_key(col_name));
16,608✔
2049
}
16,608✔
2050

2051
CollectionPtr Obj::get_collection_ptr(const Path& path) const
2052
{
12,000✔
2053
    REALM_ASSERT(path.size() > 0);
12,000✔
2054
    // First element in path must be column name
2055
    auto col_key = path[0].is_col_key() ? path[0].get_col_key() : m_table->get_column_key(path[0].get_key());
12,000✔
2056
    REALM_ASSERT(col_key);
12,000✔
2057
    size_t level = 1;
12,000✔
2058
    CollectionBasePtr collection = get_collection_ptr(col_key);
12,000✔
2059

2060
    while (level < path.size()) {
13,182✔
2061
        auto& path_elem = path[level];
1,182✔
2062
        Mixed ref;
1,182✔
2063
        if (collection->get_collection_type() == CollectionType::List) {
1,182✔
2064
            ref = collection->get_any(path_elem.get_ndx());
462✔
2065
        }
462✔
2066
        else {
720✔
2067
            ref = dynamic_cast<Dictionary*>(collection.get())->get(path_elem.get_key());
720✔
2068
        }
720✔
2069
        if (ref.is_type(type_List)) {
1,182✔
2070
            collection = collection->get_list(path_elem);
762✔
2071
        }
762✔
2072
        else if (ref.is_type(type_Dictionary)) {
420✔
2073
            collection = collection->get_dictionary(path_elem);
420✔
2074
        }
420✔
2075
        else {
×
2076
            throw InvalidArgument("Wrong path");
×
2077
        }
×
2078
        level++;
1,182✔
2079
    }
1,182✔
2080

2081
    return collection;
12,000✔
2082
}
12,000✔
2083

2084
CollectionPtr Obj::get_collection_by_stable_path(const StablePath& path) const
2085
{
10,551✔
2086
    // First element in path is phony column key
2087
    ColKey col_key = m_table->get_column_key(path[0]);
10,551✔
2088
    size_t level = 1;
10,551✔
2089
    CollectionBasePtr collection = get_collection_ptr(col_key);
10,551✔
2090

2091
    while (level < path.size()) {
11,187✔
2092
        auto& index = path[level];
648✔
2093
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
648✔
2094
            Mixed ref;
648✔
2095
            PathElement path_elem;
648✔
2096
            if (collection->get_collection_type() == CollectionType::List) {
648✔
2097
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
396✔
2098
                size_t ndx = list_of_mixed->find_index(index);
396✔
2099
                if (ndx != realm::not_found) {
396✔
2100
                    ref = list_of_mixed->get(ndx);
396✔
2101
                    path_elem = ndx;
396✔
2102
                }
396✔
2103
            }
396✔
2104
            else {
252✔
2105
                auto dict = dynamic_cast<Dictionary*>(collection.get());
252✔
2106
                size_t ndx = dict->find_index(index);
252✔
2107
                if (ndx != realm::not_found) {
252✔
2108
                    ref = dict->get_any(ndx);
240✔
2109
                    path_elem = dict->get_key(ndx).get_string();
240✔
2110
                }
240✔
2111
            }
252✔
2112
            return {ref, path_elem};
648✔
2113
        };
648✔
2114
        auto [ref, path_elem] = get_ref();
648✔
2115
        if (ref.is_type(type_List)) {
648✔
2116
            collection = collection->get_list(path_elem);
480✔
2117
        }
480✔
2118
        else if (ref.is_type(type_Dictionary)) {
168✔
2119
            collection = collection->get_dictionary(path_elem);
156✔
2120
        }
156✔
2121
        else {
12✔
2122
            return nullptr;
12✔
2123
        }
12✔
2124
        level++;
636✔
2125
    }
636✔
2126

2127
    return collection;
10,539✔
2128
}
10,551✔
2129

2130
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2131
{
131,700✔
2132
    if (col_key.is_collection()) {
131,700✔
2133
        auto collection = CollectionParent::get_collection_ptr(col_key, 0);
127,626✔
2134
        collection->set_owner(*this, col_key);
127,626✔
2135
        return collection;
127,626✔
2136
    }
127,626✔
2137
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
4,074✔
2138
    auto val = get<Mixed>(col_key);
4,074✔
2139
    if (val.is_type(type_List)) {
4,074✔
2140
        return std::make_shared<Lst<Mixed>>(*this, col_key);
1,974✔
2141
    }
1,974✔
2142
    REALM_ASSERT(val.is_type(type_Dictionary));
2,100✔
2143
    return std::make_shared<Dictionary>(*this, col_key);
2,100✔
2144
}
4,074✔
2145

2146
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
2147
{
420✔
2148
    return get_collection_ptr(get_column_key(col_name));
420✔
2149
}
420✔
2150

2151
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
2152
{
3,114✔
2153
    if (col_key.is_list()) {
3,114✔
2154
        return get_linklist_ptr(col_key);
2,994✔
2155
    }
2,994✔
2156
    else if (col_key.is_set()) {
120✔
2157
        return get_linkset_ptr(col_key);
78✔
2158
    }
78✔
2159
    else if (col_key.is_dictionary()) {
42✔
2160
        auto dict = get_dictionary(col_key);
42✔
2161
        return std::make_unique<DictionaryLinkValues>(dict);
42✔
2162
    }
42✔
2163
    return {};
×
2164
}
3,114✔
2165

2166
template <class T>
2167
inline void replace_in_linkset(Obj& obj, ColKey origin_col_key, T target, T replacement)
2168
{
288✔
2169
    Set<T> link_set(origin_col_key);
288✔
2170
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
288✔
2171

2172
    REALM_ASSERT(ndx != realm::npos); // There has to be one
288✔
2173

2174
    link_set.erase(target);
288✔
2175
    link_set.insert(replacement);
288✔
2176
}
288✔
2177

2178
inline void replace_in_dictionary(Obj& obj, ColKey origin_col_key, Mixed target, Mixed replacement)
2179
{
×
2180
    Dictionary dict(origin_col_key);
×
2181
    size_t ndx = find_link_value_in_collection(dict, obj, origin_col_key, target);
×
2182

×
2183
    REALM_ASSERT(ndx != realm::npos); // There has to be one
×
2184

×
2185
    auto key = dict.get_key(ndx);
×
2186
    dict.insert(key, replacement);
×
2187
}
×
2188

2189
void Obj::assign_pk_and_backlinks(Obj& other)
2190
{
11,760✔
2191
    struct LinkReplacer : LinkTranslator {
11,760✔
2192
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
11,760✔
2193
            : LinkTranslator(origin, origin_col_key)
20,034✔
2194
            , m_dest_orig(dest_orig)
20,034✔
2195
            , m_dest_replace(dest_replace)
20,034✔
2196
        {
28,314✔
2197
        }
28,314✔
2198
        void on_list_of_links(LnkLst&) final
11,760✔
2199
        {
27,414✔
2200
            auto linklist = m_origin_obj.get_linklist(m_origin_col_key);
27,414✔
2201
            linklist.replace_link(m_dest_orig.get_key(), m_dest_replace.get_key());
27,414✔
2202
        }
27,414✔
2203
        void on_list_of_mixed(Lst<Mixed>& list) final
11,760✔
2204
        {
11,760✔
2205
            list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
114✔
2206
        }
114✔
2207
        void on_set_of_links(LnkSet&) final
11,760✔
2208
        {
11,760✔
2209
            replace_in_linkset(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
138✔
2210
        }
138✔
2211
        void on_set_of_mixed(Set<Mixed>&) final
11,760✔
2212
        {
11,760✔
2213
            replace_in_linkset<Mixed>(m_origin_obj, m_origin_col_key, m_dest_orig.get_link(),
150✔
2214
                                      m_dest_replace.get_link());
150✔
2215
        }
150✔
2216
        void on_dictionary(Dictionary& dict) final
11,760✔
2217
        {
11,760✔
2218
            dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
252✔
2219
        }
252✔
2220
        void on_link_property(ColKey col) final
11,760✔
2221
        {
11,760✔
2222
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
180✔
2223
            // Handle links as plain integers. Backlinks has been taken care of.
2224
            // Be careful here - links are stored as value + 1 so that null link (-1) will be 0
2225
            auto new_key = m_dest_replace.get_key();
180✔
2226
            m_origin_obj.set_int(col.get_index(), new_key.value + 1);
180✔
2227
            if (Replication* repl = m_origin_obj.get_replication())
180✔
2228
                repl->set(m_origin_obj.get_table().unchecked_ptr(), col, m_origin_obj.get_key(), new_key);
132✔
2229
        }
180✔
2230
        void on_mixed_property(ColKey col) final
11,760✔
2231
        {
11,760✔
2232
            auto val = m_origin_obj.get_any(col);
66✔
2233
            if (val.is_type(type_Dictionary)) {
66✔
2234
                Dictionary dict(m_origin_obj, m_origin_col_key);
12✔
2235
                dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2236
            }
12✔
2237
            else if (val.is_type(type_List)) {
54✔
2238
                Lst<Mixed> list(m_origin_obj, m_origin_col_key);
12✔
2239
                list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2240
            }
12✔
2241
            else {
42✔
2242
                REALM_ASSERT(val.is_null() || val.get_link().get_obj_key() == m_dest_orig.get_key());
42✔
2243
                m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
42✔
2244
            }
42✔
2245
        }
66✔
2246

2247
    private:
11,760✔
2248
        const Obj& m_dest_orig;
11,760✔
2249
        const Obj& m_dest_replace;
11,760✔
2250
    };
11,760✔
2251

2252
    REALM_ASSERT(get_table() == other.get_table());
11,760✔
2253
    if (auto col_pk = m_table->get_primary_key_column()) {
11,760✔
2254
        Mixed val = other.get_any(col_pk);
11,598✔
2255
        this->set_any(col_pk, val);
11,598✔
2256
    }
11,598✔
2257
    auto nb_tombstones = m_table->m_tombstones->size();
11,760✔
2258

2259
    auto copy_links = [this, &other, nb_tombstones](ColKey col) {
11,760✔
2260
        if (nb_tombstones != m_table->m_tombstones->size()) {
10,548✔
2261
            // Object has been deleted - we are done
2262
            return IteratorControl::Stop;
×
2263
        }
×
2264

2265
        auto t = m_table->get_opposite_table(col);
10,548✔
2266
        auto c = m_table->get_opposite_column(col);
10,548✔
2267
        auto backlinks = other.get_all_backlinks(col);
10,548✔
2268

2269
        if (c.get_type() == col_type_Link && !(c.is_dictionary() || c.is_set())) {
10,548✔
2270
            auto idx = col.get_index();
9,882✔
2271
            // Transfer the backlinks from tombstone to live object
2272
            REALM_ASSERT(_get<int64_t>(idx) == 0);
9,882✔
2273
            auto other_val = other._get<int64_t>(idx);
9,882✔
2274
            set_int(idx, other_val);
9,882✔
2275
            other.set_int(idx, 0);
9,882✔
2276
        }
9,882✔
2277

2278
        for (auto bl : backlinks) {
28,314✔
2279
            auto linking_obj = t->get_object(bl);
28,314✔
2280
            LinkReplacer replacer{linking_obj, c, other, *this};
28,314✔
2281
            replacer.run();
28,314✔
2282
        }
28,314✔
2283
        return IteratorControl::AdvanceToNext;
10,548✔
2284
    };
10,548✔
2285
    m_table->for_each_backlink_column(copy_links);
11,760✔
2286
}
11,760✔
2287

2288
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
2289
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
2290
template float Obj::get<float>(ColKey col_key) const;
2291
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
2292
template double Obj::get<double>(ColKey col_key) const;
2293
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2294
template StringData Obj::get<StringData>(ColKey col_key) const;
2295
template BinaryData Obj::get<BinaryData>(ColKey col_key) const;
2296
template Timestamp Obj::get<Timestamp>(ColKey col_key) const;
2297
template ObjectId Obj::get<ObjectId>(ColKey col_key) const;
2298
template util::Optional<ObjectId> Obj::get<util::Optional<ObjectId>>(ColKey col_key) const;
2299
template ObjKey Obj::get<ObjKey>(ColKey col_key) const;
2300
template Decimal128 Obj::get<Decimal128>(ColKey col_key) const;
2301
template ObjLink Obj::get<ObjLink>(ColKey col_key) const;
2302
template Mixed Obj::get<Mixed>(realm::ColKey) const;
2303
template UUID Obj::get<UUID>(realm::ColKey) const;
2304
template util::Optional<UUID> Obj::get<util::Optional<UUID>>(ColKey col_key) const;
2305

2306
template <class T>
2307
inline void Obj::do_set_null(ColKey col_key)
2308
{
39,018✔
2309
    ColKey::Idx col_ndx = col_key.get_index();
39,018✔
2310
    Allocator& alloc = get_alloc();
39,018✔
2311
    alloc.bump_content_version();
39,018✔
2312
    Array fallback(alloc);
39,018✔
2313
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
39,018✔
2314

2315
    T values(alloc);
39,018✔
2316
    values.set_parent(&fields, col_ndx.val + 1);
39,018✔
2317
    values.init_from_parent();
39,018✔
2318
    values.set_null(m_row_ndx);
39,018✔
2319

2320
    sync(fields);
39,018✔
2321
}
39,018✔
2322

2323
template <>
2324
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2325
{
2,466✔
2326
    ColKey::Idx col_ndx = col_key.get_index();
2,466✔
2327
    size_t spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
2,466✔
2328
    Allocator& alloc = get_alloc();
2,466✔
2329
    alloc.bump_content_version();
2,466✔
2330
    Array fallback(alloc);
2,466✔
2331
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
2,466✔
2332

2333
    ArrayString values(alloc);
2,466✔
2334
    values.set_parent(&fields, col_ndx.val + 1);
2,466✔
2335
    values.set_spec(const_cast<Spec*>(&get_spec()), spec_ndx);
2,466✔
2336
    values.set_string_interner(m_table->get_string_interner(col_key));
2,466✔
2337
    values.init_from_parent();
2,466✔
2338
    values.set_null(m_row_ndx);
2,466✔
2339

2340
    sync(fields);
2,466✔
2341
}
2,466✔
2342

2343
Obj& Obj::set_null(ColKey col_key, bool is_default)
2344
{
42,036✔
2345
    ColumnType col_type = col_key.get_type();
42,036✔
2346
    // Links need special handling
2347
    if (col_type == col_type_Link) {
42,036✔
2348
        return set(col_key, null_key, is_default);
408✔
2349
    }
408✔
2350
    if (col_type == col_type_Mixed) {
41,628✔
2351
        return set(col_key, Mixed{}, is_default);
78✔
2352
    }
78✔
2353

2354
    auto attrs = col_key.get_attrs();
41,550✔
2355
    if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
41,550✔
2356
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
66✔
2357
    }
66✔
2358

2359
    checked_update_if_needed();
41,484✔
2360

2361
    SearchIndex* index = m_table->get_search_index(col_key);
41,484✔
2362
    if (index && !m_key.is_unresolved()) {
41,484✔
2363
        index->set(m_key, null{});
4,458✔
2364
    }
4,458✔
2365

2366
    switch (col_type) {
41,484✔
2367
        case col_type_Int:
5,928✔
2368
            do_set_null<ArrayIntNull>(col_key);
5,928✔
2369
            break;
5,928✔
2370
        case col_type_Bool:
5,529✔
2371
            do_set_null<ArrayBoolNull>(col_key);
5,529✔
2372
            break;
5,529✔
2373
        case col_type_Float:
6,207✔
2374
            do_set_null<ArrayFloatNull>(col_key);
6,207✔
2375
            break;
6,207✔
2376
        case col_type_Double:
6,252✔
2377
            do_set_null<ArrayDoubleNull>(col_key);
6,252✔
2378
            break;
6,252✔
2379
        case col_type_ObjectId:
3,357✔
2380
            do_set_null<ArrayObjectIdNull>(col_key);
3,357✔
2381
            break;
3,357✔
2382
        case col_type_String:
2,466✔
2383
            do_set_null<ArrayString>(col_key);
2,466✔
2384
            break;
2,466✔
2385
        case col_type_Binary:
1,248✔
2386
            do_set_null<ArrayBinary>(col_key);
1,248✔
2387
            break;
1,248✔
2388
        case col_type_Timestamp:
2,445✔
2389
            do_set_null<ArrayTimestamp>(col_key);
2,445✔
2390
            break;
2,445✔
2391
        case col_type_Decimal:
1,830✔
2392
            do_set_null<ArrayDecimal128>(col_key);
1,830✔
2393
            break;
1,830✔
2394
        case col_type_UUID:
6,222✔
2395
            do_set_null<ArrayUUIDNull>(col_key);
6,222✔
2396
            break;
6,222✔
2397
        case col_type_Mixed:
✔
2398
        case col_type_Link:
✔
2399
        case col_type_BackLink:
✔
2400
        case col_type_TypedLink:
✔
2401
            REALM_UNREACHABLE();
2402
    }
41,484✔
2403

2404
    if (Replication* repl = get_replication())
41,484✔
2405
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
21,234✔
2406
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
21,234✔
2407

2408
    return *this;
41,484✔
2409
}
41,484✔
2410

2411

2412
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2413
{
10,382,157✔
2414
    return get_table()->spec_ndx2colkey(col_ndx);
10,382,157✔
2415
}
10,382,157✔
2416

2417
size_t Obj::colkey2spec_ndx(ColKey key)
2418
{
1,011✔
2419
    return get_table()->colkey2spec_ndx(key);
1,011✔
2420
}
1,011✔
2421

2422
ColKey Obj::get_primary_key_column() const
2423
{
6,467,898✔
2424
    return m_table->get_primary_key_column();
6,467,898✔
2425
}
6,467,898✔
2426

2427
ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key)
2428
{
12,786✔
2429
    return to_ref(obj._get<int64_t>(col_key.get_index()));
12,786✔
2430
}
12,786✔
2431

2432
ref_type Obj::get_collection_ref(StableIndex index, CollectionType type) const
2433
{
3,226,425✔
2434
    if (index.is_collection()) {
3,226,425✔
2435
        return to_ref(_get<int64_t>(index.get_index()));
3,207,573✔
2436
    }
3,207,573✔
2437
    if (check_index(index)) {
18,852✔
2438
        auto val = _get<Mixed>(index.get_index());
18,756✔
2439
        if (val.is_type(DataType(int(type)))) {
18,756✔
2440
            return val.get_ref();
18,750✔
2441
        }
18,750✔
2442
        throw realm::IllegalOperation(util::format("Not a %1", type));
6✔
2443
    }
18,756✔
2444
    throw StaleAccessor("This collection is no more");
96✔
2445
}
18,852✔
2446

2447
bool Obj::check_collection_ref(StableIndex index, CollectionType type) const noexcept
2448
{
1,073,031✔
2449
    if (index.is_collection()) {
1,073,031✔
2450
        return true;
1,068,159✔
2451
    }
1,068,159✔
2452
    if (check_index(index)) {
4,872✔
2453
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
4,866✔
2454
    }
4,866✔
2455
    return false;
6✔
2456
}
4,872✔
2457

2458
void Obj::set_collection_ref(StableIndex index, ref_type ref, CollectionType type)
2459
{
514,941✔
2460
    if (index.is_collection()) {
514,941✔
2461
        set_int(index.get_index(), from_ref(ref));
509,436✔
2462
        return;
509,436✔
2463
    }
509,436✔
2464
    set_ref(index.get_index(), ref, type);
5,505✔
2465
}
5,505✔
2466

2467
void Obj::set_backlink(ColKey col_key, ObjLink new_link) const
2468
{
6,793,350✔
2469
    if (!new_link) {
6,793,350✔
2470
        return;
42,126✔
2471
    }
42,126✔
2472

2473
    auto target_table = m_table->get_parent_group()->get_table(new_link.get_table_key());
6,751,224✔
2474
    ColKey backlink_col_key;
6,751,224✔
2475
    auto type = col_key.get_type();
6,751,224✔
2476
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
6,751,224✔
2477
        // This may modify the target table
2478
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
34,854✔
2479
        // it is possible that this was a link to the same table and that adding a backlink column has
2480
        // caused the need to update this object as well.
2481
        update_if_needed();
34,854✔
2482
    }
34,854✔
2483
    else {
6,716,370✔
2484
        backlink_col_key = m_table->get_opposite_column(col_key);
6,716,370✔
2485
    }
6,716,370✔
2486
    auto obj_key = new_link.get_obj_key();
6,751,224✔
2487
    auto target_obj =
6,751,224✔
2488
        obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key) : target_table->try_get_object(obj_key);
6,751,224✔
2489
    if (!target_obj) {
6,751,224✔
2490
        throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
12✔
2491
    }
12✔
2492
    target_obj.add_backlink(backlink_col_key, m_key);
6,751,212✔
2493
}
6,751,212✔
2494

2495
bool Obj::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
2496
{
333,297✔
2497
    bool recurse = remove_backlink(col_key, old_link, state);
333,297✔
2498
    set_backlink(col_key, new_link);
333,297✔
2499
    return recurse;
333,297✔
2500
}
333,297✔
2501

2502
bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
2503
{
352,947✔
2504
    if (!old_link) {
352,947✔
2505
        return false;
227,577✔
2506
    }
227,577✔
2507

2508
    REALM_ASSERT(m_table->valid_column(col_key));
125,370✔
2509
    ObjKey old_key = old_link.get_obj_key();
125,370✔
2510
    auto target_obj = m_table->get_parent_group()->get_object(old_link);
125,370✔
2511
    TableRef target_table = target_obj.get_table();
125,370✔
2512
    ColKey backlink_col_key;
125,370✔
2513
    auto type = col_key.get_type();
125,370✔
2514
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
125,370✔
2515
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
12,402✔
2516
    }
12,402✔
2517
    else {
112,968✔
2518
        backlink_col_key = m_table->get_opposite_column(col_key);
112,968✔
2519
    }
112,968✔
2520

2521
    bool strong_links = target_table->is_embedded();
125,370✔
2522
    bool is_unres = old_key.is_unresolved();
125,370✔
2523

2524
    bool last_removed = target_obj.remove_one_backlink(backlink_col_key, m_key); // Throws
125,370✔
2525
    if (is_unres) {
125,370✔
2526
        if (last_removed) {
264✔
2527
            // Check is there are more backlinks
2528
            if (!target_obj.has_backlinks(false)) {
252✔
2529
                // Tombstones can be erased right away - there is no cascading effect
2530
                target_table->m_tombstones->erase(old_key, state);
252✔
2531
            }
252✔
2532
        }
252✔
2533
    }
264✔
2534
    else {
125,106✔
2535
        return state.enqueue_for_cascade(target_obj, strong_links, last_removed);
125,106✔
2536
    }
125,106✔
2537

2538
    return false;
264✔
2539
}
125,370✔
2540

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