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

realm / realm-core / 2131

13 Mar 2024 04:51AM UTC coverage: 91.785% (-0.05%) from 91.833%
2131

push

Evergreen

web-flow
Merge pull request #7402 from realm/tg/obj-perf

Make Obj trivial and add a separate ObjCollectionParent type

94394 of 174600 branches covered (54.06%)

496 of 559 new or added lines in 21 files covered. (88.73%)

224 existing lines in 28 files now uncovered.

242743 of 264469 relevant lines covered (91.79%)

5639637.18 hits per line

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

91.21
/src/realm/obj.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include "realm/obj.hpp"
20
#include "realm/array_basic.hpp"
21
#include "realm/array_integer.hpp"
22
#include "realm/array_bool.hpp"
23
#include "realm/array_string.hpp"
24
#include "realm/array_binary.hpp"
25
#include "realm/array_mixed.hpp"
26
#include "realm/array_timestamp.hpp"
27
#include "realm/array_decimal128.hpp"
28
#include "realm/array_key.hpp"
29
#include "realm/array_fixed_bytes.hpp"
30
#include "realm/array_backlink.hpp"
31
#include "realm/array_typed_link.hpp"
32
#include "realm/cluster_tree.hpp"
33
#include "realm/list.hpp"
34
#include "realm/set.hpp"
35
#include "realm/dictionary.hpp"
36
#if REALM_ENABLE_GEOSPATIAL
37
#include "realm/geospatial.hpp"
38
#endif
39
#include "realm/link_translator.hpp"
40
#include "realm/index_string.hpp"
41
#include "realm/object_converter.hpp"
42
#include "realm/replication.hpp"
43
#include "realm/spec.hpp"
44
#include "realm/table_view.hpp"
45
#include "realm/util/base64.hpp"
46
#include "realm/util/overload.hpp"
47

48
#include <ostream>
49

50
namespace realm {
51
namespace {
52

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

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

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

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

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

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

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

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

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

103
} // namespace
104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

779,307✔
336
    return m_valid;
1,456,125✔
337
}
1,456,125✔
338

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

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

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

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

359
TableRef Obj::get_target_table(ColKey col_key) const
360
{
7,091,328✔
361
    if (m_table) {
7,091,328✔
362
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,090,020✔
363
    }
7,090,020✔
364
    else {
1,308✔
365
        return TableRef();
1,308✔
366
    }
1,308✔
367
}
7,091,328✔
368

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

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

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

396
inline bool Obj::_update_if_needed() const
397
{
78,733,956✔
398
    auto current_version = _get_alloc().get_storage_version();
78,733,956✔
399
    if (current_version != m_storage_version) {
78,733,956✔
400
        return update();
16,410✔
401
    }
16,410✔
402
    return false;
78,717,546✔
403
}
78,717,546✔
404

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

39,273,618✔
412
    auto current_version = _get_alloc().get_storage_version();
80,421,375✔
413
    if (current_version != m_storage_version) {
80,421,375✔
414
        ClusterNode::State state = get_tree_top()->try_get(m_key);
1,343,307✔
415

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

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

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

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

42,323,892✔
448
    return _get<T>(col_key.get_index());
84,751,674✔
449
}
84,751,674✔
450

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

454
#if REALM_ENABLE_GEOSPATIAL
455

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

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

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

479
#endif
480

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

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

39,020,025✔
490
    return values.get(m_row_ndx);
78,067,383✔
491
}
78,067,383✔
492

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

39,975✔
499
    return values.get(m_row_ndx);
79,989✔
500
}
79,989✔
501

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

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

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

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

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

15✔
529
    _update_if_needed();
30✔
530

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

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

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

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

60,160,548✔
553
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
118,407,654✔
554
    char* header = alloc.translate(ref);
118,407,654✔
555
    int width = Array::get_width_from_header(header);
118,407,654✔
556
    char* data = Array::get_data_from_header(header);
118,407,654✔
557
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
118,407,654✔
558
}
×
559

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

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

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

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

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

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

534,486✔
616
        return values.get(m_row_ndx);
1,068,432✔
617
    }
1,068,432✔
618
    else {
4,381,965✔
619
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
4,381,965✔
620
    }
4,381,965✔
621
}
5,450,397✔
622

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

