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

realm / realm-core / jonathan.reams_3093

12 Mar 2024 04:31PM UTC coverage: 92.212% (+1.3%) from 90.928%
jonathan.reams_3093

push

Evergreen

web-flow
RCORE-1490 Sync collections in Mixed and nested collections (#7353)

* Update the merge nested rules to handle sentinel instructions on conflicting data types

* Allow syncing nested collections and collections in mixed

* Add integration tests with the legacy sync server

* Do not expose set_collection on a dictionary

* Fix OT rule for add_int on Mixed fields

* Small fixes

* Fix linting errors

* Apply Clear instructions received from the server on nested collections

* Add OT rule Clear vs Update

* Reduce boilerplate in collections in mixed unit test (#7421)

* Document unit-tests

* Changes after code review

* Fix test comments & update changelog

---------

Co-authored-by: Jonathan Reams <jbreams@mongodb.com>

99588 of 175432 branches covered (56.77%)

1350 of 1354 new or added lines in 7 files covered. (99.7%)

26 existing lines in 5 files now uncovered.

245244 of 265957 relevant lines covered (92.21%)

8803432.4 hits per line

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

91.17
/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
{
137,817✔
56
    coll.set_owner(obj, origin_col_key);
137,817✔
57
    return coll.find_first(link);
137,817✔
58
}
137,817✔
59

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

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

1,155✔
68
    if (Replication* repl = obj.get_replication()) {
3,465✔
69
        if constexpr (std::is_same_v<T, ObjKey>) {
3,204✔
70
            repl->link_list_nullify(link_list, ndx); // Throws
3,204✔
71
        }
2,136✔
72
        else {
2,136✔
73
            repl->list_erase(link_list, ndx); // Throws
2,136✔
74
        }
2,136✔
75
    }
3,204✔
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());
3,465✔
81
    tree.erase(ndx);
3,465✔
82
}
3,465✔
83

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

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

1,281✔
92
    if (Replication* repl = obj.get_replication()) {
3,843✔
93
        repl->set_erase(link_set, ndx, target); // Throws
3,825✔
94
    }
3,825✔
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());
3,843✔
100
    tree.erase(ndx);
3,843✔
101
}
3,843✔
102

103
} // namespace
104

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

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

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

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

127
const ClusterTree* Obj::get_tree_top() const
128
{
58,114,806✔
129
    if (m_key.is_unresolved()) {
58,114,806✔
130
        return m_table.unchecked_ptr()->m_tombstones.get();
67,149✔
131
    }
67,149✔
132
    else {
58,047,657✔
133
        return &m_table.unchecked_ptr()->m_clusters;
58,047,657✔
134
    }
58,047,657✔
135
}
58,114,806✔
136

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

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

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

158
StableIndex Obj::build_index(ColKey col_key) const
159
{
3,604,191✔
160
    if (col_key.is_collection()) {
3,604,191✔
161
        return {col_key, 0};
3,595,761✔
162
    }
3,595,761✔
163
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
8,430✔
164
    _update_if_needed();
8,430✔
165
    ArrayMixed values(_get_alloc());
8,430✔
166
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
8,430✔
167
    values.init_from_ref(ref);
8,430✔
168
    auto key = values.get_key(m_row_ndx);
8,430✔
169
    return {col_key, key};
8,430✔
170
}
1,206,795✔
171

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

185
Replication* Obj::get_replication() const
186
{
44,343,630✔
187
    return m_table->get_repl();
44,343,630✔
188
}
44,343,630✔
189

190
bool Obj::compare_values(Mixed val1, Mixed val2, ColKey ck, Obj other, StringData col_name) const
191
{
7,326✔
192
    if (val1.is_null()) {
7,326✔
193
        if (!val2.is_null())
90✔
194
            return false;
×
195
    }
4,854✔
196
    else {
7,236✔
197
        if (val1.get_type() != val2.get_type())
7,236✔
198
            return false;
×
199
        if (val1.is_type(type_Link, type_TypedLink)) {
7,236✔
200
            auto o1 = _get_linked_object(ck, val1);
342✔
201
            auto o2 = other._get_linked_object(col_name, val2);
342✔
202
            if (o1.m_table->is_embedded()) {
342✔
203
                return o1 == o2;
216✔
204
            }
216✔
205
            else {
126✔
206
                return o1.get_primary_key() == o2.get_primary_key();
126✔
207
            }
126✔
208
        }
4,710✔
209
        else {
6,894✔
210
            const auto type = val1.get_type();
6,894✔
211
            if (type == type_List) {
6,894✔
212
                Lst<Mixed> lst1(*this, ck);
9✔
213
                Lst<Mixed> lst2(other, other.get_column_key(col_name));
9✔
214
                return compare_list_in_mixed(lst1, lst2, ck, other, col_name);
9✔
215
            }
9✔
216
            else if (type == type_Set) {
6,885✔
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) {
6,885✔
222
                Dictionary dict1(*this, ck);
9✔
223
                Dictionary dict2(other, other.get_column_key(col_name));
9✔
224
                return compare_dict_in_mixed(dict1, dict2, ck, other, col_name);
9✔
225
            }
9✔
226
            return val1 == val2;
6,876✔
227
        }
6,882✔
228
    }
7,236✔
229
    return true;
90✔
230
}
2,502✔
231

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

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

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

12✔
242
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
36✔
243
            DummyParent parent(get_table(), m2.get_ref());
9✔
244
            Lst<Mixed> list(parent, 0);
9✔
245
            return compare_list_in_mixed(*val1.get_list(i), list, ck, other, col_name);
9✔
246
        }
9✔
247
        else if (m1.is_type(type_Dictionary) && m2.is_type(type_Dictionary)) {
27!
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)) {
27✔
253
            return false;
×
254
        }
×
255
    }
36✔
256
    return true;
12✔
257
}
18✔
258

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

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

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

6✔
271
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
18!
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)) {
18✔
277
            DummyParent parent(get_table(), m2.get_ref());
9✔
278
            Dictionary dict(parent, 0);
9✔
279
            return compare_dict_in_mixed(*val1.get_dictionary(k1.get_string()), dict, ck, other, col_name);
9✔
280
        }
9✔
281
        else if (!compare_values(m1, m2, ck, other, col_name)) {
9✔
282
            return false;
×
283
        }
×
284
    }
18✔
285
    return true;
12✔
286
}
18✔
287

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

2,439✔
296
        if (!ck.is_collection()) {
7,317✔
297
            if (!compare(get_any(ck), other.get_any(col_name)))
6,723✔
298
                return false;
36✔
299
        }
2,637✔
300
        else {
594✔
301
            auto coll1 = get_collection_ptr(ck);
594✔
302
            auto coll2 = other.get_collection_ptr(col_name);
594✔
303
            size_t sz = coll1->size();
594✔
304
            if (coll2->size() != sz)
594✔
305
                return false;
×
306
            if (ck.is_list() || ck.is_set()) {
594✔
307
                for (size_t i = 0; i < sz; i++) {
675✔
308
                    if (!compare(coll1->get_any(i), coll2->get_any(i)))
351✔
309
                        return false;
×
310
                }
351✔
311
            }
324✔
312
            if (ck.is_dictionary()) {
594✔
313
                auto dict1 = dynamic_cast<Dictionary*>(coll1.get());
270✔
314
                auto dict2 = dynamic_cast<Dictionary*>(coll2.get());
270✔
315
                for (size_t i = 0; i < sz; i++) {
477✔
316
                    auto [key, value] = dict1->get_pair(i);
216✔
317
                    auto val2 = dict2->try_get(key);
216✔
318
                    if (!val2)
216✔
319
                        return false;
×
320
                    if (!compare(value, *val2))
216✔
321
                        return false;
9✔
322
                }
216✔
323
            }
270✔
324
        }
594✔
325
    }
7,317✔
326
    return true;
4,551✔
327
}
4,581✔
328

329
bool Obj::is_valid() const noexcept
330
{
2,240,214✔
331
    // Cache valid state. If once invalid, it can never become valid again
781,452✔
332
    if (m_valid)
2,240,214✔
333
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
2,238,609✔
334
                                    m_table.unchecked_ptr()->is_valid(m_key));
1,578,708✔
335

781,452✔
336
    return m_valid;
2,240,214✔
337
}
2,240,214✔
338

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

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

349
ColKey Obj::get_column_key(StringData col_name) const
350
{
5,662,599✔
351
    return get_table()->get_column_key(col_name);
5,662,599✔
352
}
5,662,599✔
353

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

