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

realm / realm-core / jorgen.edelbo_334

01 Jul 2024 07:22AM UTC coverage: 90.829% (-0.04%) from 90.865%
jorgen.edelbo_334

Pull #7803

Evergreen

jedelbo
Merge branch 'next-major' into feature/string-compression
Pull Request #7803: Feature/string compression

102912 of 180568 branches covered (56.99%)

1141 of 1267 new or added lines in 33 files covered. (90.06%)

172 existing lines in 24 files now uncovered.

218291 of 240332 relevant lines covered (90.83%)

7818396.4 hits per line

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

90.07
/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
{
116,214✔
56
    coll.set_owner(obj, origin_col_key);
116,214✔
57
    return coll.find_first(link);
116,214✔
58
}
116,214✔
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)
119,459,871✔
109
    , m_key(key)
119,459,871✔
110
    , m_mem(mem)
119,459,871✔
111
    , m_row_ndx(row_ndx)
119,459,871✔
112
    , m_valid(true)
119,459,871✔
113
{
243,949,320✔
114
    m_storage_version = get_alloc().get_storage_version();
243,949,320✔
115
}
243,949,320✔
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
{
54,468✔
124
    return ObjLink(m_table->get_key(), m_key);
54,468✔
125
}
54,468✔
126

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

137
Allocator& Obj::get_alloc() const
138
{
363,475,872✔
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;
363,475,872✔
144
}
363,475,872✔
145

146
Allocator& Obj::_get_alloc() const noexcept
147
{
443,963,673✔
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;
443,963,673✔
151
}
443,963,673✔
152

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

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

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

185
Replication* Obj::get_replication() const
186
{
32,784,312✔
187
    return m_table->get_repl();
32,784,312✔
188
}
32,784,312✔
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
        auto other_table = other.get_table();
24✔
243
        auto other_col_key = other_table->get_column_key(col_name);
24✔
244
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
24✔
245
            DummyParent parent(other_table, m2.get_ref(), other_col_key);
6✔
246
            Lst<Mixed> list(parent, 0);
6✔
247
            return compare_list_in_mixed(*val1.get_list(i), list, ck, other, col_name);
6✔
248
        }
6✔
249
        else if (m1.is_type(type_Dictionary) && m2.is_type(type_Dictionary)) {
18!
NEW
250
            DummyParent parent(other_table, m2.get_ref(), other_col_key);
×
251
            Dictionary dict(parent, 0);
×
252
            return compare_dict_in_mixed(*val1.get_dictionary(i), dict, ck, other, col_name);
×
253
        }
×
254
        else if (!compare_values(m1, m2, ck, other, col_name)) {
18✔
255
            return false;
×
256
        }
×
257
    }
24✔
258
    return true;
6✔
259
}
12✔
260

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

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

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

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

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

300
        if (!ck.is_collection()) {
4,902✔
301
            if (!compare(get_any(ck), other.get_any(col_name)))
4,482✔
302
                return false;
24✔
303
        }
4,482✔
304
        else {
420✔
305
            auto coll1 = get_collection_ptr(ck);
420✔
306
            auto coll2 = other.get_collection_ptr(col_name);
420✔
307
            size_t sz = coll1->size();
420✔
308
            if (coll2->size() != sz)
420✔
309
                return false;
×
310
            if (ck.is_list() || ck.is_set()) {
420✔
311
                for (size_t i = 0; i < sz; i++) {
6,642✔
312
                    if (!compare(coll1->get_any(i), coll2->get_any(i)))
6,402✔
313
                        return false;
×
314
                }
6,402✔
315
            }
240✔
316
            if (ck.is_dictionary()) {
420✔
317
                auto dict1 = dynamic_cast<Dictionary*>(coll1.get());
180✔
318
                auto dict2 = dynamic_cast<Dictionary*>(coll2.get());
180✔
319
                for (size_t i = 0; i < sz; i++) {
318✔
320
                    auto [key, value] = dict1->get_pair(i);
144✔
321
                    auto val2 = dict2->try_get(key);
144✔
322
                    if (!val2)
144✔
323
                        return false;
×
324
                    if (!compare(value, *val2))
144✔
325
                        return false;
6✔
326
                }
144✔
327
            }
180✔
328
        }
420✔
329
    }
4,902✔
330
    return true;
3,048✔
331
}
3,078✔
332

333
bool Obj::is_valid() const noexcept
334
{
1,458,186✔
335
    // Cache valid state. If once invalid, it can never become valid again
336
    if (m_valid)
1,458,186✔
337
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,456,803✔
338
                                    m_table.unchecked_ptr()->is_valid(m_key));
1,456,542✔
339

340
    return m_valid;
1,458,186✔
341
}
1,458,186✔
342

343
void Obj::remove()
344
{
129,807✔
345
    m_table.cast_away_const()->remove_object(m_key);
129,807✔
346
}
129,807✔
347

348
void Obj::invalidate()
349
{
3,615✔
350
    m_key = m_table.cast_away_const()->invalidate_object(m_key);
3,615✔
351
}
3,615✔
352

353
ColKey Obj::get_column_key(StringData col_name) const
354
{
3,778,092✔
355
    return get_table()->get_column_key(col_name);
3,778,092✔
356
}
3,778,092✔
357

358
TableKey Obj::get_table_key() const
359
{
×
360
    return get_table()->get_key();
×
361
}
×
362

363
TableRef Obj::get_target_table(ColKey col_key) const
364
{
7,102,311✔
365
    if (m_table) {
7,102,311✔
366
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,101,756✔
367
    }
7,101,756✔
368
    else {
555✔
369
        return TableRef();
555✔
370
    }
555✔
371
}
7,102,311✔
372

373
TableRef Obj::get_target_table(ObjLink link) const
374
{
×
375
    if (m_table) {
×
376
        return m_table.unchecked_ptr()->get_parent_group()->get_table(link.get_table_key());
×
377
    }
×
378
    else {
×
379
        return TableRef();
×
380
    }
×
381
}
×
382

383
bool Obj::update() const
384
{
163,008✔
385
    // Get a new object from key
386
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
163,008✔
387

388
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
163,008✔
389
    if (changes) {
163,008✔
390
        m_mem = new_obj.m_mem;
22,317✔
391
        m_row_ndx = new_obj.m_row_ndx;
22,317✔
392
        ++m_version_counter;
22,317✔
393
    }
22,317✔
394
    // Always update versions
395
    m_storage_version = new_obj.m_storage_version;
163,008✔
396
    m_table = new_obj.m_table;
163,008✔
397
    return changes;
163,008✔
398
}
163,008✔
399

400
inline bool Obj::_update_if_needed() const
401
{
81,817,725✔
402
    auto current_version = _get_alloc().get_storage_version();
81,817,725✔
403
    if (current_version != m_storage_version) {
81,817,725✔
404
        return update();
18,129✔
405
    }
18,129✔
406
    return false;
81,799,596✔
407
}
81,817,725✔
408