3,440,049✔
633
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
6,880,116✔
634
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
6,880,116✔
635
}
6,880,116✔
636

637
Mixed Obj::get_any(ColKey col_key) const
638
{
15,141,015✔
639
    m_table->check_column(col_key);
15,141,015✔
640
    auto col_ndx = col_key.get_index();
15,141,015✔
641
    if (col_key.is_collection()) {
15,141,015✔
642
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
6,924✔
643
        return Mixed(ref, get_table()->get_collection_type(col_key));
6,924✔
644
    }
6,924✔
645
    switch (col_key.get_type()) {
15,134,091✔
646
        case col_type_Int:
10,201,815✔
647
            if (col_key.get_attrs().test(col_attr_Nullable)) {
10,201,815✔
648
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
516,375✔
649
            }
516,375✔
650
            else {
9,685,440✔
651
                return Mixed{_get<int64_t>(col_ndx)};
9,685,440✔
652
            }
9,685,440✔
653
        case col_type_Bool:
202,014✔
654
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
202,014✔
655
        case col_type_Float:
8,007✔
656
            return Mixed{_get<util::Optional<float>>(col_ndx)};
8,007✔
657
        case col_type_Double:
19,617✔
658
            return Mixed{_get<util::Optional<double>>(col_ndx)};
19,617✔
659
        case col_type_String:
4,096,911✔
660
            return Mixed{_get<String>(col_ndx)};
4,096,911✔
661
        case col_type_Binary:
1,605✔
662
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
663
        case col_type_Mixed:
14,271✔
664
            return _get<Mixed>(col_ndx);
14,271✔
665
        case col_type_Timestamp:
166,668✔
666
            return Mixed{_get<Timestamp>(col_ndx)};
166,668✔
667
        case col_type_Decimal:
7,860✔
668
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
669
        case col_type_ObjectId:
361,632✔
670
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
361,632✔
671
        case col_type_UUID:
52,317✔
672
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
52,317✔
673
        case col_type_Link:
42,258✔
674
            return Mixed{_get<ObjKey>(col_ndx)};
42,258✔
675
        default:
✔
676
            REALM_UNREACHABLE();
677
            break;
×
678
    }
×
679
    return {};
×
680
}
×
681

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

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

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

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

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

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

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

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

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

756
bool Obj::is_null(ColKey col_key) const
757
{
5,608,227✔
758
    checked_update_if_needed();
5,608,227✔
759
    ColumnAttrMask attr = col_key.get_attrs();
5,608,227✔
760
    ColKey::Idx col_ndx = col_key.get_index();
5,608,227✔
761
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,608,227✔
762
        switch (col_key.get_type()) {
1,636,428✔
763
            case col_type_Int:
465,186✔
764
                return do_is_null<ArrayIntNull>(col_ndx);
465,186✔
765
            case col_type_Bool:
396,498✔
766
                return do_is_null<ArrayBoolNull>(col_ndx);
396,498✔
767
            case col_type_Float:
9,465✔
768
                return do_is_null<ArrayFloatNull>(col_ndx);
9,465✔
769
            case col_type_Double:
3,267✔
770
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
771
            case col_type_String:
331,482✔
772
                return do_is_null<ArrayString>(col_ndx);
331,482✔
773
            case col_type_Binary:
267✔
774
                return do_is_null<ArrayBinary>(col_ndx);
267✔
775
            case col_type_Mixed:
1,377✔
776
                return do_is_null<ArrayMixed>(col_ndx);
1,377✔
777
            case col_type_Timestamp:
404,427✔
778
                return do_is_null<ArrayTimestamp>(col_ndx);
404,427✔
779
            case col_type_Link:
21,984✔
780
                return do_is_null<ArrayKey>(col_ndx);
21,984✔
781
            case col_type_ObjectId:
231✔
782
                return do_is_null<ArrayObjectIdNull>(col_ndx);
231✔
783
            case col_type_Decimal:
2,013✔
784
                return do_is_null<ArrayDecimal128>(col_ndx);
2,013✔
785
            case col_type_UUID:
231✔
786
                return do_is_null<ArrayUUIDNull>(col_ndx);
231✔
787
            default:
✔
788
                REALM_UNREACHABLE();
789
        }
1,636,428✔
790
    }
