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

realm / realm-core / 2130

13 Mar 2024 04:51AM UTC coverage: 92.078% (+0.2%) from 91.833%
2130

push

Evergreen

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

Make Obj trivial and add a separate ObjCollectionParent type

94732 of 174812 branches covered (54.19%)

531 of 559 new or added lines in 22 files covered. (94.99%)

45 existing lines in 13 files now uncovered.

244506 of 265543 relevant lines covered (92.08%)

5982312.84 hits per line

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

96.53
/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
{
173,889,000✔
114
    m_storage_version = get_alloc().get_storage_version();
173,889,000✔
115
}
173,889,000✔
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,598,369✔
129
    if (m_key.is_unresolved()) {
38,598,369✔
130
        return m_table.unchecked_ptr()->m_tombstones.get();
44,766✔
131
    }
44,766✔
132
    else {
38,553,603✔
133
        return &m_table.unchecked_ptr()->m_clusters;
38,553,603✔
134
    }
38,553,603✔
135
}
38,598,369✔
136

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

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

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

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

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

185
Replication* Obj::get_replication() const
186
{
29,373,123✔
187
    return m_table->get_repl();
29,373,123✔
188
}
29,373,123✔
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,455,177✔
331
    // Cache valid state. If once invalid, it can never become valid again
779,628✔
332
    if (m_valid)
1,455,177✔
333
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,453,794✔
334
                                    m_table.unchecked_ptr()->is_valid(m_key));
795,459✔
335

779,628✔
336
    return m_valid;
1,455,177✔
337
}
1,455,177✔
338

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

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

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

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

359
TableRef Obj::get_target_table(ColKey col_key) const
360
{
7,094,298✔
361
    if (m_table) {
7,094,298✔
362
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,093,605✔
363
    }
7,093,605✔
364
    else {
693✔
365
        return TableRef();
693✔
366
    }
693✔
367
}
7,094,298✔
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
{
486,354✔
381
    // Get a new object from key
82,908✔
382
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
486,354✔
383

82,908✔
384
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
486,354✔
385
    if (changes) {
486,354✔
386
        m_mem = new_obj.m_mem;
64,431✔
387
        m_row_ndx = new_obj.m_row_ndx;
64,431✔
388
        CollectionParent::m_parent_version++;
64,431✔
389
    }
64,431✔
390
    // Always update versions
82,908✔
391
    m_storage_version = new_obj.m_storage_version;
486,354✔
392
    m_table = new_obj.m_table;
486,354✔
393
    return changes;
486,354✔
394
}
486,354✔
395

396
inline bool Obj::_update_if_needed() const
397
{
78,496,992✔
398
    auto current_version = _get_alloc().get_storage_version();
78,496,992✔
399
    if (current_version != m_storage_version) {
78,496,992✔
400
        return update();
16,452✔
401
    }
16,452✔
402
    return false;
78,480,540✔
403
}
78,480,540✔
404

405
UpdateStatus Obj::update_if_needed_with_status() const
406
{
56,854,788✔
407
    if (!m_table) {
56,854,788✔
408
        // Table deleted
177✔
409
        return UpdateStatus::Detached;
348✔
410
    }
348✔
411

38,794,011✔
412
    auto current_version = get_alloc().get_storage_version();
56,854,440✔
413
    if (current_version != m_storage_version) {
56,854,440✔
414
        ClusterNode::State state = get_tree_top()->try_get(m_key);
1,019,490✔
415

670,137✔
416
        if (!state) {
1,019,490✔
417
            // Object deleted
2,610✔
418
            return UpdateStatus::Detached;
5,199✔
419
        }
5,199✔
420

667,527✔
421
        // Always update versions
667,527✔
422
        m_storage_version = current_version;
1,014,291✔
423
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
1,014,291✔
424
            m_mem = state.mem;
171,519✔
425
            m_row_ndx = state.index;
171,519✔
426
            CollectionParent::m_parent_version++;
171,519✔
427
            return UpdateStatus::Updated;
171,519✔
428
        }
171,519✔
429
    }
56,677,722✔
430
    return UpdateStatus::NoChange;
56,677,722✔
431
}
56,677,722✔
432

433
template <class T>
434
T Obj::get(ColKey col_key) const
16,810,590✔
435
{
59,115,654✔
436
    m_table->check_column(col_key);
42,305,073✔
437
    ColumnType type = col_key.get_type();
42,305,073✔
438
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
42,305,073!
439

16,810,590✔
440
    return _get<T>(col_key.get_index());
42,305,064✔
441
}
42,305,064✔
442

443
template UUID Obj::_get(ColKey::Idx col_ndx) const;
42,192,426✔
444
template util::Optional<UUID> Obj::_get(ColKey::Idx col_ndx) const;
42,192,426✔
445

42,192,426✔
446
#if REALM_ENABLE_GEOSPATIAL
42,192,426✔
447

42,192,426✔
448
template <>
42,192,426✔
449
Geospatial Obj::get(ColKey col_key) const
42,192,426✔
450
{
42✔
451
    m_table->check_column(col_key);
42✔
452
    ColumnType type = col_key.get_type();
42✔
453
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
42✔
454
    return Geospatial::from_link(get_linked_object(col_key));
42✔
455
}
42✔
456

457
template <>
458
std::optional<Geospatial> Obj::get(ColKey col_key) const
42✔
459
{
48✔
460
    m_table->check_column(col_key);
48✔
461
    ColumnType type = col_key.get_type();
48✔
462
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
48✔
463

42✔
464
    auto geo = get_linked_object(col_key);
6✔
465
    if (!geo) {
6✔
466
        return {};
6✔
467
    }
12✔
468
    return Geospatial::from_link(geo);
6✔
469
}
6✔
470

6✔
471
#endif
6✔
472

6✔
473
template <class T>
6✔
474
T Obj::_get(ColKey::Idx col_ndx) const
6✔
475
{
38,970,324✔
476
    _update_if_needed();
38,970,318✔
477

478
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
38,970,318✔
479
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
38,970,318✔
480
    values.init_from_ref(ref);
38,970,318✔
481

482
    return values.get(m_row_ndx);
38,970,318✔
483
}
77,858,775✔
484

38,888,457✔
485
Mixed Obj::get_unfiltered_mixed(ColKey::Idx col_ndx) const
38,888,457✔
486
{
38,927,346✔
487
    ArrayMixed values(get_alloc());
38,927,346✔
488
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
38,927,346✔
489
    values.init_from_ref(ref);
38,927,346✔
490

38,888,457✔
491
    return values.get(m_row_ndx);
38,927,346✔
492
}
38,889✔
493

494
template <>
39,969✔
495
Mixed Obj::_get<Mixed>(ColKey::Idx col_ndx) const
39,969✔
496
{
73,917✔
497
    _update_if_needed();
73,917✔
498
    Mixed m = get_unfiltered_mixed(col_ndx);
73,917✔
499
    return m.is_unresolved_link() ? Mixed{} : m;
73,878✔
500
}
73,917✔
501

502
template <>
503
ObjKey Obj::_get<ObjKey>(ColKey::Idx col_ndx) const
504
{
197,019✔
505
    _update_if_needed();
197,019✔
506

35,037✔
507
    ArrayKey values(_get_alloc());
197,019✔
508
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
197,019✔
509
    values.init_from_ref(ref);
161,982✔
510

511
    ObjKey k = values.get(m_row_ndx);
161,982✔
512
    return k.is_unresolved() ? ObjKey{} : k;
320,574✔
513
}
323,685✔
514

161,703✔
515
bool Obj::is_unresolved(ColKey col_key) const
161,703✔
516
{
161,718✔
517
    m_table->check_column(col_key);
161,718✔
518
    ColumnType type = col_key.get_type();
161,718✔
519
    REALM_ASSERT(type == col_type_Link);
161,718✔
520

161,703✔
521
    _update_if_needed();
161,718✔
522

523
    return get_unfiltered_link(col_key).is_unresolved();
15✔
524
}
30✔
525

15✔
526
ObjKey Obj::get_unfiltered_link(ColKey col_key) const
15✔
527
{
164,256✔
528
    ArrayKey values(get_alloc());
164,256✔
529
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
164,256✔
530
    values.init_from_ref(ref);
164,256✔
531

15✔
532
    return values.get(m_row_ndx);
164,256✔
533
}
164,241✔
534

535
template <>
164,208✔
536
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
164,208✔
537
{
60,137,781✔
538
    // manual inline of _update_if_needed():
164,208✔
539
    auto& alloc = _get_alloc();
60,137,781✔
540
    auto current_version = alloc.get_storage_version();
60,137,781✔
541
    if (current_version != m_storage_version) {
60,137,781✔
542
        update();
68,571✔
543
    }
68,571✔
544

545
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
121,004,727✔
546
    char* header = alloc.translate(ref);
121,004,727✔
547
    int width = Array::get_width_from_header(header);
121,004,727✔
548
    char* data = Array::get_data_from_header(header);
121,004,727✔
549
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
121,004,727✔
550
}
71,742✔
551

71,742✔
552
template <>
61,031,154✔
553
int64_t Obj::get<int64_t>(ColKey col_key) const
61,031,154✔
554
{
113,625,072✔
555
    m_table->check_column(col_key);
113,625,072✔
556
    ColumnType type = col_key.get_type();
113,625,072✔
557
    REALM_ASSERT(type == col_type_Int);
113,625,072✔
558

559
    if (col_key.get_attrs().test(col_attr_Nullable)) {
52,593,918✔
560
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
3,894✔
561
        if (!val) {
3,894✔
562
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
55,483,041✔
563
        }
55,483,041✔
564
        return *val;
55,486,929✔
565
    }
55,486,929✔
566
    else {
108,073,062✔
567
        return _get<int64_t>(col_key.get_index());
108,073,062✔
568
    }
52,593,918✔
569
}
52,597,812✔
570

3✔
571
template <>
3✔
572
bool Obj::get<bool>(ColKey col_key) const
3,891✔
573
{
235,380✔
574
    m_table->check_column(col_key);
55,710,633✔
575
    ColumnType type = col_key.get_type();
55,710,633✔
576
    REALM_ASSERT(type == col_type_Bool);
55,710,633✔
577

55,483,038✔
578
    if (col_key.get_attrs().test(col_attr_Nullable)) {
231,489✔
579
        auto val = _get<util::Optional<bool>>(col_key.get_index());
15✔
580
        if (!val) {
15✔
581
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
245,136✔
582
        }
245,136✔
583
        return *val;
245,151✔
584
    }
245,151✔
585
    else {
476,610✔
586
        return _get<bool>(col_key.get_index());
476,610✔
587
    }
231,489✔
588
}
231,504✔
589