359
TableRef Obj::get_target_table(ColKey col_key) const
360
{
10,639,545✔
361
    if (m_table) {
10,639,647✔
362
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
10,638,258✔
363
    }
10,638,258✔
364
    else {
2,147,485,036✔
365
        return TableRef();
2,147,485,036✔
366
    }
2,147,485,036✔
367
}
10,639,545✔
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
{
1,207,962✔
381
    // Get a new object from key
399,447✔
382
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
1,207,962✔
383

399,447✔
384
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
1,207,962✔
385
    if (changes) {
1,207,962✔
386
        m_mem = new_obj.m_mem;
154,752✔
387
        m_row_ndx = new_obj.m_row_ndx;
154,752✔
388
        CollectionParent::m_parent_version++;
154,752✔
389
    }
154,752✔
390
    // Always update versions
399,447✔
391
    m_storage_version = new_obj.m_storage_version;
1,207,962✔
392
    m_table = new_obj.m_table;
1,207,962✔
393
    return changes;
1,207,962✔
394
}
1,207,962✔
395

396
inline bool Obj::_update_if_needed() const
397
{
117,436,782✔
398
    auto current_version = _get_alloc().get_storage_version();
117,436,782✔
399
    if (current_version != m_storage_version) {
117,436,782✔
400
        return update();
23,331✔
401
    }
23,331✔
402
    return false;
117,413,451✔
403
}
117,421,602✔
404

405
UpdateStatus Obj::update_if_needed_with_status() const
406
{
54,101,052✔
407
    if (!m_table) {
54,101,052✔
408
        // Table deleted
171✔
409
        return UpdateStatus::Detached;
513✔
410
    }
513✔
411

18,020,397✔
412
    auto current_version = get_alloc().get_storage_version();
54,100,539✔
413
    if (current_version != m_storage_version) {
54,100,539✔
414
        ClusterNode::State state = get_tree_top()->try_get(m_key);
1,044,060✔
415

346,659✔
416
        if (!state) {
1,044,060✔
417
            // Object deleted
2,589✔
418
            return UpdateStatus::Detached;
7,767✔
419
        }
7,767✔
420

344,070✔
421
        // Always update versions
344,070✔
422
        m_storage_version = current_version;
1,036,293✔
423
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
1,036,293✔
424
            m_mem = state.mem;
200,037✔
425
            m_row_ndx = state.index;
200,037✔
426
            CollectionParent::m_parent_version++;
200,037✔
427
            return UpdateStatus::Updated;
200,037✔
428
        }
200,037✔
429
    }
36,265,689✔
430
    return UpdateStatus::NoChange;
53,892,735✔
431
}
53,962,020✔
432

433
template <class T>
434
T Obj::get(ColKey col_key) const
435
{
126,462,192✔
436
    m_table->check_column(col_key);
126,462,192✔
437
    ColumnType type = col_key.get_type();
126,462,192✔
438
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
126,462,192✔
439

42,165,927✔
440
    return _get<T>(col_key.get_index());
126,462,192✔
441
}
126,462,192✔
442

443
template UUID Obj::_get(ColKey::Idx col_ndx) const;
444
template util::Optional<UUID> Obj::_get(ColKey::Idx col_ndx) const;
445

446
#if REALM_ENABLE_GEOSPATIAL
447

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

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

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

471
#endif
472

473
template <class T>
474
T Obj::_get(ColKey::Idx col_ndx) const
475
{
116,460,096✔
476
    _update_if_needed();
116,460,096✔
477

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

38,851,824✔
482
    return values.get(m_row_ndx);
116,460,096✔
483
}
116,460,096✔
484

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

29,808✔
491
    return values.get(m_row_ndx);
107,523✔
492
}
107,523✔
493

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

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

161,664✔
507
    ArrayKey values(_get_alloc());
485,262✔
508
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
485,262✔
509
    values.init_from_ref(ref);
485,262✔
510

161,664✔
511
    ObjKey k = values.get(m_row_ndx);
485,262✔
512
    return k.is_unresolved() ? ObjKey{} : k;
482,151✔
513
}
485,262✔
514

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

15✔
521
    _update_if_needed();
45✔
522

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

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

164,253✔
532
    return values.get(m_row_ndx);
492,672✔
533
}
492,672✔
534

535
template <>
536
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
537
{
196,574,511✔
538
    // manual inline of _update_if_needed():
61,386,195✔
539
    auto& alloc = _get_alloc();
196,574,511✔
540
    auto current_version = alloc.get_storage_version();
196,574,511✔
541
    if (current_version != m_storage_version) {
196,574,511✔
542
        update();
212,463✔
543
    }
212,463✔
544

61,386,195✔
545
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
196,574,511✔
546
    char* header = alloc.translate(ref);
196,574,511✔
547
    int width = Array::get_width_from_header(header);
196,574,511✔
548
    char* data = Array::get_data_from_header(header);
196,574,511✔
549
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
196,574,511✔
550
}
×
551

552
template <>
553
int64_t Obj::get<int64_t>(ColKey col_key) const
554
{
178,106,676✔
555
    m_table->check_column(col_key);
178,106,676✔
556
    ColumnType type = col_key.get_type();
178,106,676✔
557
    REALM_ASSERT(type == col_type_Int);
178,106,676✔
558

55,893,471✔
559
    if (col_key.get_attrs().test(col_attr_Nullable)) {
178,106,676✔
560
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
11,682✔
561
        if (!val) {
11,682✔
562
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
9✔
563
        }
9✔
564
        return *val;
11,673✔
565
    }
11,676✔
566
    else {
178,094,994✔
567
        return _get<int64_t>(col_key.get_index());
178,094,994✔
568
    }
178,094,994✔
569
}
178,106,676✔
570

571
template <>
572
bool Obj::get<bool>(ColKey col_key) const
573
{
836,325✔
574
    m_table->check_column(col_key);
836,325✔
575
    ColumnType type = col_key.get_type();
836,325✔
576
    REALM_ASSERT(type == col_type_Bool);
836,325✔
577

303,858✔
578
    if (col_key.get_attrs().test(col_attr_Nullable)) {
836,325✔
579
        auto val = _get<util::Optional<bool>>(col_key.get_index());
45✔
580
        if (!val) {
45✔
581
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
×
582
        }
×
583
        return *val;
45✔
584
    }
45✔
585
    else {
836,280✔
586
        return _get<bool>(col_key.get_index());
836,280✔
587
    }
836,280✔
588
}
836,325✔
589

590
template <>
591
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
592
{
8,015,061✔
593
    // manual inline of _update_if_needed():
2,696,370✔
594
    auto& alloc = _get_alloc();
8,015,061✔
595
    auto current_version = alloc.get_storage_version();
8,015,061✔
596
    if (current_version != m_storage_version) {
8,015,061✔
597
        update();
8,661✔
598
    }
8,661✔
599

2,696,370✔
600
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
8,015,061✔
601
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
8,015,061✔
602
    auto& spec = get_spec();
8,015,061✔
603
    if (spec.is_string_enum_type(spec_ndx)) {
8,015,061✔
604
        ArrayString values(get_alloc());
1,593,474✔
605
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
1,593,474✔
606
        values.init_from_ref(ref);
1,593,474✔
607

527,061✔
608
        return values.get(m_row_ndx);
1,593,474✔
609
    }
1,593,474✔
610
    else {
6,421,587✔
611
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
6,421,587✔
612
    }
6,421,587✔
613
}
8,015,061✔
614

615
template <>
616
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
617
{
10,320,147✔
618
    // manual inline of _update_if_needed():
3,440,028✔
619
    auto& alloc = _get_alloc();
10,320,147✔
620
    auto current_version = alloc.get_storage_version();
10,320,147✔
621
    if (current_version != m_storage_version) {
10,320,147✔
622
        update();
198✔
623
    }
198✔
624

3,440,028✔
625
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
10,320,147✔
626
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
10,320,147✔
627
}
10,320,147✔
628