409
UpdateStatus Obj::update_if_needed() const
410
{
84,165,945✔
411
    if (!m_table) {
84,165,945✔
412
        // Table deleted
413
        return UpdateStatus::Detached;
354✔
414
    }
354✔
415

416
    auto current_version = _get_alloc().get_storage_version();
84,165,591✔
417
    if (current_version != m_storage_version) {
84,165,591✔
418
        ClusterNode::State state = get_tree_top()->try_get(m_key);
1,364,766✔
419

420
        if (!state) {
1,364,766✔
421
            // Object deleted
422
            return UpdateStatus::Detached;
5,220✔
423
        }
5,220✔
424

425
        // Always update versions
426
        m_storage_version = current_version;
1,359,546✔
427
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
1,359,546✔
428
            m_mem = state.mem;
198,372✔
429
            m_row_ndx = state.index;
198,372✔
430
            ++m_version_counter;
198,372✔
431
            return UpdateStatus::Updated;
198,372✔
432
        }
198,372✔
433
    }
1,359,546✔
434
    return UpdateStatus::NoChange;
83,961,999✔
435
}
84,165,591✔
436

437
void Obj::checked_update_if_needed() const
438
{
39,387,606✔
439
    if (update_if_needed() == UpdateStatus::Detached) {
39,387,606✔
440
        m_table.check();
18✔
441
        get_tree_top()->get(m_key); // should always throw
18✔
442
    }
18✔
443
}
39,387,606✔
444

445
template <class T>
446
T Obj::get(ColKey col_key) const
447
{
84,696,564✔
448
    m_table->check_column(col_key);
84,696,564✔
449
    ColumnType type = col_key.get_type();
84,696,564✔
450
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
84,696,564✔
451

452
    return _get<T>(col_key.get_index());
84,696,564✔
453
}
84,696,564✔
454

455
template UUID Obj::_get(ColKey::Idx col_ndx) const;
456
template util::Optional<UUID> Obj::_get(ColKey::Idx col_ndx) const;
457

458
#if REALM_ENABLE_GEOSPATIAL
459

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

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

476
    auto geo = get_linked_object(col_key);
12✔
477
    if (!geo) {
12✔
478
        return {};
12✔
479
    }
12✔
480
    return Geospatial::from_link(geo);
×
481
}
12✔
482

483
#endif
484

485
template <class T>
486
T Obj::_get(ColKey::Idx col_ndx) const
487
{
81,143,061✔
488
    _update_if_needed();
81,143,061✔
489

490
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
81,143,061✔
491
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
81,143,061✔
492
    values.init_from_ref(ref);
81,143,061✔
493

494
    return values.get(m_row_ndx);
81,143,061✔
495
}
81,143,061✔
496

497
Mixed Obj::get_unfiltered_mixed(ColKey::Idx col_ndx) const
498
{
105,039✔
499
    ArrayMixed values(get_alloc());
105,039✔
500
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
105,039✔
501
    values.init_from_ref(ref);
105,039✔
502
    values.set_string_interner(m_table->get_string_interner(col_ndx));
105,039✔
503

504
    return values.get(m_row_ndx);
105,039✔
505
}
105,039✔
506

507
template <>
508
Mixed Obj::_get<Mixed>(ColKey::Idx col_ndx) const
509
{
88,005✔
510
    _update_if_needed();
88,005✔
511
    Mixed m = get_unfiltered_mixed(col_ndx);
88,005✔
512
    return m.is_unresolved_link() ? Mixed{} : m;
88,005✔
513
}
88,005✔
514

515
template <>
516
ObjKey Obj::_get<ObjKey>(ColKey::Idx col_ndx) const
517
{
318,366✔
518
    _update_if_needed();
318,366✔
519

520
    ArrayKey values(_get_alloc());
318,366✔
521
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
318,366✔
522
    values.init_from_ref(ref);
318,366✔
523

524
    ObjKey k = values.get(m_row_ndx);
318,366✔
525
    return k.is_unresolved() ? ObjKey{} : k;
318,366✔
526
}
318,366✔
527

528
bool Obj::is_unresolved(ColKey col_key) const
529
{
30✔
530
    m_table->check_column(col_key);
30✔
531
    ColumnType type = col_key.get_type();
30✔
532
    REALM_ASSERT(type == col_type_Link);
30✔
533

534
    _update_if_needed();
30✔
535

536
    return get_unfiltered_link(col_key).is_unresolved();
30✔
537
}
30✔
538

539
ObjKey Obj::get_unfiltered_link(ColKey col_key) const
540
{
316,506✔
541
    ArrayKey values(get_alloc());
316,506✔
542
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
316,506✔
543
    values.init_from_ref(ref);
316,506✔
544

545
    return values.get(m_row_ndx);
316,506✔
546
}
316,506✔
547

548
template <>
549
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
550
{
127,346,172✔
551
    // manual inline of _update_if_needed():
552
    auto& alloc = _get_alloc();
127,346,172✔
553
    auto current_version = alloc.get_storage_version();
127,346,172✔
554
    if (current_version != m_storage_version) {
127,346,172✔
555
        update();
142,320✔
556
    }
142,320✔
557
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
127,346,172✔
558
    char* header = alloc.translate(ref);
127,346,172✔
559
    return Array::get(header, m_row_ndx);
127,346,172✔
560
}
127,346,172✔
561

562
template <>
563
int64_t Obj::get<int64_t>(ColKey col_key) const
564
{
117,616,053✔
565
    m_table->check_column(col_key);
117,616,053✔
566
    ColumnType type = col_key.get_type();
117,616,053✔
567
    REALM_ASSERT(type == col_type_Int);
117,616,053✔
568

569
    if (col_key.get_attrs().test(col_attr_Nullable)) {
117,616,053✔
570
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
3,213,018✔
571
        if (!val) {
3,213,018✔
572
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
6✔
573
        }
6✔
574
        return *val;
3,213,012✔
575
    }
3,213,018✔
576
    else {
114,403,035✔
577
        return _get<int64_t>(col_key.get_index());
114,403,035✔
578
    }
114,403,035✔
579
}
117,616,053✔
580

581
template <>
582
bool Obj::get<bool>(ColKey col_key) const
583
{
494,889✔
584
    m_table->check_column(col_key);
494,889✔
585
    ColumnType type = col_key.get_type();
494,889✔
586
    REALM_ASSERT(type == col_type_Bool);
494,889✔
587

588
    if (col_key.get_attrs().test(col_attr_Nullable)) {
494,889✔
589
        auto val = _get<util::Optional<bool>>(col_key.get_index());
30✔
590
        if (!val) {
30✔
591
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
×
592
        }
×
593
        return *val;
30✔
594
    }
30✔
595
    else {
494,859✔
596
        return _get<bool>(col_key.get_index());
494,859✔
597
    }
494,859✔
598
}
494,889✔
599

600
template <>
601
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
602
{
65,413,080✔
603
    // manual inline of _update_if_needed():
604
    auto& alloc = _get_alloc();
65,413,080✔
605
    auto current_version = alloc.get_storage_version();
65,413,080✔
606
    if (current_version != m_storage_version) {
65,413,080✔
607
        update();
2,427✔
608
    }
2,427✔
609

610
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
65,413,080✔
611
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
65,413,080✔
612
    auto& spec = get_spec();
65,413,080✔
613
    if (spec.is_string_enum_type(spec_ndx)) {
65,413,080✔
614
        ArrayString values(get_alloc());
1,064,301✔
615
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
1,064,301✔
616
        values.init_from_ref(ref);
1,064,301✔
617

618
        return values.get(m_row_ndx);
1,064,301✔
619
    }
1,064,301✔
620
    else {
64,348,779✔
621
        ArrayString values(get_alloc());
64,348,779✔
622
        auto col_key = m_table->leaf_ndx2colkey(col_ndx);
64,348,779✔
623
        values.set_string_interner(m_table->get_string_interner(col_key));
64,348,779✔
624
        values.init_from_ref(ref);
64,348,779✔
625
        return values.get(m_row_ndx);
64,348,779✔
626
    }
64,348,779✔
627
}
65,413,080✔
628