1,636,428✔
791
    return false;
4,836,171✔
792
}
5,608,227✔
793

794

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

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

6,573✔
806
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
16,203✔
807
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
15,711✔
808
    });
16,203✔
809
}
13,017✔
810

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1147
REALM_FORCEINLINE void Obj::sync(Node& arr)
1148
{
37,038,141✔
1149
    auto ref = arr.get_ref();
37,038,141✔
1150
    if (arr.has_missing_parent_update()) {
37,038,141✔
1151
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
269,088✔
1152
    }
269,088✔
1153
    if (m_mem.get_ref() != ref) {
37,038,141✔
1154
        m_mem = arr.get_mem();
529,440✔
1155
        m_storage_version = arr.get_alloc().get_storage_version();
529,440✔
1156
    }
529,440✔
1157
}
37,038,141✔
1158

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

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

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

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

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

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

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

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

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

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

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

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

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

9,791,802✔
1303
    SearchIndex* index = m_table->get_search_index(col_key);
19,641,669✔
1304
    if (index && !m_key.is_unresolved()) {
19,641,669✔
1305
        index->set(m_key, value);
183,366✔
1306
    }
183,366✔
1307

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

9,791,802✔
1327
    sync(fields);
19,641,669✔
1328

9,791,802✔
1329
    if (Replication* repl = get_replication()) {
19,641,669✔
1330
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
10,239,555✔
1331
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
10,239,375✔
1332
    }
10,239,555✔
1333

9,791,802✔
1334
    return *this;
19,641,669✔
1335
}
19,641,669✔
1336

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

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

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

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

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

10,176✔
1405
    sync(fields);
20,394✔
1406

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

10,170✔
1411
    return *this;
20,388✔
1412
}
20,400✔
1413

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

130,866✔
1437
    if (target_key != old_key) {
261,765✔
1438
        CascadeState state(CascadeState::Mode::Strong);
260,916✔
1439

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

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

130,479✔
1452
        values.set(m_row_ndx, target_key);
260,916✔
1453

130,479✔
1454
        sync(fields);
260,916✔
1455

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

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

130,866✔
1465
    return *this;
261,765✔
1466
}
261,765✔
1467

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

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

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

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

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

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

1499
        sync(fields);
×
1500

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

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

1510
    return *this;
×
1511
}
×
1512

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

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

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

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

6,807✔
1559
        values.set(m_row_ndx, target_key);
13,668✔
1560

6,807✔
1561
        sync(fields);
13,668✔
1562

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

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

6,807✔
1572
    return result;
13,668✔
1573
}
13,668✔
1574

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

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

1607
#if REALM_ENABLE_GEOSPATIAL
1608

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

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

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

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

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

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

1658
#endif
1659

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

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

4,846,677✔
1675
    check_range(value);
9,718,692✔
1676

4,846,677✔
1677
    SearchIndex* index = m_table->get_search_index(col_key);
9,718,692✔
1678
    if (index && !m_key.is_unresolved()) {
9,718,692!
1679
        index->set(m_key, value);
472,173✔
1680
    }
472,173✔
1681

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

4,846,677✔
1694
    sync(fields);
9,718,692✔
1695

4,846,677✔
1696
    if (Replication* repl = get_replication())
9,718,692✔
1697
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
5,956,551✔
1698
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
5,956,512✔
1699

4,846,677✔
1700
    return *this;
9,718,692✔
1701
}
9,718,692✔
1702

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

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

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

266,745✔
1728
    sync(fields);
534,003✔
1729
}
534,003✔
1730

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

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

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

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

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

3,377,310✔
1760
    backlinks.add(m_row_ndx, origin_key);
6,752,868✔
1761

3,377,310✔
1762
    sync(fields);
6,752,868✔
1763
}
6,752,868✔
1764

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

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

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

98,469✔
1779
    sync(fields);
196,926✔
1780

98,469✔
1781
    return ret;
196,926✔
1782
}
196,926✔
1783

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

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

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

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

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

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

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

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

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

1887

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2093
CollectionPtr Obj::get_collection_by_stable_path(const StablePath& path) const
2094
{
10,536✔
2095
    // First element in path is phony column key
5,268✔
2096
    ColKey col_key = m_table->get_column_key(path[0]);
10,536✔
2097
    size_t level = 1;
10,536✔
2098
    CollectionBasePtr collection = get_collection_ptr(col_key);
10,536✔
2099

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

5,268✔
2136
    return collection;
10,530✔
2137
}
10,536✔
2138