629
Mixed Obj::get_any(ColKey col_key) const
630
{
21,599,730✔
631
    m_table->check_column(col_key);
21,599,730✔
632
    auto col_ndx = col_key.get_index();
21,599,730✔
633
    if (col_key.is_collection()) {
21,599,730✔
634
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
10,386✔
635
        return Mixed(ref, get_table()->get_collection_type(col_key));
10,386✔
636
    }
10,386✔
637
    switch (col_key.get_type()) {
21,589,344✔
638
        case col_type_Int:
14,253,870✔
639
            if (col_key.get_attrs().test(col_attr_Nullable)) {
14,253,870✔
640
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
688,539✔
641
            }
688,539✔
642
            else {
13,565,331✔
643
                return Mixed{_get<int64_t>(col_ndx)};
13,565,331✔
644
            }
13,565,331✔
645
        case col_type_Bool:
295,284✔
646
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
295,284✔
647
        case col_type_Float:
12,003✔
648
            return Mixed{_get<util::Optional<float>>(col_ndx)};
12,003✔
649
        case col_type_Double:
29,418✔
650
            return Mixed{_get<util::Optional<double>>(col_ndx)};
29,418✔
651
        case col_type_String:
6,196,542✔
652
            return Mixed{_get<String>(col_ndx)};
6,196,542✔
653
        case col_type_Binary:
2,400✔
654
            return Mixed{_get<Binary>(col_ndx)};
2,400✔
655
        case col_type_Mixed:
21,246✔
656
            return _get<Mixed>(col_ndx);
21,246✔
657
        case col_type_Timestamp:
236,094✔
658
            return Mixed{_get<Timestamp>(col_ndx)};
236,094✔
659
        case col_type_Decimal:
11,790✔
660
            return Mixed{_get<Decimal128>(col_ndx)};
11,790✔
661
        case col_type_ObjectId:
412,647✔
662
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
412,647✔
663
        case col_type_UUID:
78,468✔
664
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
78,468✔
665
        case col_type_Link:
63,330✔
666
            return Mixed{_get<ObjKey>(col_ndx)};
63,330✔
667
        default:
✔
668
            REALM_UNREACHABLE();
669
            break;
×
670
    }
6,616,944✔
671
    return {};
×
672
}
6,616,944✔
673

674
Mixed Obj::get_primary_key() const
675
{
230,157✔
676
    auto col = m_table->get_primary_key_column();
230,157✔
677
    return col ? get_any(col) : Mixed{get_key()};
230,034✔
678
}
230,157✔
679

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

688
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
689
{
40,101✔
690
    Obj obj;
40,101✔
691
    if (!link.is_null()) {
40,101✔
692
        TableRef target_table;
27,528✔
693
        if (link.is_type(type_TypedLink)) {
27,528✔
694
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
486✔
695
        }
486✔
696
        else {
27,042✔
697
            target_table = get_target_table(link_col_key);
27,042✔
698
        }
27,042✔
699
        obj = target_table->get_object(link.get<ObjKey>());
27,528✔
700
    }
27,528✔
701
    return obj;
40,101✔
702
}
40,101✔
703

704
Obj Obj::get_parent_object() const
705
{
18✔
706
    Obj obj;
18✔
707
    update_if_needed();
18✔
708

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

6✔
721
    return obj;
18✔
722
}
18✔
723

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

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

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

748
bool Obj::is_null(ColKey col_key) const
749
{
6,878,172✔
750
    update_if_needed();
6,878,172✔
751
    ColumnAttrMask attr = col_key.get_attrs();
6,878,172✔
752
    ColKey::Idx col_ndx = col_key.get_index();
6,878,172✔
753
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
6,878,172✔
754
        switch (col_key.get_type()) {
1,996,824✔
755
            case col_type_Int:
601,251✔
756
                return do_is_null<ArrayIntNull>(col_ndx);
601,251✔
757
            case col_type_Bool:
380,430✔
758
                return do_is_null<ArrayBoolNull>(col_ndx);
380,430✔
759
            case col_type_Float:
14,112✔
760
                return do_is_null<ArrayFloatNull>(col_ndx);
14,112✔
761
            case col_type_Double:
4,815✔
762
                return do_is_null<ArrayDoubleNull>(col_ndx);
4,815✔
763
            case col_type_String:
522,351✔
764
                return do_is_null<ArrayString>(col_ndx);
522,351✔
765
            case col_type_Binary:
315✔
766
                return do_is_null<ArrayBinary>(col_ndx);
315✔
767
            case col_type_Mixed:
1,416✔
768
                return do_is_null<ArrayMixed>(col_ndx);
1,416✔
769
            case col_type_Timestamp:
435,879✔
770
                return do_is_null<ArrayTimestamp>(col_ndx);
435,879✔
771
            case col_type_Link:
32,799✔
772
                return do_is_null<ArrayKey>(col_ndx);
32,799✔
773
            case col_type_ObjectId:
261✔
774
                return do_is_null<ArrayObjectIdNull>(col_ndx);
261✔
775
            case col_type_Decimal:
2,934✔
776
                return do_is_null<ArrayDecimal128>(col_ndx);
2,934✔
777
            case col_type_UUID:
261✔
778
                return do_is_null<ArrayUUIDNull>(col_ndx);
261✔
779
            default:
✔
780
                REALM_UNREACHABLE();
781
        }
1,996,824✔
782
    }
1,996,824✔
783
    return false;
5,605,782✔
784
}
6,878,172✔
785

786

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

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

6,513✔
798
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
24,261✔
799
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
23,769✔
800
    });
24,261✔
801
}
19,287✔
802

803
size_t Obj::get_backlink_count() const
804
{
218,217✔
805
    update_if_needed();
218,217✔
806

72,696✔
807
    size_t cnt = 0;
218,217✔
808
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
571,605✔
809
        cnt += get_backlink_cnt(backlink_col_key);
571,605✔
810
        return IteratorControl::AdvanceToNext;
571,605✔
811
    });
571,605✔
812
    return cnt;
218,217✔
813
}
218,217✔
814

815
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
816
{
48,036✔
817
    update_if_needed();
48,036✔
818

15,984✔
819
    size_t cnt = 0;
48,036✔
820
    if (TableKey origin_table_key = origin.get_key()) {
48,036✔
821
        ColKey backlink_col_key;
48,033✔
822
        auto type = origin_col_key.get_type();
48,033✔
823
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
48,033✔
824
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
18✔
825
        }
18✔
826
        else {
48,015✔
827
            backlink_col_key = origin.get_opposite_column(origin_col_key);
48,015✔
828
        }
48,015✔
829

15,981✔
830
        cnt = get_backlink_cnt(backlink_col_key);
48,033✔
831
    }
48,033✔
832
    return cnt;
48,036✔
833
}
48,036✔
834

835
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
836
{
13,535,301✔
837
    ColKey backlink_col_key;
13,535,301✔
838
    auto type = origin_col_key.get_type();
13,535,301✔
839
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
13,535,316✔
840
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
54✔
841
    }
54✔
842
    else {
13,535,247✔
843
        backlink_col_key = origin.get_opposite_column(origin_col_key);
13,535,247✔
844
    }
13,535,247✔
845
    return get_backlink(backlink_col_key, backlink_ndx);
13,535,301✔
846
}
13,535,301✔
847

848
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key) const
849
{
1,044✔
850
    TableView tv(src_table, src_col_key, *this);
1,044✔
851
    tv.do_sync();
1,044✔
852
    return tv;
1,044✔
853
}
1,044✔
854

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

4,511,745✔
862
    ArrayBacklink backlinks(alloc);
13,535,337✔
863
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
13,535,337✔
864
    backlinks.init_from_parent();
13,535,337✔
865
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
13,535,337✔
866
}
13,535,337✔
867

868
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
869
{
364,119✔
870
    update_if_needed();
364,119✔
871

121,296✔
872
    get_table()->check_column(backlink_col);
364,119✔
873
    Allocator& alloc = get_alloc();
364,119✔
874
    Array fields(alloc);
364,119✔
875
    fields.init_from_mem(m_mem);
364,119✔
876

121,296✔
877
    ArrayBacklink backlinks(alloc);
364,119✔
878
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
364,119✔
879
    backlinks.init_from_parent();
364,119✔
880

121,296✔
881
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
364,119✔
882
    std::vector<ObjKey> vec;
364,119✔
883
    vec.reserve(cnt);
364,119✔
884
    for (size_t i = 0; i < cnt; i++) {
718,446✔
885
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
354,327✔
886
    }
354,327✔
887
    return vec;
364,119✔
888
}
364,119✔
889

890
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
891
{
643,926✔
892
    Allocator& alloc = get_alloc();
643,926✔
893
    Array fields(alloc);
643,926✔
894
    fields.init_from_mem(m_mem);
643,926✔
895

214,464✔
896
    ArrayBacklink backlinks(alloc);
643,926✔
897
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
643,926✔
898
    backlinks.init_from_parent();
643,926✔
899

214,464✔
900
    return backlinks.get_backlink_count(m_row_ndx);
643,926✔
901
}
643,926✔
902

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

63,372✔
915
    Allocator& alloc = get_alloc();
190,038✔
916
    Array fields(alloc);
190,038✔
917
    fields.init_from_mem(m_mem);
190,038✔
918

63,372✔
919
    ArrayBacklink backlinks(alloc);
190,038✔
920
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
190,038✔
921
    backlinks.init_from_parent();
190,038✔
922

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

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

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

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

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