629
template <>
630
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
631
{
6,880,203✔
632
    // manual inline of _update_if_needed():
633
    auto& alloc = _get_alloc();
6,880,203✔
634
    auto current_version = alloc.get_storage_version();
6,880,203✔
635
    if (current_version != m_storage_version) {
6,880,203✔
636
        update();
132✔
637
    }
132✔
638

639
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
6,880,203✔
640
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
6,880,203✔
641
}
6,880,203✔
642

643
Mixed Obj::get_any(ColKey col_key) const
644
{
75,045,588✔
645
    m_table->check_column(col_key);
75,045,588✔
646
    auto col_ndx = col_key.get_index();
75,045,588✔
647
    if (col_key.is_collection()) {
75,045,588✔
648
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
6,924✔
649
        return Mixed(ref, get_table()->get_collection_type(col_key));
6,924✔
650
    }
6,924✔
651
    switch (col_key.get_type()) {
75,038,664✔
652
        case col_type_Int:
10,162,752✔
653
            if (col_key.get_attrs().test(col_attr_Nullable)) {
10,162,752✔
654
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
462,654✔
655
            }
462,654✔
656
            else {
9,700,098✔
657
                return Mixed{_get<int64_t>(col_ndx)};
9,700,098✔
658
            }
9,700,098✔
659
        case col_type_Bool:
201,312✔
660
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
201,312✔
661
        case col_type_Float:
8,007✔
662
            return Mixed{_get<util::Optional<float>>(col_ndx)};
8,007✔
663
        case col_type_Double:
19,617✔
664
            return Mixed{_get<util::Optional<double>>(col_ndx)};
19,617✔
665
        case col_type_String:
64,084,236✔
666
            return Mixed{_get<String>(col_ndx)};
64,084,236✔
667
        case col_type_Binary:
1,605✔
668
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
669
        case col_type_Mixed:
22,557✔
670
            return _get<Mixed>(col_ndx);
22,557✔
671
        case col_type_Timestamp:
173,757✔
672
            return Mixed{_get<Timestamp>(col_ndx)};
173,757✔
673
        case col_type_Decimal:
7,860✔
674
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
675
        case col_type_ObjectId:
349,704✔
676
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
349,704✔
677
        case col_type_UUID:
54,717✔
678
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
54,717✔
679
        case col_type_Link:
42,597✔
680
            return Mixed{_get<ObjKey>(col_ndx)};
42,597✔
681
        default:
✔
682
            REALM_UNREACHABLE();
683
            break;
×
684
    }
75,038,664✔
685
    return {};
×
686
}
75,038,664✔
687

688
Mixed Obj::get_primary_key() const
689
{
157,461✔
690
    auto col = m_table->get_primary_key_column();
157,461✔
691
    return col ? get_any(col) : Mixed{get_key()};
157,461✔
692
}
157,461✔
693

694
/* FIXME: Make this one fast too!
695
template <>
696
ObjKey Obj::_get(size_t col_ndx) const
697
{
698
    return ObjKey(_get<int64_t>(col_ndx));
699
}
700
*/
701

702
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
703
{
27,030✔
704
    Obj obj;
27,030✔
705
    if (!link.is_null()) {
27,030✔
706
        TableRef target_table;
18,648✔
707
        if (link.is_type(type_TypedLink)) {
18,648✔
708
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
324✔
709
        }
324✔
710
        else {
18,324✔
711
            target_table = get_target_table(link_col_key);
18,324✔
712
        }
18,324✔
713
        obj = target_table->get_object(link.get<ObjKey>());
18,648✔
714
    }
18,648✔
715
    return obj;
27,030✔
716
}
27,030✔
717

718
Obj Obj::get_parent_object() const
719
{
12✔
720
    Obj obj;
12✔
721
    checked_update_if_needed();
12✔
722

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

735
    return obj;
12✔
736
}
12✔
737

738
template <class T>
739
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
740
{
1,223,451✔
741
    T values(get_alloc());
1,223,451✔
742
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,223,451✔
743
    values.init_from_ref(ref);
1,223,451✔
744
    return values.is_null(m_row_ndx);
1,223,451✔
745
}
1,223,451✔
746

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

760
size_t Obj::get_link_count(ColKey col_key) const
761
{
108✔
762
    return get_list<ObjKey>(col_key).size();
108✔
763
}
108✔
764

765
bool Obj::is_null(ColKey col_key) const
766
{
5,455,050✔
767
    checked_update_if_needed();
5,455,050✔
768
    ColumnAttrMask attr = col_key.get_attrs();
5,455,050✔
769
    ColKey::Idx col_ndx = col_key.get_index();
5,455,050✔
770
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,455,050✔
771
        switch (col_key.get_type()) {
1,602,744✔
772
            case col_type_Int:
357,183✔
773
                return do_is_null<ArrayIntNull>(col_ndx);
357,183✔
774
            case col_type_Bool:
349,617✔
775
                return do_is_null<ArrayBoolNull>(col_ndx);
349,617✔
776
            case col_type_Float:
9,321✔
777
                return do_is_null<ArrayFloatNull>(col_ndx);
9,321✔
778
            case col_type_Double:
3,267✔
779
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
780
            case col_type_String: {
379,296✔
781
                ArrayString values(get_alloc());
379,296✔
782
                ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
379,296✔
783
                values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
379,296✔
784
                // TODO: Set string interner if needed
785
                values.set_string_interner(m_table->get_string_interner(col_key));
379,296✔
786
                values.init_from_ref(ref);
379,296✔
787
                return values.is_null(m_row_ndx);
379,296✔
NEW
788
            }
×
789
                // return do_is_null<ArrayString>(col_ndx);
790
            case col_type_Binary:
267✔
791
                return do_is_null<ArrayBinary>(col_ndx);
267✔
792
            case col_type_Mixed:
1,995✔
793
                return do_is_null<ArrayMixed>(col_ndx);
1,995✔
794
            case col_type_Timestamp:
477,048✔
795
                return do_is_null<ArrayTimestamp>(col_ndx);
477,048✔
796
            case col_type_Link:
22,278✔
797
                return do_is_null<ArrayKey>(col_ndx);
22,278✔
798
            case col_type_ObjectId:
231✔
799
                return do_is_null<ArrayObjectIdNull>(col_ndx);
231✔
800
            case col_type_Decimal:
2,013✔
801
                return do_is_null<ArrayDecimal128>(col_ndx);
2,013✔
802
            case col_type_UUID:
231✔
803
                return do_is_null<ArrayUUIDNull>(col_ndx);
231✔
804
            default:
✔
805
                REALM_UNREACHABLE();
806
        }
1,602,744✔
807
    }
1,602,744✔
808
    return false;
3,852,306✔
809
}
5,455,050✔
810

811

812
// Figure out if this object has any remaining backlinkss
813
bool Obj::has_backlinks(bool only_strong_links) const
814
{
13,113✔
815
    const Table& target_table = *m_table;
13,113✔
816

817
    // If we only look for strong links and the table is not embedded,
818
    // then there is no relevant backlinks to find.
819
    if (only_strong_links && !target_table.is_embedded()) {
13,113✔
820
        return false;
×
821
    }
×
822

823
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
16,506✔
824
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
16,506✔
825
    });