2139
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2140
{
129,741✔
2141
    if (col_key.is_collection()) {
129,741✔
2142
        auto collection = CollectionParent::get_collection_ptr(col_key, 0);
127,215✔
2143
        collection->set_owner(*this, col_key);
127,215✔
2144
        return collection;
127,215✔
2145
    }
127,215✔
2146
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
2,526✔
2147
    auto val = get<Mixed>(col_key);
2,526✔
2148
    if (val.is_type(type_List)) {
2,526✔
2149
        return std::make_shared<Lst<Mixed>>(*this, col_key);
1,428✔
2150
    }
1,428✔
2151
    REALM_ASSERT(val.is_type(type_Dictionary));
1,098✔
2152
    return std::make_shared<Dictionary>(*this, col_key);
1,098✔
2153
}
1,098✔
2154

2155
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
2156
{
396✔
2157
    return get_collection_ptr(get_column_key(col_name));
396✔
2158
}
396✔
2159

2160
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
2161
{
3,114✔
2162
    if (col_key.is_list()) {
3,114✔
2163
        return get_linklist_ptr(col_key);
2,994✔
2164
    }
2,994✔
2165
    else if (col_key.is_set()) {
120✔
2166
        return get_linkset_ptr(col_key);
78✔
2167
    }
78✔
2168
    else if (col_key.is_dictionary()) {
42✔
2169
        auto dict = get_dictionary(col_key);
42✔
2170
        return std::make_unique<DictionaryLinkValues>(dict);
42✔
2171
    }
42✔
2172
    return {};
×
2173
}
×
2174

2175
template <class T>
2176
inline void replace_in_linkset(Obj& obj, ColKey origin_col_key, T target, T replacement)
2177
{
288✔
2178
    Set<T> link_set(origin_col_key);
288✔
2179
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
288✔
2180

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

144✔
2183
    link_set.erase(target);
288✔
2184
    link_set.insert(replacement);
288✔
2185
}
288✔
2186

2187
inline void replace_in_dictionary(Obj& obj, ColKey origin_col_key, Mixed target, Mixed replacement)
2188
{
×
2189
    Dictionary dict(origin_col_key);
×
2190
    size_t ndx = find_link_value_in_collection(dict, obj, origin_col_key, target);
×
2191

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

×
2194
    auto key = dict.get_key(ndx);
×
2195
    dict.insert(key, replacement);
×
2196
}
×
2197

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

5,817✔
2256
    private:
11,676✔
2257
        const Obj& m_dest_orig;
11,676✔
2258
        const Obj& m_dest_replace;
11,676✔
2259
    };
11,676✔
2260

5,817✔
2261
    REALM_ASSERT(get_table() == other.get_table());
11,676✔
2262
    if (auto col_pk = m_table->get_primary_key_column()) {
11,676✔
2263
        Mixed val = other.get_any(col_pk);
11,514✔
2264
        this->set_any(col_pk, val);
11,514✔
2265
    }
11,514✔
2266
    auto nb_tombstones = m_table->m_tombstones->size();
11,676✔
2267

5,817✔
2268
    auto copy_links = [this, &other, nb_tombstones](ColKey col) {
11,031✔
2269
        if (nb_tombstones != m_table->m_tombstones->size()) {
10,428✔
2270
            // Object has been deleted - we are done
2271
            return IteratorControl::Stop;
×
2272
        }
×
2273

5,214✔
2274
        auto t = m_table->get_opposite_table(col);
10,428✔
2275
        auto c = m_table->get_opposite_column(col);
10,428✔
2276
        auto backlinks = other.get_all_backlinks(col);
10,428✔
2277

5,214✔
2278
        if (c.get_type() == col_type_Link && !(c.is_dictionary() || c.is_set())) {
10,428✔
2279
            auto idx = col.get_index();
9,762✔
2280
            // Transfer the backlinks from tombstone to live object
4,881✔
2281
            REALM_ASSERT(_get<int64_t>(idx) == 0);
9,762✔
2282
            auto other_val = other._get<int64_t>(idx);
9,762✔
2283
            set_int(idx, other_val);
9,762✔
2284
            other.set_int(idx, 0);
9,762✔
2285
        }
9,762✔
2286

5,214✔
2287
        for (auto bl : backlinks) {
34,314✔
2288
            auto linking_obj = t->get_object(bl);
34,314✔
2289
            LinkReplacer replacer{linking_obj, c, other, *this};
34,314✔
2290
            replacer.run();
34,314✔
2291
        }
34,314✔
2292
        return IteratorControl::AdvanceToNext;
10,428✔
2293
    };