1015
FullPath Obj::get_path() const
1016
{
353,865✔
1017
    FullPath result;
353,865✔
1018
    if (m_table->is_embedded()) {
353,865✔
1019
        REALM_ASSERT(get_backlink_count() == 1);
210,273✔
1020
        m_table->for_each_backlink_column([&](ColKey col_key) {
346,101✔
1021
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
346,101✔
1022
            if (backlinks.size() == 1) {
346,101✔
1023
                TableRef origin_table = m_table->get_opposite_table(col_key);
210,273✔
1024
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
210,273✔
1025
                auto next_col_key = m_table->get_opposite_column(col_key);
210,273✔
1026

70,050✔
1027
                ColumnAttrMask attr = next_col_key.get_attrs();
210,273✔
1028
                Mixed index;
210,273✔
1029
                if (attr.test(col_attr_List)) {
210,273✔
1030
                    REALM_ASSERT(next_col_key.get_type() == col_type_Link);
72,819✔
1031
                    Lst<ObjKey> link_list(next_col_key);
72,819✔
1032
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
72,819✔
1033
                    REALM_ASSERT(i != realm::not_found);
72,819✔
1034
                    result = link_list.get_path();
72,819✔
1035
                    result.path_from_top.emplace_back(i);
72,819✔
1036
                }
72,819✔
1037
                else if (attr.test(col_attr_Dictionary)) {
137,454✔
1038
                    Dictionary dict(next_col_key);
57,258✔
1039
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
57,258✔
1040
                    REALM_ASSERT(ndx != realm::not_found);
57,258✔
1041
                    result = dict.get_path();
57,258✔
1042
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
57,258✔
1043
                }
57,258✔
1044
                else {
80,196✔
1045
                    result = obj.get_path();
80,196✔
1046
                    if (result.path_from_top.empty()) {
80,196✔
1047
                        result.path_from_top.push_back(next_col_key);
17,943✔
1048
                    }
17,943✔
1049
                    else {
62,253✔
1050
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
62,253✔
1051
                    }
62,253✔
1052
                }
80,196✔
1053

70,050✔
1054
                return IteratorControl::Stop; // early out
210,273✔
1055
            }
210,273✔
1056
            return IteratorControl::AdvanceToNext; // try next column
135,828✔
1057
        });
205,878✔
1058
    }
210,273✔
1059
    else {
143,592✔
1060
        result.top_objkey = get_key();
143,592✔
1061
        result.top_table = get_table()->get_key();
143,592✔
1062
    }
143,592✔
1063
    return result;
353,865✔
1064
}
353,865✔
1065

1066
std::string Obj::get_id() const
1067
{
270✔
1068
    std::ostringstream ostr;
270✔
1069
    auto path = get_path();
270✔
1070
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
270✔
1071
    ostr << top_table->get_class_name() << '[';
270✔
1072
    if (top_table->get_primary_key_column()) {
270✔
1073
        ostr << top_table->get_primary_key(path.top_objkey);
×
1074
    }
×
1075
    else {
270✔
1076
        ostr << path.top_objkey;
270✔
1077
    }
270✔
1078
    ostr << ']';
270✔
1079
    if (!path.path_from_top.empty()) {
270✔
1080
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
270✔
1081
        path.path_from_top[0] = PathElement(prop_name);
270✔
1082
        ostr << path.path_from_top;
270✔
1083
    }
270✔
1084
    return ostr.str();
270✔
1085
}
270✔
1086

1087
Path Obj::get_short_path() const noexcept
1088
{
547,917✔
1089
    return {};
547,917✔
1090
}
547,917✔
1091

1092
ColKey Obj::get_col_key() const noexcept
1093
{
×
1094
    return {};
×
1095
}
×
1096

1097
StablePath Obj::get_stable_path() const noexcept
1098
{
2,864,577✔
1099
    return {};
2,864,577✔
1100
}
2,864,577✔
1101

1102
void Obj::add_index(Path& path, const Index& index) const
1103
{
678,021✔
1104
    if (path.empty()) {
678,021✔
1105
        path.emplace_back(get_table()->get_column_key(index));
673,566✔
1106
    }
673,566✔
1107
    else {
4,455✔
1108
        StringData col_name = get_table()->get_column_name(index);
4,455✔
1109
        path.emplace_back(col_name);
4,455✔
1110
    }
4,455✔
1111
}
678,021✔
1112

1113
std::string Obj::to_string() const
1114
{
18✔
1115
    std::ostringstream ostr;
18✔
1116
    to_json(ostr);
18✔
1117
    return ostr.str();
18✔
1118
}
18✔
1119

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

1126
/*********************************** Obj *************************************/
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
{
55,377,192✔
1141
    auto ref = arr.get_ref();
55,377,192✔
1142
    if (arr.has_missing_parent_update()) {
55,377,192✔
1143
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
403,482✔
1144
    }
403,482✔
1145
    if (m_mem.get_ref() != ref) {
55,377,192✔
1146
        m_mem = arr.get_mem();
802,422✔
1147
        m_storage_version = arr.get_alloc().get_storage_version();
802,422✔
1148
    }
802,422✔
1149
}
55,377,192✔
1150

1151
template <>
1152
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
1153
{
14,703✔
1154
    update_if_needed();
14,703✔
1155
    get_table()->check_column(col_key);
14,703✔
1156
    auto type = col_key.get_type();
14,703✔
1157
    auto col_ndx = col_key.get_index();
14,703✔
1158
    bool recurse = false;
14,703✔
1159
    CascadeState state;
14,703✔
1160

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

4,833✔
1170
    Mixed old_value = get_unfiltered_mixed(col_ndx);
14,703✔
1171
    if (old_value.is_type(type_TypedLink)) {
14,703✔
1172
        if (old_value == value) {
198✔
1173
            return *this;
36✔
1174
        }
36✔
1175
        auto old_link = old_value.get<ObjLink>();
162✔
1176
        recurse = remove_backlink(col_key, old_link, state);
162✔
1177
    }
162✔
1178
    else if (old_value.is_type(type_Dictionary)) {
14,505✔
1179
        Dictionary dict(*this, col_key);
48✔
1180
        recurse = dict.remove_backlinks(state);
48✔
1181
    }
48✔
1182
    else if (old_value.is_type(type_List)) {
14,457✔
1183
        Lst<Mixed> list(*this, col_key);
66✔
1184
        recurse = list.remove_backlinks(state);
66✔
1185
    }
66✔
1186

4,833✔
1187
    if (value.is_type(type_TypedLink)) {
14,679✔
1188
        if (m_table->is_asymmetric()) {
1,404✔
1189
            throw IllegalOperation("Links not allowed in asymmetric tables");
18✔
1190
        }
18✔
1191
        auto new_link = value.get<ObjLink>();
1,386✔
1192
        m_table->get_parent_group()->validate(new_link);
1,386✔
1193
        set_backlink(col_key, new_link);
1,386✔
1194
    }
1,386✔
1195

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

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

4,815✔
1213
    sync(fields);
14,649✔
1214

4,815✔
1215
    if (Replication* repl = get_replication())
14,649✔
1216
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
8,421✔
1217
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
8,421✔
1218

4,815✔
1219
    if (recurse)
14,649✔
1220
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1221

4,815✔
1222
    return *this;
14,649✔
1223
}
14,667✔
1224

1225
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1226
{
1,162,443✔
1227
    if (value.is_null()) {
1,162,443✔
1228
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
468✔
1229
        set_null(col_key);
468✔
1230
    }
468✔
1231
    else {
1,161,975✔
1232
        switch (col_key.get_type()) {
1,161,975✔
1233
            case col_type_Int:
1,086,996✔
1234
                if (col_key.get_attrs().test(col_attr_Nullable)) {
1,086,996✔
1235
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
2,673✔
1236
                }
2,673✔
1237
                else {
1,084,323✔
1238
                    set(col_key, value.get_int(), is_default);
1,084,323✔
1239
                }
1,084,323✔
1240
                break;
1,086,996✔
1241
            case col_type_Bool:
162✔
1242
                set(col_key, value.get_bool(), is_default);
162✔
1243
                break;
162✔
1244
            case col_type_Float:
369✔
1245
                set(col_key, value.get_float(), is_default);
369✔
1246
                break;
369✔
1247
            case col_type_Double:
2,124✔
1248
                set(col_key, value.get_double(), is_default);
2,124✔
1249
                break;
2,124✔
1250
            case col_type_String:
35,790✔
1251
                set(col_key, value.get_string(), is_default);
35,790✔
1252
                break;
35,790✔
1253
            case col_type_Binary:
26,163✔
1254
                set(col_key, value.get<Binary>(), is_default);
26,163✔
1255
                break;
26,163✔
1256
            case col_type_Mixed:
576✔
1257
                set(col_key, value, is_default);
576✔
1258
                break;
576✔
1259
            case col_type_Timestamp:
7,389✔
1260
                set(col_key, value.get<Timestamp>(), is_default);
7,389✔
1261
                break;
7,389✔
1262
            case col_type_ObjectId:
1,980✔
1263
                set(col_key, value.get<ObjectId>(), is_default);
1,980✔
1264
                break;
1,980✔
1265
            case col_type_Decimal:
153✔
1266
                set(col_key, value.get<Decimal128>(), is_default);
153✔
1267
                break;
153✔
1268
            case col_type_UUID:
162✔
1269
                set(col_key, value.get<UUID>(), is_default);
162✔
1270
                break;
162✔
1271
            case col_type_Link:
108✔
1272
                set(col_key, value.get<ObjKey>(), is_default);
108✔
1273
                break;
108✔
1274
            case col_type_TypedLink:
✔
1275
                set(col_key, value.get<ObjLink>(), is_default);
×
1276
                break;
×
1277
            default:
✔
1278
                break;
×
1279
        }
1,162,266✔
1280
    }
1,162,266✔
1281
    return *this;
1,162,332✔
1282
}
1,162,422✔
1283