16,506✔
826
}
13,113✔
827

828
size_t Obj::get_backlink_count() const
829
{
182,994✔
830
    checked_update_if_needed();
182,994✔
831

832
    size_t cnt = 0;
182,994✔
833
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
483,522✔
834
        cnt += get_backlink_cnt(backlink_col_key);
483,522✔
835
        return IteratorControl::AdvanceToNext;
483,522✔
836
    });
483,522✔
837
    return cnt;
182,994✔
838
}
182,994✔
839

840
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
841
{
32,064✔
842
    checked_update_if_needed();
32,064✔
843

844
    size_t cnt = 0;
32,064✔
845
    if (TableKey origin_table_key = origin.get_key()) {
32,064✔
846
        ColKey backlink_col_key;
32,064✔
847
        auto type = origin_col_key.get_type();
32,064✔
848
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
32,064✔
849
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
12✔
850
        }
12✔
851
        else {
32,052✔
852
            backlink_col_key = origin.get_opposite_column(origin_col_key);
32,052✔
853
        }
32,052✔
854

855
        cnt = get_backlink_cnt(backlink_col_key);
32,064✔
856
    }
32,064✔
857
    return cnt;
32,064✔
858
}
32,064✔
859

860
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
861
{
9,023,577✔
862
    ColKey backlink_col_key;
9,023,577✔
863
    auto type = origin_col_key.get_type();
9,023,577✔
864
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
9,023,580✔
865
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
866
    }
36✔
867
    else {
9,023,541✔
868
        backlink_col_key = origin.get_opposite_column(origin_col_key);
9,023,541✔
869
    }
9,023,541✔
870
    return get_backlink(backlink_col_key, backlink_ndx);
9,023,577✔
871
}
9,023,577✔
872

873
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key) const
874
{
696✔
875
    TableView tv(src_table, src_col_key, *this);
696✔
876
    tv.do_sync();
696✔
877
    return tv;
696✔
878
}
696✔
879

880
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
881
{
9,023,583✔
882
    get_table()->check_column(backlink_col);
9,023,583✔
883
    Allocator& alloc = get_alloc();
9,023,583✔
884
    Array fields(alloc);
9,023,583✔
885
    fields.init_from_mem(m_mem);
9,023,583✔
886

887
    ArrayBacklink backlinks(alloc);
9,023,583✔
888
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
9,023,583✔
889
    backlinks.init_from_parent();
9,023,583✔
890
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
9,023,583✔
891
}
9,023,583✔
892

893
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
894
{
305,358✔
895
    checked_update_if_needed();
305,358✔
896

897
    get_table()->check_column(backlink_col);
305,358✔
898
    Allocator& alloc = get_alloc();
305,358✔
899
    Array fields(alloc);
305,358✔
900
    fields.init_from_mem(m_mem);
305,358✔
901

902
    ArrayBacklink backlinks(alloc);
305,358✔
903
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
305,358✔
904
    backlinks.init_from_parent();
305,358✔
905

906
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
305,358✔
907
    std::vector<ObjKey> vec;
305,358✔
908
    vec.reserve(cnt);
305,358✔
909
    for (size_t i = 0; i < cnt; i++) {
572,490✔
910
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
267,132✔
911
    }
267,132✔
912
    return vec;
305,358✔
913
}
305,358✔
914

915
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
916
{
532,110✔
917
    Allocator& alloc = get_alloc();
532,110✔
918
    Array fields(alloc);
532,110✔
919
    fields.init_from_mem(m_mem);
532,110✔
920

921
    ArrayBacklink backlinks(alloc);
532,110✔
922
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
532,110✔
923
    backlinks.init_from_parent();
532,110✔
924

925
    return backlinks.get_backlink_count(m_row_ndx);
532,110✔
926
}
532,110✔
927

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

940
    Allocator& alloc = get_alloc();
120,714✔
941
    Array fields(alloc);
120,714✔
942
    fields.init_from_mem(m_mem);
120,714✔
943

944
    ArrayBacklink backlinks(alloc);
120,714✔
945
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
120,714✔
946
    backlinks.init_from_parent();
120,714✔
947

948
    REALM_ASSERT(backlinks.verify_backlink(m_row_ndx, origin_key.value));
120,714✔
949
#else
950
    static_cast<void>(origin);
951
    static_cast<void>(origin_col_key);
952
    static_cast<void>(origin_key);
953
#endif
954
}
120,714✔
955

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

999
    private:
90✔
1000
        Mixed m_index;
90✔
1001
        Obj m_dest_obj;
90✔
1002
    };
90✔
1003

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

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

1040
FullPath Obj::get_path() const
1041
{
310,440✔
1042
    FullPath result;
310,440✔
1043
    if (m_table->is_embedded()) {
310,440✔
1044
        REALM_ASSERT(get_backlink_count() == 1);
177,096✔
1045
        m_table->for_each_backlink_column([&](ColKey col_key) {
293,226✔
1046
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
293,226✔
1047
            if (backlinks.size() == 1) {
293,226✔
1048
                TableRef origin_table = m_table->get_opposite_table(col_key);
177,096✔
1049
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
177,096✔
1050
                auto next_col_key = m_table->get_opposite_column(col_key);
177,096✔
1051

1052
                ColumnAttrMask attr = next_col_key.get_attrs();
177,096✔
1053
                Mixed index;
177,096✔
1054
                if (attr.test(col_attr_List)) {
177,096✔
1055
                    REALM_ASSERT(next_col_key.get_type() == col_type_Link);
61,998✔
1056
                    Lst<ObjKey> link_list(next_col_key);
61,998✔
1057
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
61,998✔
1058
                    REALM_ASSERT(i != realm::not_found);
61,998✔
1059
                    result = link_list.get_path();
61,998✔
1060
                    result.path_from_top.emplace_back(i);
61,998✔
1061
                }
61,998✔
1062
                else if (attr.test(col_attr_Dictionary)) {
115,098✔
1063
                    Dictionary dict(next_col_key);
49,056✔
1064
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
49,056✔
1065
                    REALM_ASSERT(ndx != realm::not_found);
49,056✔
1066
                    result = dict.get_path();
49,056✔
1067
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
49,056✔
1068
                }
49,056✔
1069
                else {
66,042✔
1070
                    result = obj.get_path();
66,042✔
1071
                    if (result.path_from_top.empty()) {
66,042✔
1072
                        result.path_from_top.push_back(next_col_key);
15,540✔
1073
                    }
15,540✔
1074
                    else {
50,502✔
1075
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
50,502✔
1076
                    }
50,502✔
1077
                }
66,042✔
1078

1079
                return IteratorControl::Stop; // early out
177,096✔
1080
            }
177,096✔
1081
            return IteratorControl::AdvanceToNext; // try next column
116,130✔
1082
        });
293,226✔
1083
    }
177,096✔
1084
    else {
133,344✔
1085
        result.top_objkey = get_key();
133,344✔
1086
        result.top_table = get_table()->get_key();
133,344✔
1087
    }
133,344✔
1088
    return result;
310,440✔
1089
}
310,440✔
1090