590
template <>
591
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
15✔
592
{
2,671,134✔
593
    // manual inline of _update_if_needed():
245,121✔
594
    auto& alloc = _get_alloc();
2,916,240✔
595
    auto current_version = alloc.get_storage_version();
2,916,240✔
596
    if (current_version != m_storage_version) {
2,916,255✔
597
        update();
2,934✔
598
    }
2,934✔
599

600
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
5,359,560✔
601
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
5,359,560✔
602
    auto& spec = get_spec();
5,359,560✔
603
    if (spec.is_string_enum_type(spec_ndx)) {
5,359,560✔
604
        ArrayString values(get_alloc());
3,221,508✔
605
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
535,935✔
606
        values.init_from_ref(ref);
535,935✔
607

2,688,441✔
608
        return values.get(m_row_ndx);
3,221,508✔
609
    }
3,221,508✔
610
    else {
4,826,493✔
611
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
4,826,493✔
612
    }
2,666,715✔
613
}
3,199,782✔
614

528,663✔
615
template <>
528,663✔
616
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
528,663✔
617
{
3,968,730✔
618
    // manual inline of _update_if_needed():
2,159,778✔
619
    auto& alloc = _get_alloc();
5,599,845✔
620
    auto current_version = alloc.get_storage_version();
5,599,845✔
621
    if (current_version != m_storage_version) {
6,128,508✔
622
        update();
66✔
623
    }
66✔
624

625
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
6,880,119✔
626
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
6,880,119✔
627
}
6,880,119✔
628

3,440,052✔
629
Mixed Obj::get_any(ColKey col_key) const
3,440,052✔
630
{
8,478,096✔
631
    m_table->check_column(col_key);
8,478,096✔
632
    auto col_ndx = col_key.get_index();
11,918,082✔
633
    if (col_key.is_collection()) {
11,918,082✔
634
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
3,443,514✔
635
        return Mixed(ref, get_table()->get_collection_type(col_key));
3,443,514✔
636
    }
3,462✔
637
    switch (col_key.get_type()) {
8,474,568✔
638
        case col_type_Int:
12,699,357✔
639
            if (col_key.get_attrs().test(col_attr_Nullable)) {
12,699,357✔
640
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
6,942,663✔
641
            }
6,942,663✔
642
            else {
5,760,156✔
643
                return Mixed{_get<int64_t>(col_ndx)};
5,760,156✔
644
            }
5,760,156✔
645
        case col_type_Bool:
6,781,380✔
646
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
4,288,605✔
647
        case col_type_Float:
4,192,443✔
648
            return Mixed{_get<util::Optional<float>>(col_ndx)};
257,682✔
649
        case col_type_Double:
263,487✔
650
            return Mixed{_get<util::Optional<double>>(col_ndx)};
3,944,577✔
651
        case col_type_String:
5,969,325✔
652
            return Mixed{_get<String>(col_ndx)};
5,969,325✔
653
        case col_type_Binary:
101,046✔
654
            return Mixed{_get<Binary>(col_ndx)};
101,046✔
655
        case col_type_Mixed:
11,139✔
656
            return _get<Mixed>(col_ndx);
11,139✔
657
        case col_type_Timestamp:
90,954✔
658
            return Mixed{_get<Timestamp>(col_ndx)};
90,954✔
659
        case col_type_Decimal:
2,080,494✔
660
            return Mixed{_get<Decimal128>(col_ndx)};
2,080,494✔
661
        case col_type_ObjectId:
183,306✔
662
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
183,306✔
663
        case col_type_UUID:
33,294✔
664
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
33,294✔
665
        case col_type_Link:
103,611✔
666
            return Mixed{_get<ObjKey>(col_ndx)};
103,611✔
667
        default:
3,930✔
668
            REALM_UNREACHABLE();
3,930✔
669
            break;
179,142✔
670
    }
179,142✔
671
    return {};
26,151✔
672
}
26,151✔
673

21,072✔
674
Mixed Obj::get_primary_key() const
21,072✔
675
{
76,731✔
676
    auto col = m_table->get_primary_key_column();
76,731✔
677
    return col ? get_any(col) : Mixed{get_key()};
76,608✔
678
}
76,731✔
679

680
/* FIXME: Make this one fast too!
681
template <>
682
ObjKey Obj::_get(size_t col_ndx) const
683
{
76,263✔
684
    return ObjKey(_get<int64_t>(col_ndx));
76,263✔
685
}
76,263✔
686
*/
76,263✔
687

688
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
689
{
13,380✔
690
    Obj obj;
13,380✔
691
    if (!link.is_null()) {
13,380✔
692
        TableRef target_table;
9,189✔
693
        if (link.is_type(type_TypedLink)) {
9,189✔
694
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
162✔
695
        }
162✔
696
        else {
9,027✔
697
            target_table = get_target_table(link_col_key);
22,389✔
698
        }
22,389✔
699
        obj = target_table->get_object(link.get<ObjKey>());
22,551✔
700
    }
18,360✔
701
    return obj;
22,551✔
702
}
13,542✔
703

162✔
704
Obj Obj::get_parent_object() const
9,009✔
705
{
9,015✔
706
    Obj obj;
9,015✔
707
    update_if_needed();
9,177✔
708

9,171✔
709
    if (!m_table->is_embedded()) {
13,368✔
710
        throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded");
13,362✔
711
    }
712
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
9✔
713
        if (get_backlink_cnt(backlink_col_key) == 1) {
15✔
714
            auto obj_key = get_backlink(backlink_col_key, 0);
12✔
715
            obj = m_table->get_opposite_table(backlink_col_key)->get_object(obj_key);
12✔
716
            return IteratorControl::Stop;
12✔
717
        }
12✔
718
        return IteratorControl::AdvanceToNext;
3✔
719
    });
3✔
720

9✔
721
    return obj;
15✔
722
}
12✔
723

6✔
724
template <class T>
6✔
725
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
6✔
726
{
645,525✔
727
    T values(get_alloc());
645,525✔
728
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
645,528✔
729
    values.init_from_ref(ref);
645,528✔
730
    return values.is_null(m_row_ndx);
645,528✔
731
}
645,522✔
732

733
template <>
734
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
534,528✔
735
{
637,488✔
736
    ArrayString values(get_alloc());
637,488✔
737
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
637,488✔
738
    values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
637,488✔
739
    values.init_from_ref(ref);
637,488✔
740
    return values.is_null(m_row_ndx);
102,960✔
741
}
102,960✔
742

743
size_t Obj::get_link_count(ColKey col_key) const
170,931✔
744
{
170,985✔
745
    return get_list<ObjKey>(col_key).size();
170,985✔
746
}
170,985✔
747

170,931✔
748
bool Obj::is_null(ColKey col_key) const
170,931✔
749
{
3,722,142✔
750
    update_if_needed();
3,551,211✔
751
    ColumnAttrMask attr = col_key.get_attrs();
3,551,211✔
752
    ColKey::Idx col_ndx = col_key.get_index();
3,551,265✔
753
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
3,551,265✔
754
        switch (col_key.get_type()) {
748,551✔
755
            case col_type_Int:
198,900✔
756
                return do_is_null<ArrayIntNull>(col_ndx);
198,900✔
757
            case col_type_Bool:
1,937,820✔
758
                return do_is_null<ArrayBoolNull>(col_ndx);
1,937,820✔
759
            case col_type_Float:
1,700,259✔
760
                return do_is_null<ArrayFloatNull>(col_ndx);
1,700,259✔
761
            case col_type_Double:
1,697,424✔
762
                return do_is_null<ArrayDoubleNull>(col_ndx);
707,160✔
763
            case col_type_String:
248,064✔
764
                return do_is_null<ArrayString>(col_ndx);
248,064✔
765
            case col_type_Binary:
207,762✔
766
                return do_is_null<ArrayBinary>(col_ndx);
207,762✔
767
            case col_type_Mixed:
5,013✔
768
                return do_is_null<ArrayMixed>(col_ndx);
5,013✔
769
            case col_type_Timestamp:
186,123✔
770
                return do_is_null<ArrayTimestamp>(col_ndx);
186,123✔
771
            case col_type_Link:
182,103✔
772
                return do_is_null<ArrayKey>(col_ndx);
182,103✔
773
            case col_type_ObjectId:
249✔
774
                return do_is_null<ArrayObjectIdNull>(col_ndx);
249✔
775
            case col_type_Decimal:
1,695✔
776
                return do_is_null<ArrayDecimal128>(col_ndx);
1,695✔
777
            case col_type_UUID:
163,848✔
778
                return do_is_null<ArrayUUIDNull>(col_ndx);
163,848✔
779
            default:
10,815✔
780
                REALM_UNREACHABLE();
10,815✔
781
        }
748,527✔
782
    }
748,527✔
783
    return false;
2,803,635✔
784
}
3,552,132✔
785

30✔
786

30✔
787
// Figure out if this object has any remaining backlinkss
788
bool Obj::has_backlinks(bool only_strong_links) const
789
{
711,615✔
790
    const Table& target_table = *m_table;
711,615✔
791

1,695,705✔
792
    // If we only look for strong links and the table is not embedded,
1,695,705✔
793
    // then there is no relevant backlinks to find.
794
    if (only_strong_links && !target_table.is_embedded()) {
6,174✔
795
        return false;
796
    }
797

6,522✔
798
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
14,652✔
799
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
14,160✔
800
    });
14,652✔
801
}
12,696✔
802

6,522✔
803
size_t Obj::get_backlink_count() const
804
{
87,153✔
805
    update_if_needed();
93,675✔
806

8,076✔
807
    size_t cnt = 0;
95,229✔
808
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
237,177✔
809
        cnt += get_backlink_cnt(backlink_col_key);
235,623✔
810
        return IteratorControl::AdvanceToNext;
229,101✔
811
    });
229,101✔
812
    return cnt;
174,060✔
813
}
174,060✔
814

86,907✔
815
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
86,907✔
816
{
244,359✔
817
    update_if_needed();
244,359✔
818

228,363✔
819
    size_t cnt = 0;
244,359✔
820
    if (TableKey origin_table_key = origin.get_key()) {
102,903✔
821
        ColKey backlink_col_key;
102,903✔
822
        auto type = origin_col_key.get_type();
15,996✔
823
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
15,996✔
824
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
16,053✔
825
        }
16,053✔
826
        else {
32,037✔
827
            backlink_col_key = origin.get_opposite_column(origin_col_key);
32,037✔
828
        }
32,037✔
829

16,047✔
830
        cnt = get_backlink_cnt(backlink_col_key);
32,043✔
831
    }