10,428✔
2294
    m_table->for_each_backlink_column(copy_links);
11,676✔
2295
}
11,676✔
2296

2297
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
2298
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
2299
template float Obj::get<float>(ColKey col_key) const;
2300
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
2301
template double Obj::get<double>(ColKey col_key) const;
2302
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2303
template StringData Obj::get<StringData>(ColKey col_key) const;
2304
template BinaryData Obj::get<BinaryData>(ColKey col_key) const;
2305
template Timestamp Obj::get<Timestamp>(ColKey col_key) const;
2306
template ObjectId Obj::get<ObjectId>(ColKey col_key) const;
2307
template util::Optional<ObjectId> Obj::get<util::Optional<ObjectId>>(ColKey col_key) const;
2308
template ObjKey Obj::get<ObjKey>(ColKey col_key) const;
2309
template Decimal128 Obj::get<Decimal128>(ColKey col_key) const;
2310
template ObjLink Obj::get<ObjLink>(ColKey col_key) const;
2311
template Mixed Obj::get<Mixed>(realm::ColKey) const;
2312
template UUID Obj::get<UUID>(realm::ColKey) const;
2313
template util::Optional<UUID> Obj::get<util::Optional<UUID>>(ColKey col_key) const;
2314

2315
template <class T>
2316
inline void Obj::do_set_null(ColKey col_key)
2317
{
39,327✔
2318
    ColKey::Idx col_ndx = col_key.get_index();
39,327✔
2319
    Allocator& alloc = get_alloc();
39,327✔
2320
    alloc.bump_content_version();
39,327✔
2321
    Array fallback(alloc);
39,327✔
2322
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
39,327✔
2323

19,848✔
2324
    T values(alloc);
39,327✔
2325
    values.set_parent(&fields, col_ndx.val + 1);
39,327✔
2326
    values.init_from_parent();
39,327✔
2327
    values.set_null(m_row_ndx);
39,327✔
2328

19,848✔
2329
    sync(fields);
39,327✔
2330
}
39,327✔
2331

2332
template <>
2333
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2334
{
2,592✔
2335
    ColKey::Idx col_ndx = col_key.get_index();
2,592✔
2336
    size_t spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
2,592✔
2337
    Allocator& alloc = get_alloc();
2,592✔
2338
    alloc.bump_content_version();
2,592✔
2339
    Array fallback(alloc);
2,592✔
2340
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
2,592✔
2341

1,365✔
2342
    ArrayString values(alloc);
2,592✔
2343
    values.set_parent(&fields, col_ndx.val + 1);
2,592✔
2344
    values.set_spec(const_cast<Spec*>(&get_spec()), spec_ndx);
2,592✔
2345
    values.init_from_parent();
2,592✔
2346
    values.set_null(m_row_ndx);
2,592✔
2347

1,365✔
2348
    sync(fields);
2,592✔
2349
}
2,592✔
2350

2351
Obj& Obj::set_null(ColKey col_key, bool is_default)
2352
{
42,513✔
2353
    ColumnType col_type = col_key.get_type();
42,513✔
2354
    // Links need special handling
21,510✔
2355
    if (col_type == col_type_Link) {
42,513✔
2356
        set(col_key, null_key);
462✔
2357
    }
462✔
2358
    else if (col_type == col_type_Mixed) {
42,051✔
2359
        set(col_key, Mixed{});
66✔
2360
    }
66✔
2361
    else {
41,985✔
2362
        auto attrs = col_key.get_attrs();
41,985✔
2363
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
41,985✔
2364
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
66✔
2365
                              m_table->get_column_name(col_key));
66✔
2366
        }
66✔
2367