1091
std::string Obj::get_id() const
1092
{
37,755✔
1093
    std::ostringstream ostr;
37,755✔
1094
    auto path = get_path();
37,755✔
1095
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
37,755✔
1096
    ostr << top_table->get_class_name() << '[';
37,755✔
1097
    if (top_table->get_primary_key_column()) {
37,755✔
1098
        ostr << top_table->get_primary_key(path.top_objkey);
37,605✔
1099
    }
37,605✔
1100
    else {
150✔
1101
        ostr << path.top_objkey;
150✔
1102
    }
150✔
1103
    ostr << ']';
37,755✔
1104
    if (!path.path_from_top.empty()) {
37,755✔
1105
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
27,777✔
1106
        path.path_from_top[0] = PathElement(prop_name);
27,777✔
1107
        ostr << path.path_from_top;
27,777✔
1108
    }
27,777✔
1109
    return ostr.str();
37,755✔
1110
}
37,755✔
1111

1112
Path Obj::get_short_path() const noexcept
1113
{
371,649✔
1114
    return {};
371,649✔
1115
}
371,649✔
1116

1117
ColKey Obj::get_col_key() const noexcept
1118
{
×
1119
    return {};
×
1120
}
×
1121

1122
StablePath Obj::get_stable_path() const noexcept
1123
{
1,939,266✔
1124
    return {};
1,939,266✔
1125
}
1,939,266✔
1126

1127
void Obj::add_index(Path& path, const CollectionParent::Index& index) const
1128
{
482,721✔
1129
    if (path.empty()) {
482,721✔
1130
        path.emplace_back(get_table()->get_column_key(index));
479,475✔
1131
    }
479,475✔
1132
    else {
3,246✔
1133
        StringData col_name = get_table()->get_column_name(index);
3,246✔
1134
        path.emplace_back(col_name);
3,246✔
1135
    }
3,246✔
1136
}
482,721✔
1137

1138
std::string Obj::to_string() const
1139
{
12✔
1140
    std::ostringstream ostr;
12✔
1141
    to_json(ostr);
12✔
1142
    return ostr.str();
12✔
1143
}
12✔
1144

1145
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
1146
{
×
1147
    obj.to_json(ostr);
×
1148
    return ostr;
×
1149
}
×
1150

1151
/*********************************** Obj *************************************/
1152

1153
bool Obj::ensure_writeable()
1154
{
×
1155
    Allocator& alloc = get_alloc();
×
1156
    if (alloc.is_read_only(m_mem.get_ref())) {
×
1157
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
×
1158
        m_storage_version = alloc.get_storage_version();
×
1159
        return true;
×
1160
    }
×
1161
    return false;
×
1162
}
×
1163

1164
REALM_FORCEINLINE void Obj::sync(Node& arr)
1165
{
40,211,499✔
1166
    auto ref = arr.get_ref();
40,211,499✔
1167
    if (arr.has_missing_parent_update()) {
40,211,499✔
1168
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
270,117✔
1169
    }
270,117✔
1170
    if (m_mem.get_ref() != ref) {
40,211,499✔
1171
        m_mem = arr.get_mem();
528,060✔
1172
        m_storage_version = arr.get_alloc().get_storage_version();
528,060✔
1173
    }
528,060✔
1174
}
40,211,499✔
1175

1176
// helper functions for filtering out calls to set_string_interner()
1177
template <class T>
1178
inline void Obj::set_string_interner(T&, ColKey)
1179
{
7,943,928✔
1180
}
7,943,928✔
1181
template <>
1182
inline void Obj::set_string_interner(ArrayString& values, ColKey col_key)
1183
{
3,115,128✔
1184
    values.set_string_interner(m_table->get_string_interner(col_key));
3,115,128✔
1185
}
3,115,128✔
1186
template <>
1187
inline void Obj::set_string_interner(ArrayMixed& values, ColKey col_key)
1188
{
15,570✔
1189
    values.set_string_interner(m_table->get_string_interner(col_key));
15,570✔
1190
}
15,570✔
1191

1192
// helper functions for filtering out calls to set_spec()
1193
template <class T>
1194
inline void Obj::set_spec(T&, ColKey)
1195
{
7,943,763✔
1196
}
7,943,763✔
1197
template <>
1198
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1199
{
3,115,416✔
1200
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
3,115,416✔
1201
    Spec* spec = const_cast<Spec*>(&get_spec());
3,115,416✔
1202
    values.set_spec(spec, spec_ndx);
3,115,416✔
1203
}
3,115,416✔
1204

1205
template <>
1206
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
1207
{
17,034✔
1208
    checked_update_if_needed();
17,034✔
1209
    get_table()->check_column(col_key);
17,034✔
1210
    auto type = col_key.get_type();
17,034✔
1211
    auto col_ndx = col_key.get_index();
17,034✔
1212
    bool recurse = false;
17,034✔
1213
    CascadeState state;
17,034✔
1214

1215
    if (type != col_type_Mixed)
17,034✔
1216
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
×
1217
    if (value_is_null(value) && !col_key.is_nullable()) {
17,034✔
1218
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
×
1219
    }
×
1220
    if (value.is_type(type_Link)) {
17,034✔
1221
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
×
1222
    }
×
1223

1224
    Mixed old_value = get_unfiltered_mixed(col_ndx);
17,034✔
1225
    if (!value.is_same_type(old_value) || value != old_value) {
17,034✔
1226
        if (old_value.is_type(type_TypedLink)) {
15,438✔
1227
            auto old_link = old_value.get<ObjLink>();
114✔
1228
            recurse = remove_backlink(col_key, old_link, state);
114✔
1229
        }
114✔
1230
        else if (old_value.is_type(type_Dictionary)) {
15,324✔
1231
            Dictionary dict(*this, col_key);
264✔
1232
            recurse = dict.remove_backlinks(state);
264✔
1233
        }
264✔
1234
        else if (old_value.is_type(type_List)) {
15,060✔
1235
            Lst<Mixed> list(*this, col_key);
426✔
1236
            recurse = list.remove_backlinks(state);
426✔
1237
        }
426✔
1238

1239
        if (value.is_type(type_TypedLink)) {
15,438✔
1240
            if (m_table->is_asymmetric()) {
960✔
1241
                throw IllegalOperation("Links not allowed in asymmetric tables");
12✔
1242
            }
12✔
1243
            auto new_link = value.get<ObjLink>();
948✔
1244
            m_table->get_parent_group()->validate(new_link);
948✔
1245
            set_backlink(col_key, new_link);
948✔
1246
        }
948✔
1247

1248
        SearchIndex* index = m_table->get_search_index(col_key);
15,426✔
1249
        // The following check on unresolved is just a precaution as it should not
1250
        // be possible to hit that while Mixed is not a supported primary key type.
1251
        if (index && !m_key.is_unresolved()) {
15,426✔
1252
            index->set(m_key, value.is_unresolved_link() ? Mixed() : value);
2,316✔
1253
        }
2,316✔
1254

1255
        Allocator& alloc = get_alloc();
15,426✔
1256
        alloc.bump_content_version();
15,426✔
1257
        Array fallback(alloc);
15,426✔
1258
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
15,426✔
1259
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
15,426✔
1260
        ArrayMixed values(alloc);
15,426✔
1261
        values.set_parent(&fields, col_ndx.val + 1);
15,426✔
1262
        set_string_interner(values, col_key);
15,426✔
1263
        values.init_from_parent();
15,426✔
1264
        values.set(m_row_ndx, value);
15,426✔
1265
        if (value.is_type(type_Dictionary, type_List)) {
15,426✔
1266
            values.set_key(m_row_ndx, CollectionParent::generate_key(0x10));
3,384✔
1267
        }
3,384✔
1268

1269
        sync(fields);
15,426✔
1270
    }
15,426✔
1271

1272
    if (Replication* repl = get_replication())
17,022✔
1273
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
10,356✔
1274
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
10,356✔
1275

1276
    if (recurse)
17,022✔
1277
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1278

1279
    return *this;
17,022✔
1280
}
17,034✔
1281