1284
template <>
1285
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1286
{
29,329,353✔
1287
    update_if_needed();
29,329,353✔
1288
    get_table()->check_column(col_key);
29,329,353✔
1289
    auto col_ndx = col_key.get_index();
29,329,353✔
1290

9,895,032✔
1291
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
29,329,353✔
1292
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1293
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1294

9,895,032✔
1295
    SearchIndex* index = m_table->get_search_index(col_key);
29,329,353✔
1296
    if (index && !m_key.is_unresolved()) {
29,329,353✔
1297
        index->set(m_key, value);
274,947✔
1298
    }
274,947✔
1299

9,895,032✔
1300
    Allocator& alloc = get_alloc();
29,329,353✔
1301
    alloc.bump_content_version();
29,329,353✔
1302
    Array fallback(alloc);
29,329,353✔
1303
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
29,329,353✔
1304
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
29,329,353✔
1305
    auto attr = col_key.get_attrs();
29,329,353✔
1306
    if (attr.test(col_attr_Nullable)) {
29,329,353✔
1307
        ArrayIntNull values(alloc);
3,786,750✔
1308
        values.set_parent(&fields, col_ndx.val + 1);
3,786,750✔
1309
        values.init_from_parent();
3,786,750✔
1310
        values.set(m_row_ndx, value);
3,786,750✔
1311
    }
3,786,750✔
1312
    else {
25,542,603✔
1313
        ArrayInteger values(alloc);
25,542,603✔
1314
        values.set_parent(&fields, col_ndx.val + 1);
25,542,603✔
1315
        values.init_from_parent();
25,542,603✔
1316
        values.set(m_row_ndx, value);
25,542,603✔
1317
    }
25,542,603✔
1318

9,895,032✔
1319
    sync(fields);
29,329,353✔
1320

9,895,032✔
1321
    if (Replication* repl = get_replication()) {
29,329,353✔
1322
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
15,368,808✔
1323
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
15,368,622✔
1324
    }
15,368,808✔
1325

9,895,032✔
1326
    return *this;
29,329,353✔
1327
}
29,329,353✔
1328

1329
Obj& Obj::add_int(ColKey col_key, int64_t value)
1330
{
30,540✔
1331
    update_if_needed();
30,540✔
1332
    get_table()->check_column(col_key);
30,540✔
1333
    auto col_ndx = col_key.get_index();
30,540✔
1334

10,185✔
1335
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
30,534✔
1336
        uint64_t ua = uint64_t(a);
30,522✔
1337
        uint64_t ub = uint64_t(b);
30,522✔
1338
        return int64_t(ua + ub);
30,522✔
1339
    };
30,522✔
1340

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

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

10,173✔
1367
        auto attr = col_key.get_attrs();
30,456✔
1368
        if (attr.test(col_attr_Nullable)) {
30,456✔
1369
            ArrayIntNull values(alloc);
135✔
1370
            values.set_parent(&fields, col_ndx.val + 1);
135✔
1371
            values.init_from_parent();
135✔
1372
            util::Optional<int64_t> old = values.get(m_row_ndx);
135✔
1373
            if (old) {
135✔
1374
                auto new_val = add_wrap(*old, value);
126✔
1375
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
126✔
1376
                    index->set(m_key, new_val);
×
1377
                }
×
1378
                values.set(m_row_ndx, new_val);
126✔
1379
            }
126✔
1380
            else {
9✔
1381
                throw IllegalOperation("No prior value");
9✔
1382
            }
9✔
1383
        }
20,283✔
1384
        else {
30,321✔
1385
            ArrayInteger values(alloc);
30,321✔
1386
            values.set_parent(&fields, col_ndx.val + 1);
30,321✔
1387
            values.init_from_parent();
30,321✔
1388
            int64_t old = values.get(m_row_ndx);
30,321✔
1389
            auto new_val = add_wrap(old, value);
30,321✔
1390
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
30,321✔
1391
                index->set(m_key, new_val);
9✔
1392
            }
9✔
1393
            values.set(m_row_ndx, new_val);
30,321✔
1394
        }
30,321✔
1395
    }
30,456✔
1396

10,185✔
1397
    sync(fields);
30,528✔
1398

10,179✔
1399
    if (Replication* repl = get_replication()) {
30,522✔
1400
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
12,531✔
1401
    }
12,531✔
1402

10,179✔
1403
    return *this;
30,522✔
1404
}
30,540✔
1405

1406
template <>
1407
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
1408
{
392,577✔
1409
    update_if_needed();
392,577✔
1410
    get_table()->check_column(col_key);
392,577✔
1411
    ColKey::Idx col_ndx = col_key.get_index();
392,577✔
1412
    ColumnType type = col_key.get_type();
392,577✔
1413
    if (type != col_type_Link)
392,577✔
1414
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
×
1415
    TableRef target_table = get_target_table(col_key);
392,577✔
1416
    TableKey target_table_key = target_table->get_key();
392,577✔
1417
    if (target_key) {
392,577✔
1418
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
388,701✔
1419
        if (!ct->is_valid(target_key)) {
391,737✔
1420
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
18✔
1421
        }
18✔
1422
        if (target_table->is_embedded()) {
391,737✔
1423
            throw IllegalOperation(
×
1424
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
×
1425
        }
×
1426
    }
392,295✔
1427
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
392,577✔
1428

130,860✔
1429
    if (target_key != old_key) {
392,577✔
1430
        CascadeState state(CascadeState::Mode::Strong);
391,260✔
1431

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

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

130,410✔
1444
        values.set(m_row_ndx, target_key);
391,260✔
1445

130,410✔
1446
        sync(fields);
391,260✔
1447

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

130,410✔
1453
        if (recurse)
391,260✔
1454
            target_table->remove_recursive(state);
387✔
1455
    }
391,260✔
1456

130,860✔
1457
    return *this;
392,577✔
1458
}
392,577✔
1459

1460
template <>
1461
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
1462
{
×
1463
    update_if_needed();
×
1464
    get_table()->check_column(col_key);
×
1465
    ColKey::Idx col_ndx = col_key.get_index();
×
1466
    ColumnType type = col_key.get_type();
×
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

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);
×
1478
        _update_if_needed();
×
1479

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);
×
1484
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
1485
        ArrayTypedLink values(alloc);
×
1486
        values.set_parent(&fields, col_ndx.val + 1);
×
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
×
1496
        }
×
1497

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

1502
    return *this;
×
1503
}
×
1504

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

6,807✔
1536
    if (target_key != old_key) {
20,478✔
1537
        CascadeState state;
20,478✔
1538

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

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

6,807✔
1551
        values.set(m_row_ndx, target_key);
20,478✔
1552

6,807✔
1553
        sync(fields);
20,478✔
1554

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

6,807✔
1560
        if (recurse)
20,478✔
1561
            target_table->remove_recursive(state);
72✔
1562
    }
20,478✔
1563

6,807✔
1564
    return result;
20,478✔
1565
}
20,478✔
1566

1567
namespace {
1568
template <class T>
1569
inline void check_range(const T&)
1570
{
3,207,696✔
1571
}
3,207,696✔
1572
template <>
1573
inline void check_range(const StringData& val)
1574
{
3,602,091✔
1575
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
3,602,091✔
1576
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
1,202,577✔
1577
}
3,602,091✔
1578
template <>
1579
inline void check_range(const BinaryData& val)
1580
{
7,748,661✔
1581
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
7,748,661✔
1582
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
2,581,620✔
1583
}
7,748,661✔
1584
} // namespace
1585