32,043✔
832
    return cnt;
16,002✔
833
}
16,002✔
834

16,041✔
835
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
16,041✔
836
{
4,527,792✔
837
    ColKey backlink_col_key;
4,527,798✔
838
    auto type = origin_col_key.get_type();
4,527,798✔
839
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
4,527,804✔
840
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
16,065✔
841
    }
16,065✔
842
    else {
4,511,733✔
843
        backlink_col_key = origin.get_opposite_column(origin_col_key);
4,511,733✔
844
    }
9,023,550✔
845
    return get_backlink(backlink_col_key, backlink_ndx);
9,023,568✔
846
}
9,023,568✔
847

4,511,817✔
848
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key) const
18✔
849
{
366✔
850
    TableView tv(src_table, src_col_key, *this);
4,512,147✔
851
    tv.do_sync();
4,512,147✔
852
    return tv;
4,512,147✔
853
}
4,512,165✔
854

4,511,817✔
855
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
856
{
4,511,769✔
857
    get_table()->check_column(backlink_col);
4,512,117✔
858
    Allocator& alloc = get_alloc();
4,512,117✔
859
    Array fields(alloc);
4,512,117✔
860
    fields.init_from_mem(m_mem);
4,512,117✔
861

348✔
862
    ArrayBacklink backlinks(alloc);
4,511,769✔
863
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
4,511,769✔
864
    backlinks.init_from_parent();
9,023,592✔
865
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
9,023,592✔
866
}
9,023,592✔
867

4,511,823✔
868
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
4,511,823✔
869
{
4,656,555✔
870
    update_if_needed();
4,656,555✔
871

4,511,823✔
872
    get_table()->check_column(backlink_col);
4,656,555✔
873
    Allocator& alloc = get_alloc();
4,656,555✔
874
    Array fields(alloc);
4,656,555✔
875
    fields.init_from_mem(m_mem);
144,732✔
876

877
    ArrayBacklink backlinks(alloc);
289,002✔
878
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
289,002✔
879
    backlinks.init_from_parent();
289,002✔
880

144,270✔
881
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
289,002✔
882
    std::vector<ObjKey> vec;
289,002✔
883
    vec.reserve(cnt);
289,002✔
884
    for (size_t i = 0; i < cnt; i++) {
421,524✔
885
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
276,792✔
886
    }
276,792✔
887
    return vec;
289,002✔
888
}
289,002✔
889

144,270✔
890
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
144,270✔
891
{
397,506✔
892
    Allocator& alloc = get_alloc();
529,779✔
893
    Array fields(alloc);
385,509✔
894
    fields.init_from_mem(m_mem);
385,509✔
895

144,270✔
896
    ArrayBacklink backlinks(alloc);
397,506✔
897
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
253,236✔
898
    backlinks.init_from_parent();
253,236✔
899

252,495✔
900
    return backlinks.get_backlink_count(m_row_ndx);
505,731✔
901
}
505,731✔
902

252,495✔
903
void Obj::verify_backlink(const Table& origin, ColKey origin_col_key, ObjKey origin_key) const
252,495✔
904
{
315,828✔
905
#ifdef REALM_DEBUG
315,828✔
906
    ColKey backlink_col_key;
315,828✔
907
    auto type = origin_col_key.get_type();
315,828✔
908
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
315,828✔
909
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
252,495✔
910
    }
911
    else {
63,333✔
912
        backlink_col_key = origin.get_opposite_column(origin_col_key);
126,666✔
913
    }
126,666✔
914

63,333✔
915
    Allocator& alloc = get_alloc();
126,666✔
916
    Array fields(alloc);
126,666✔
917
    fields.init_from_mem(m_mem);
63,333✔
918

919
    ArrayBacklink backlinks(alloc);
126,666✔
920
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
126,666✔
921
    backlinks.init_from_parent();
126,666✔
922

63,333✔
923
    REALM_ASSERT(backlinks.verify_backlink(m_row_ndx, origin_key.value));
126,666✔
924
#else
63,333✔
925
    static_cast<void>(origin);
63,333✔
926
    static_cast<void>(origin_col_key);
63,333✔
927
    static_cast<void>(origin_key);
63,333✔
928
#endif
63,333✔
929
}
126,666✔
930

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

974
    private:
45✔
975
        Mixed m_index;
90✔
976
        Obj m_dest_obj;
90✔
977
    };
90✔
978

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

24✔
1002
Obj::FatPath Obj::get_fat_path() const
18✔
1003
{
33✔
1004
    FatPath result;
39✔
1005
    auto sizer = [&](size_t size) {
36✔
1006
        result.reserve(size);
36✔
1007
    };
36✔
1008
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
60✔
1009
        result.push_back({o2, col, idx});
15✔
1010
    };
15✔
1011
    traverse_path(step, sizer);
30✔
1012
    return result;
30✔
1013
}
30✔
1014

15✔
1015
FullPath Obj::get_path() const
15✔
1016
{
147,144✔
1017
    FullPath result;
147,144✔
1018
    if (m_table->is_embedded()) {
147,144✔
1019
        REALM_ASSERT(get_backlink_count() == 1);
84,519✔
1020
        m_table->for_each_backlink_column([&](ColKey col_key) {
138,741✔
1021
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
138,741✔
1022
            if (backlinks.size() == 1) {
138,726✔
1023
                TableRef origin_table = m_table->get_opposite_table(col_key);
84,504✔
1024
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
231,141✔
1025
                auto next_col_key = m_table->get_opposite_column(col_key);
231,141✔
1026

146,637✔
1027
                ColumnAttrMask attr = next_col_key.get_attrs();
168,762✔
1028
                Mixed index;
222,768✔
1029
                if (attr.test(col_attr_List)) {
222,768✔
1030
                    REALM_ASSERT(next_col_key.get_type() == col_type_Link);
167,352✔
1031
                    Lst<ObjKey> link_list(next_col_key);
113,346✔
1032
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
113,346✔
1033
                    REALM_ASSERT(i != realm::not_found);
113,346✔
1034
                    result = link_list.get_path();
113,346✔
1035
                    result.path_from_top.emplace_back(i);
113,346✔
1036
                }
113,346✔
1037
                else if (attr.test(col_attr_Dictionary)) {
139,674✔
1038
                    Dictionary dict(next_col_key);
51,849✔
1039
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
51,849✔
1040
                    REALM_ASSERT(ndx != realm::not_found);
51,849✔
1041
                    result = dict.get_path();
51,849✔
1042
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
51,849✔
1043
                }
51,849✔
1044
                else {
61,599✔
1045
                    result = obj.get_path();
87,825✔
1046
                    if (result.path_from_top.empty()) {
55,344✔
1047
                        result.path_from_top.push_back(next_col_key);
30,093✔
1048
                    }
30,093✔
1049
                    else {
48,012✔
1050
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
48,012✔
1051
                    }
48,012✔
1052
                }
65,064✔
1053

32,481✔
1054
                return IteratorControl::Stop; // early out
116,985✔
1055
            }
91,734✔
1056
            return IteratorControl::AdvanceToNext; // try next column
61,452✔
1057
        });
79,473✔
1058
    }
109,755✔
1059
    else {
87,876✔
1060
        result.top_objkey = get_key();
95,106✔
1061
        result.top_table = get_table()->get_key();
146,883✔
1062
    }
146,883✔
1063
    return result;
231,387✔
1064
}
201,135✔
1065

54,006✔
1066
std::string Obj::get_id() const
84,258✔
1067
{
77,148✔
1068
    std::ostringstream ostr;
77,148✔
1069
    auto path = get_path();
77,148✔
1070
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
77,148✔
1071
    ostr << top_table->get_class_name() << '[';
161,406✔
1072
    if (top_table->get_primary_key_column()) {
161,406✔
1073
        ostr << top_table->get_primary_key(path.top_objkey);
14,634✔
1074
    }
14,634✔
1075
    else {
14,781✔
1076
        ostr << path.top_objkey;
14,781✔
1077
    }
14,781✔
1078
    ostr << ']';
29,415✔
1079
    if (!path.path_from_top.empty()) {
29,415✔
1080
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
24,429✔
1081
        path.path_from_top[0] = PathElement(prop_name);
24,294✔
1082
        ostr << path.path_from_top;
24,294✔
1083
    }
9,918✔
1084
    return ostr.str();
14,904✔
1085
}
14,904✔
1086

14,646✔
1087
Path Obj::get_short_path() const noexcept
14,646✔
1088
{
193,425✔
1089
    return {};
193,425✔
1090
}
193,425✔
1091

9,660✔
1092
ColKey Obj::get_col_key() const noexcept
14,646✔
1093
{
14,646✔
1094
    return {};
1095
}
1096

183,159✔
1097
StablePath Obj::get_stable_path() const noexcept
183,159✔
1098
{
1,140,642✔
1099
    return {};
957,483✔
1100
}
957,483✔
1101

1102
void Obj::add_index(Path& path, const Index& index) const
1103
{
235,695✔
1104
    if (path.empty()) {
235,695✔
1105
        path.emplace_back(get_table()->get_column_key(index));
234,072✔
1106
    }
1,190,913✔
1107
    else {
958,464✔
1108
        StringData col_name = get_table()->get_column_name(index);
958,464✔
1109
        path.emplace_back(col_name);
1,623✔
1110
    }
1,623✔
1111
}
470,640✔
1112

234,945✔
1113
std::string Obj::to_string() const
233,322✔
1114
{
233,328✔
1115
    std::ostringstream ostr;
1,629✔
1116
    to_json(ostr);
1,629✔
1117
    return ostr.str();
1,629✔
1118
}
1,629✔
1119

234,945✔
1120
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
1121
{
1122
    obj.to_json(ostr);
6✔
1123
    return ostr;
6✔
1124
}
6✔
1125

6✔
1126
/*********************************** Obj *************************************/
6✔
1127

1128
bool Obj::ensure_writeable()
1129
{
×
1130
    Allocator& alloc = get_alloc();
×
1131
    if (alloc.is_read_only(m_mem.get_ref())) {
×
1132
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
×
1133
        m_storage_version = alloc.get_storage_version();
1134
        return true;
1135
    }
1136
    return false;
1137
}
×
1138

1139
REALM_FORCEINLINE void Obj::sync(Node& arr)
1140
{
18,480,870✔
1141
    auto ref = arr.get_ref();
18,480,870✔
1142
    if (arr.has_missing_parent_update()) {
18,480,870✔
1143
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
134,952✔
1144
    }
134,952✔
1145
    if (m_mem.get_ref() != ref) {
18,480,870✔
1146
        m_mem = arr.get_mem();
257,253✔
1147
        m_storage_version = arr.get_alloc().get_storage_version();
257,253✔
1148
    }
18,512,514✔
1149
}
36,736,131✔
1150