1282
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1283
{
813,564✔
1284
    if (value.is_null()) {
813,564✔
1285
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
312✔
1286
        set_null(col_key, is_default);
312✔
1287
    }
312✔
1288
    else {
813,252✔
1289
        switch (col_key.get_type()) {
813,252✔
1290
            case col_type_Int:
729,735✔
1291
                if (col_key.get_attrs().test(col_attr_Nullable)) {
729,735✔
1292
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
3,099✔
1293
                }
3,099✔
1294
                else {
726,636✔
1295
                    set(col_key, value.get_int(), is_default);
726,636✔
1296
                }
726,636✔
1297
                break;
729,735✔
1298
            case col_type_Bool:
105✔
1299
                set(col_key, value.get_bool(), is_default);
105✔
1300
                break;
105✔
1301
            case col_type_Float:
2,646✔
1302
                set(col_key, value.get_float(), is_default);
2,646✔
1303
                break;
2,646✔
1304
            case col_type_Double:
3,816✔
1305
                set(col_key, value.get_double(), is_default);
3,816✔
1306
                break;
3,816✔
1307
            case col_type_String:
33,846✔
1308
                set(col_key, value.get_string(), is_default);
33,846✔
1309
                break;
33,846✔
1310
            case col_type_Binary:
19,842✔
1311
                set(col_key, value.get<Binary>(), is_default);
19,842✔
1312
                break;
19,842✔
1313
            case col_type_Mixed:
3,066✔
1314
                set(col_key, value, is_default);
3,066✔
1315
                break;
3,066✔
1316
            case col_type_Timestamp:
8,919✔
1317
                set(col_key, value.get<Timestamp>(), is_default);
8,919✔
1318
                break;
8,919✔
1319
            case col_type_ObjectId:
4,968✔
1320
                set(col_key, value.get<ObjectId>(), is_default);
4,968✔
1321
                break;
4,968✔
1322
            case col_type_Decimal:
2,502✔
1323
                set(col_key, value.get<Decimal128>(), is_default);
2,502✔
1324
                break;
2,502✔
1325
            case col_type_UUID:
3,708✔
1326
                set(col_key, value.get<UUID>(), is_default);
3,708✔
1327
                break;
3,708✔
1328
            case col_type_Link:
72✔
1329
                set(col_key, value.get<ObjKey>(), is_default);
72✔
1330
                break;
72✔
1331
            case col_type_TypedLink:
✔
1332
                set(col_key, value.get<ObjLink>(), is_default);
×
1333
                break;
×
1334
            default:
✔
1335
                break;
×
1336
        }
813,252✔
1337
    }
813,252✔
1338
    return *this;
813,591✔
1339
}
813,564✔
1340

1341
template <>
1342
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1343
{
21,409,236✔
1344
    checked_update_if_needed();
21,409,236✔
1345
    get_table()->check_column(col_key);
21,409,236✔
1346
    auto col_ndx = col_key.get_index();
21,409,236✔
1347

1348
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
21,409,236✔
1349
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1350
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1351

1352
    SearchIndex* index = m_table->get_search_index(col_key);
21,409,236✔
1353
    if (index && !m_key.is_unresolved()) {
21,409,236✔
1354
        index->set(m_key, value);
183,858✔
1355
    }
183,858✔
1356

1357
    Allocator& alloc = get_alloc();
21,409,236✔
1358
    alloc.bump_content_version();
21,409,236✔
1359
    Array fallback(alloc);
21,409,236✔
1360
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
21,409,236✔
1361
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
21,409,236✔
1362
    auto attr = col_key.get_attrs();
21,409,236✔
1363
    if (attr.test(col_attr_Nullable)) {
21,409,236✔
1364
        ArrayIntNull values(alloc);
3,637,824✔
1365
        values.set_parent(&fields, col_ndx.val + 1);
3,637,824✔
1366
        values.init_from_parent();
3,637,824✔
1367
        values.set(m_row_ndx, value);
3,637,824✔
1368
    }
3,637,824✔
1369
    else {
17,771,412✔
1370
        ArrayInteger values(alloc);
17,771,412✔
1371
        values.set_parent(&fields, col_ndx.val + 1);
17,771,412✔
1372
        values.init_from_parent();
17,771,412✔
1373
        values.set(m_row_ndx, value);
17,771,412✔
1374
    }
17,771,412✔
1375

1376
    sync(fields);
21,409,236✔
1377

1378
    if (Replication* repl = get_replication()) {
21,409,236✔
1379
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
11,472,570✔
1380
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
11,472,570✔
1381
    }
11,472,570✔
1382

1383
    return *this;
21,409,236✔
1384
}
21,409,236✔
1385

1386
Obj& Obj::add_int(ColKey col_key, int64_t value)
1387
{
20,478✔
1388
    checked_update_if_needed();
20,478✔
1389
    get_table()->check_column(col_key);
20,478✔
1390
    auto col_ndx = col_key.get_index();
20,478✔
1391

1392
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
20,478✔
1393
        uint64_t ua = uint64_t(a);
20,466✔
1394
        uint64_t ub = uint64_t(b);
20,466✔
1395
        return int64_t(ua + ub);
20,466✔
1396
    };
20,466✔
1397

1398
    Allocator& alloc = get_alloc();
20,478✔
1399
    alloc.bump_content_version();
20,478✔
1400
    Array fallback(alloc);
20,478✔
1401
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
20,478✔
1402
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
20,478✔
1403

1404
    if (col_key.get_type() == col_type_Mixed) {
20,478✔
1405
        ArrayMixed values(alloc);
150✔
1406
        values.set_parent(&fields, col_ndx.val + 1);
150✔
1407
        set_string_interner(values, col_key);
150✔
1408
        values.init_from_parent();
150✔
1409
        Mixed old = values.get(m_row_ndx);
150✔
1410
        if (old.is_type(type_Int)) {
150✔
1411
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
144✔
1412
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
144✔
1413
                index->set(m_key, new_val);
×
1414
            }
×
1415
            values.set(m_row_ndx, Mixed(new_val));
144✔
1416
        }
144✔
1417
        else {
6✔
1418
            throw IllegalOperation("Value not an int");
6✔
1419
        }
6✔
1420
    }
150✔
1421
    else {
20,328✔
1422
        if (col_key.get_type() != col_type_Int)
20,328✔
1423
            throw IllegalOperation("Property not an int");
×
1424

1425
        auto attr = col_key.get_attrs();
20,328✔
1426
        if (attr.test(col_attr_Nullable)) {
20,328✔
1427
            ArrayIntNull values(alloc);
111✔
1428
            values.set_parent(&fields, col_ndx.val + 1);
111✔
1429
            values.init_from_parent();
111✔
1430
            util::Optional<int64_t> old = values.get(m_row_ndx);
111✔
1431
            if (old) {
111✔
1432
                auto new_val = add_wrap(*old, value);
105✔
1433
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
105✔
1434
                    index->set(m_key, new_val);
×
1435
                }
×
1436
                values.set(m_row_ndx, new_val);
105✔
1437
            }
105✔
1438
            else {
6✔
1439
                throw IllegalOperation("No prior value");
6✔
1440
            }
6✔
1441
        }