1586
// helper functions for filtering out calls to set_spec()
1587
template <class T>
1588
inline void Obj::set_spec(T&, ColKey)
1589
{
10,957,884✔
1590
}
10,957,884✔
1591
template <>
1592
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1593
{
3,601,686✔
1594
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
3,601,686✔
1595
    Spec* spec = const_cast<Spec*>(&get_spec());
3,601,686✔
1596
    values.set_spec(spec, spec_ndx);
3,601,686✔
1597
}
3,601,686✔
1598

1599
#if REALM_ENABLE_GEOSPATIAL
1600

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

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

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

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

6✔
1630
    if (type != ColumnTypeTraits<Link>::column_id)
18✔
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));
×
1636

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

1650
#endif
1651

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

4,861,287✔
1661
    if (type != ColumnTypeTraits<T>::column_id)
14,559,570✔
1662
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
NEW
1663
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1664
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
14,559,570!
1665
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
9✔
1666

4,861,284✔
1667
    check_range(value);
14,559,561✔
1668

4,861,284✔
1669
    SearchIndex* index = m_table->get_search_index(col_key);
14,559,561✔
1670
    if (index && !m_key.is_unresolved()) {
14,559,561!
1671
        index->set(m_key, value);
706,836✔
1672
    }
706,836✔
1673

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

4,861,284✔
1686
    sync(fields);
14,559,561✔
1687

4,861,284✔
1688
    if (Replication* repl = get_replication())
14,559,561✔
1689
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
8,932,995✔
1690
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
8,932,971✔
1691

4,861,284✔
1692
    return *this;
14,559,561✔
1693
}
14,559,564✔
1694

1695
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
1696
INSTANTIATE_OBJ_SET(bool);
1697
INSTANTIATE_OBJ_SET(StringData);
1698
INSTANTIATE_OBJ_SET(float);
1699
INSTANTIATE_OBJ_SET(double);
1700
INSTANTIATE_OBJ_SET(Decimal128);
1701
INSTANTIATE_OBJ_SET(Timestamp);
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
{
799,767✔
1708
    update_if_needed();
799,767✔
1709

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

266,139✔
1720
    sync(fields);
799,767✔
1721
}
799,767✔
1722

1723
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
1724
{
3,417✔
1725
    update_if_needed();
3,417✔
1726

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

270✔
1737
    sync(fields);
3,417✔
1738
}
3,417✔
1739

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

3,377,484✔
1748
    ArrayBacklink backlinks(alloc);
10,131,999✔
1749
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
10,131,999✔
1750
    backlinks.init_from_parent();
10,131,999✔
1751

3,377,484✔
1752
    backlinks.add(m_row_ndx, origin_key);
10,131,999✔
1753

3,377,484✔
1754
    sync(fields);
10,131,999✔
1755
}
10,131,999✔
1756

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

98,403✔
1765
    ArrayBacklink backlinks(alloc);
295,290✔
1766
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
295,290✔
1767
    backlinks.init_from_parent();
295,290✔
1768

98,403✔
1769
    bool ret = backlinks.remove(m_row_ndx, origin_key);
295,290✔
1770

98,403✔
1771
    sync(fields);
295,290✔
1772

98,403✔
1773
    return ret;
295,290✔
1774
}
295,290✔
1775

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

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

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

6✔
1815
        if (Replication* repl = get_replication())
18✔
1816
            repl->nullify_link(m_table.unchecked_ptr(), col,
×
1817
                               m_key); // Throws
×
1818
    }
18✔
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)) {
×
1824
        Lst<Mixed> list(*this, col);
×
1825
        result = list.nullify(target.get_link());
×
1826
    }
×
1827
    REALM_ASSERT(result);
27✔
1828
    static_cast<void>(result);
27✔
1829
}
27✔
1830

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