18,255,261✔
1151
template <>
134,985✔
1152
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
134,985✔
1153
{
18,260,202✔
1154
    update_if_needed();
278,055✔
1155
    get_table()->check_column(col_key);
278,055✔
1156
    auto type = col_key.get_type();
278,055✔
1157
    auto col_ndx = col_key.get_index();
18,260,202✔
1158
    bool recurse = false;
4,941✔
1159
    CascadeState state;
4,941✔
1160

1161
    if (type != col_type_Mixed)
9,873✔
1162
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
4,932✔
1163
    if (value_is_null(value) && !col_key.is_nullable()) {
9,873✔
1164
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
4,932✔
1165
    }
4,932✔
1166
    if (value.is_type(type_Link)) {
9,873✔
1167
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
4,932✔
1168
    }
4,932✔
1169

4,932✔
1170
    Mixed old_value = get_unfiltered_mixed(col_ndx);
4,941✔
1171
    if (old_value.is_type(type_TypedLink)) {
9,873✔
1172
        if (old_value == value) {
66✔
1173
            return *this;
12✔
1174
        }
4,944✔
1175
        auto old_link = old_value.get<ObjLink>();
54✔
1176
        recurse = remove_backlink(col_key, old_link, state);
54✔
1177
    }
4,986✔
1178
    else if (old_value.is_type(type_Dictionary)) {
9,807✔
1179
        Dictionary dict(*this, col_key);
4,950✔
1180
        recurse = dict.remove_backlinks(state);
84✔
1181
    }
30✔
1182
    else if (old_value.is_type(type_List)) {
4,869✔
1183
        Lst<Mixed> list(*this, col_key);
84✔
1184
        recurse = list.remove_backlinks(state);
84✔
1185
    }
84✔
1186

4,866✔
1187
    if (value.is_type(type_TypedLink)) {
4,947✔
1188
        if (m_table->is_asymmetric()) {
486✔
1189
            throw IllegalOperation("Links not allowed in asymmetric tables");
24✔
1190
        }
4,854✔
1191
        auto new_link = value.get<ObjLink>();
492✔
1192
        m_table->get_parent_group()->validate(new_link);
492✔
1193
        set_backlink(col_key, new_link);
492✔
1194
    }
5,394✔
1195

4,932✔
1196
    SearchIndex* index = m_table->get_search_index(col_key);
5,391✔
1197
    // The following check on unresolved is just a precaution as it should not
6✔
1198
    // be possible to hit that while Mixed is not a supported primary key type.
6✔
1199
    if (index && !m_key.is_unresolved()) {
5,385✔
1200
        index->set(m_key, value.is_unresolved_link() ? Mixed() : value);
1,029✔
1201
    }
1,032✔
1202

462✔
1203
    Allocator& alloc = get_alloc();
9,843✔
1204
    alloc.bump_content_version();
9,843✔
1205
    Array fallback(alloc);
9,837✔
1206
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,837✔
1207
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,837✔
1208
    ArrayMixed values(alloc);
5,493✔
1209
    values.set_parent(&fields, col_ndx.val + 1);
5,493✔
1210
    values.init_from_parent();
9,837✔
1211
    values.set(m_row_ndx, value);
9,837✔
1212

4,914✔
1213
    sync(fields);
9,837✔
1214

4,914✔
1215
    if (Replication* repl = get_replication())
9,837✔
1216
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
7,761✔
1217
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
7,761✔
1218

4,914✔
1219
    if (recurse)
9,837✔
1220
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
4,914✔
1221

4,914✔
1222
    return *this;
9,837✔
1223
}
9,843✔
1224

2,838✔
1225
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
2,838✔
1226
{
403,416✔
1227
    if (value.is_null()) {
403,416✔
1228
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
156✔
1229
        set_null(col_key);
5,070✔
1230
    }
5,070✔
1231
    else {
403,266✔
1232
        switch (col_key.get_type()) {
398,346✔
1233
            case col_type_Int:
359,085✔
1234
                if (col_key.get_attrs().test(col_attr_Nullable)) {
741,015✔
1235
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
382,803✔
1236
                }
1,029✔
1237
                else {
358,368✔
1238
                    set(col_key, value.get_int(), is_default);
358,368✔
1239
                }
739,986✔
1240
                break;
740,859✔
1241
            case col_type_Bool:
363,606✔
1242
                set(col_key, value.get_bool(), is_default);
363,606✔
1243
                break;
930✔
1244
            case col_type_Float:
999✔
1245
                set(col_key, value.get_float(), is_default);
362,799✔
1246
                break;
362,799✔
1247
            case col_type_Double:
363,384✔
1248
                set(col_key, value.get_double(), is_default);
364,260✔
1249
                break;
762✔
1250
            case col_type_String:
24,633✔
1251
                set(col_key, value.get_string(), is_default);
24,633✔
1252
                break;
24,702✔
1253
            case col_type_Binary:
8,844✔
1254
                set(col_key, value.get<Binary>(), is_default);
8,844✔
1255
                break;
9,429✔
1256
            case col_type_Mixed:
924✔
1257
                set(col_key, value, is_default);
924✔
1258
                break;
5,775✔
1259
            case col_type_Timestamp:
8,808✔
1260
                set(col_key, value.get<Timestamp>(), is_default);
8,808✔
1261
                break;
11,970✔
1262
            case col_type_ObjectId:
9,381✔
1263
                set(col_key, value.get<ObjectId>(), is_default);
9,381✔
1264
                break;
879✔
1265
            case col_type_Decimal:
270✔
1266
                set(col_key, value.get<Decimal128>(), is_default);
270✔
1267
                break;
2,121✔
1268
            case col_type_UUID:
2,124✔
1269
                set(col_key, value.get<UUID>(), is_default);
2,124✔
1270
                break;
714✔
1271
            case col_type_Link:
696✔
1272
                set(col_key, value.get<ObjKey>(), is_default);
696✔
1273
                break;
87✔
1274
            case col_type_TypedLink:
51✔
1275
                set(col_key, value.get<ObjLink>(), is_default);
51✔
1276
                break;
54✔
1277
            default:
54✔
1278
                break;
54✔
1279
        }
396,789✔
1280
    }
396,789✔
1281
    return *this;
396,789✔
1282
}
396,753✔
1283

1284
template <>
1285
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1286
{
9,784,023✔
1287
    update_if_needed();
10,165,872✔
1288
    get_table()->check_column(col_key);
10,165,872✔
1289
    auto col_ndx = col_key.get_index();
10,165,872✔
1290

381,849✔
1291
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
9,784,023✔
1292
        throw InvalidArgument(ErrorCodes::TypeMismatch,
1293
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
1294

9,612,318✔
1295
    SearchIndex* index = m_table->get_search_index(col_key);
19,396,341✔
1296
    if (index && !m_key.is_unresolved()) {
19,396,341✔
1297
        index->set(m_key, value);
9,703,860✔
1298
    }
9,703,860✔
1299

9,612,318✔
1300
    Allocator& alloc = get_alloc();
9,784,023✔
1301
    alloc.bump_content_version();
9,784,023✔
1302
    Array fallback(alloc);
19,396,341✔
1303
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
19,396,341✔
1304
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
19,396,341✔
1305
    auto attr = col_key.get_attrs();
9,875,505✔
1306
    if (attr.test(col_attr_Nullable)) {
9,875,505✔
1307
        ArrayIntNull values(alloc);
10,874,409✔
1308
        values.set_parent(&fields, col_ndx.val + 1);
10,874,409✔
1309
        values.init_from_parent();
10,874,409✔
1310
        values.set(m_row_ndx, value);
10,874,409✔
1311
    }
10,874,409✔
1312
    else {
18,134,250✔
1313
        ArrayInteger values(alloc);
18,134,250✔
1314
        values.set_parent(&fields, col_ndx.val + 1);
18,134,250✔
1315
        values.init_from_parent();
9,783,978✔
1316
        values.set(m_row_ndx, value);
9,783,978✔
1317
    }
9,783,978✔
1318

1,262,046✔
1319
    sync(fields);
11,046,069✔
1320

8,350,272✔
1321
    if (Replication* repl = get_replication()) {
18,134,295✔
1322
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
13,455,408✔
1323
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
13,455,225✔
1324
    }
13,455,408✔
1325

8,350,272✔
1326
    return *this;
19,396,341✔
1327
}
19,396,341✔
1328

9,612,318✔
1329
Obj& Obj::add_int(ColKey col_key, int64_t value)
9,612,318✔
1330
{
5,140,863✔
1331
    update_if_needed();
5,140,863✔
1332
    get_table()->check_column(col_key);
5,140,863✔
1333
    auto col_ndx = col_key.get_index();
9,622,500✔
1334

9,612,318✔
1335
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
9,622,494✔
1336
        uint64_t ua = uint64_t(a);
10,176✔
1337
        uint64_t ub = uint64_t(b);
10,176✔
1338
        return int64_t(ua + ub);
20,355✔
1339
    };
20,355✔
1340

10,179✔
1341
    Allocator& alloc = get_alloc();
20,361✔
1342
    alloc.bump_content_version();
20,361✔
1343
    Array fallback(alloc);
20,361✔
1344
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
20,355✔
1345
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
20,355✔
1346

10,173✔
1347
    if (col_key.get_type() == col_type_Mixed) {
20,355✔
1348
        ArrayMixed values(alloc);
10,215✔
1349
        values.set_parent(&fields, col_ndx.val + 1);
10,215✔
1350
        values.init_from_parent();
10,215✔
1351
        Mixed old = values.get(m_row_ndx);
10,215✔
1352
        if (old.is_type(type_Int)) {
10,215✔
1353
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
10,212✔
1354
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
10,212✔
1355
                index->set(m_key, new_val);
10,179✔
1356
            }
36✔
1357
            values.set(m_row_ndx, Mixed(new_val));
69✔
1358
        }
69✔
1359
        else {
39✔
1360
            throw IllegalOperation("Value not an int");
39✔
1361
        }
36✔
1362
    }
10,179✔
1363
    else {
10,146✔
1364
        if (col_key.get_type() != col_type_Int)
10,146✔
1365
            throw IllegalOperation("Property not an int");
33✔
1366

33✔
1367
        auto attr = col_key.get_attrs();
10,149✔
1368
        if (attr.test(col_attr_Nullable)) {
10,149✔
1369
            ArrayIntNull values(alloc);
48✔
1370
            values.set_parent(&fields, col_ndx.val + 1);
10,188✔
1371
            values.init_from_parent();
10,188✔
1372
            util::Optional<int64_t> old = values.get(m_row_ndx);
10,188✔
1373
            if (old) {
45✔
1374
                auto new_val = add_wrap(*old, value);
10,185✔
1375
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
10,185✔
1376
                    index->set(m_key, new_val);
10,143✔
1377
                }
51✔
1378
                values.set(m_row_ndx, new_val);
93✔
1379
            }
93✔
1380
            else {
54✔
1381
                throw IllegalOperation("No prior value");
54✔
1382
            }
51✔
1383
        }
10,149✔
1384
        else {
10,101✔
1385
            ArrayInteger values(alloc);
10,101✔
1386
            values.set_parent(&fields, col_ndx.val + 1);
10,149✔
1387
            values.init_from_parent();
10,149✔
1388
            int64_t old = values.get(m_row_ndx);
10,104✔
1389
            auto new_val = add_wrap(old, value);
10,104✔
1390
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
10,104✔
1391
                index->set(m_key, new_val);
10,095✔
1392
            }
10,095✔
1393
            values.set(m_row_ndx, new_val);
20,193✔
1394
        }
20,193✔
1395
    }
20,238✔
1396

10,092✔
1397
    sync(fields);
20,268✔
1398

10,092✔
1399
    if (Replication* repl = get_replication()) {
10,179✔
1400
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
4,182✔
1401
    }
14,271✔
1402

10,092✔
1403
    return *this;
20,319✔
1404
}
20,361✔
1405

10,179✔
1406
template <>
10,173✔
1407
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
10,173✔
1408
{
135,066✔
1409
    update_if_needed();
135,066✔
1410
    get_table()->check_column(col_key);
141,063✔
1411
    ColKey::Idx col_ndx = col_key.get_index();
141,063✔
1412
    ColumnType type = col_key.get_type();
141,069✔
1413
    if (type != col_type_Link)
130,890✔
1414
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
1415
    TableRef target_table = get_target_table(col_key);
130,890✔
1416
    TableKey target_table_key = target_table->get_key();
261,765✔
1417
    if (target_key) {
261,765✔
1418
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
258,459✔
1419
        if (!ct->is_valid(target_key)) {
261,495✔
1420
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
130,881✔
1421
        }
130,881✔
1422
        if (target_table->is_embedded()) {
130,620✔
1423
            throw IllegalOperation(
130,875✔
1424
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
130,875✔
1425
        }
130,875✔
1426
    }
261,486✔
1427
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
261,486✔
1428

6✔
1429
    if (target_key != old_key) {
130,896✔
1430
        CascadeState state(CascadeState::Mode::Strong);
261,102✔
1431

1432
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
130,506✔
1433
        _update_if_needed();
130,506✔
1434

130,875✔
1435
        Allocator& alloc = get_alloc();
261,381✔
1436
        alloc.bump_content_version();
261,381✔
1437
        Array fallback(alloc);
261,381✔
1438
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
260,985✔
1439
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
260,985✔
1440
        ArrayKey values(alloc);
260,985✔
1441
        values.set_parent(&fields, col_ndx.val + 1);
260,985✔
1442
        values.init_from_parent();
260,985✔
1443

130,479✔
1444
        values.set(m_row_ndx, target_key);
260,985✔
1445

130,479✔
1446
        sync(fields);
260,985✔
1447

130,479✔
1448
        if (Replication* repl = get_replication()) {
260,985✔
1449
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
149,466✔
1450
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
149,466✔
1451
        }
149,466✔
1452

130,479✔
1453
        if (recurse)
260,985✔
1454
            target_table->remove_recursive(state);
130,608✔
1455
    }
260,985✔
1456

130,479✔
1457
    return *this;
149,832✔
1458
}
149,832✔
1459

18,942✔
1460
template <>
130,479✔
1461
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
130,479✔
1462
{
129✔
1463
    update_if_needed();
130,479✔
1464
    get_table()->check_column(col_key);
130,875✔
1465
    ColKey::Idx col_ndx = col_key.get_index();
130,875✔
1466
    ColumnType type = col_key.get_type();
130,875✔
1467
    if (type != col_type_TypedLink)
×
1468
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
1469
    m_table->get_parent_group()->validate(target_link);
1470

NEW
1471
    ObjLink old_link = get<ObjLink>(col_key); // Will update if needed
×
1472

1473
    if (target_link != old_link) {
×
1474
        CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All
×
1475
                                                                  : CascadeState::Mode::Strong);
×
1476

1477
        bool recurse = replace_backlink(col_key, old_link, target_link, state);
×
UNCOV
1478
        _update_if_needed();
×
1479

UNCOV
1480
        Allocator& alloc = get_alloc();
×
1481
        alloc.bump_content_version();
×
1482
        Array fallback(alloc);
×
1483
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
×
UNCOV
1484
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
1485
        ArrayTypedLink values(alloc);
×
1486
        values.set_parent(&fields, col_ndx.val + 1);
×
UNCOV
1487
        values.init_from_parent();
×
1488

1489
        values.set(m_row_ndx, target_link);
×
1490

1491
        sync(fields);
×
1492

1493
        if (Replication* repl = get_replication()) {
×
1494
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
×
1495
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
×
UNCOV
1496
        }
×
1497

UNCOV
1498
        if (recurse)
×
1499
            const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
UNCOV
1500
    }
×
1501

1502
    return *this;
×
1503
}
×
1504

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

24✔
1536
    if (target_key != old_key) {
6,885✔
1537
        CascadeState state;
6,885✔
1538

12✔
1539
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
6,873✔
1540
        _update_if_needed();
6,873✔
1541

24✔
1542
        Allocator& alloc = get_alloc();
6,885✔
1543
        alloc.bump_content_version();
13,671✔
1544
        Array fallback(alloc);
13,671✔
1545
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
13,671✔
1546
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
13,671✔
1547
        ArrayKey values(alloc);
13,671✔
1548
        values.set_parent(&fields, col_ndx.val + 1);
13,671✔
1549
        values.init_from_parent();
13,671✔
1550

6,810✔
1551
        values.set(m_row_ndx, target_key);
13,671✔
1552

6,810✔
1553
        sync(fields);
13,671✔
1554

6,810✔
1555
        if (Replication* repl = get_replication()) {
13,671✔
1556
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
13,347✔
1557
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
13,347✔
1558
        }
13,347✔
1559

6,810✔
1560
        if (recurse)
13,671✔
1561
            target_table->remove_recursive(state);
6,834✔
1562
    }
13,671✔
1563

6,810✔
1564
    return result;
13,347✔
1565
}
13,347✔
1566

6,486✔
1567
namespace {
6,810✔
1568
template <class T>
6,810✔
1569
inline void check_range(const T&)
24✔
1570
{
1,084,164✔
1571
}
1,084,164✔
1572
template <>
6,810✔
1573
inline void check_range(const StringData& val)
6,810✔
1574
{
1,221,714✔
1575
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
1,221,714✔
1576
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
3✔
1577
}
1,221,714✔
1578
template <>
1,063,665✔
1579
inline void check_range(const BinaryData& val)
1,063,665✔
1580
{
2,584,116✔
1581
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
2,584,116✔
1582
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
1,188,723✔
1583
}
3,772,836✔
1584
} // namespace
1,188,720✔
1585

1,188,720✔
1586
// helper functions for filtering out calls to set_spec()
1587
template <class T>
1588
inline void Obj::set_spec(T&, ColKey)
2,579,064✔
1589
{
6,240,480✔
1590
}
6,240,480✔
1591
template <>
2,579,064✔
1592
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1593
{
1,221,057✔
1594
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
1,221,057✔
1595
    Spec* spec = const_cast<Spec*>(&get_spec());
1,221,057✔
1596
    values.set_spec(spec, spec_ndx);
1,221,057✔
1597
}
4,865,601✔
1598

3,644,544✔
1599
#if REALM_ENABLE_GEOSPATIAL
1600

1601
template <>
1,189,017✔
1602
Obj& Obj::set(ColKey col_key, Geospatial value, bool)
1,189,017✔
1603
{
1,189,140✔
1604
    update_if_needed();
1,189,140✔
1605
    get_table()->check_column(col_key);
1,189,140✔
1606
    auto type = col_key.get_type();
123✔
1607

1608
    if (type != ColumnTypeTraits<Link>::column_id)
123✔
1609
        throw InvalidArgument(ErrorCodes::TypeMismatch,
3✔
1610
                              util::format("Property '%1' must be a link to set a Geospatial value",
3✔
1611
                                           get_table()->get_column_name(col_key)));
126✔
1612

123✔
1613
    Obj geo = get_linked_object(col_key);
243✔
1614
    if (!geo) {
243✔
1615
        geo = create_and_set_linked_object(col_key);
231✔
1616
    }
231✔
1617
    value.assign_to(geo);
123✔
1618
    return *this;
123✔
1619
}
123✔
1620

120✔
1621
template <>
120✔
1622
Obj& Obj::set(ColKey col_key, std::optional<Geospatial> value, bool)
120✔
1623
{
114✔
1624
    update_if_needed();
114✔
1625
    auto table = get_table();
126✔
1626
    table->check_column(col_key);
126✔
1627
    auto type = col_key.get_type();
126✔
1628
    auto attrs = col_key.get_attrs();
6✔
1629

1630
    if (type != ColumnTypeTraits<Link>::column_id)
6✔
1631
        throw InvalidArgument(ErrorCodes::TypeMismatch,
9✔
1632
                              util::format("Property '%1' must be a link to set a Geospatial value",
9✔
1633
                                           get_table()->get_column_name(col_key)));
9✔
1634
    if (!value && !attrs.test(col_attr_Nullable))
9✔
1635
        throw NotNullable(Group::table_name_to_class_name(table->get_name()), table->get_column_name(col_key));
6✔
1636

6✔
1637
    if (!value) {
9✔
1638
        set_null(col_key);
9✔
1639
    }
6✔
1640
    else {
3✔
1641
        Obj geo = get_linked_object(col_key);
3✔
1642
        if (!geo) {
3!
1643
            geo = create_and_set_linked_object(col_key);
×
1644
        }
3✔
1645
        value->assign_to(geo);
3✔
1646
    }
3✔
1647
    return *this;
6✔
1648
}
3✔
1649

1650
#endif
1651

1652
template <class T>
1653
Obj& Obj::set(ColKey col_key, T value, bool is_default)
1654
{
4,883,358✔
1655
    update_if_needed();
4,883,361✔
1656
    get_table()->check_column(col_key);
4,883,361✔
1657
    auto type = col_key.get_type();
4,883,358✔
1658
    auto attrs = col_key.get_attrs();
4,883,358✔
1659
    auto col_ndx = col_key.get_index();
4,883,358✔
1660

1661
    if (type != ColumnTypeTraits<T>::column_id)
4,883,358✔
1662
        throw InvalidArgument(ErrorCodes::TypeMismatch,
4,831,968✔
1663
                              util::format("Property not a %1", ColumnTypeTraits<T>::column_id));
4,831,968✔
1664
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
9,715,326!
1665
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
4,831,971✔
1666

4,831,968✔
1667
    check_range(value);
9,715,323✔
1668

4,831,968✔
1669
    SearchIndex* index = m_table->get_search_index(col_key);
9,715,323✔
1670
    if (index && !m_key.is_unresolved()) {
4,883,355!
1671
        index->set(m_key, value);
235,935✔
1672
    }
5,067,903✔
1673

3✔
1674
    Allocator& alloc = get_alloc();
9,715,320✔
1675
    alloc.bump_content_version();
9,715,320✔
1676
    Array fallback(alloc);
9,715,320✔
1677
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,715,320✔
1678
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,715,320✔
1679
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
5,119,386✔
1680
    LeafType values(alloc);
5,119,386✔
1681
    values.set_parent(&fields, col_ndx.val + 1);
9,715,320✔
1682
    set_spec<LeafType>(values, col_key);
9,715,320✔
1683
    values.init_from_parent();
9,715,320✔
1684
    values.set(m_row_ndx, value);
9,715,320✔
1685

4,831,965✔
1686
    sync(fields);
9,715,320✔
1687

4,831,965✔
1688
    if (Replication* repl = get_replication())
9,715,320✔
1689
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
7,825,218✔
1690
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
7,825,188✔
1691

4,831,965✔
1692
    return *this;
9,715,320✔
1693
}
9,715,320✔
1694

4,831,965✔
1695
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
4,831,965✔
1696
INSTANTIATE_OBJ_SET(bool);
4,831,965✔
1697
INSTANTIATE_OBJ_SET(StringData);
2,967,513✔
1698
INSTANTIATE_OBJ_SET(float);
2,967,513✔
1699
INSTANTIATE_OBJ_SET(double);
4,831,965✔
1700
INSTANTIATE_OBJ_SET(Decimal128);
4,831,965✔
1701
INSTANTIATE_OBJ_SET(Timestamp);
4,831,965✔
1702
INSTANTIATE_OBJ_SET(BinaryData);
1703
INSTANTIATE_OBJ_SET(ObjectId);
1704
INSTANTIATE_OBJ_SET(UUID);
1705

1706
void Obj::set_int(ColKey::Idx col_ndx, int64_t value)
1707
{
266,487✔
1708
    update_if_needed();
266,487✔
1709

1710
    Allocator& alloc = get_alloc();
266,487✔
1711
    alloc.bump_content_version();
266,487✔
1712
    Array fallback(alloc);
266,487✔
1713
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
266,487✔
1714
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
266,487✔
1715
    Array values(alloc);
533,412✔
1716
    values.set_parent(&fields, col_ndx.val + 1);
533,412✔
1717
    values.init_from_parent();
533,412✔
1718
    values.set(m_row_ndx, value);
533,412✔
1719

266,925✔
1720
    sync(fields);
533,412✔
1721
}
533,412✔
1722

266,925✔
1723
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
266,925✔
1724
{
268,500✔
1725
    update_if_needed();
268,500✔
1726

266,925✔
1727
    Allocator& alloc = get_alloc();
268,500✔
1728
    alloc.bump_content_version();
268,500✔
1729
    Array fallback(alloc);
268,500✔
1730
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
1,575✔
1731
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
1,575✔
1732
    ArrayMixed values(alloc);
3,156✔
1733
    values.set_parent(&fields, col_ndx.val + 1);
3,156✔
1734
    values.init_from_parent();
3,156✔
1735
    values.set(m_row_ndx, Mixed(value, type));
3,156✔
1736

1,581✔
1737
    sync(fields);
3,156✔
1738
}
3,156✔
1739

1,581✔
1740
void Obj::add_backlink(ColKey backlink_col_key, ObjKey origin_key)
1,581✔
1741
{
3,379,641✔
1742
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
3,379,641✔
1743
    Allocator& alloc = get_alloc();
3,379,641✔
1744
    alloc.bump_content_version();
3,379,641✔
1745
    Array fallback(alloc);
3,379,641✔
1746
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
3,379,641✔
1747

1748
    ArrayBacklink backlinks(alloc);
3,378,060✔
1749
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
6,755,334✔
1750
    backlinks.init_from_parent();
6,755,334✔
1751

3,377,274✔
1752
    backlinks.add(m_row_ndx, origin_key);
6,755,334✔
1753

3,377,274✔
1754
    sync(fields);
6,755,334✔
1755
}
6,755,334✔
1756

3,377,274✔
1757
bool Obj::remove_one_backlink(ColKey backlink_col_key, ObjKey origin_key)
3,377,274✔
1758
{
3,475,791✔
1759
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
3,475,791✔
1760
    Allocator& alloc = get_alloc();
3,475,791✔
1761
    alloc.bump_content_version();
3,475,791✔
1762
    Array fallback(alloc);
3,475,791✔
1763
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
3,475,791✔
1764

1765
    ArrayBacklink backlinks(alloc);
98,517✔
1766
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
196,992✔
1767
    backlinks.init_from_parent();
196,992✔
1768

98,475✔
1769
    bool ret = backlinks.remove(m_row_ndx, origin_key);
196,992✔
1770

98,475✔
1771
    sync(fields);
196,992✔
1772

98,475✔
1773
    return ret;
196,992✔
1774
}
196,992✔
1775

98,475✔
1776
template <class ValueType>
98,475✔
1777
inline void Obj::nullify_single_link(ColKey col, ValueType target)
98,475✔
1778
{
98,922✔
1779
    ColKey::Idx origin_col_ndx = col.get_index();
98,922✔
1780
    Allocator& alloc = get_alloc();
98,922✔
1781
    Array fallback(alloc);
98,922✔
1782
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
98,922✔
1783
    using ArrayType = typename ColumnTypeTraits<ValueType>::cluster_leaf_type;
447✔
1784
    ArrayType links(alloc);
447✔
1785
    links.set_parent(&fields, origin_col_ndx.val + 1);
447✔
1786
    links.init_from_parent();
891✔
1787
    // Ensure we are nullifying correct link
444✔
1788
    REALM_ASSERT(links.get(m_row_ndx) == target);
891✔
1789
    links.set(m_row_ndx, ValueType{});
891✔
1790
    sync(fields);
891✔
1791

444✔
1792
    if (Replication* repl = get_replication())
891✔
1793
        repl->nullify_link(m_table.unchecked_ptr(), col,
825✔
1794
                           m_key); // Throws
825✔
1795
}
891✔
1796

444✔
1797
template <>
444✔
1798
inline void Obj::nullify_single_link<Mixed>(ColKey col, Mixed target)
444✔
1799
{
453✔
1800
    ColKey::Idx origin_col_ndx = col.get_index();
453✔
1801
    Allocator& alloc = get_alloc();
387✔
1802
    Array fallback(alloc);
387✔
1803
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
453✔
1804
    ArrayMixed mixed(alloc);
9✔
1805
    mixed.set_parent(&fields, origin_col_ndx.val + 1);
9✔
1806
    mixed.init_from_parent();
9✔
1807
    auto val = mixed.get(m_row_ndx);
18✔
1808
    bool result = false;
18✔
1809
    if (val.is_type(type_TypedLink)) {
18✔
1810
        // Ensure we are nullifying correct link
9✔
1811
        result = (val == target);
15✔
1812
        mixed.set(m_row_ndx, Mixed{});
15✔
1813
        sync(fields);
15✔
1814

9✔
1815
        if (Replication* repl = get_replication())
15✔
1816
            repl->nullify_link(m_table.unchecked_ptr(), col,
9✔
1817
                               m_key); // Throws
9✔
1818
    }
12✔
1819
    else if (val.is_type(type_Dictionary)) {
9✔
1820
        Dictionary dict(*this, col);
9✔
1821
        result = dict.nullify(target.get_link());
9✔
1822
    }
9✔
1823
    else if (val.is_type(type_List)) {
6!
1824
        Lst<Mixed> list(*this, col);
×
1825
        result = list.nullify(target.get_link());
×
1826
    }
6✔
1827
    REALM_ASSERT(result);
12✔
1828
    static_cast<void>(result);
12✔
1829
}
12✔
1830

3✔
1831
void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) &&
1832
{
3,489✔
1833
    REALM_ASSERT(get_alloc().get_storage_version() == m_storage_version);
3,489✔
1834

1835
    struct LinkNullifier : public LinkTranslator {
3,498✔
1836
        LinkNullifier(Obj origin_obj, ColKey origin_col, ObjLink target)
3,498✔
1837
            : LinkTranslator(origin_obj, origin_col)
3,498✔
1838
            , m_target_link(target)
3,489✔
1839
        {
3,489✔
1840
        }
6,975✔
1841
        void on_list_of_links(LnkLst&) final
6,975✔
1842
        {
4,641✔
1843
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
4,641✔
1844
        }
4,641✔
1845
        void on_list_of_mixed(Lst<Mixed>& list) final
6,975✔
1846
        {
3,723✔
1847
            list.nullify(m_target_link);
3,723✔
1848
        }
3,723✔
1849
        void on_set_of_links(LnkSet&) final
6,975✔
1850
        {
4,617✔
1851
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,286✔
1852
        }
2,286✔
1853
        void on_set_of_mixed(Set<Mixed>&) final
6,975✔
1854
        {
3,636✔
1855
            nullify_set(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
387✔
1856
        }
387✔
1857
        void on_dictionary(Dictionary& dict) final
6,975✔
1858
        {
3,846✔
1859
            dict.nullify(m_target_link);
1,491✔
1860
        }
1,491✔
1861
        void on_link_property(ColKey origin_col_key) final
6,975✔
1862
        {
3,933✔
1863
            m_origin_obj.nullify_single_link<ObjKey>(origin_col_key, m_target_link.get_obj_key());
597✔
1864
        }
597✔
1865
        void on_mixed_property(ColKey origin_col_key) final
6,975✔
1866
        {
3,495✔
1867
            m_origin_obj.nullify_single_link<Mixed>(origin_col_key, Mixed{m_target_link});
369✔
1868
        }
369✔
1869

3,486✔
1870
    private:
6,975✔
1871
        ObjLink m_target_link;
3,933✔
1872
    } nullifier{*this, origin_col_key, target_link};
3,933✔
1873

3,486✔
1874
    nullifier.run();
6,975✔
1875

9✔
1876
    get_alloc().bump_content_version();
3,498✔
1877
}
6,975✔
1878

3,486✔
1879

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

1931
private:
1932
    Obj m_dest_orig;
1933
    Obj m_dest_replace;
1934
};
1935

1936
void Obj::handle_multiple_backlinks_during_schema_migration()
1937
{
27✔
1938
    REALM_ASSERT(!m_table->get_primary_key_column());
27✔
1939
    converters::EmbeddedObjectConverter embedded_obj_tracker;
27✔
1940
    auto copy_links = [&](ColKey col) {
54✔
1941
        auto opposite_table = m_table->get_opposite_table(col);
54✔
1942
        auto opposite_column = m_table->get_opposite_column(col);
54✔
1943
        auto backlinks = get_all_backlinks(col);
54✔
1944
        for (auto backlink : backlinks) {
30,159✔
1945
            // create a new obj
27✔
1946
            auto obj = m_table->create_object();
30,186✔
1947
            embedded_obj_tracker.track(*this, obj);
30,186✔
1948
            auto linking_obj = opposite_table->get_object(backlink);
30,213✔
1949
            // change incoming links to point to the newly created object
54✔
1950
            EmbeddedObjectLinkMigrator{linking_obj, opposite_column, *this, obj}.run();
30,213✔
1951
        }
30,213✔
1952
        embedded_obj_tracker.process_pending();
30,213✔
1953
        return IteratorControl::AdvanceToNext;
30,213✔
1954
    };
30,213✔
1955
    m_table->for_each_backlink_column(copy_links);
30,186✔
1956
}
30,186✔
1957

30,159✔
1958
LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const
30,159✔
1959
{
179,208✔
1960
    auto list = CollectionParent::get_listbase_ptr(col_key);
149,103✔
1961
    list->set_owner(*this, col_key);
149,103✔
1962
    return list;
149,103✔
1963
}
149,076✔
1964

27✔
1965
SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const
1966
{
21,750✔
1967
    auto set = CollectionParent::get_setbase_ptr(col_key);
171,417✔
1968
    set->set_owner(*this, col_key);
171,417✔
1969
    return set;
171,417✔
1970
}
171,417✔
1971

149,667✔
1972
Dictionary Obj::get_dictionary(ColKey col_key) const
1973
{
46,284✔
1974
    REALM_ASSERT(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed);
68,043✔
1975
    update_if_needed();
68,043✔
1976
    return Dictionary(Obj(*this), col_key);
68,043✔
1977
}
68,043✔
1978

21,759✔
1979
Obj& Obj::set_collection(ColKey col_key, CollectionType type)
1980
{
1,131✔
1981
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
46,668✔
1982
    if ((col_key.is_dictionary() && type == CollectionType::Dictionary) ||
46,668✔
1983
        (col_key.is_list() && type == CollectionType::List)) {
46,659✔
1984
        return *this;
45,552✔
1985
    }
45,552✔
1986
    update_if_needed();
1,116✔
1987
    Mixed new_val(0, type);
1,116✔
1988

1,137✔
1989
    if (type == CollectionType::Set) {
2,253✔
1990
        throw IllegalOperation("Set nested in Mixed is not supported");
1,137✔
1991
    }
1,137✔
1992

15✔
1993
    ArrayMixed arr(_get_alloc());
1,131✔
1994
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
2,238✔
1995
    arr.init_from_ref(ref);
2,238✔
1996
    auto old_val = arr.get(m_row_ndx);
2,238✔
1997

1,122✔
1998
    if (old_val != new_val) {
1,116✔
1999
        CascadeState state;
942✔
2000
        if (old_val.is_type(type_TypedLink)) {
2,064✔
2001
            remove_backlink(col_key, old_val.get<ObjLink>(), state);
1,122✔
2002
        }
1,122✔
2003
        else if (old_val.is_type(type_Dictionary)) {
2,064✔
2004
            Dictionary dict(*this, col_key);
1,179✔
2005
            dict.remove_backlinks(state);
1,179✔
2006
        }
1,179✔
2007
        else if (old_val.is_type(type_List)) {
1,833✔
2008
            Lst<Mixed> list(*this, col_key);
1,017✔
2009
            list.remove_backlinks(state);
69✔
2010
        }
69✔
2011

948✔
2012
        if (SearchIndex* index = m_table->get_search_index(col_key)) {
999✔
2013
            index->set(m_key, new_val);
60✔
2014
        }
60✔
2015

891✔
2016
        Allocator& alloc = _get_alloc();
1,011✔
2017
        alloc.bump_content_version();
1,011✔
2018

69✔
2019
        Array fallback(alloc);
1,890✔
2020
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
1,890✔
2021
        ArrayMixed values(alloc);
945✔
2022
        values.set_parent(&fields, col_key.get_index().val + 1);
945✔
2023
        values.init_from_parent();
1,890✔
2024

948✔
2025
        values.set(m_row_ndx, new_val);
1,890✔
2026
        values.set_key(m_row_ndx, generate_key(0x10));
1,890✔
2027

948✔
2028
        sync(fields);
1,890✔
2029

948✔
2030
        if (Replication* repl = get_replication())
1,890✔
2031
            repl->set(m_table.unchecked_ptr(), col_key, m_key, new_val); // Throws
1,842✔
2032
    }
1,890✔
2033

948✔
2034
    return *this;
2,064✔
2035
}
2,064✔
2036

948✔
2037
DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const
948✔
2038
{
1,179✔
2039
    return std::make_shared<Dictionary>(get_dictionary(col_key));
1,128✔
2040
}
1,179✔
2041

1,122✔
2042
DictionaryPtr Obj::get_dictionary_ptr(const Path& path) const
1,122✔
2043
{
1,230✔
2044
    return std::dynamic_pointer_cast<Dictionary>(get_collection_ptr(path));
108✔
2045
}
108✔
2046

231✔
2047
Dictionary Obj::get_dictionary(StringData col_name) const
231✔
2048
{
8,562✔
2049
    return get_dictionary(get_column_key(col_name));
8,331✔
2050
}
8,331✔
2051

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

2061
    while (level < path.size()) {
11,943✔
2062
        auto& path_elem = path[level];
6,177✔
2063
        Mixed ref;
6,177✔
2064
        if (collection->get_collection_type() == CollectionType::List) {
6,177✔
2065
            ref = collection->get_any(path_elem.get_ndx());
5,967✔
2066
        }
5,967✔
2067
        else {
5,976✔
2068
            ref = dynamic_cast<Dictionary*>(collection.get())->get(path_elem.get_key());
5,976✔
2069
        }
6,387✔
2070
        if (ref.is_type(type_List)) {
822✔
2071
            collection = collection->get_list(path_elem);
654✔
2072
        }
654✔
2073
        else if (ref.is_type(type_Dictionary)) {
369✔
2074
            collection = collection->get_dictionary(path_elem);
369✔
2075
        }
378✔
2076
        else {
210✔
2077
            throw InvalidArgument("Wrong path");
210✔
2078
        }
411✔
2079
        level++;
654✔
2080
    }
654✔
2081

168✔
2082
    return collection;
5,934✔
2083
}
5,934✔
2084

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

5,766✔
2092
    while (level < path.size()) {
5,577✔
2093
        auto& index = path[level];
315✔
2094
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
5,583✔
2095
            Mixed ref;
5,583✔
2096
            PathElement path_elem;
5,583✔
2097
            if (collection->get_collection_type() == CollectionType::List) {
5,583✔
2098
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
5,457✔
2099
                size_t ndx = list_of_mixed->find_index(index);
5,457✔
2100
                if (ndx != realm::not_found) {
5,766✔
2101
                    ref = list_of_mixed->get(ndx);
504✔
2102
                    path_elem = ndx;
504✔
2103
                }
504✔
2104
            }
504✔
2105
            else {
441✔
2106
                auto dict = dynamic_cast<Dictionary*>(collection.get());
315✔
2107
                size_t ndx = dict->find_index(index);
315✔
2108
                if (ndx != realm::not_found) {
315✔
2109
                    ref = dict->get_any(ndx);
309✔
2110
                    path_elem = dict->get_key(ndx).get_string();
309✔
2111
                }
309✔
2112
            }
315✔
2113
            return {ref, path_elem};
441✔
2114
        };
441✔
2115
        auto [ref, path_elem] = get_ref();
441✔
2116
        if (ref.is_type(type_List)) {
441✔
2117
            collection = collection->get_list(path_elem);
351✔
2118
        }
351✔
2119
        else if (ref.is_type(type_Dictionary)) {
204✔
2120
            collection = collection->get_dictionary(path_elem);
204✔
2121
        }
393✔
2122
        else {
321✔
2123
            return nullptr;
321✔
2124
        }
321✔
2125
        level++;
540✔
2126
    }
540✔
2127

84✔
2128
    return collection;
5,340✔
2129
}
5,346✔
2130

6✔
2131
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
6✔
2132
{
65,322✔
2133
    if (col_key.is_collection()) {
65,625✔
2134
        auto collection = CollectionParent::get_collection_ptr(col_key);
64,362✔
2135
        collection->set_owner(*this, col_key);
69,321✔
2136
        return collection;
69,321✔
2137
    }
69,321✔
2138
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
1,263✔
2139
    auto val = get<Mixed>(col_key);
1,263✔
2140
    if (val.is_type(type_List)) {
65,688✔
2141
        return std::make_shared<Lst<Mixed>>(*this, col_key);
65,139✔
2142
    }
63,876✔
2143
    REALM_ASSERT(val.is_type(type_Dictionary));
63,711✔
2144
    return std::make_shared<Dictionary>(*this, col_key);
63,711✔
2145
}
63,711✔
2146

1,263✔
2147
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
1,263✔
2148
{
1,461✔
2149
    return get_collection_ptr(get_column_key(col_name));
912✔
2150
}
912✔
2151

549✔
2152
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
549✔
2153
{
2,106✔
2154
    if (col_key.is_list()) {
1,557✔
2155
        return get_linklist_ptr(col_key);
1,497✔
2156
    }
1,695✔
2157
    else if (col_key.is_set()) {
258✔
2158
        return get_linkset_ptr(col_key);
237✔
2159
    }
39✔
2160
    else if (col_key.is_dictionary()) {
21✔
2161
        auto dict = get_dictionary(col_key);
1,578✔
2162
        return std::make_unique<DictionaryLinkValues>(dict);
1,578✔
2163
    }
1,518✔
2164
    return {};
1,497✔
2165
}
60✔
2166

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

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

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

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

144✔
2184
    REALM_ASSERT(ndx != realm::npos); // There has to be one
144✔
2185

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

2190
void Obj::assign_pk_and_backlinks(Obj& other)
2191
{
5,859✔
2192
    struct LinkReplacer : LinkTranslator {
5,859✔
2193
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
5,859✔
2194
            : LinkTranslator(origin, origin_col_key)
5,859✔
2195
            , m_dest_orig(dest_orig)
5,859✔
2196
            , m_dest_replace(dest_replace)
5,859✔
2197
        {
17,157✔
2198
        }
17,157✔
2199
        void on_list_of_links(LnkLst&) final
11,706✔
2200
        {
19,554✔
2201
            auto linklist = m_origin_obj.get_linklist(m_origin_col_key);
19,554✔
2202
            linklist.replace_link(m_dest_orig.get_key(), m_dest_replace.get_key());
19,554✔
2203
        }
19,554✔
2204
        void on_list_of_mixed(Lst<Mixed>& list) final
11,706✔
2205
        {
17,214✔
2206
            list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
17,214✔
2207
        }
5,904✔
2208
        void on_set_of_links(LnkSet&) final
19,566✔
2209
        {
13,776✔
2210
            replace_in_linkset(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
13,776✔
2211
        }
13,776✔
2212
        void on_set_of_mixed(Set<Mixed>&) final
11,706✔
2213
        {
5,922✔
2214
            replace_in_linkset<Mixed>(m_origin_obj, m_origin_col_key, m_dest_orig.get_link(),
132✔
2215
                                      m_dest_replace.get_link());
132✔
2216
        }
5,922✔
2217
        void on_dictionary(Dictionary& dict) final
11,706✔
2218
        {
195✔
2219
            dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
195✔
2220
        }
5,973✔
2221
        void on_link_property(ColKey col) final
11,706✔
2222
        {
3,165✔
2223
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
3,165✔
2224
            // Handle links as plain integers. Backlinks has been taken care of.
75✔
2225
            // Be careful here - links are stored as value + 1 so that null link (-1) will be 0
5,847✔
2226
            auto new_key = m_dest_replace.get_key();
8,937✔
2227
            m_origin_obj.set_int(col.get_index(), new_key.value + 1);
3,216✔
2228
            if (Replication* repl = m_origin_obj.get_replication())
3,216✔
2229
                repl->set(m_origin_obj.get_table().unchecked_ptr(), col, m_origin_obj.get_key(), new_key);
8,913✔
2230
        }
8,937✔
2231
        void on_mixed_property(ColKey col) final
8,949✔
2232
        {
3,123✔
2233
            auto val = m_origin_obj.get_any(col);
3,123✔
2234
            if (val.is_type(type_Dictionary)) {
3,123✔
2235
                Dictionary dict(m_origin_obj, m_origin_col_key);
3,096✔
2236
                dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
3,096✔
2237
            }
3,072✔
2238
            else if (val.is_type(type_List)) {
3,117✔
2239
                Lst<Mixed> list(m_origin_obj, m_origin_col_key);
5,853✔
2240
                list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
5,853✔
2241
            }
39✔
2242
            else {
54✔
2243
                REALM_ASSERT(val.is_null() || val.get_link().get_obj_key() == m_dest_orig.get_key());
27✔
2244
                m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
27✔
2245
            }
27✔
2246
        }
60✔
2247

6✔
2248
    private:
5,865✔
2249
        const Obj& m_dest_orig;
5,865✔
2250
        const Obj& m_dest_replace;
5,880✔
2251
    };
5,880✔
2252

21✔
2253
    REALM_ASSERT(get_table() == other.get_table());
5,880✔
2254
    if (auto col_pk = m_table->get_primary_key_column()) {
5,892✔
2255
        Mixed val = other.get_any(col_pk);
11,625✔
2256
        this->set_any(col_pk, val);
11,625✔
2257
    }
11,625✔
2258
    auto nb_tombstones = m_table->m_tombstones->size();
11,706✔
2259

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

5,766✔
2266
        auto t = m_table->get_opposite_table(col);
11,061✔
2267
        auto c = m_table->get_opposite_column(col);
11,061✔
2268
        auto backlinks = other.get_all_backlinks(col);
11,061✔
2269

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

5,214✔
2279
        for (auto bl : backlinks) {
22,038✔
2280
            auto linking_obj = t->get_object(bl);
22,038✔
2281
            LinkReplacer replacer{linking_obj, c, other, *this};
22,038✔
2282
            replacer.run();
22,038✔
2283
        }
22,038✔
2284
        return IteratorControl::AdvanceToNext;
10,095✔
2285
    };
10,095✔
2286
    m_table->for_each_backlink_column(copy_links);
11,073✔
2287
}
23,016✔
2288

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

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

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

19,539✔
2321
    sync(fields);
39,294✔
2322
}
39,294✔
2323

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

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

1,275✔
2340
    sync(fields);
2,571✔
2341
}
2,571✔
2342

1,275✔
2343
Obj& Obj::set_null(ColKey col_key, bool is_default)
1,275✔
2344
{
22,620✔
2345
    ColumnType col_type = col_key.get_type();
22,620✔
2346
    // Links need special handling
1,275✔
2347
    if (col_type == col_type_Link) {
22,620✔
2348
        set(col_key, null_key);
1,506✔
2349
    }
1,506✔
2350
    else if (col_type == col_type_Mixed) {
21,114✔
2351
        set(col_key, Mixed{});
33✔
2352
    }
21,144✔
2353
    else {
42,192✔
2354
        auto attrs = col_key.get_attrs();
42,192✔
2355
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
42,192✔
2356
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
264✔
2357
                              m_table->get_column_name(col_key));
264✔
2358
        }
20,913✔
2359

33✔
2360
        update_if_needed();
21,081✔
2361

20,847✔
2362
        SearchIndex* index = m_table->get_search_index(col_key);
41,895✔
2363
        if (index && !m_key.is_unresolved()) {
41,895✔
2364
            index->set(m_key, null{});
2,415✔
2365
        }
2,415✔
2366

33✔
2367
        switch (col_type) {
41,862✔
2368
            case col_type_Int:
23,775✔
2369
                do_set_null<ArrayIntNull>(col_key);
23,775✔
2370
                break;
23,775✔
2371
            case col_type_Bool:
23,727✔
2372
                do_set_null<ArrayBoolNull>(col_key);
5,286✔
2373
                break;
5,286✔
2374
            case col_type_Float:
23,955✔
2375
                do_set_null<ArrayFloatNull>(col_key);
23,955✔
2376
                break;
6,111✔
2377
            case col_type_Double:
6,150✔
2378
                do_set_null<ArrayDoubleNull>(col_key);
6,150✔
2379
                break;
5,973✔
2380
            case col_type_ObjectId:
4,476✔
2381
                do_set_null<ArrayObjectIdNull>(col_key);
4,476✔
2382
                break;
4,719✔
2383
            case col_type_String:
4,332✔
2384
                do_set_null<ArrayString>(col_key);
4,332✔
2385
                break;
4,473✔
2386
            case col_type_Binary:
3,801✔
2387
                do_set_null<ArrayBinary>(col_key);
3,801✔
2388
                break;
2,244✔
2389
            case col_type_Timestamp:
2,862✔
2390
                do_set_null<ArrayTimestamp>(col_key);
2,862✔
2391
                break;
2,517✔
2392
            case col_type_Decimal:
2,190✔
2393
                do_set_null<ArrayDecimal128>(col_key);
2,190✔
2394
                break;
1,539✔
2395
            case col_type_UUID:
3,720✔
2396
                do_set_null<ArrayUUIDNull>(col_key);
3,720✔
2397
                break;
4,323✔
2398
            case col_type_Mixed:
1,227✔
2399
            case col_type_Link:
1,227✔
2400
            case col_type_BackLink:
915✔
2401
            case col_type_TypedLink:
915✔
2402
                REALM_UNREACHABLE();
915✔
2403
        }
24,225✔
2404
    }
24,225✔
2405

3,177✔
2406
    if (Replication* repl = get_replication())
21,315✔
2407
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
11,160✔
2408
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
11,151✔
2409

2410
    return *this;
21,315✔
2411
}
42,159✔
2412

20,814✔
2413

21,111✔
2414
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
21,111✔
2415
{
4,291,143✔
2416
    return get_table()->spec_ndx2colkey(col_ndx);
4,291,143✔
2417
}
4,301,379✔
2418

21,078✔
2419
size_t Obj::colkey2spec_ndx(ColKey key)
21,111✔
2420
{
873✔
2421
    return get_table()->colkey2spec_ndx(key);
873✔
2422
}
873✔
2423

4,300,326✔
2424
ColKey Obj::get_primary_key_column() const
4,300,326✔
2425
{
6,929,724✔
2426
    return m_table->get_primary_key_column();
2,629,398✔
2427
}
2,629,398✔
2428

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

2,637,825✔
2434
ref_type Obj::get_collection_ref(Index index, CollectionType type) const
2,637,825✔
2435
{
4,243,974✔
2436
    if (index.is_collection()) {
1,606,149✔
2437
        return to_ref(_get<int64_t>(index.get_index()));
1,599,627✔
2438
    }
1,606,020✔
2439
    if (check_index(index)) {
12,915✔
2440
        auto val = _get<Mixed>(index.get_index());
12,873✔
2441
        if (val.is_type(DataType(int(type)))) {
6,480✔
2442
            return val.get_ref();
6,477✔
2443
        }
1,628,580✔
2444
        throw realm::IllegalOperation(util::format("Not a %1", type));
1,622,106✔
2445
    }
1,614,459✔
2446
    throw StaleAccessor("This collection is no more");
1,614,498✔
2447
}
7,689✔
2448

7,602✔
2449
bool Obj::check_collection_ref(Index index, CollectionType type) const noexcept
7,602✔
2450
{
434,094✔
2451
    if (index.is_collection()) {
434,094✔
2452
        return true;
425,226✔
2453
    }
425,226✔
2454
    if (check_index(index)) {
1,317✔
2455
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
1,314✔
2456
    }
1,269✔
2457
    return false;
3✔
2458
}
426,471✔
2459

426,468✔
2460
void Obj::set_collection_ref(Index index, ref_type ref, CollectionType type)
425,196✔
2461
{
680,403✔
2462
    if (index.is_collection()) {
256,479✔
2463
        set_int(index.get_index(), from_ref(ref));
254,901✔
2464
        return;
254,901✔
2465
    }
253,635✔
2466
    set_ref(index.get_index(), ref, type);
1,578✔
2467
}
1,575✔
2468

2469
} // namespace realm
255,657✔
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