111✔
1442
        else {
20,217✔
1443
            ArrayInteger values(alloc);
20,217✔
1444
            values.set_parent(&fields, col_ndx.val + 1);
20,217✔
1445
            values.init_from_parent();
20,217✔
1446
            int64_t old = values.get(m_row_ndx);
20,217✔
1447
            auto new_val = add_wrap(old, value);
20,217✔
1448
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
20,217✔
1449
                index->set(m_key, new_val);
6✔
1450
            }
6✔
1451
            values.set(m_row_ndx, new_val);
20,217✔
1452
        }
20,217✔
1453
    }
20,328✔
1454

1455
    sync(fields);
20,466✔
1456

1457
    if (Replication* repl = get_replication()) {
20,466✔
1458
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
8,472✔
1459
    }
8,472✔
1460

1461
    return *this;
20,466✔
1462
}
20,478✔
1463

1464
template <>
1465
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
1466
{
255,780✔
1467
    checked_update_if_needed();
255,780✔
1468
    get_table()->check_column(col_key);
255,780✔
1469
    ColKey::Idx col_ndx = col_key.get_index();
255,780✔
1470
    ColumnType type = col_key.get_type();
255,780✔
1471
    if (type != col_type_Link)
255,780✔
1472
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
×
1473
    TableRef target_table = get_target_table(col_key);
255,780✔
1474
    TableKey target_table_key = target_table->get_key();
255,780✔
1475
    if (target_key) {
255,780✔
1476
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
255,276✔
1477
        if (!ct->is_valid(target_key)) {
255,276✔
1478
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
12✔
1479
        }
12✔
1480
        if (target_table->is_embedded()) {
255,276✔
1481
            throw IllegalOperation(
×
1482
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
×
1483
        }
×
1484
    }
255,276✔
1485
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
255,780✔
1486
    CascadeState state(CascadeState::Mode::Strong);
255,780✔
1487
    bool recurse = false;
255,780✔
1488

1489
    if (target_key != old_key) {
255,780✔
1490
        recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
255,024✔
1491
        _update_if_needed();
255,024✔
1492

1493
        Allocator& alloc = get_alloc();
255,024✔
1494
        alloc.bump_content_version();
255,024✔
1495
        Array fallback(alloc);
255,024✔
1496
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
255,024✔
1497
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
255,024✔
1498
        ArrayKey values(alloc);
255,024✔
1499
        values.set_parent(&fields, col_ndx.val + 1);
255,024✔
1500
        values.init_from_parent();
255,024✔
1501

1502
        values.set(m_row_ndx, target_key);
255,024✔
1503

1504
        sync(fields);
255,024✔
1505
    }
255,024✔
1506

1507
    if (Replication* repl = get_replication()) {
255,780✔
1508
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
32,616✔
1509
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
32,616✔
1510
    }
32,616✔
1511

1512
    if (recurse)
255,780✔
1513
        target_table->remove_recursive(state);
258✔
1514

1515
    return *this;
255,780✔
1516
}
255,780✔
1517

1518
template <>
1519
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
1520
{
×
1521
    checked_update_if_needed();
×
1522
    get_table()->check_column(col_key);
×
1523
    ColKey::Idx col_ndx = col_key.get_index();
×
1524
    ColumnType type = col_key.get_type();
×
1525
    if (type != col_type_TypedLink)
×
1526
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
×
1527
    m_table->get_parent_group()->validate(target_link);
×
1528

1529
    ObjLink old_link = get<ObjLink>(col_key); // Will update if needed
×
1530
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
1531
    bool recurse = false;
×
1532

1533
    if (target_link != old_link) {
×
1534
        recurse = replace_backlink(col_key, old_link, target_link, state);
×
1535
        _update_if_needed();
×
1536

1537
        Allocator& alloc = get_alloc();
×
1538
        alloc.bump_content_version();
×
1539
        Array fallback(alloc);
×
1540
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
×
1541
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
1542
        ArrayTypedLink values(alloc);
×
1543
        values.set_parent(&fields, col_ndx.val + 1);
×
1544
        values.init_from_parent();
×
1545

1546
        values.set(m_row_ndx, target_link);
×
1547

1548
        sync(fields);
×
1549
    }
×
1550

1551
    if (Replication* repl = get_replication()) {
×
1552
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
×
1553
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
×
1554
    }
×
1555

1556
    if (recurse)
×
1557
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1558

1559
    return *this;
×
1560
}
×
1561

1562
Obj Obj::create_and_set_linked_object(ColKey col_key, bool is_default)
1563
{
13,971✔
1564
    checked_update_if_needed();
13,971✔
1565
    get_table()->check_column(col_key);
13,971✔
1566
    ColKey::Idx col_ndx = col_key.get_index();
13,971✔
1567
    ColumnType type = col_key.get_type();
13,971✔
1568
    if (type != col_type_Link)
13,971✔
1569
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link type");
×
1570
    TableRef target_table = get_target_table(col_key);
13,971✔
1571
    Table& t = *target_table;
13,971✔
1572
    // Only links to embedded objects are allowed.
1573
    REALM_ASSERT(t.is_embedded() || !get_table()->is_asymmetric());
13,971!
1574
    // Incoming links to asymmetric objects are disallowed.
1575
    REALM_ASSERT(!t.is_asymmetric());
13,971✔
1576
    TableKey target_table_key = t.get_key();
13,971✔
1577
    auto result = t.is_embedded() ? t.create_linked_object() : t.create_object();
13,971✔
1578
    auto target_key = result.get_key();
13,971✔
1579
    ObjKey old_key = get<ObjKey>(col_key); // Will update if needed
13,971✔
1580
    if (old_key != ObjKey()) {
13,971✔
1581
        if (t.is_embedded()) {
48✔
1582
            // If this is an embedded object and there was already an embedded object here, then we need to
1583
            // emit an instruction to set the old embedded object to null to clear the old object on other
1584
            // sync clients. Without this, you'll only see the Set ObjectValue instruction, which is idempotent,
1585
            // and then array operations will have a corrupted prior_size.
1586
            if (Replication* repl = get_replication()) {
48✔
1587
                repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
24✔
1588
                          is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
24✔
1589
            }
24✔
1590
        }
48✔
1591
    }
48✔
1592

1593
    REALM_ASSERT(target_key != old_key); // We will always create a new object
13,971✔
1594
    CascadeState state;
13,971✔
1595

1596
    bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
13,971✔
1597
    _update_if_needed();
13,971✔
1598

1599
    Allocator& alloc = get_alloc();
13,971✔
1600
    alloc.bump_content_version();
13,971✔
1601
    Array fallback(alloc);
13,971✔
1602
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
13,971✔
1603
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
13,971✔
1604
    ArrayKey values(alloc);
13,971✔
1605
    values.set_parent(&fields, col_ndx.val + 1);
13,971✔
1606
    values.init_from_parent();
13,971✔
1607

1608
    values.set(m_row_ndx, target_key);
13,971✔
1609

1610
    sync(fields);
13,971✔
1611

1612
    if (Replication* repl = get_replication()) {
13,971✔
1613
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
13,323✔
1614
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
13,323✔
1615
    }
13,323✔
1616

1617
    if (recurse)
13,971✔
1618
        target_table->remove_recursive(state);
48✔
1619

1620
    return result;
13,971✔
1621
}
13,971✔
1622

1623
namespace {
1624
template <class T>
1625
inline void check_range(const T&)
1626
{
2,778,300✔
1627
}
2,778,300✔
1628
template <>
1629
inline void check_range(const StringData& val)
1630
{
3,115,146✔
1631
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
3,115,146✔
1632
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
6✔
1633
}
3,115,146✔
1634
template <>
1635
inline void check_range(const BinaryData& val)
1636
{
5,165,016✔
1637
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
5,165,016✔
1638
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
6✔
1639
}
5,165,016✔
1640
} // namespace
1641

1642
#if REALM_ENABLE_GEOSPATIAL
1643

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

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

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

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

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

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

1693
#endif
1694

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

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

1710
    check_range(value);
11,059,164✔
1711

1712
    SearchIndex* index = m_table->get_search_index(col_key);
11,059,164✔
1713
    if (index && !m_key.is_unresolved()) {
11,059,164✔
1714
        index->set(m_key, value);
477,156✔
1715
    }
477,156✔
1716

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

1730
    sync(fields);
11,059,164✔
1731

1732
    if (Replication* repl = get_replication())
11,059,164✔
1733
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
7,184,592✔
1734
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
7,184,592✔
1735

1736
    return *this;
11,059,164✔
1737
}
11,059,170✔
1738

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

1750
void Obj::set_int(ColKey::Idx col_ndx, int64_t value)
1751
{
529,224✔
1752
    checked_update_if_needed();
529,224✔
1753

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

1764
    sync(fields);
529,224✔
1765
}
529,224✔
1766

1767
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
1768
{
5,580✔
1769
    checked_update_if_needed();
5,580✔
1770

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

1781
    sync(fields);
5,580✔
1782
}
5,580✔
1783

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

1792
    ArrayBacklink backlinks(alloc);
6,750,681✔
1793
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
6,750,681✔
1794
    backlinks.init_from_parent();
6,750,681✔
1795

1796
    backlinks.add(m_row_ndx, origin_key);
6,750,681✔
1797

1798
    sync(fields);
6,750,681✔
1799
}
6,750,681✔
1800

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

1809
    ArrayBacklink backlinks(alloc);
197,748✔
1810
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
197,748✔
1811
    backlinks.init_from_parent();
197,748✔
1812

1813
    bool ret = backlinks.remove(m_row_ndx, origin_key);
197,748✔
1814

1815
    sync(fields);
197,748✔
1816

1817
    return ret;
197,748✔
1818
}
197,748✔
1819

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

1836
    if (Replication* repl = get_replication())
903✔
1837
        repl->nullify_link(m_table.unchecked_ptr(), col,
771✔
1838
                           m_key); // Throws
771✔
1839
}
903✔
1840

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

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

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

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

1914
    private:
6,999✔
1915
        ObjLink m_target_link;
6,999✔
1916
    } nullifier{*this, origin_col_key, target_link};
6,999✔
1917

1918
    nullifier.run();
6,999✔
1919

1920
    get_alloc().bump_content_version();
6,999✔
1921
}
6,999✔
1922

1923

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

1975
private:
1976
    Obj m_dest_orig;
1977
    Obj m_dest_replace;
1978
};
1979

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

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

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

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

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

2035
    return *this;
4,470✔
2036
}
4,470✔
2037

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

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

2048
Dictionary Obj::get_dictionary(StringData col_name) const
2049
{
16,626✔
2050
    return get_dictionary(get_column_key(col_name));
16,626✔
2051
}
16,626✔
2052

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

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

2083
    return collection;
12,000✔
2084
}
12,000✔
2085

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

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

2129
    return collection;
10,539✔
2130
}
10,551✔
2131

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

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

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

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

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

2176
    link_set.erase(target);
288✔
2177
    link_set.insert(replacement);
288✔
2178
}
288✔
2179

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

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

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

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

2249
    private:
11,745✔
2250
        const Obj& m_dest_orig;
11,745✔
2251
        const Obj& m_dest_replace;
11,745✔
2252
    };
11,745✔
2253

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

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

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

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

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

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

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

2317
    T values(alloc);
38,976✔
2318
    values.set_parent(&fields, col_ndx.val + 1);
38,976✔
2319
    values.init_from_parent();
38,976✔
2320
    values.set_null(m_row_ndx);
38,976✔
2321

2322
    sync(fields);
38,976✔
2323
}
38,976✔
2324

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

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

2342
    sync(fields);
2,475✔
2343
}
2,475✔
2344

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

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

2361
    checked_update_if_needed();
41,451✔
2362

2363
    SearchIndex* index = m_table->get_search_index(col_key);
41,451✔
2364
    if (index && !m_key.is_unresolved()) {
41,451✔
2365
        index->set(m_key, null{});
4,422✔
2366
    }
4,422✔
2367

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

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

2410
    return *this;
41,451✔
2411
}
41,451✔
2412

2413

2414
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2415
{
10,383,126✔
2416
    return get_table()->spec_ndx2colkey(col_ndx);
10,383,126✔
2417
}
10,383,126✔
2418

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

2424
ColKey Obj::get_primary_key_column() const
2425
{
6,468,378✔
2426
    return m_table->get_primary_key_column();
6,468,378✔
2427
}
6,468,378✔
2428

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

2434
ref_type Obj::get_collection_ref(StableIndex index, CollectionType type) const
2435
{
3,237,276✔
2436
    if (index.is_collection()) {
3,237,276✔
2437
        return to_ref(_get<int64_t>(index.get_index()));
3,218,190✔
2438
    }
3,218,190✔
2439
    if (check_index(index)) {
19,086✔
2440
        auto val = _get<Mixed>(index.get_index());
19,014✔
2441
        if (val.is_type(DataType(int(type)))) {
19,014✔
2442
            return val.get_ref();
19,008✔
2443
        }
19,008✔
2444
        throw realm::IllegalOperation(util::format("Not a %1", type));
6✔
2445
    }
19,014✔
2446
    throw StaleAccessor("This collection is no more");
72✔
2447
}
19,086✔
2448

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

2460
void Obj::set_collection_ref(StableIndex index, ref_type ref, CollectionType type)
2461
{
514,863✔
2462
    if (index.is_collection()) {
514,863✔
2463
        set_int(index.get_index(), from_ref(ref));
509,283✔
2464
        return;
509,283✔
2465
    }
509,283✔
2466
    set_ref(index.get_index(), ref, type);
5,580✔
2467
}
5,580✔
2468

2469
void Obj::set_backlink(ColKey col_key, ObjLink new_link) const
2470
{
6,792,792✔
2471
    if (!new_link) {
6,792,792✔
2472
        return;
42,126✔
2473
    }
42,126✔
2474

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

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

2504
bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
2505
{
353,037✔
2506
    if (!old_link) {
353,037✔
2507
        return false;
227,667✔
2508
    }
227,667✔
2509

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

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

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

2540
    return false;
264✔
2541
}
125,370✔
2542

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