3,486✔
1835
    struct LinkNullifier : public LinkTranslator {
10,461✔
1836
        LinkNullifier(Obj origin_obj, ColKey origin_col, ObjLink target)
10,461✔
1837
            : LinkTranslator(origin_obj, origin_col)
10,461✔
1838
            , m_target_link(target)
10,461✔
1839
        {
10,461✔
1840
        }
10,461✔
1841
        void on_list_of_links(LnkLst&) final
10,461✔
1842
        {
8,127✔
1843
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
3,465✔
1844
        }
3,465✔
1845
        void on_list_of_mixed(Lst<Mixed>& list) final
10,461✔
1846
        {
7,209✔
1847
            list.nullify(m_target_link);
711✔
1848
        }
711✔
1849
        void on_set_of_links(LnkSet&) final
10,461✔
1850
        {
8,103✔
1851
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
3,393✔
1852
        }
3,393✔
1853
        void on_set_of_mixed(Set<Mixed>&) final
10,461✔
1854
        {
7,122✔
1855
            nullify_set(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
450✔
1856
        }
450✔
1857
        void on_dictionary(Dictionary& dict) final
10,461✔
1858
        {
7,332✔
1859
            dict.nullify(m_target_link);
1,080✔
1860
        }
1,080✔
1861
        void on_link_property(ColKey origin_col_key) final
10,461✔
1862
        {
7,419✔
1863
            m_origin_obj.nullify_single_link<ObjKey>(origin_col_key, m_target_link.get_obj_key());
1,335✔
1864
        }
1,335✔
1865
        void on_mixed_property(ColKey origin_col_key) final
10,461✔
1866
        {
6,981✔
1867
            m_origin_obj.nullify_single_link<Mixed>(origin_col_key, Mixed{m_target_link});
27✔
1868
        }
27✔
1869

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

3,486✔
1874
    nullifier.run();
10,461✔
1875

3,486✔
1876
    get_alloc().bump_content_version();
10,461✔
1877
}
10,461✔
1878

1879

1880
struct EmbeddedObjectLinkMigrator : public LinkTranslator {
1881
    EmbeddedObjectLinkMigrator(Obj origin, ColKey origin_col, Obj dest_orig, Obj dest_replace)
1882
        : LinkTranslator(origin, origin_col)
30,159✔
1883
        , m_dest_orig(dest_orig)
30,159✔
1884
        , m_dest_replace(dest_replace)
30,159✔
1885
    {
90,477✔
1886
    }
90,477✔
1887
    void on_list_of_links(LnkLst& list) final
1888
    {
252✔
1889
        auto n = list.find_first(m_dest_orig.get_key());
252✔
1890
        REALM_ASSERT(n != realm::npos);
252✔
1891
        list.set(n, m_dest_replace.get_key());
252✔
1892
    }
252✔
1893
    void on_dictionary(Dictionary& dict) final
1894
    {
90✔
1895
        auto pos = dict.find_any(m_dest_orig.get_link());
90✔
1896
        REALM_ASSERT(pos != realm::npos);
90✔
1897
        Mixed key = dict.get_key(pos);
90✔
1898
        dict.insert(key, m_dest_replace.get_link());
90✔
1899
    }
90✔
1900
    void on_link_property(ColKey col) final
1901
    {
90,135✔
1902
        REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
90,135✔
1903
        m_origin_obj.set(col, m_dest_replace.get_key());
90,135✔
1904
    }
90,135✔
1905
    void on_set_of_links(LnkSet&) final
1906
    {
×
1907
        // this should never happen because sets of embedded objects are not allowed at the schema level
1908
        REALM_UNREACHABLE();
1909
    }
×
1910
    // The following cases have support here but are expected to fail later on in the
1911
    // migration due to core not yet supporting untyped Mixed links to embedded objects.
1912
    void on_set_of_mixed(Set<Mixed>& set) final
1913
    {
×
1914
        auto did_erase_pair = set.erase(m_dest_orig.get_link());
×
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
{
81✔
1938
    REALM_ASSERT(!m_table->get_primary_key_column());
81✔
1939
    converters::EmbeddedObjectConverter embedded_obj_tracker;
81✔
1940
    auto copy_links = [&](ColKey col) {
162✔
1941
        auto opposite_table = m_table->get_opposite_table(col);
162✔
1942
        auto opposite_column = m_table->get_opposite_column(col);
162✔
1943
        auto backlinks = get_all_backlinks(col);
162✔
1944
        for (auto backlink : backlinks) {
90,477✔
1945
            // create a new obj
30,159✔
1946
            auto obj = m_table->create_object();
90,477✔
1947
            embedded_obj_tracker.track(*this, obj);
90,477✔
1948
            auto linking_obj = opposite_table->get_object(backlink);
90,477✔
1949
            // change incoming links to point to the newly created object
30,159✔
1950
            EmbeddedObjectLinkMigrator{linking_obj, opposite_column, *this, obj}.run();
90,477✔
1951
        }
90,477✔
1952
        embedded_obj_tracker.process_pending();
162✔
1953
        return IteratorControl::AdvanceToNext;
162✔
1954
    };
162✔
1955
    m_table->for_each_backlink_column(copy_links);
81✔
1956
}
81✔
1957

1958
LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const
1959
{
448,830✔
1960
    auto list = CollectionParent::get_listbase_ptr(col_key);
448,830✔
1961
    list->set_owner(*this, col_key);
448,830✔
1962
    return list;
448,830✔
1963
}
448,830✔
1964

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

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

1979
Obj& Obj::set_collection(ColKey col_key, CollectionType type)
1980
{
2,451✔
1981
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
2,451✔
1982
    if ((col_key.is_dictionary() && type == CollectionType::Dictionary) ||
2,451✔
1983
        (col_key.is_list() && type == CollectionType::List)) {
2,442✔
1984
        return *this;
45✔
1985
    }
45✔
1986
    update_if_needed();
2,406✔
1987
    Mixed new_val(0, type);
2,406✔
1988

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

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

174✔
1998
    if (old_val != new_val) {
2,406✔
1999
        CascadeState state;
2,052✔
2000
        if (old_val.is_type(type_TypedLink)) {
2,052✔
2001
            remove_backlink(col_key, old_val.get<ObjLink>(), state);
×
2002
        }
×
2003
        else if (old_val.is_type(type_Dictionary)) {
2,052✔
2004
            Dictionary dict(*this, col_key);
117✔
2005
            dict.remove_backlinks(state);
117✔
2006
        }
117✔
2007
        else if (old_val.is_type(type_List)) {
1,935✔
2008
            Lst<Mixed> list(*this, col_key);
141✔
2009
            list.remove_backlinks(state);
141✔
2010
        }
141✔
2011

168✔
2012
        if (SearchIndex* index = m_table->get_search_index(col_key)) {
2,052✔
2013
            index->set(m_key, new_val);
9✔
2014
        }
9✔
2015

168✔
2016
        Allocator& alloc = _get_alloc();
2,052✔
2017
        alloc.bump_content_version();
2,052✔
2018

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

168✔
2025
        values.set(m_row_ndx, new_val);
2,052✔
2026
        values.set_key(m_row_ndx, generate_key(0x10));
2,052✔
2027

168✔
2028
        sync(fields);
2,052✔
2029

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

174✔
2034
    return *this;
2,406✔
2035
}
2,406✔
2036

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

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

2047
Dictionary Obj::get_dictionary(StringData col_name) const
2048
{
24,825✔
2049
    return get_dictionary(get_column_key(col_name));
24,825✔
2050
}
24,825✔
2051

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

5,610✔
2061
    while (level < path.size()) {
18,066✔
2062
        auto& path_elem = path[level];
924✔
2063
        Mixed ref;
924✔
2064
        if (collection->get_collection_type() == CollectionType::List) {
924✔
2065
            ref = collection->get_any(path_elem.get_ndx());
483✔
2066
        }
483✔
2067
        else {
441✔
2068
            ref = dynamic_cast<Dictionary*>(collection.get())->get(path_elem.get_key());
441✔
2069
        }
441✔
2070
        if (ref.is_type(type_List)) {
924✔
2071
            collection = collection->get_list(path_elem);
576✔
2072
        }
576✔
2073
        else if (ref.is_type(type_Dictionary)) {
348✔
2074
            collection = collection->get_dictionary(path_elem);
348✔
2075
        }
348✔
2076
        else {
×
2077
            throw InvalidArgument("Wrong path");
×
2078
        }
×
2079
        level++;
924✔
2080
    }
924✔
2081

5,610✔
2082
    return collection;
17,142✔
2083
}
17,142✔
2084

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

5,052✔
2092
    while (level < path.size()) {
16,377✔
2093
        auto& index = path[level];
801✔
2094
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
801✔
2095
            Mixed ref;
801✔
2096
            PathElement path_elem;
801✔
2097
            if (collection->get_collection_type() == CollectionType::List) {
801✔
2098
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
495✔
2099
                size_t ndx = list_of_mixed->find_index(index);
495✔
2100
                if (ndx != realm::not_found) {
495✔
2101
                    ref = list_of_mixed->get(ndx);
495✔
2102
                    path_elem = ndx;
495✔
2103
                }
495✔
2104
            }
495✔
2105
            else {
306✔
2106
                auto dict = dynamic_cast<Dictionary*>(collection.get());
306✔
2107
                size_t ndx = dict->find_index(index);
306✔
2108
                if (ndx != realm::not_found) {
306✔
2109
                    ref = dict->get_any(ndx);
294✔
2110
                    path_elem = dict->get_key(ndx).get_string();
294✔
2111
                }
294✔
2112
            }
306✔
2113
            return {ref, path_elem};
801✔
2114
        };
801✔
2115
        auto [ref, path_elem] = get_ref();
801✔
2116
        if (ref.is_type(type_List)) {
801✔
2117
            collection = collection->get_list(path_elem);
624✔
2118
        }
624✔
2119
        else if (ref.is_type(type_Dictionary)) {
177✔
2120
            collection = collection->get_dictionary(path_elem);
165✔
2121
        }
165✔
2122
        else {
12✔
2123
            return nullptr;
12✔
2124
        }
12✔
2125
        level++;
789✔
2126
    }
789✔
2127

5,052✔
2128
    return collection;
15,576✔
2129
}
15,588✔
2130

2131
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2132
{
193,299✔
2133
    if (col_key.is_collection()) {
193,299✔
2134
        auto collection = CollectionParent::get_collection_ptr(col_key);
190,377✔
2135
        collection->set_owner(*this, col_key);
190,377✔
2136
        return collection;
190,377✔
2137
    }
190,377✔
2138
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
2,922✔
2139
    auto val = get<Mixed>(col_key);
2,922✔
2140
    if (val.is_type(type_List)) {
2,922✔
2141
        return std::make_shared<Lst<Mixed>>(*this, col_key);
1,656✔
2142
    }
1,656✔
2143
    REALM_ASSERT(val.is_type(type_Dictionary));
1,266✔
2144
    return std::make_shared<Dictionary>(*this, col_key);
1,266✔
2145
}
1,980✔
2146

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

2152
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
2153
{
4,671✔
2154
    if (col_key.is_list()) {
4,671✔
2155
        return get_linklist_ptr(col_key);
4,491✔
2156
    }
4,491✔
2157
    else if (col_key.is_set()) {
180✔
2158
        return get_linkset_ptr(col_key);
117✔
2159
    }
117✔
2160
    else if (col_key.is_dictionary()) {
63✔
2161
        auto dict = get_dictionary(col_key);
63✔
2162
        return std::make_unique<DictionaryLinkValues>(dict);
63✔
2163
    }
63✔
2164
    return {};
×
2165
}
1,557✔
2166

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

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

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

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

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

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

2190
void Obj::assign_pk_and_backlinks(Obj& other)
2191
{
17,550✔
2192
    struct LinkReplacer : LinkTranslator {
17,550✔
2193
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
17,550✔
2194
            : LinkTranslator(origin, origin_col_key)
28,851✔
2195
            , m_dest_orig(dest_orig)
28,851✔
2196
            , m_dest_replace(dest_replace)
28,851✔
2197
        {
51,471✔
2198
        }
51,471✔
2199
        void on_list_of_links(LnkLst&) final
17,550✔
2200
        {
41,121✔
2201
            auto linklist = m_origin_obj.get_linklist(m_origin_col_key);
41,121✔
2202
            linklist.replace_link(m_dest_orig.get_key(), m_dest_replace.get_key());
41,121✔
2203
        }
41,121✔
2204
        void on_list_of_mixed(Lst<Mixed>& list) final
17,550✔
2205
        {
11,775✔
2206
            list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
171✔
2207
        }
171✔
2208
        void on_set_of_links(LnkSet&) final
17,550✔
2209
        {
11,787✔
2210
            replace_in_linkset(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
207✔
2211
        }
207✔
2212
        void on_set_of_mixed(Set<Mixed>&) final
17,550✔
2213
        {
11,793✔
2214
            replace_in_linkset<Mixed>(m_origin_obj, m_origin_col_key, m_dest_orig.get_link(),
225✔
2215
                                      m_dest_replace.get_link());
225✔
2216
        }
225✔
2217
        void on_dictionary(Dictionary& dict) final
17,550✔
2218
        {
11,844✔
2219
            dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
378✔
2220
        }
378✔
2221
        void on_link_property(ColKey col) final
17,550✔
2222
        {
14,808✔
2223
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
9,270✔
2224
            // Handle links as plain integers. Backlinks has been taken care of.
3,090✔
2225
            // Be careful here - links are stored as value + 1 so that null link (-1) will be 0
3,090✔
2226
            auto new_key = m_dest_replace.get_key();
9,270✔
2227
            m_origin_obj.set_int(col.get_index(), new_key.value + 1);
9,270✔
2228
            if (Replication* repl = m_origin_obj.get_replication())
9,270✔
2229
                repl->set(m_origin_obj.get_table().unchecked_ptr(), col, m_origin_obj.get_key(), new_key);
9,198✔
2230
        }
9,270✔
2231
        void on_mixed_property(ColKey col) final
17,550✔
2232
        {
11,751✔
2233
            auto val = m_origin_obj.get_any(col);
99✔
2234
            if (val.is_type(type_Dictionary)) {
99✔
2235
                Dictionary dict(m_origin_obj, m_origin_col_key);
18✔
2236
                dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
18✔
2237
            }
18✔
2238
            else if (val.is_type(type_List)) {
81✔
2239
                Lst<Mixed> list(m_origin_obj, m_origin_col_key);
18✔
2240
                list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
18✔
2241
            }
18✔
2242
            else {
63✔
2243
                REALM_ASSERT(val.is_null() || val.get_link().get_obj_key() == m_dest_orig.get_key());
63✔
2244
                m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
63✔
2245
            }
63✔
2246
        }
99✔
2247

5,862✔
2248
    private:
17,550✔
2249
        const Obj& m_dest_orig;
17,550✔
2250
        const Obj& m_dest_replace;
17,550✔
2251
    };
17,550✔
2252

5,862✔
2253
    REALM_ASSERT(get_table() == other.get_table());
17,550✔
2254
    if (auto col_pk = m_table->get_primary_key_column()) {
17,550✔
2255
        Mixed val = other.get_any(col_pk);
17,307✔
2256
        this->set_any(col_pk, val);
17,307✔
2257
    }
17,307✔
2258
    auto nb_tombstones = m_table->m_tombstones->size();
17,550✔
2259

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

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

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

5,214✔
2279
        for (auto bl : backlinks) {
51,471✔
2280
            auto linking_obj = t->get_object(bl);
51,471✔
2281
            LinkReplacer replacer{linking_obj, c, other, *this};
51,471✔
2282
            replacer.run();
51,471✔
2283
        }
51,471✔
2284
        return IteratorControl::AdvanceToNext;
15,642✔
2285
    };
15,642✔
2286
    m_table->for_each_backlink_column(copy_links);
17,550✔
2287
}
17,550✔
2288

2289
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
2290
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
2291
template float Obj::get<float>(ColKey col_key) const;
2292
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
2293
template double Obj::get<double>(ColKey col_key) const;
2294
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2295
template StringData Obj::get<StringData>(ColKey col_key) const;
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
{
58,869✔
2310
    ColKey::Idx col_ndx = col_key.get_index();
58,869✔
2311
    Allocator& alloc = get_alloc();
58,869✔
2312
    alloc.bump_content_version();
58,869✔
2313
    Array fallback(alloc);
58,869✔
2314
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
58,869✔
2315

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

19,836✔
2321
    sync(fields);
58,869✔
2322
}
58,869✔
2323

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

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

1,311✔
2340
    sync(fields);
3,771✔
2341
}
3,771✔
2342