21,213✔
2368
        checked_update_if_needed();
41,919✔
2369

21,213✔
2370
        SearchIndex* index = m_table->get_search_index(col_key);
41,919✔
2371
        if (index && !m_key.is_unresolved()) {
41,919✔
2372
            index->set(m_key, null{});
4,593✔
2373
        }
4,593✔
2374

21,213✔
2375
        switch (col_type) {
41,919✔
2376
            case col_type_Int:
5,703✔
2377
                do_set_null<ArrayIntNull>(col_key);
5,703✔
2378
                break;
5,703✔
2379
            case col_type_Bool:
5,688✔
2380
                do_set_null<ArrayBoolNull>(col_key);
5,688✔
2381
                break;
5,688✔
2382
            case col_type_Float:
6,441✔
2383
                do_set_null<ArrayFloatNull>(col_key);
6,441✔
2384
                break;
6,441✔
2385
            case col_type_Double:
6,321✔
2386
                do_set_null<ArrayDoubleNull>(col_key);
6,321✔
2387
                break;
6,321✔
2388
            case col_type_ObjectId:
3,306✔
2389
                do_set_null<ArrayObjectIdNull>(col_key);
3,306✔
2390
                break;
3,306✔
2391
            case col_type_String:
2,592✔
2392
                do_set_null<ArrayString>(col_key);
2,592✔
2393
                break;
2,592✔
2394
            case col_type_Binary:
1,248✔
2395
                do_set_null<ArrayBinary>(col_key);
1,248✔
2396
                break;
1,248✔
2397
            case col_type_Timestamp:
2,460✔
2398
                do_set_null<ArrayTimestamp>(col_key);
2,460✔
2399
                break;
2,460✔
2400
            case col_type_Decimal:
1,830✔
2401
                do_set_null<ArrayDecimal128>(col_key);
1,830✔
2402
                break;
1,830✔
2403
            case col_type_UUID:
6,330✔
2404
                do_set_null<ArrayUUIDNull>(col_key);
6,330✔
2405
                break;
6,330✔
2406
            case col_type_Mixed:
✔
2407
            case col_type_Link:
✔
2408
            case col_type_BackLink:
✔
2409
            case col_type_TypedLink:
✔
2410
                REALM_UNREACHABLE();
2411
        }
41,919✔
2412
    }
41,919✔
2413

21,510✔
2414
    if (Replication* repl = get_replication())
42,480✔
2415
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
21,999✔
2416
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
21,993✔
2417

21,477✔
2418
    return *this;
42,447✔
2419
}
42,513✔
2420

2421

2422
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2423
{
8,580,060✔
2424
    return get_table()->spec_ndx2colkey(col_ndx);
8,580,060✔
2425
}
8,580,060✔
2426

2427
size_t Obj::colkey2spec_ndx(ColKey key)
2428
{
1,011✔
2429
    return get_table()->colkey2spec_ndx(key);
1,011✔
2430
}
1,011✔
2431

2432
ColKey Obj::get_primary_key_column() const
2433
{
5,266,272✔
2434
    return m_table->get_primary_key_column();
5,266,272✔
2435
}
5,266,272✔
2436

2437
ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key)
2438
{
12,786✔
2439
    return to_ref(obj._get<int64_t>(col_key.get_index()));
12,786✔
2440
}
12,786✔
2441

2442
ref_type Obj::get_collection_ref(StableIndex index, CollectionType type) const
2443
{
3,247,881✔
2444
    if (index.is_collection()) {
3,247,881✔
2445
        return to_ref(_get<int64_t>(index.get_index()));
3,232,491✔
2446
    }
3,232,491✔
2447
    if (check_index(index)) {
15,390✔
2448
        auto val = _get<Mixed>(index.get_index());
15,204✔
2449
        if (val.is_type(DataType(int(type)))) {
15,204✔
2450
            return val.get_ref();
15,198✔
2451
        }
15,198✔
2452
        throw realm::IllegalOperation(util::format("Not a %1", type));
6✔
2453
    }
6✔
2454
    throw StaleAccessor("This collection is no more");
186✔
2455
}
186✔
2456

2457
bool Obj::check_collection_ref(StableIndex index, CollectionType type) const noexcept
2458
{
852,963✔
2459
    if (index.is_collection()) {
852,963✔
2460
        return true;
850,419✔
2461
    }
850,419✔
2462
    if (check_index(index)) {
2,544✔
2463
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
2,538✔
2464
    }
2,538✔
2465
    return false;
6✔
2466
}
6✔
2467

2468
void Obj::set_collection_ref(StableIndex index, ref_type ref, CollectionType type)
2469
{
511,515✔
2470
    if (index.is_collection()) {
511,515✔
2471
        set_int(index.get_index(), from_ref(ref));
508,347✔
2472
        return;
508,347✔
2473
    }
508,347✔
2474
    set_ref(index.get_index(), ref, type);
3,168✔
2475
}
3,168✔
2476

2477
void Obj::set_backlink(ColKey col_key, ObjLink new_link) const
2478
{
6,795,345✔
2479
    if (!new_link) {
6,795,345✔
2480
        return;
41,850✔
2481
    }
41,850✔
2482

3,377,436✔
2483
    auto target_table = m_table->get_parent_group()->get_table(new_link.get_table_key());
6,753,495✔
2484
    ColKey backlink_col_key;
6,753,495✔
2485
    auto type = col_key.get_type();
6,753,495✔
2486
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
6,753,495✔
2487
        // This may modify the target table
17,154✔
2488
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
34,524✔
2489
        // it is possible that this was a link to the same table and that adding a backlink column has
17,154✔
2490
        // caused the need to update this object as well.
17,154✔
2491
        update_if_needed();
34,524✔
2492
    }
34,524✔
2493
    else {
6,718,971✔
2494
        backlink_col_key = m_table->get_opposite_column(col_key);
6,718,971✔
2495
    }
6,718,971✔
2496
    auto obj_key = new_link.get_obj_key();
6,753,495✔
2497
    auto target_obj =
6,753,495✔
2498
        obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key) : target_table->try_get_object(obj_key);
6,736,563✔
2499
    if (!target_obj) {
6,753,495✔
2500
        throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
12✔
2501
    }
12✔
2502
    target_obj.add_backlink(backlink_col_key, m_key);
6,753,483✔
2503
}
6,753,483✔
2504

2505
bool Obj::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
2506
{
338,466✔
2507
    bool recurse = remove_backlink(col_key, old_link, state);
338,466✔
2508
    set_backlink(col_key, new_link);
338,466✔
2509
    return recurse;
338,466✔
2510
}
338,466✔
2511

2512
bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
2513
{
358,017✔
2514
    if (!old_link) {
358,017✔
2515
        return false;
232,773✔
2516
    }
232,773✔
2517

62,652✔
2518
    REALM_ASSERT(m_table->valid_column(col_key));
125,244✔
2519
    ObjKey old_key = old_link.get_obj_key();
125,244✔
2520
    auto target_obj = m_table->get_parent_group()->get_object(old_link);
125,244✔
2521
    TableRef target_table = target_obj.get_table();
125,244✔
2522
    ColKey backlink_col_key;
125,244✔
2523
    auto type = col_key.get_type();
125,244✔
2524
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
125,253✔
2525
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
12,336✔
2526
    }
12,336✔
2527
    else {
112,908✔
2528
        backlink_col_key = m_table->get_opposite_column(col_key);
112,908✔
2529
    }
112,908✔
2530

62,652✔
2531
    bool strong_links = target_table->is_embedded();
125,244✔
2532
    bool is_unres = old_key.is_unresolved();
125,244✔
2533

62,652✔
2534
    bool last_removed = target_obj.remove_one_backlink(backlink_col_key, m_key); // Throws
125,244✔
2535
    if (is_unres) {
125,244✔
2536
        if (last_removed) {
264✔
2537
            // Check is there are more backlinks
126✔
2538
            if (!target_obj.has_backlinks(false)) {
252✔
2539
                // Tombstones can be erased right away - there is no cascading effect
126✔
2540
                target_table->m_tombstones->erase(old_key, state);
252✔
2541
            }
252✔
2542
        }
252✔
2543
    }
264✔
2544
    else {
124,980✔
2545
        return state.enqueue_for_cascade(target_obj, strong_links, last_removed);
124,980✔
2546
    }
124,980✔
2547

132✔
2548
    return false;
264✔
2549
}
264✔
2550

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