2343
Obj& Obj::set_null(ColKey col_key, bool is_default)
2344
{
63,531✔
2345
    ColumnType col_type = col_key.get_type();
63,531✔
2346
    // Links need special handling
21,444✔
2347
    if (col_type == col_type_Link) {
63,531✔
2348
        set(col_key, null_key);
693✔
2349
    }
693✔
2350
    else if (col_type == col_type_Mixed) {
62,838✔
2351
        set(col_key, Mixed{});
99✔
2352
    }
99✔
2353
    else {
62,739✔
2354
        auto attrs = col_key.get_attrs();
62,739✔
2355
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
62,739✔
2356
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
99✔
2357
                              m_table->get_column_name(col_key));
99✔
2358
        }
99✔
2359

21,147✔
2360
        update_if_needed();
62,640✔
2361

21,147✔
2362
        SearchIndex* index = m_table->get_search_index(col_key);
62,640✔
2363
        if (index && !m_key.is_unresolved()) {
62,640✔
2364
            index->set(m_key, null{});
7,035✔
2365
        }
7,035✔
2366

21,147✔
2367
        switch (col_type) {
62,640✔
2368
            case col_type_Int:
8,811✔
2369
                do_set_null<ArrayIntNull>(col_key);
8,811✔
2370
                break;
8,811✔
2371
            case col_type_Bool:
8,463✔
2372
                do_set_null<ArrayBoolNull>(col_key);
8,463✔
2373
                break;
8,463✔
2374
            case col_type_Float:
9,486✔
2375
                do_set_null<ArrayFloatNull>(col_key);
9,486✔
2376
                break;
9,486✔
2377
            case col_type_Double:
9,453✔
2378
                do_set_null<ArrayDoubleNull>(col_key);
9,453✔
2379
                break;
9,453✔
2380
            case col_type_ObjectId:
4,983✔
2381
                do_set_null<ArrayObjectIdNull>(col_key);
4,983✔
2382
                break;
4,983✔
2383
            case col_type_String:
3,771✔
2384
                do_set_null<ArrayString>(col_key);
3,771✔
2385
                break;
3,771✔
2386
            case col_type_Binary:
1,872✔
2387
                do_set_null<ArrayBinary>(col_key);
1,872✔
2388
                break;
1,872✔
2389
            case col_type_Timestamp:
3,699✔
2390
                do_set_null<ArrayTimestamp>(col_key);
3,699✔
2391
                break;
3,699✔
2392
            case col_type_Decimal:
2,745✔
2393
                do_set_null<ArrayDecimal128>(col_key);
2,745✔
2394
                break;
2,745✔
2395
            case col_type_UUID:
9,357✔
2396
                do_set_null<ArrayUUIDNull>(col_key);
9,357✔
2397
                break;
9,357✔
2398
            case col_type_Mixed:
✔
2399
            case col_type_Link:
✔
2400
            case col_type_BackLink:
✔
2401
            case col_type_TypedLink:
✔
2402
                REALM_UNREACHABLE();
2403
        }
62,640✔
2404
    }
62,640✔
2405

21,444✔
2406
    if (Replication* repl = get_replication())
63,465✔
2407
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
32,898✔
2408
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
32,892✔
2409

21,411✔
2410
    return *this;
63,432✔
2411
}
63,531✔
2412

2413

2414
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2415
{
12,882,390✔
2416
    return get_table()->spec_ndx2colkey(col_ndx);
12,882,390✔
2417
}
12,882,390✔
2418

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

2424
ColKey Obj::get_primary_key_column() const
2425
{
7,905,276✔
2426
    return m_table->get_primary_key_column();
7,905,276✔
2427
}
7,905,276✔
2428

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

2434
ref_type Obj::get_collection_ref(Index index, CollectionType type) const
2435
{
4,780,350✔
2436
    if (index.is_collection()) {
4,780,350✔
2437
        return to_ref(_get<int64_t>(index.get_index()));
4,765,227✔
2438
    }
4,765,227✔
2439
    if (check_index(index)) {
15,123✔
2440
        auto val = _get<Mixed>(index.get_index());
14,409✔
2441
        if (val.is_type(DataType(int(type)))) {
14,409✔
2442
            return val.get_ref();
14,400✔
2443
        }
14,400✔
2444
        throw realm::IllegalOperation(util::format("Not a %1", type));
9✔
2445
    }
6,474✔
2446
    throw StaleAccessor("This collection is no more");
714✔
2447
}
7,182✔
2448

2449
bool Obj::check_collection_ref(Index index, CollectionType type) const noexcept
2450
{
1,278,483✔
2451
    if (index.is_collection()) {
1,278,483✔
2452
        return true;
1,275,615✔
2453
    }
1,275,615✔
2454
    if (check_index(index)) {
2,868✔
2455
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
2,859✔
2456
    }
2,859✔
2457
    return false;
9✔
2458
}
1,278✔
2459

2460
void Obj::set_collection_ref(Index index, ref_type ref, CollectionType type)
2461
{
764,640✔
2462
    if (index.is_collection()) {
764,640✔
2463
        set_int(index.get_index(), from_ref(ref));
761,220✔
2464
        return;
761,220✔
2465
    }
761,220✔
2466
    set_ref(index.get_index(), ref, type);
3,420✔
2467
}
3,420✔
2468

2469
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc