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

realm / realm-core / nicola.cabiddu_1042

27 Sep 2023 06:04PM UTC coverage: 91.085% (-1.8%) from 92.915%
nicola.cabiddu_1042

Pull #6766

Evergreen

nicola-cab
Fix logic for dictionaries
Pull Request #6766: Client Reset for collections in mixed / nested collections

97276 of 178892 branches covered (0.0%)

1994 of 2029 new or added lines in 7 files covered. (98.28%)

4556 existing lines in 112 files now uncovered.

237059 of 260260 relevant lines covered (91.09%)

6321099.55 hits per line

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

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

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

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

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

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

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

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

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

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

103
} // namespace
104

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

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

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

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

127
const ClusterTree* Obj::get_tree_top() const
128
{
38,419,479✔
129
    if (m_key.is_unresolved()) {
38,419,479✔
130
        return m_table.unchecked_ptr()->m_tombstones.get();
19,572✔
131
    }
19,572✔
132
    else {
38,399,907✔
133
        return &m_table.unchecked_ptr()->m_clusters;
38,399,907✔
134
    }
38,399,907✔
135
}
38,419,479✔
136

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

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

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

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

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

183
Replication* Obj::get_replication() const
184
{
29,222,832✔
185
    return m_table->get_repl();
29,222,832✔
186
}
29,222,832✔
187

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

225
bool Obj::compare_list_in_mixed(Lst<Mixed>& val1, Lst<Mixed>& val2, ColKey ck, Obj other, StringData col_name) const
226
{
12✔
227
    if (val1.size() != val2.size())
12✔
UNCOV
228
        return false;
×
229

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

12✔
232
        auto m1 = val1.get_any(i);
24✔
233
        auto m2 = val2.get_any(i);
24✔
234

12✔
235
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
24✔
236
            DummyParent parent(get_table(), m2.get_ref());
6✔
237
            Lst<Mixed> list(parent, 0);
6✔
238
            return compare_list_in_mixed(*val1.get_list(i), list, ck, other, col_name);
6✔
239
        }
6✔
240
        else if (m1.is_type(type_Dictionary) && m2.is_type(type_Dictionary)) {
18!
UNCOV
241
            DummyParent parent(get_table(), m2.get_ref());
×
UNCOV
242
            Dictionary dict(parent, 0);
×
UNCOV
243
            return compare_dict_in_mixed(*val1.get_dictionary(i), dict, ck, other, col_name);
×
UNCOV
244
        }
×
245
        else if (!compare_values(m1, m2, ck, other, col_name)) {
18✔
UNCOV
246
            return false;
×
UNCOV
247
        }
×
248
    }
24✔
249
    return true;
9✔
250
}
12✔
251

252
bool Obj::compare_dict_in_mixed(Dictionary& val1, Dictionary& val2, ColKey ck, Obj other, StringData col_name) const
253
{
12✔
254
    if (val1.size() != val2.size())
12✔
UNCOV
255
        return false;
×
256

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

6✔
259
        auto [k1, m1] = val1.get_pair(i);
12✔
260
        auto [k2, m2] = val2.get_pair(i);
12✔
261
        if (k1 != k2)
12✔
UNCOV
262
            return false;
×
263

6✔
264
        if (m1.is_type(type_List) && m2.is_type(type_List)) {
12!
UNCOV
265
            DummyParent parent(get_table(), m2.get_ref());
×
UNCOV
266
            Lst<Mixed> list(parent, 0);
×
UNCOV
267
            return compare_list_in_mixed(*val1.get_list(k1.get_string()), list, ck, other, col_name);
×
UNCOV
268
        }
×
269
        else if (m1.is_type(type_Dictionary) && m2.is_type(type_Dictionary)) {
12✔
270
            DummyParent parent(get_table(), m2.get_ref());
6✔
271
            Dictionary dict(parent, 0);
6✔
272
            return compare_dict_in_mixed(*val1.get_dictionary(k1.get_string()), dict, ck, other, col_name);
6✔
273
        }
6✔
274
        else if (!compare_values(m1, m2, ck, other, col_name)) {
6✔
UNCOV
275
            return false;
×
UNCOV
276
        }
×
277
    }
12✔
278
    return true;
9✔
279
}
12✔
280

281
bool Obj::operator==(const Obj& other) const
282
{
3,054✔
283
    for (auto ck : m_table->get_column_keys()) {
4,878✔
284
        StringData col_name = m_table->get_column_name(ck);
4,878✔
285
        auto compare = [&](Mixed m1, Mixed m2) {
4,869✔
286
            return compare_values(m1, m2, ck, other, col_name);
4,860✔
287
        };
4,860✔
288

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

322
bool Obj::is_valid() const noexcept
323
{
1,459,725✔
324
    // Cache valid state. If once invalid, it can never become valid again
781,683✔
325
    if (m_valid)
1,459,725✔
326
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,458,336✔
327
                                    m_table.unchecked_ptr()->is_valid(m_key));
797,097✔
328

781,683✔
329
    return m_valid;
1,459,725✔
330
}
1,459,725✔
331

332
void Obj::remove()
333
{
129,201✔
334
    m_table.cast_away_const()->remove_object(m_key);
129,201✔
335
}
129,201✔
336

337
void Obj::invalidate()
338
{
64,371✔
339
    m_key = m_table.cast_away_const()->invalidate_object(m_key);
64,371✔
340
}
64,371✔
341

342
ColKey Obj::get_column_key(StringData col_name) const
343
{
3,848,961✔
344
    return get_table()->get_column_key(col_name);
3,848,961✔
345
}
3,848,961✔
346

347
TableKey Obj::get_table_key() const
348
{
18,966✔
349
    return get_table()->get_key();
18,966✔
350
}
18,966✔
351

352
TableRef Obj::get_target_table(ColKey col_key) const
353
{
7,075,392✔
354
    if (m_table) {
7,075,476✔
355
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,074,735✔
356
    }
7,074,735✔
357
    else {
2,147,484,388✔
358
        return TableRef();
2,147,484,388✔
359
    }
2,147,484,388✔
360
}
7,075,392✔
361

362
TableRef Obj::get_target_table(ObjLink link) const
UNCOV
363
{
×
UNCOV
364
    if (m_table) {
×
UNCOV
365
        return m_table.unchecked_ptr()->get_parent_group()->get_table(link.get_table_key());
×
UNCOV
366
    }
×
UNCOV
367
    else {
×
UNCOV
368
        return TableRef();
×
UNCOV
369
    }
×
UNCOV
370
}
×
371

372
bool Obj::update() const
373
{
826,236✔
374
    // Get a new object from key
412,206✔
375
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
826,236✔
376

412,206✔
377
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
826,236✔
378
    if (changes) {
826,236✔
379
        m_mem = new_obj.m_mem;
133,194✔
380
        m_row_ndx = new_obj.m_row_ndx;
133,194✔
381
    }
133,194✔
382
    // Always update versions
412,206✔
383
    m_storage_version = new_obj.m_storage_version;
826,236✔
384
    m_table = new_obj.m_table;
826,236✔
385
    return changes;
826,236✔
386
}
826,236✔
387

388
inline bool Obj::_update_if_needed() const
389
{
79,267,101✔
390
    auto current_version = _get_alloc().get_storage_version();
79,267,101✔
391
    if (current_version != m_storage_version) {
79,267,101✔
392
        return update();
14,268✔
393
    }
14,268✔
394
    return false;
79,252,833✔
395
}
79,252,833✔
396

397
UpdateStatus Obj::update_if_needed_with_status() const
398
{
35,743,083✔
399
    if (!m_table) {
35,743,083✔
400
        // Table deleted
171✔
401
        return UpdateStatus::Detached;
342✔
402
    }
342✔
403

17,846,487✔
404
    auto current_version = get_alloc().get_storage_version();
35,742,741✔
405
    if (current_version != m_storage_version) {
35,742,741✔
406
        ClusterNode::State state = get_tree_top()->try_get(m_key);
639,651✔
407

319,617✔
408
        if (!state) {
639,651✔
409
            // Object deleted
2,589✔
410
            return UpdateStatus::Detached;
5,178✔
411
        }
5,178✔
412

317,028✔
413
        // Always update versions
317,028✔
414
        m_storage_version = current_version;
634,473✔
415
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
634,473✔
416
            m_mem = state.mem;
143,325✔
417
            m_row_ndx = state.index;
143,325✔
418
            return UpdateStatus::Updated;
143,325✔
419
        }
143,325✔
420
    }
35,594,238✔
421
    return UpdateStatus::NoChange;
35,594,238✔
422
}
35,594,238✔
423

424
template <class T>
425
T Obj::get(ColKey col_key) const
426
{
77,981,457✔
427
    m_table->check_column(col_key);
77,981,457✔
428
    ColumnType type = col_key.get_type();
77,981,457✔
429
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
77,981,457!
430

38,915,181✔
431
    return _get<T>(col_key.get_index());
77,981,457✔
432
}
77,981,457✔
433

434
template UUID Obj::_get(ColKey::Idx col_ndx) const;
435
template util::Optional<UUID> Obj::_get(ColKey::Idx col_ndx) const;
436

437
#if REALM_ENABLE_GEOSPATIAL
438

439
template <>
440
Geospatial Obj::get(ColKey col_key) const
441
{
84✔
442
    m_table->check_column(col_key);
84✔
443
    ColumnType type = col_key.get_type();
84✔
444
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
84✔
445
    return Geospatial::from_link(get_linked_object(col_key));
84✔
446
}
84✔
447

448
template <>
449
std::optional<Geospatial> Obj::get(ColKey col_key) const
450
{
12✔
451
    m_table->check_column(col_key);
12✔
452
    ColumnType type = col_key.get_type();
12✔
453
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
12✔
454

6✔
455
    auto geo = get_linked_object(col_key);
12✔
456
    if (!geo) {
12✔
457
        return {};
12✔
458
    }
12✔
UNCOV
459
    return Geospatial::from_link(geo);
×
UNCOV
460
}
×
461

462
#endif
463

464
template <class T>
465
T Obj::_get(ColKey::Idx col_ndx) const
466
{
78,725,403✔
467
    _update_if_needed();
78,725,403✔
468

39,287,043✔
469
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
78,725,403✔
470
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
78,725,403✔
471
    values.init_from_ref(ref);
78,725,403✔
472

39,287,043✔
473
    return values.get(m_row_ndx);
78,725,403✔
474
}
78,725,403✔
475

476
template <>
477
ObjKey Obj::_get<ObjKey>(ColKey::Idx col_ndx) const
478
{
314,952✔
479
    _update_if_needed();
314,952✔
480

157,422✔
481
    ArrayKey values(_get_alloc());
314,952✔
482
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
314,952✔
483
    values.init_from_ref(ref);
314,952✔
484

157,422✔
485
    ObjKey k = values.get(m_row_ndx);
314,952✔
486
    return k.is_unresolved() ? ObjKey{} : k;
314,841✔
487
}
314,952✔
488

489
bool Obj::is_unresolved(ColKey col_key) const
490
{
30✔
491
    m_table->check_column(col_key);
30✔
492
    ColumnType type = col_key.get_type();
30✔
493
    REALM_ASSERT(type == col_type_Link);
30✔
494

15✔
495
    _update_if_needed();
30✔
496

15✔
497
    return get_unfiltered_link(col_key).is_unresolved();
30✔
498
}
30✔
499

500
ObjKey Obj::get_unfiltered_link(ColKey col_key) const
501
{
315,186✔
502
    ArrayKey values(get_alloc());
315,186✔
503
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
315,186✔
504
    values.init_from_ref(ref);
315,186✔
505

157,566✔
506
    return values.get(m_row_ndx);
315,186✔
507
}
315,186✔
508

509
template <>
510
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
511
{
143,220,834✔
512
    // manual inline of _update_if_needed():
72,093,756✔
513
    auto& alloc = _get_alloc();
143,220,834✔
514
    auto current_version = alloc.get_storage_version();
143,220,834✔
515
    if (current_version != m_storage_version) {
143,220,834✔
516
        update();
142,287✔
517
    }
142,287✔
518

72,093,756✔
519
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
143,220,834✔
520
    char* header = alloc.translate(ref);
143,220,834✔
521
    int width = Array::get_width_from_header(header);
143,220,834✔
522
    char* data = Array::get_data_from_header(header);
143,220,834✔
523
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
143,220,834✔
UNCOV
524
}
×
525

526
template <>
527
int64_t Obj::get<int64_t>(ColKey col_key) const
528
{
105,583,494✔
529
    m_table->check_column(col_key);
105,583,494✔
530
    ColumnType type = col_key.get_type();
105,583,494✔
531
    REALM_ASSERT(type == col_type_Int);
105,583,494✔
532

54,185,484✔
533
    if (col_key.get_attrs().test(col_attr_Nullable)) {
105,583,494✔
534
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
6,006✔
535
        if (!val) {
6,006✔
536
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
6✔
537
        }
6✔
538
        return *val;
6,000✔
539
    }
6,000✔
540
    else {
105,577,488✔
541
        return _get<int64_t>(col_key.get_index());
105,577,488✔
542
    }
105,577,488✔
543
}
105,583,494✔
544

545
template <>
546
bool Obj::get<bool>(ColKey col_key) const
547
{
589,482✔
548
    m_table->check_column(col_key);
589,482✔
549
    ColumnType type = col_key.get_type();
589,482✔
550
    REALM_ASSERT(type == col_type_Bool);
589,482✔
551

290,694✔
552
    if (col_key.get_attrs().test(col_attr_Nullable)) {
589,482✔
553
        auto val = _get<util::Optional<bool>>(col_key.get_index());
30✔
554
        if (!val) {
30✔
UNCOV
555
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
×
UNCOV
556
        }
×
557
        return *val;
30✔
558
    }
30✔
559
    else {
589,452✔
560
        return _get<bool>(col_key.get_index());
589,452✔
561
    }
589,452✔
562
}
589,482✔
563

564
template <>
565
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
566
{
12,101,316✔
567
    // manual inline of _update_if_needed():
6,048,333✔
568
    auto& alloc = _get_alloc();
12,101,316✔
569
    auto current_version = alloc.get_storage_version();
12,101,316✔
570
    if (current_version != m_storage_version) {
12,101,316✔
571
        update();
16,230✔
572
    }
16,230✔
573

6,048,333✔
574
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
12,101,316✔
575
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
12,101,316✔
576
    auto& spec = get_spec();
12,101,316✔
577
    if (spec.is_string_enum_type(spec_ndx)) {
12,101,316✔
578
        ArrayString values(get_alloc());
3,011,532✔
579
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
3,011,532✔
580
        values.init_from_ref(ref);
3,011,532✔
581

1,504,296✔
582
        return values.get(m_row_ndx);
3,011,532✔
583
    }
3,011,532✔
584
    else {
9,089,784✔
585
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
9,089,784✔
586
    }
9,089,784✔
587
}
12,101,316✔
588

589
template <>
590
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
591
{
156,684✔
592
    // manual inline of _update_if_needed():
78,336✔
593
    auto& alloc = _get_alloc();
156,684✔
594
    auto current_version = alloc.get_storage_version();
156,684✔
595
    if (current_version != m_storage_version) {
156,684✔
596
        update();
132✔
597
    }
132✔
598

78,336✔
599
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
156,684✔
600
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
156,684✔
601
}
156,684✔
602

603
Mixed Obj::get_any(ColKey col_key) const
604
{
47,382,882✔
605
    m_table->check_column(col_key);
47,382,882✔
606
    auto col_ndx = col_key.get_index();
47,382,882✔
607
    if (col_key.is_collection()) {
47,382,882✔
608
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
5,700✔
609
        return Mixed(ref, get_table()->get_collection_type(col_key));
5,700✔
610
    }
5,700✔
611
    switch (col_key.get_type()) {
47,377,182✔
612
        case col_type_Int:
35,513,511✔
613
            if (col_key.get_attrs().test(col_attr_Nullable)) {
35,513,511✔
614
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
707,214✔
615
            }
707,214✔
616
            else {
34,806,297✔
617
                return Mixed{_get<int64_t>(col_ndx)};
34,806,297✔
618
            }
34,806,297✔
619
        case col_type_Bool:
391,416✔
620
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
391,416✔
621
        case col_type_Float:
8,007✔
622
            return Mixed{_get<util::Optional<float>>(col_ndx)};
8,007✔
623
        case col_type_Double:
19,617✔
624
            return Mixed{_get<util::Optional<double>>(col_ndx)};
19,617✔
625
        case col_type_String:
10,737,771✔
626
            return Mixed{_get<String>(col_ndx)};
10,737,771✔
627
        case col_type_Binary:
1,605✔
628
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
629
        case col_type_Mixed:
6,525✔
630
            return _get<Mixed>(col_ndx);
6,525✔
631
        case col_type_Timestamp:
233,193✔
632
            return Mixed{_get<Timestamp>(col_ndx)};
233,193✔
633
        case col_type_Decimal:
7,860✔
634
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
635
        case col_type_ObjectId:
434,685✔
636
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
434,685✔
637
        case col_type_UUID:
137,049✔
638
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
137,049✔
639
        case col_type_Link:
40,584✔
640
            return Mixed{_get<ObjKey>(col_ndx)};
40,584✔
UNCOV
641
        default:
✔
UNCOV
642
            REALM_UNREACHABLE();
×
UNCOV
643
            break;
×
UNCOV
644
    }
×
UNCOV
645
    return {};
×
UNCOV
646
}
×
647

648
Mixed Obj::get_primary_key() const
649
{
100,602✔
650
    auto col = m_table->get_primary_key_column();
100,602✔
651
    return col ? get_any(col) : Mixed{get_key()};
100,506✔
652
}
100,602✔
653

654
/* FIXME: Make this one fast too!
655
template <>
656
ObjKey Obj::_get(size_t col_ndx) const
657
{
658
    return ObjKey(_get<int64_t>(col_ndx));
659
}
660
*/
661

662
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
663
{
25,275✔
664
    Obj obj;
25,275✔
665
    if (!link.is_null()) {
25,275✔
666
        TableRef target_table;
17,109✔
667
        if (link.is_type(type_TypedLink)) {
17,109✔
668
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
324✔
669
        }
324✔
670
        else {
16,785✔
671
            target_table = get_target_table(link_col_key);
16,785✔
672
        }
16,785✔
673
        obj = target_table->get_object(link.get<ObjKey>());
17,109✔
674
    }
17,109✔
675
    return obj;
25,275✔
676
}
25,275✔
677

678
Obj Obj::get_parent_object() const
679
{
12✔
680
    Obj obj;
12✔
681
    update_if_needed();
12✔
682

6✔
683
    if (!m_table->is_embedded()) {
12✔
UNCOV
684
        throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded");
×
UNCOV
685
    }
×
686
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
18✔
687
        if (get_backlink_cnt(backlink_col_key) == 1) {
18✔
688
            auto obj_key = get_backlink(backlink_col_key, 0);
12✔
689
            obj = m_table->get_opposite_table(backlink_col_key)->get_object(obj_key);
12✔
690
            return IteratorControl::Stop;
12✔
691
        }
12✔
692
        return IteratorControl::AdvanceToNext;
6✔
693
    });
6✔
694

6✔
695
    return obj;
12✔
696
}
12✔
697

698
template <class T>
699
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
700
{
1,334,142✔
701
    T values(get_alloc());
1,334,142✔
702
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,334,142✔
703
    values.init_from_ref(ref);
1,334,142✔
704
    return values.is_null(m_row_ndx);
1,334,142✔
705
}
1,334,142✔
706

707
template <>
708
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
709
{
421,527✔
710
    ArrayString values(get_alloc());
421,527✔
711
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
421,527✔
712
    values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
421,527✔
713
    values.init_from_ref(ref);
421,527✔
714
    return values.is_null(m_row_ndx);
421,527✔
715
}
421,527✔
716

717
size_t Obj::get_link_count(ColKey col_key) const
718
{
108✔
719
    return get_list<ObjKey>(col_key).size();
108✔
720
}
108✔
721

722
bool Obj::is_null(ColKey col_key) const
723
{
5,789,607✔
724
    update_if_needed();
5,789,607✔
725
    ColumnAttrMask attr = col_key.get_attrs();
5,789,607✔
726
    ColKey::Idx col_ndx = col_key.get_index();
5,789,607✔
727
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,789,607✔
728
        switch (col_key.get_type()) {
1,755,669✔
729
            case col_type_Int:
429,033✔
730
                return do_is_null<ArrayIntNull>(col_ndx);
429,033✔
731
            case col_type_Bool:
474,633✔
732
                return do_is_null<ArrayBoolNull>(col_ndx);
474,633✔
733
            case col_type_Float:
9,189✔
734
                return do_is_null<ArrayFloatNull>(col_ndx);
9,189✔
735
            case col_type_Double:
3,267✔
736
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
737
            case col_type_String:
421,527✔
738
                return do_is_null<ArrayString>(col_ndx);
421,527✔
739
            case col_type_Binary:
267✔
740
                return do_is_null<ArrayBinary>(col_ndx);
267✔
741
            case col_type_Mixed:
285✔
742
                return do_is_null<ArrayMixed>(col_ndx);
285✔
743
            case col_type_Timestamp:
394,413✔
744
                return do_is_null<ArrayTimestamp>(col_ndx);
394,413✔
745
            case col_type_Link:
20,580✔
746
                return do_is_null<ArrayKey>(col_ndx);
20,580✔
747
            case col_type_ObjectId:
231✔
748
                return do_is_null<ArrayObjectIdNull>(col_ndx);
231✔
749
            case col_type_Decimal:
2,013✔
750
                return do_is_null<ArrayDecimal128>(col_ndx);
2,013✔
751
            case col_type_UUID:
231✔
752
                return do_is_null<ArrayUUIDNull>(col_ndx);
231✔
UNCOV
753
            default:
✔
UNCOV
754
                REALM_UNREACHABLE();
×
755
        }
1,755,669✔
756
    }
1,755,669✔
757
    return false;
4,861,524✔
758
}
5,789,607✔
759

760

761
// Figure out if this object has any remaining backlinkss
762
bool Obj::has_backlinks(bool only_strong_links) const
763
{
81,744✔
764
    const Table& target_table = *m_table;
81,744✔
765

40,911✔
766
    // If we only look for strong links and the table is not embedded,
40,911✔
767
    // then there is no relevant backlinks to find.
40,911✔
768
    if (only_strong_links && !target_table.is_embedded()) {
81,744✔
769
        return false;
×
UNCOV
770
    }
×
771

40,911✔
772
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
81,744✔
773
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
24,045✔
774
    });
24,327✔
775
}
81,744✔
776

777
size_t Obj::get_backlink_count() const
778
{
185,070✔
779
    update_if_needed();
185,070✔
780

92,382✔
781
    size_t cnt = 0;
185,070✔
782
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
462,306✔
783
        cnt += get_backlink_cnt(backlink_col_key);
462,306✔
784
        return IteratorControl::AdvanceToNext;
462,306✔
785
    });
462,306✔
786
    return cnt;
185,070✔
787
}
185,070✔
788

789
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
790
{
14,106✔
791
    update_if_needed();
14,106✔
792

7,044✔
793
    size_t cnt = 0;
14,106✔
794
    if (TableKey origin_table_key = origin.get_key()) {
14,106✔
795
        ColKey backlink_col_key;
14,106✔
796
        auto type = origin_col_key.get_type();
14,106✔
797
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
14,106✔
798
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
12✔
799
        }
12✔
800
        else {
14,094✔
801
            backlink_col_key = origin.get_opposite_column(origin_col_key);
14,094✔
802
        }
14,094✔
803

7,044✔
804
        cnt = get_backlink_cnt(backlink_col_key);
14,106✔
805
    }
14,106✔
806
    return cnt;
14,106✔
807
}
14,106✔
808

809
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
810
{
14,640✔
811
    ColKey backlink_col_key;
14,640✔
812
    auto type = origin_col_key.get_type();
14,640✔
813
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
14,640✔
814
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
815
    }
36✔
816
    else {
14,604✔
817
        backlink_col_key = origin.get_opposite_column(origin_col_key);
14,604✔
818
    }
14,604✔
819
    return get_backlink(backlink_col_key, backlink_ndx);
14,640✔
820
}
14,640✔
821

822
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key)
823
{
696✔
824
    TableView tv(src_table, src_col_key, *this);
696✔
825
    tv.do_sync();
696✔
826
    return tv;
696✔
827
}
696✔
828

829
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
830
{
14,652✔
831
    get_table()->check_column(backlink_col);
14,652✔
832
    Allocator& alloc = get_alloc();
14,652✔
833
    Array fields(alloc);
14,652✔
834
    fields.init_from_mem(m_mem);
14,652✔
835

7,317✔
836
    ArrayBacklink backlinks(alloc);
14,652✔
837
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
14,652✔
838
    backlinks.init_from_parent();
14,652✔
839
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
14,652✔
840
}
14,652✔
841

842
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
843
{
297,105✔
844
    update_if_needed();
297,105✔
845

148,290✔
846
    get_table()->check_column(backlink_col);
297,105✔
847
    Allocator& alloc = get_alloc();
297,105✔
848
    Array fields(alloc);
297,105✔
849
    fields.init_from_mem(m_mem);
297,105✔
850

148,290✔
851
    ArrayBacklink backlinks(alloc);
297,105✔
852
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
297,105✔
853
    backlinks.init_from_parent();
297,105✔
854

148,290✔
855
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
297,105✔
856
    std::vector<ObjKey> vec;
297,105✔
857
    vec.reserve(cnt);
297,105✔
858
    for (size_t i = 0; i < cnt; i++) {
549,066✔
859
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
251,961✔
860
    }
251,961✔
861
    return vec;
297,105✔
862
}
297,105✔
863

864
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
865
{
500,757✔
866
    Allocator& alloc = get_alloc();
500,757✔
867
    Array fields(alloc);
500,757✔
868
    fields.init_from_mem(m_mem);
500,757✔
869

249,945✔
870
    ArrayBacklink backlinks(alloc);
500,757✔
871
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
500,757✔
872
    backlinks.init_from_parent();
500,757✔
873

249,945✔
874
    return backlinks.get_backlink_count(m_row_ndx);
500,757✔
875
}
500,757✔
876

877
void Obj::verify_backlink(const Table& origin, ColKey origin_col_key, ObjKey origin_key) const
878
{
60,660✔
879
#ifdef REALM_DEBUG
60,660✔
880
    ColKey backlink_col_key;
60,660✔
881
    auto type = origin_col_key.get_type();
60,660✔
882
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
60,660✔
UNCOV
883
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
×
UNCOV
884
    }
×
885
    else {
60,660✔
886
        backlink_col_key = origin.get_opposite_column(origin_col_key);
60,660✔
887
    }
60,660✔
888

30,330✔
889
    Allocator& alloc = get_alloc();
60,660✔
890
    Array fields(alloc);
60,660✔
891
    fields.init_from_mem(m_mem);
60,660✔
892

30,330✔
893
    ArrayBacklink backlinks(alloc);
60,660✔
894
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
60,660✔
895
    backlinks.init_from_parent();
60,660✔
896

30,330✔
897
    REALM_ASSERT(backlinks.verify_backlink(m_row_ndx, origin_key.value));
60,660✔
898
#else
899
    static_cast<void>(origin);
900
    static_cast<void>(origin_col_key);
901
    static_cast<void>(origin_key);
902
#endif
903
}
60,660✔
904

905
void Obj::traverse_path(Visitor v, PathSizer ps, size_t path_length) const
906
{
90✔
907
    struct BacklinkTraverser : public LinkTranslator {
90✔
908
        BacklinkTraverser(Obj origin, ColKey origin_col_key, Obj dest)
90✔
909
            : LinkTranslator(origin, origin_col_key)
90✔
910
            , m_dest_obj(dest)
90✔
911
        {
69✔
912
        }
48✔
913
        void on_list_of_links(LnkLst& ll) final
90✔
914
        {
60✔
915
            auto i = ll.find_first(m_dest_obj.get_key());
30✔
916
            REALM_ASSERT(i != realm::npos);
30✔
917
            m_index = Mixed(int64_t(i));
30✔
918
        }
30✔
919
        void on_dictionary(Dictionary& dict) final
90✔
920
        {
48✔
921
            for (auto it : dict) {
12✔
922
                if (it.second.is_type(type_TypedLink) && it.second.get_link() == m_dest_obj.get_link()) {
12✔
923
                    m_index = it.first;
6✔
924
                    break;
6✔
925
                }
6✔
926
            }
12✔
927
            REALM_ASSERT(!m_index.is_null());
6✔
928
        }
6✔
929
        void on_list_of_mixed(Lst<Mixed>&) final
90✔
930
        {
45✔
UNCOV
931
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
×
UNCOV
932
        }
×
933
        void on_set_of_links(LnkSet&) final
90✔
934
        {
45✔
UNCOV
935
            REALM_UNREACHABLE(); // sets of embedded objects are not allowed at the schema level
×
UNCOV
936
        }
×
937
        void on_set_of_mixed(Set<Mixed>&) final
90✔
938
        {
45✔
UNCOV
939
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
×
UNCOV
940
        }
×
941
        void on_link_property(ColKey) final {}
51✔
942
        void on_mixed_property(ColKey) final {}
45✔
943
        Mixed result()
90✔
944
        {
69✔
945
            return m_index;
48✔
946
        }
48✔
947

45✔
948
    private:
90✔
949
        Mixed m_index;
90✔
950
        Obj m_dest_obj;
90✔
951
    };
90✔
952

45✔
953
    if (m_table->is_embedded()) {
90✔
954
        REALM_ASSERT(get_backlink_count() == 1);
48✔
955
        m_table->for_each_backlink_column([&](ColKey col_key) {
84✔
956
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
84✔
957
            if (backlinks.size() == 1) {
84✔
958
                TableRef tr = m_table->get_opposite_table(col_key);
48✔
959
                Obj obj = tr->get_object(backlinks[0]); // always the first (and only)
48✔
960
                auto next_col_key = m_table->get_opposite_column(col_key);
48✔
961
                BacklinkTraverser traverser{obj, next_col_key, *this};
48✔
962
                traverser.run();
48✔
963
                Mixed index = traverser.result();
48✔
964
                obj.traverse_path(v, ps, path_length + 1);
48✔
965
                v(obj, next_col_key, index);
48✔
966
                return IteratorControl::Stop; // early out
48✔
967
            }
48✔
968
            return IteratorControl::AdvanceToNext; // try next column
36✔
969
        });
36✔
970
    }
48✔
971
    else {
42✔
972
        ps(path_length);
42✔
973
    }
42✔
974
}
90✔
975

976
Obj::FatPath Obj::get_fat_path() const
977
{
30✔
978
    FatPath result;
30✔
979
    auto sizer = [&](size_t size) {
30✔
980
        result.reserve(size);
30✔
981
    };
30✔
982
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
30✔
983
        result.push_back({o2, col, idx});
30✔
984
    };
30✔
985
    traverse_path(step, sizer);
30✔
986
    return result;
30✔
987
}
30✔
988

989
FullPath Obj::get_path() const
990
{
308,190✔
991
    FullPath result;
308,190✔
992
    if (m_table->is_embedded()) {
308,190✔
993
        REALM_ASSERT(get_backlink_count() == 1);
180,363✔
994
        m_table->for_each_backlink_column([&](ColKey col_key) {
285,567✔
995
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
285,567✔
996
            if (backlinks.size() == 1) {
285,567✔
997
                TableRef origin_table = m_table->get_opposite_table(col_key);
180,363✔
998
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
180,363✔
999
                auto next_col_key = m_table->get_opposite_column(col_key);
180,363✔
1000

90,027✔
1001
                ColumnAttrMask attr = next_col_key.get_attrs();
180,363✔
1002
                Mixed index;
180,363✔
1003
                if (attr.test(col_attr_List)) {
180,363✔
1004
                    REALM_ASSERT(next_col_key.get_type() == col_type_LinkList);
70,614✔
1005
                    Lst<ObjKey> link_list(next_col_key);
70,614✔
1006
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
70,614✔
1007
                    REALM_ASSERT(i != realm::not_found);
70,614✔
1008
                    result = link_list.get_path();
70,614✔
1009
                    result.path_from_top.emplace_back(i);
70,614✔
1010
                }
70,614✔
1011
                else if (attr.test(col_attr_Dictionary)) {
109,749✔
1012
                    Dictionary dict(next_col_key);
44,274✔
1013
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
44,274✔
1014
                    REALM_ASSERT(ndx != realm::not_found);
44,274✔
1015
                    result = dict.get_path();
44,274✔
1016
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
44,274✔
1017
                }
44,274✔
1018
                else {
65,475✔
1019
                    result = obj.get_path();
65,475✔
1020
                    if (result.path_from_top.empty()) {
65,475✔
1021
                        result.path_from_top.push_back(next_col_key);
16,167✔
1022
                    }
16,167✔
1023
                    else {
49,308✔
1024
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
49,308✔
1025
                    }
49,308✔
1026
                }
65,475✔
1027

90,027✔
1028
                return IteratorControl::Stop; // early out
180,363✔
1029
            }
180,363✔
1030
            return IteratorControl::AdvanceToNext; // try next column
105,204✔
1031
        });
105,204✔
1032
    }
180,363✔
1033
    else {
127,827✔
1034
        result.top_objkey = get_key();
127,827✔
1035
        result.top_table = get_table()->get_key();
127,827✔
1036
    }
127,827✔
1037
    return result;
308,190✔
1038
}
308,190✔
1039

1040
std::string Obj::get_id() const
1041
{
34,440✔
1042
    std::ostringstream ostr;
34,440✔
1043
    auto path = get_path();
34,440✔
1044
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
34,440✔
1045
    ostr << top_table->get_class_name() << '[';
34,440✔
1046
    if (top_table->get_primary_key_column()) {
34,440✔
1047
        ostr << top_table->get_primary_key(path.top_objkey);
29,076✔
1048
    }
29,076✔
1049
    else {
5,364✔
1050
        ostr << path.top_objkey;
5,364✔
1051
    }
5,364✔
1052
    ostr << ']';
34,440✔
1053
    if (!path.path_from_top.empty()) {
34,440✔
1054
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
34,440✔
1055
        path.path_from_top[0] = PathElement(prop_name);
34,440✔
1056
        ostr << path.path_from_top;
34,440✔
1057
    }
34,440✔
1058
    return ostr.str();
34,440✔
1059
}
34,440✔
1060

1061
Path Obj::get_short_path() const noexcept
1062
{
382,605✔
1063
    return {};
382,605✔
1064
}
382,605✔
1065

1066
StablePath Obj::get_stable_path() const noexcept
1067
{
1,911,657✔
1068
    return {};
1,911,657✔
1069
}
1,911,657✔
1070

1071
void Obj::add_index(Path& path, const Index& index) const
1072
{
497,511✔
1073
    if (path.empty()) {
497,511✔
1074
        path.emplace_back(get_table()->get_column_key(index));
494,265✔
1075
    }
494,265✔
1076
    else {
3,246✔
1077
        StringData col_name = get_table()->get_column_name(index);
3,246✔
1078
        path.emplace_back(col_name);
3,246✔
1079
    }
3,246✔
1080
}
497,511✔
1081

1082
std::string Obj::to_string() const
1083
{
24✔
1084
    std::ostringstream ostr;
24✔
1085
    to_json(ostr, 0, {});
24✔
1086
    return ostr.str();
24✔
1087
}
24✔
1088

1089
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
UNCOV
1090
{
×
UNCOV
1091
    obj.to_json(ostr, -1, {});
×
UNCOV
1092
    return ostr;
×
UNCOV
1093
}
×
1094

1095
/*********************************** Obj *************************************/
1096

1097
bool Obj::ensure_writeable()
UNCOV
1098
{
×
UNCOV
1099
    Allocator& alloc = get_alloc();
×
UNCOV
1100
    if (alloc.is_read_only(m_mem.get_ref())) {
×
UNCOV
1101
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
×
UNCOV
1102
        m_storage_version = alloc.get_storage_version();
×
UNCOV
1103
        return true;
×
UNCOV
1104
    }
×
UNCOV
1105
    return false;
×
UNCOV
1106
}
×
1107

1108
REALM_FORCEINLINE void Obj::sync(Node& arr)
1109
{
36,609,255✔
1110
    auto ref = arr.get_ref();
36,609,255✔
1111
    if (arr.has_missing_parent_update()) {
36,609,255✔
1112
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
269,070✔
1113
    }
269,070✔
1114
    if (m_mem.get_ref() != ref) {
36,609,255✔
1115
        m_mem = arr.get_mem();
633,633✔
1116
        m_storage_version = arr.get_alloc().get_storage_version();
633,633✔
1117
    }
633,633✔
1118
}
36,609,255✔
1119

1120
template <>
1121
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
1122
{
10,308✔
1123
    update_if_needed();
10,308✔
1124
    get_table()->check_column(col_key);
10,308✔
1125
    auto type = col_key.get_type();
10,308✔
1126
    auto col_ndx = col_key.get_index();
10,308✔
1127
    bool recurse = false;
10,308✔
1128
    CascadeState state;
10,308✔
1129

5,148✔
1130
    if (type != col_type_Mixed)
10,308✔
UNCOV
1131
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
×
1132
    if (value_is_null(value) && !col_key.is_nullable()) {
10,308✔
UNCOV
1133
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
×
UNCOV
1134
    }
×
1135
    if (value.is_type(type_Link)) {
10,308✔
UNCOV
1136
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
×
UNCOV
1137
    }
×
1138

5,148✔
1139
    Mixed old_value = get<Mixed>(col_key);
10,308✔
1140
    ObjLink old_link{};
10,308✔
1141
    ObjLink new_link{};
10,308✔
1142
    if (old_value.is_type(type_TypedLink)) {
10,308✔
1143
        old_link = old_value.get<ObjLink>();
120✔
1144
        recurse = remove_backlink(col_key, old_link, state);
120✔
1145
    }
120✔
1146
    else if (old_value.is_type(type_Dictionary)) {
10,188✔
1147
        Dictionary dict(*this, col_key);
54✔
1148
        dict.remove_backlinks(state);
54✔
1149
    }
54✔
1150
    else if (old_value.is_type(type_List)) {
10,134✔
1151
        Lst<Mixed> list(*this, col_key);
54✔
1152
        list.remove_backlinks(state);
54✔
1153
    }
54✔
1154

5,148✔
1155
    if (value.is_type(type_TypedLink)) {
10,308✔
1156
        if (m_table->is_asymmetric()) {
930✔
1157
            throw IllegalOperation("Links not allowed in asymmetric tables");
12✔
1158
        }
12✔
1159
        new_link = value.template get<ObjLink>();
918✔
1160
        m_table->get_parent_group()->validate(new_link);
918✔
1161
        if (new_link == old_link)
918✔
1162
            return *this;
18✔
1163
        set_backlink(col_key, new_link);
900✔
1164
    }
900✔
1165

5,148✔
1166
    SearchIndex* index = m_table->get_search_index(col_key);
10,293✔
1167
    // The following check on unresolved is just a precaution as it should not
5,133✔
1168
    // be possible to hit that while Mixed is not a supported primary key type.
5,133✔
1169
    if (index && !m_key.is_unresolved()) {
10,278✔
1170
        index->set(m_key, value);
882✔
1171
    }
882✔
1172

5,133✔
1173
    Allocator& alloc = get_alloc();
10,278✔
1174
    alloc.bump_content_version();
10,278✔
1175
    Array fallback(alloc);
10,278✔
1176
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
10,278✔
1177
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
10,278✔
1178
    ArrayMixed values(alloc);
10,278✔
1179
    values.set_parent(&fields, col_ndx.val + 1);
10,278✔
1180
    values.init_from_parent();
10,278✔
1181
    values.set(m_row_ndx, value);
10,278✔
1182

5,133✔
1183
    sync(fields);
10,278✔
1184

5,133✔
1185
    if (Replication* repl = get_replication())
10,278✔
1186
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
6,156✔
1187
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
6,156✔
1188

5,133✔
1189
    if (recurse)
10,278✔
UNCOV
1190
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1191

5,133✔
1192
    return *this;
10,278✔
1193
}
10,308✔
1194

1195
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1196
{
790,545✔
1197
    if (value.is_null()) {
790,545✔
1198
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
252✔
1199
        set_null(col_key);
252✔
1200
    }
252✔
1201
    else {
790,293✔
1202
        switch (col_key.get_type()) {
790,293✔
1203
            case col_type_Int:
727,335✔
1204
                if (col_key.get_attrs().test(col_attr_Nullable)) {
727,335✔
1205
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
3,990✔
1206
                }
3,990✔
1207
                else {
723,345✔
1208
                    set(col_key, value.get_int(), is_default);
723,345✔
1209
                }
723,345✔
1210
                break;
727,335✔
1211
            case col_type_Bool:
108✔
1212
                set(col_key, value.get_bool(), is_default);
108✔
1213
                break;
108✔
1214
            case col_type_Float:
246✔
1215
                set(col_key, value.get_float(), is_default);
246✔
1216
                break;
246✔
1217
            case col_type_Double:
1,416✔
1218
                set(col_key, value.get_double(), is_default);
1,416✔
1219
                break;
1,416✔
1220
            case col_type_String:
36,750✔
1221
                set(col_key, value.get_string(), is_default);
36,750✔
1222
                break;
36,750✔
1223
            case col_type_Binary:
17,439✔
1224
                set(col_key, value.get<Binary>(), is_default);
17,439✔
1225
                break;
17,439✔
1226
            case col_type_Mixed:
288✔
1227
                set(col_key, value, is_default);
288✔
1228
                break;
288✔
1229
            case col_type_Timestamp:
5,307✔
1230
                set(col_key, value.get<Timestamp>(), is_default);
5,307✔
1231
                break;
5,307✔
1232
            case col_type_ObjectId:
1,134✔
1233
                set(col_key, value.get<ObjectId>(), is_default);
1,134✔
1234
                break;
1,134✔
1235
            case col_type_Decimal:
102✔
1236
                set(col_key, value.get<Decimal128>(), is_default);
102✔
1237
                break;
102✔
1238
            case col_type_UUID:
108✔
1239
                set(col_key, value.get<UUID>(), is_default);
108✔
1240
                break;
108✔
1241
            case col_type_Link:
72✔
1242
                set(col_key, value.get<ObjKey>(), is_default);
72✔
1243
                break;
72✔
UNCOV
1244
            case col_type_TypedLink:
✔
UNCOV
1245
                set(col_key, value.get<ObjLink>(), is_default);
×
UNCOV
1246
                break;
×
UNCOV
1247
            default:
✔
1248
                break;
×
1249
        }
790,356✔
1250
    }
790,356✔
1251
    return *this;
790,356✔
1252
}
790,356✔
1253

1254
template <>
1255
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1256
{
19,265,283✔
1257
    update_if_needed();
19,265,283✔
1258
    get_table()->check_column(col_key);
19,265,283✔
1259
    auto col_ndx = col_key.get_index();
19,265,283✔
1260

9,540,642✔
1261
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
19,265,283✔
UNCOV
1262
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
UNCOV
1263
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1264

9,540,642✔
1265
    SearchIndex* index = m_table->get_search_index(col_key);
19,265,283✔
1266
    if (index && !m_key.is_unresolved()) {
19,265,283✔
1267
        index->set(m_key, value);
186,129✔
1268
    }
186,129✔
1269

9,540,642✔
1270
    Allocator& alloc = get_alloc();
19,265,283✔
1271
    alloc.bump_content_version();
19,265,283✔
1272
    Array fallback(alloc);
19,265,283✔
1273
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
19,265,283✔
1274
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
19,265,283✔
1275
    auto attr = col_key.get_attrs();
19,265,283✔
1276
    if (attr.test(col_attr_Nullable)) {
19,265,283✔
1277
        ArrayIntNull values(alloc);
2,528,319✔
1278
        values.set_parent(&fields, col_ndx.val + 1);
2,528,319✔
1279
        values.init_from_parent();
2,528,319✔
1280
        values.set(m_row_ndx, value);
2,528,319✔
1281
    }
2,528,319✔
1282
    else {
16,736,964✔
1283
        ArrayInteger values(alloc);
16,736,964✔
1284
        values.set_parent(&fields, col_ndx.val + 1);
16,736,964✔
1285
        values.init_from_parent();
16,736,964✔
1286
        values.set(m_row_ndx, value);
16,736,964✔
1287
    }
16,736,964✔
1288

9,540,642✔
1289
    sync(fields);
19,265,283✔
1290

9,540,642✔
1291
    if (Replication* repl = get_replication()) {
19,265,283✔
1292
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
10,266,069✔
1293
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
10,265,583✔
1294
    }
10,266,069✔
1295

9,540,642✔
1296
    return *this;
19,265,283✔
1297
}
19,265,283✔
1298

1299
Obj& Obj::add_int(ColKey col_key, int64_t value)
1300
{
17,829✔
1301
    update_if_needed();
17,829✔
1302
    get_table()->check_column(col_key);
17,829✔
1303
    auto col_ndx = col_key.get_index();
17,829✔
1304

8,790✔
1305
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
17,823✔
1306
        uint64_t ua = uint64_t(a);
17,817✔
1307
        uint64_t ub = uint64_t(b);
17,817✔
1308
        return int64_t(ua + ub);
17,817✔
1309
    };
17,817✔
1310

8,790✔
1311
    Allocator& alloc = get_alloc();
17,829✔
1312
    alloc.bump_content_version();
17,829✔
1313
    Array fallback(alloc);
17,829✔
1314
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
17,829✔
1315
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
17,829✔
1316

8,790✔
1317
    if (col_key.get_type() == col_type_Mixed) {
17,829✔
1318
        ArrayMixed values(alloc);
24✔
1319
        values.set_parent(&fields, col_ndx.val + 1);
24✔
1320
        values.init_from_parent();
24✔
1321
        Mixed old = values.get(m_row_ndx);
24✔
1322
        if (old.is_type(type_Int)) {
24✔
1323
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
18✔
1324
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
18✔
UNCOV
1325
                index->set(m_key, new_val);
×
UNCOV
1326
            }
×
1327
            values.set(m_row_ndx, Mixed(new_val));
18✔
1328
        }
18✔
1329
        else {
6✔
1330
            throw IllegalOperation("Value not an int");
6✔
1331
        }
6✔
1332
    }
17,805✔
1333
    else {
17,805✔
1334
        if (col_key.get_type() != col_type_Int)
17,805✔
UNCOV
1335
            throw IllegalOperation("Property not an int");
×
1336

8,778✔
1337
        auto attr = col_key.get_attrs();
17,805✔
1338
        if (attr.test(col_attr_Nullable)) {
17,805✔
1339
            ArrayIntNull values(alloc);
339✔
1340
            values.set_parent(&fields, col_ndx.val + 1);
339✔
1341
            values.init_from_parent();
339✔
1342
            util::Optional<int64_t> old = values.get(m_row_ndx);
339✔
1343
            if (old) {
339✔
1344
                auto new_val = add_wrap(*old, value);
333✔
1345
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
333✔
UNCOV
1346
                    index->set(m_key, new_val);
×
UNCOV
1347
                }
×
1348
                values.set(m_row_ndx, new_val);
333✔
1349
            }
333✔
1350
            else {
6✔
1351
                throw IllegalOperation("No prior value");
6✔
1352
            }
6✔
1353
        }
17,466✔
1354
        else {
17,466✔
1355
            ArrayInteger values(alloc);
17,466✔
1356
            values.set_parent(&fields, col_ndx.val + 1);
17,466✔
1357
            values.init_from_parent();
17,466✔
1358
            int64_t old = values.get(m_row_ndx);
17,466✔
1359
            auto new_val = add_wrap(old, value);
17,466✔
1360
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
17,466✔
1361
                index->set(m_key, new_val);
6✔
1362
            }
6✔
1363
            values.set(m_row_ndx, new_val);
17,466✔
1364
        }
17,466✔
1365
    }
17,805✔
1366

8,790✔
1367
    sync(fields);
17,823✔
1368

8,784✔
1369
    if (Replication* repl = get_replication()) {
17,817✔
1370
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
10,431✔
1371
    }
10,431✔
1372

8,784✔
1373
    return *this;
17,817✔
1374
}
17,829✔
1375

1376
template <>
1377
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
1378
{
254,505✔
1379
    update_if_needed();
254,505✔
1380
    get_table()->check_column(col_key);
254,505✔
1381
    ColKey::Idx col_ndx = col_key.get_index();
254,505✔
1382
    ColumnType type = col_key.get_type();
254,505✔
1383
    if (type != col_type_Link)
254,505✔
UNCOV
1384
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
×
1385
    TableRef target_table = get_target_table(col_key);
254,505✔
1386
    TableKey target_table_key = target_table->get_key();
254,505✔
1387
    if (target_key) {
254,505✔
1388
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
253,872✔
1389
        if (!ct->is_valid(target_key)) {
253,953✔
1390
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
12✔
1391
        }
12✔
1392
        if (target_table->is_embedded()) {
253,953✔
UNCOV
1393
            throw IllegalOperation(
×
UNCOV
1394
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
×
UNCOV
1395
        }
×
1396
    }
254,505✔
1397
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
254,505✔
1398

127,224✔
1399
    if (target_key != old_key) {
254,505✔
1400
        CascadeState state(CascadeState::Mode::Strong);
253,695✔
1401

126,846✔
1402
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
253,695✔
1403
        _update_if_needed();
253,695✔
1404

126,846✔
1405
        Allocator& alloc = get_alloc();
253,695✔
1406
        alloc.bump_content_version();
253,695✔
1407
        Array fallback(alloc);
253,695✔
1408
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
253,695✔
1409
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
253,695✔
1410
        ArrayKey values(alloc);
253,695✔
1411
        values.set_parent(&fields, col_ndx.val + 1);
253,695✔
1412
        values.init_from_parent();
253,695✔
1413

126,846✔
1414
        values.set(m_row_ndx, target_key);
253,695✔
1415

126,846✔
1416
        sync(fields);
253,695✔
1417

126,846✔
1418
        if (Replication* repl = get_replication()) {
253,695✔
1419
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
31,797✔
1420
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
31,797✔
1421
        }
31,797✔
1422

126,846✔
1423
        if (recurse)
253,695✔
1424
            target_table->remove_recursive(state);
258✔
1425
    }
253,695✔
1426

127,224✔
1427
    return *this;
254,505✔
1428
}
254,505✔
1429

1430
template <>
1431
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
UNCOV
1432
{
×
UNCOV
1433
    update_if_needed();
×
UNCOV
1434
    get_table()->check_column(col_key);
×
UNCOV
1435
    ColKey::Idx col_ndx = col_key.get_index();
×
UNCOV
1436
    ColumnType type = col_key.get_type();
×
UNCOV
1437
    if (type != col_type_TypedLink)
×
UNCOV
1438
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
×
UNCOV
1439
    m_table->get_parent_group()->validate(target_link);
×
1440

UNCOV
1441
    ObjLink old_link = get<ObjLink>(col_key); // Will update if needed
×
1442

UNCOV
1443
    if (target_link != old_link) {
×
UNCOV
1444
        CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All
×
UNCOV
1445
                                                                  : CascadeState::Mode::Strong);
×
1446

UNCOV
1447
        bool recurse = replace_backlink(col_key, old_link, target_link, state);
×
UNCOV
1448
        _update_if_needed();
×
1449

UNCOV
1450
        Allocator& alloc = get_alloc();
×
UNCOV
1451
        alloc.bump_content_version();
×
UNCOV
1452
        Array fallback(alloc);
×
UNCOV
1453
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
×
UNCOV
1454
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
UNCOV
1455
        ArrayTypedLink values(alloc);
×
UNCOV
1456
        values.set_parent(&fields, col_ndx.val + 1);
×
UNCOV
1457
        values.init_from_parent();
×
1458

UNCOV
1459
        values.set(m_row_ndx, target_link);
×
1460

UNCOV
1461
        sync(fields);
×
1462

UNCOV
1463
        if (Replication* repl = get_replication()) {
×
UNCOV
1464
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
×
UNCOV
1465
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
×
1466
        }
×
1467

UNCOV
1468
        if (recurse)
×
UNCOV
1469
            const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
UNCOV
1470
    }
×
1471

UNCOV
1472
    return *this;
×
UNCOV
1473
}
×
1474

1475
Obj Obj::create_and_set_linked_object(ColKey col_key, bool is_default)
1476
{
12,780✔
1477
    update_if_needed();
12,780✔
1478
    get_table()->check_column(col_key);
12,780✔
1479
    ColKey::Idx col_ndx = col_key.get_index();
12,780✔
1480
    ColumnType type = col_key.get_type();
12,780✔
1481
    if (type != col_type_Link)
12,780✔
UNCOV
1482
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link type");
×
1483
    TableRef target_table = get_target_table(col_key);
12,780✔
1484
    Table& t = *target_table;
12,780✔
1485
    // Only links to embedded objects are allowed.
6,366✔
1486
    REALM_ASSERT(t.is_embedded() || !get_table()->is_asymmetric());
12,780!
1487
    // Incoming links to asymmetric objects are disallowed.
6,366✔
1488
    REALM_ASSERT(!t.is_asymmetric());
12,780✔
1489
    TableKey target_table_key = t.get_key();
12,780✔
1490
    auto result = t.is_embedded() ? t.create_linked_object() : t.create_object();
12,780✔
1491
    auto target_key = result.get_key();
12,780✔
1492
    ObjKey old_key = get<ObjKey>(col_key); // Will update if needed
12,780✔
1493
    if (old_key != ObjKey()) {
12,780✔
1494
        if (t.is_embedded()) {
48✔
1495
            // If this is an embedded object and there was already an embedded object here, then we need to
24✔
1496
            // emit an instruction to set the old embedded object to null to clear the old object on other
24✔
1497
            // sync clients. Without this, you'll only see the Set ObjectValue instruction, which is idempotent,
24✔
1498
            // and then array operations will have a corrupted prior_size.
24✔
1499
            if (Replication* repl = get_replication()) {
48✔
1500
                repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
24✔
1501
                          is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
24✔
1502
            }
24✔
1503
        }
48✔
1504
    }
48✔
1505

6,366✔
1506
    if (target_key != old_key) {
12,780✔
1507
        CascadeState state;
12,780✔
1508

6,366✔
1509
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
12,780✔
1510
        _update_if_needed();
12,780✔
1511

6,366✔
1512
        Allocator& alloc = get_alloc();
12,780✔
1513
        alloc.bump_content_version();
12,780✔
1514
        Array fallback(alloc);
12,780✔
1515
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
12,780✔
1516
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
12,780✔
1517
        ArrayKey values(alloc);
12,780✔
1518
        values.set_parent(&fields, col_ndx.val + 1);
12,780✔
1519
        values.init_from_parent();
12,780✔
1520

6,366✔
1521
        values.set(m_row_ndx, target_key);
12,780✔
1522

6,366✔
1523
        sync(fields);
12,780✔
1524

6,366✔
1525
        if (Replication* repl = get_replication()) {
12,780✔
1526
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
12,132✔
1527
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
12,132✔
1528
        }
12,132✔
1529

6,366✔
1530
        if (recurse)
12,780✔
1531
            target_table->remove_recursive(state);
48✔
1532
    }
12,780✔
1533

6,366✔
1534
    return result;
12,780✔
1535
}
12,780✔
1536

1537
namespace {
1538
template <class T>
1539
inline void check_range(const T&)
1540
{
2,140,428✔
1541
}
2,140,428✔
1542
template <>
1543
inline void check_range(const StringData& val)
1544
{
2,474,721✔
1545
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
2,474,721✔
1546
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
1,224,048✔
1547
}
2,474,721✔
1548
template <>
1549
inline void check_range(const BinaryData& val)
1550
{
5,116,713✔
1551
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
5,116,713✔
1552
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
2,556,678✔
1553
}
5,116,713✔
1554
} // namespace
1555

1556
// helper functions for filtering out calls to set_spec()
1557
template <class T>
1558
inline void Obj::set_spec(T&, ColKey)
1559
{
7,257,738✔
1560
}
7,257,738✔
1561
template <>
1562
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1563
{
2,474,817✔
1564
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
2,474,817✔
1565
    Spec* spec = const_cast<Spec*>(&get_spec());
2,474,817✔
1566
    values.set_spec(spec, spec_ndx);
2,474,817✔
1567
}
2,474,817✔
1568

1569
#if REALM_ENABLE_GEOSPATIAL
1570

1571
template <>
1572
Obj& Obj::set(ColKey col_key, Geospatial value, bool)
1573
{
246✔
1574
    update_if_needed();
246✔
1575
    get_table()->check_column(col_key);
246✔
1576
    auto type = col_key.get_type();
246✔
1577

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

120✔
1583
    Obj geo = get_linked_object(col_key);
240✔
1584
    if (!geo) {
240✔
1585
        geo = create_and_set_linked_object(col_key);
216✔
1586
    }
216✔
1587
    value.assign_to(geo);
240✔
1588
    return *this;
240✔
1589
}
240✔
1590

1591
template <>
1592
Obj& Obj::set(ColKey col_key, std::optional<Geospatial> value, bool)
1593
{
12✔
1594
    update_if_needed();
12✔
1595
    auto table = get_table();
12✔
1596
    table->check_column(col_key);
12✔
1597
    auto type = col_key.get_type();
12✔
1598
    auto attrs = col_key.get_attrs();
12✔
1599

6✔
1600
    if (type != ColumnTypeTraits<Link>::column_id)
12✔
1601
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1602
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1603
                                           get_table()->get_column_name(col_key)));
6✔
1604
    if (!value && !attrs.test(col_attr_Nullable))
6✔
UNCOV
1605
        throw NotNullable(Group::table_name_to_class_name(table->get_name()), table->get_column_name(col_key));
×
1606

3✔
1607
    if (!value) {
6✔
1608
        set_null(col_key);
6✔
1609
    }
6✔
1610
    else {
×
UNCOV
1611
        Obj geo = get_linked_object(col_key);
×
UNCOV
1612
        if (!geo) {
×
UNCOV
1613
            geo = create_and_set_linked_object(col_key);
×
UNCOV
1614
        }
×
UNCOV
1615
        value->assign_to(geo);
×
UNCOV
1616
    }
×
1617
    return *this;
6✔
1618
}
6✔
1619

1620
#endif
1621

1622
template <class T>
1623
Obj& Obj::set(ColKey col_key, T value, bool is_default)
1624
{
9,734,406✔
1625
    update_if_needed();
9,734,406✔
1626
    get_table()->check_column(col_key);
9,734,406✔
1627
    auto type = col_key.get_type();
9,734,406✔
1628
    auto attrs = col_key.get_attrs();
9,734,406✔
1629
    auto col_ndx = col_key.get_index();
9,734,406✔
1630

4,849,005✔
1631
    if (type != ColumnTypeTraits<T>::column_id)
9,734,406✔
UNCOV
1632
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
UNCOV
1633
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1634
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
9,734,406!
1635
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
6✔
1636

4,849,002✔
1637
    check_range(value);
9,734,400✔
1638

4,849,002✔
1639
    SearchIndex* index = m_table->get_search_index(col_key);
9,734,400✔
1640
    if (index && !m_key.is_unresolved()) {
9,734,400!
1641
        index->set(m_key, value);
472,077✔
1642
    }
472,077✔
1643

4,849,002✔
1644
    Allocator& alloc = get_alloc();
9,734,400✔
1645
    alloc.bump_content_version();
9,734,400✔
1646
    Array fallback(alloc);
9,734,400✔
1647
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,734,400✔
1648
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,734,400✔
1649
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
9,734,400✔
1650
    LeafType values(alloc);
9,734,400✔
1651
    values.set_parent(&fields, col_ndx.val + 1);
9,734,400✔
1652
    set_spec<LeafType>(values, col_key);
9,734,400✔
1653
    values.init_from_parent();
9,734,400✔
1654
    values.set(m_row_ndx, value);
9,734,400✔
1655

4,849,002✔
1656
    sync(fields);
9,734,400✔
1657

4,849,002✔
1658
    if (Replication* repl = get_replication())
9,734,400✔
1659
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
6,018,918✔
1660
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
6,018,621✔
1661

4,849,002✔
1662
    return *this;
9,734,400✔
1663
}
9,734,400✔
1664

1665
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
1666
INSTANTIATE_OBJ_SET(bool);
1667
INSTANTIATE_OBJ_SET(StringData);
1668
INSTANTIATE_OBJ_SET(float);
1669
INSTANTIATE_OBJ_SET(double);
1670
INSTANTIATE_OBJ_SET(Decimal128);
1671
INSTANTIATE_OBJ_SET(Timestamp);
1672
INSTANTIATE_OBJ_SET(BinaryData);
1673
INSTANTIATE_OBJ_SET(ObjectId);
1674
INSTANTIATE_OBJ_SET(UUID);
1675

1676
void Obj::set_int(ColKey::Idx col_ndx, int64_t value)
1677
{
502,119✔
1678
    update_if_needed();
502,119✔
1679

250,341✔
1680
    Allocator& alloc = get_alloc();
502,119✔
1681
    alloc.bump_content_version();
502,119✔
1682
    Array fallback(alloc);
502,119✔
1683
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
502,119✔
1684
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
502,119✔
1685
    Array values(alloc);
502,119✔
1686
    values.set_parent(&fields, col_ndx.val + 1);
502,119✔
1687
    values.init_from_parent();
502,119✔
1688
    values.set(m_row_ndx, value);
502,119✔
1689

250,341✔
1690
    sync(fields);
502,119✔
1691
}
502,119✔
1692

1693
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
1694
{
1,626✔
1695
    update_if_needed();
1,626✔
1696

813✔
1697
    Allocator& alloc = get_alloc();
1,626✔
1698
    alloc.bump_content_version();
1,626✔
1699
    Array fallback(alloc);
1,626✔
1700
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
1,626✔
1701
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
1,626✔
1702
    ArrayMixed values(alloc);
1,626✔
1703
    values.set_parent(&fields, col_ndx.val + 1);
1,626✔
1704
    values.init_from_parent();
1,626✔
1705
    values.set(m_row_ndx, Mixed(value, type));
1,626✔
1706

813✔
1707
    sync(fields);
1,626✔
1708
}
1,626✔
1709

1710
void Obj::add_backlink(ColKey backlink_col_key, ObjKey origin_key)
1711
{
6,729,510✔
1712
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
6,729,510✔
1713
    Allocator& alloc = get_alloc();
6,729,510✔
1714
    alloc.bump_content_version();
6,729,510✔
1715
    Array fallback(alloc);
6,729,510✔
1716
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
6,729,510✔
1717

3,363,669✔
1718
    ArrayBacklink backlinks(alloc);
6,729,510✔
1719
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
6,729,510✔
1720
    backlinks.init_from_parent();
6,729,510✔
1721

3,363,669✔
1722
    backlinks.add(m_row_ndx, origin_key);
6,729,510✔
1723

3,363,669✔
1724
    sync(fields);
6,729,510✔
1725
}
6,729,510✔
1726

1727
bool Obj::remove_one_backlink(ColKey backlink_col_key, ObjKey origin_key)
1728
{
204,129✔
1729
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
204,129✔
1730
    Allocator& alloc = get_alloc();
204,129✔
1731
    alloc.bump_content_version();
204,129✔
1732
    Array fallback(alloc);
204,129✔
1733
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
204,129✔
1734

102,069✔
1735
    ArrayBacklink backlinks(alloc);
204,129✔
1736
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
204,129✔
1737
    backlinks.init_from_parent();
204,129✔
1738

102,069✔
1739
    bool ret = backlinks.remove(m_row_ndx, origin_key);
204,129✔
1740

102,069✔
1741
    sync(fields);
204,129✔
1742

102,069✔
1743
    return ret;
204,129✔
1744
}
204,129✔
1745

1746
template <class ValueType>
1747
inline void Obj::nullify_single_link(ColKey col, ValueType target)
1748
{
891✔
1749
    ColKey::Idx origin_col_ndx = col.get_index();
891✔
1750
    Allocator& alloc = get_alloc();
891✔
1751
    Array fallback(alloc);
891✔
1752
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
891✔
1753
    using ArrayType = typename ColumnTypeTraits<ValueType>::cluster_leaf_type;
891✔
1754
    ArrayType links(alloc);
891✔
1755
    links.set_parent(&fields, origin_col_ndx.val + 1);
891✔
1756
    links.init_from_parent();
891✔
1757
    // Ensure we are nullifying correct link
444✔
1758
    REALM_ASSERT(links.get(m_row_ndx) == target);
891✔
1759
    links.set(m_row_ndx, ValueType{});
891✔
1760
    sync(fields);
891✔
1761

444✔
1762
    if (Replication* repl = get_replication())
891✔
1763
        repl->nullify_link(m_table.unchecked_ptr(), col,
759✔
1764
                           m_key); // Throws
759✔
1765
}
891✔
1766

1767
template <>
1768
inline void Obj::nullify_single_link<Mixed>(ColKey col, Mixed target)
1769
{
12✔
1770
    ColKey::Idx origin_col_ndx = col.get_index();
12✔
1771
    Allocator& alloc = get_alloc();
12✔
1772
    Array fallback(alloc);
12✔
1773
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
12✔
1774
    ArrayMixed mixed(alloc);
12✔
1775
    mixed.set_parent(&fields, origin_col_ndx.val + 1);
12✔
1776
    mixed.init_from_parent();
12✔
1777
    auto val = mixed.get(m_row_ndx);
12✔
1778
    bool result = false;
12✔
1779
    if (val.is_type(type_TypedLink)) {
12✔
1780
        // Ensure we are nullifying correct link
3✔
1781
        result = (val == target);
6✔
1782
        mixed.set(m_row_ndx, Mixed{});
6✔
1783
        sync(fields);
6✔
1784

3✔
1785
        if (Replication* repl = get_replication())
6✔
UNCOV
1786
            repl->nullify_link(m_table.unchecked_ptr(), col,
×
UNCOV
1787
                               m_key); // Throws
×
1788
    }
6✔
1789
    else if (val.is_type(type_Dictionary)) {
6✔
1790
        Dictionary dict(*this, col);
6✔
1791
        result = dict.nullify(target.get_link());
6✔
1792
    }
6✔
UNCOV
1793
    else if (val.is_type(type_List)) {
×
UNCOV
1794
        Lst<Mixed> list(*this, col);
×
UNCOV
1795
        result = list.nullify(target.get_link());
×
UNCOV
1796
    }
×
1797
    REALM_ASSERT(result);
12✔
1798
    static_cast<void>(result);
12✔
1799
}
12✔
1800

1801
void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) &&
1802
{
5,253✔
1803
    REALM_ASSERT(get_alloc().get_storage_version() == m_storage_version);
5,253✔
1804

2,625✔
1805
    struct LinkNullifier : public LinkTranslator {
5,253✔
1806
        LinkNullifier(Obj origin_obj, ColKey origin_col, ObjLink target)
5,253✔
1807
            : LinkTranslator(origin_obj, origin_col)
5,253✔
1808
            , m_target_link(target)
5,253✔
1809
        {
5,253✔
1810
        }
5,253✔
1811
        void on_list_of_links(LnkLst&) final
5,253✔
1812
        {
3,570✔
1813
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
1,890✔
1814
        }
1,890✔
1815
        void on_list_of_mixed(Lst<Mixed>& list) final
5,253✔
1816
        {
2,664✔
1817
            list.nullify(m_target_link);
78✔
1818
        }
78✔
1819
        void on_set_of_links(LnkSet&) final
5,253✔
1820
        {
3,639✔
1821
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,028✔
1822
        }
2,028✔
1823
        void on_set_of_mixed(Set<Mixed>&) final
5,253✔
1824
        {
2,658✔
1825
            nullify_set(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
66✔
1826
        }
66✔
1827
        void on_dictionary(Dictionary& dict) final
5,253✔
1828
        {
2,769✔
1829
            dict.nullify(m_target_link);
288✔
1830
        }
288✔
1831
        void on_link_property(ColKey origin_col_key) final
5,253✔
1832
        {
3,072✔
1833
            m_origin_obj.nullify_single_link<ObjKey>(origin_col_key, m_target_link.get_obj_key());
891✔
1834
        }
891✔
1835
        void on_mixed_property(ColKey origin_col_key) final
5,253✔
1836
        {
2,631✔
1837
            m_origin_obj.nullify_single_link<Mixed>(origin_col_key, Mixed{m_target_link});
12✔
1838
        }
12✔
1839

2,625✔
1840
    private:
5,253✔
1841
        ObjLink m_target_link;
5,253✔
1842
    } nullifier{*this, origin_col_key, target_link};
5,253✔
1843

2,625✔
1844
    nullifier.run();
5,253✔
1845

2,625✔
1846
    get_alloc().bump_content_version();
5,253✔
1847
}
5,253✔
1848

1849

1850
struct EmbeddedObjectLinkMigrator : public LinkTranslator {
1851
    EmbeddedObjectLinkMigrator(Obj origin, ColKey origin_col, Obj dest_orig, Obj dest_replace)
1852
        : LinkTranslator(origin, origin_col)
1853
        , m_dest_orig(dest_orig)
1854
        , m_dest_replace(dest_replace)
1855
    {
60,318✔
1856
    }
60,318✔
1857
    void on_list_of_links(LnkLst& list) final
1858
    {
168✔
1859
        auto n = list.find_first(m_dest_orig.get_key());
168✔
1860
        REALM_ASSERT(n != realm::npos);
168✔
1861
        list.set(n, m_dest_replace.get_key());
168✔
1862
    }
168✔
1863
    void on_dictionary(Dictionary& dict) final
1864
    {
60✔
1865
        auto pos = dict.find_any(m_dest_orig.get_link());
60✔
1866
        REALM_ASSERT(pos != realm::npos);
60✔
1867
        Mixed key = dict.get_key(pos);
60✔
1868
        dict.insert(key, m_dest_replace.get_link());
60✔
1869
    }
60✔
1870
    void on_link_property(ColKey col) final
1871
    {
60,090✔
1872
        REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
60,090✔
1873
        m_origin_obj.set(col, m_dest_replace.get_key());
60,090✔
1874
    }
60,090✔
1875
    void on_set_of_links(LnkSet&) final
UNCOV
1876
    {
×
1877
        // this should never happen because sets of embedded objects are not allowed at the schema level
UNCOV
1878
        REALM_UNREACHABLE();
×
UNCOV
1879
    }
×
1880
    // The following cases have support here but are expected to fail later on in the
1881
    // migration due to core not yet supporting untyped Mixed links to embedded objects.
1882
    void on_set_of_mixed(Set<Mixed>& set) final
UNCOV
1883
    {
×
UNCOV
1884
        auto did_erase_pair = set.erase(m_dest_orig.get_link());
×
UNCOV
1885
        REALM_ASSERT(did_erase_pair.second);
×
UNCOV
1886
        set.insert(m_dest_replace.get_link());
×
UNCOV
1887
    }
×
1888
    void on_list_of_mixed(Lst<Mixed>& list) final
UNCOV
1889
    {
×
UNCOV
1890
        auto n = list.find_any(m_dest_orig.get_link());
×
UNCOV
1891
        REALM_ASSERT(n != realm::npos);
×
UNCOV
1892
        list.insert_any(n, m_dest_replace.get_link());
×
UNCOV
1893
    }
×
1894
    void on_mixed_property(ColKey col) final
UNCOV
1895
    {
×
UNCOV
1896
        REALM_ASSERT(m_origin_obj.get<Mixed>(col).is_null() ||
×
UNCOV
1897
                     m_origin_obj.get<Mixed>(col) == m_dest_orig.get_link());
×
UNCOV
1898
        m_origin_obj.set_any(col, m_dest_replace.get_link());
×
UNCOV
1899
    }
×
1900

1901
private:
1902
    Obj m_dest_orig;
1903
    Obj m_dest_replace;
1904
};
1905

1906
void Obj::handle_multiple_backlinks_during_schema_migration()
1907
{
54✔
1908
    REALM_ASSERT(!m_table->get_primary_key_column());
54✔
1909
    converters::EmbeddedObjectConverter embedded_obj_tracker;
54✔
1910
    auto copy_links = [&](ColKey col) {
108✔
1911
        auto opposite_table = m_table->get_opposite_table(col);
108✔
1912
        auto opposite_column = m_table->get_opposite_column(col);
108✔
1913
        auto backlinks = get_all_backlinks(col);
108✔
1914
        for (auto backlink : backlinks) {
60,318✔
1915
            // create a new obj
30,159✔
1916
            auto obj = m_table->create_object();
60,318✔
1917
            embedded_obj_tracker.track(*this, obj);
60,318✔
1918
            auto linking_obj = opposite_table->get_object(backlink);
60,318✔
1919
            // change incoming links to point to the newly created object
30,159✔
1920
            EmbeddedObjectLinkMigrator{linking_obj, opposite_column, *this, obj}.run();
60,318✔
1921
        }
60,318✔
1922
        embedded_obj_tracker.process_pending();
108✔
1923
        return IteratorControl::AdvanceToNext;
108✔
1924
    };
108✔
1925
    m_table->for_each_backlink_column(copy_links);
54✔
1926
}
54✔
1927

1928
LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const
1929
{
316,113✔
1930
    auto list = CollectionParent::get_listbase_ptr(col_key);
316,113✔
1931
    list->set_owner(*this, col_key);
316,113✔
1932
    return list;
316,113✔
1933
}
316,113✔
1934

1935
SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const
1936
{
42,762✔
1937
    auto set = CollectionParent::get_setbase_ptr(col_key);
42,762✔
1938
    set->set_owner(*this, col_key);
42,762✔
1939
    return set;
42,762✔
1940
}
42,762✔
1941

1942
Dictionary Obj::get_dictionary(ColKey col_key) const
1943
{
84,951✔
1944
    REALM_ASSERT(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed);
84,951✔
1945
    update_if_needed();
84,951✔
1946
    return Dictionary(Obj(*this), col_key);
84,951✔
1947
}
84,951✔
1948

1949
Obj& Obj::set_collection(ColKey col_key, CollectionType type)
1950
{
1,206✔
1951
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
1,206✔
1952
    update_if_needed();
1,206✔
1953
    Mixed new_val(0, type);
1,206✔
1954

603✔
1955
    ArrayMixed values(_get_alloc());
1,206✔
1956
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
1,206✔
1957
    values.init_from_ref(ref);
1,206✔
1958
    auto old_val = values.get(m_row_ndx);
1,206✔
1959

603✔
1960
    if (old_val != new_val) {
1,206✔
1961
        set(col_key, Mixed(0, type));
960✔
1962
        // Update ref after write
480✔
1963
        ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
960✔
1964
        values.init_from_ref(ref);
960✔
1965
        values.set_key(m_row_ndx, generate_key(0x10));
960✔
1966
    }
960✔
1967
    return *this;
1,206✔
1968
}
1,206✔
1969

1970
DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const
1971
{
348✔
1972
    return std::make_shared<Dictionary>(get_dictionary(col_key));
348✔
1973
}
348✔
1974

1975
DictionaryPtr Obj::get_dictionary_ptr(const Path& path) const
UNCOV
1976
{
×
UNCOV
1977
    return std::dynamic_pointer_cast<Dictionary>(get_collection_ptr(path));
×
UNCOV
1978
}
×
1979

1980
Dictionary Obj::get_dictionary(StringData col_name) const
1981
{
16,062✔
1982
    return get_dictionary(get_column_key(col_name));
16,062✔
1983
}
16,062✔
1984

1985
CollectionPtr Obj::get_collection_ptr(const Path& path) const
1986
{
11,148✔
1987
    REALM_ASSERT(path.size() > 0);
11,148✔
1988
    // First element in path must be column name
5,574✔
1989
    auto col_key = path[0].is_col_key() ? path[0].get_col_key() : m_table->get_column_key(path[0].get_key());
11,094✔
1990
    REALM_ASSERT(col_key);
11,148✔
1991
    size_t level = 1;
11,148✔
1992
    CollectionBasePtr collection = get_collection_ptr(col_key);
11,148✔
1993

5,574✔
1994
    while (level < path.size()) {
11,280✔
1995
        auto& path_elem = path[level];
132✔
1996
        Mixed ref;
132✔
1997
        if (collection->get_collection_type() == CollectionType::List) {
132✔
1998
            ref = collection->get_any(path_elem.get_ndx());
90✔
1999
        }
90✔
2000
        else {
42✔
2001
            ref = dynamic_cast<Dictionary*>(collection.get())->get(path_elem.get_key());
42✔
2002
        }
42✔
2003
        if (ref.is_type(type_List)) {
132✔
2004
            collection = collection->get_list(path_elem);
108✔
2005
        }
108✔
2006
        else if (ref.is_type(type_Set)) {
24✔
UNCOV
2007
            collection = collection->get_set(path_elem);
×
UNCOV
2008
        }
×
2009
        else if (ref.is_type(type_Dictionary)) {
24✔
2010
            collection = collection->get_dictionary(path_elem);
24✔
2011
        }
24✔
UNCOV
2012
        else {
×
UNCOV
2013
            throw InvalidArgument("Wrong path");
×
UNCOV
2014
        }
×
2015
        level++;
132✔
2016
    }
132✔
2017

5,574✔
2018
    return collection;
11,148✔
2019
}
11,148✔
2020

2021
CollectionPtr Obj::get_collection_by_stable_path(const StablePath& path) const
2022
{
10,392✔
2023
    // First element in path is phony column key
5,196✔
2024
    ColKey col_key = m_table->get_column_key(path[0]);
10,392✔
2025
    size_t level = 1;
10,392✔
2026
    CollectionBasePtr collection = get_collection_ptr(col_key);
10,392✔
2027

5,196✔
2028
    while (level < path.size()) {
10,920✔
2029
        auto& index = path[level];
540✔
2030
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
540✔
2031
            if (collection->get_collection_type() == CollectionType::List) {
540✔
2032
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
288✔
2033
                size_t ndx = list_of_mixed->find_index(index);
288✔
2034
                if (ndx == realm::not_found)
288✔
NEW
2035
                    return {Mixed{}, PathElement{}};
×
2036
                return {list_of_mixed->get(ndx), PathElement(ndx)};
288✔
2037
            }
288✔
2038
            else {
252✔
2039
                auto dict = dynamic_cast<Dictionary*>(collection.get());
252✔
2040
                size_t ndx = dict->find_index(index);
252✔
2041
                if (ndx == realm::not_found)
252✔
2042
                    return {Mixed{}, PathElement{}};
12✔
2043
                return {dict->get_any(ndx), PathElement(dict->get_key(ndx).get_string())};
240✔
2044
            }
240✔
2045
        };
540✔
2046
        auto [ref, path_elem] = get_ref();
540✔
2047
        if (ref.is_type(type_List)) {
540✔
2048
            collection = collection->get_list(path_elem);
372✔
2049
        }
372✔
2050
        else if (ref.is_type(type_Set)) {
168✔
UNCOV
2051
            collection = collection->get_set(path_elem);
×
UNCOV
2052
        }
×
2053
        else if (ref.is_type(type_Dictionary)) {
168✔
2054
            collection = collection->get_dictionary(path_elem);
156✔
2055
        }
156✔
2056
        else {
12✔
2057
            return nullptr;
12✔
2058
        }
12✔
2059
        level++;
528✔
2060
    }
528✔
2061

5,196✔
2062
    return collection;
10,386✔
2063
}
10,392✔
2064

2065
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2066
{
129,297✔
2067
    if (col_key.is_collection()) {
129,297✔
2068
        auto collection = CollectionParent::get_collection_ptr(col_key);
127,221✔
2069
        collection->set_owner(*this, col_key);
127,221✔
2070
        return collection;
127,221✔
2071
    }
127,221✔
2072
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
2,076✔
2073
    auto val = get<Mixed>(col_key);
2,076✔
2074
    if (val.is_type(type_List)) {
2,076✔
2075
        return std::make_shared<Lst<Mixed>>(*this, col_key);
1,236✔
2076
    }
1,236✔
2077
    else if (val.is_type(type_Set)) {
840✔
2078
        return std::make_shared<Set<Mixed>>(*this, col_key);
42✔
2079
    }
42✔
2080
    REALM_ASSERT(val.is_type(type_Dictionary));
798✔
2081
    return std::make_shared<Dictionary>(*this, col_key);
798✔
2082
}
798✔
2083

2084
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
2085
{
396✔
2086
    return get_collection_ptr(get_column_key(col_name));
396✔
2087
}
396✔
2088

2089
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
2090
{
3,114✔
2091
    if (col_key.is_list()) {
3,114✔
2092
        return get_linklist_ptr(col_key);
2,994✔
2093
    }
2,994✔
2094
    else if (col_key.is_set()) {
120✔
2095
        return get_linkset_ptr(col_key);
78✔
2096
    }
78✔
2097
    else if (col_key.is_dictionary()) {
42✔
2098
        auto dict = get_dictionary(col_key);
42✔
2099
        return std::make_unique<DictionaryLinkValues>(dict);
42✔
2100
    }
42✔
UNCOV
2101
    return {};
×
UNCOV
2102
}
×
2103

2104
template <class T>
2105
inline void replace_in_linklist(Obj& obj, ColKey origin_col_key, T target, T replacement)
2106
{
9,342✔
2107
    Lst<T> link_list(origin_col_key);
9,342✔
2108
    size_t ndx = find_link_value_in_collection(link_list, obj, origin_col_key, target);
9,342✔
2109

4,671✔
2110
    REALM_ASSERT(ndx != realm::npos); // There has to be one
9,342✔
2111

4,671✔
2112
    link_list.set(ndx, replacement);
9,342✔
2113
}
9,342✔
2114

2115
template <class T>
2116
inline void replace_in_linkset(Obj& obj, ColKey origin_col_key, T target, T replacement)
2117
{
144✔
2118
    Set<T> link_set(origin_col_key);
144✔
2119
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
144✔
2120

72✔
2121
    REALM_ASSERT(ndx != realm::npos); // There has to be one
144✔
2122

72✔
2123
    link_set.erase(target);
144✔
2124
    link_set.insert(replacement);
144✔
2125
}
144✔
2126

2127
inline void replace_in_dictionary(Obj& obj, ColKey origin_col_key, Mixed target, Mixed replacement)
2128
{
×
UNCOV
2129
    Dictionary dict(origin_col_key);
×
2130
    size_t ndx = find_link_value_in_collection(dict, obj, origin_col_key, target);
×
2131

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

×
2134
    auto key = dict.get_key(ndx);
×
UNCOV
2135
    dict.insert(key, replacement);
×
2136
}
×
2137

2138

2139
void Obj::assign_pk_and_backlinks(const Obj& other)
2140
{
14,046✔
2141
    struct LinkReplacer : LinkTranslator {
14,046✔
2142
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
14,046✔
2143
            : LinkTranslator(origin, origin_col_key)
14,046✔
2144
            , m_dest_orig(dest_orig)
14,046✔
2145
            , m_dest_replace(dest_replace)
14,046✔
2146
        {
11,955✔
2147
        }
9,876✔
2148
        void on_list_of_links(LnkLst&) final
14,046✔
2149
        {
11,688✔
2150
            replace_in_linklist(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
9,342✔
2151
        }
9,342✔
2152
        void on_list_of_mixed(Lst<Mixed>& list) final
14,046✔
2153
        {
7,038✔
2154
            list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
42✔
2155
        }
42✔
2156
        void on_set_of_links(LnkSet&) final
14,046✔
2157
        {
7,050✔
2158
            replace_in_linkset(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
66✔
2159
        }
66✔
2160
        void on_set_of_mixed(Set<Mixed>&) final
14,046✔
2161
        {
7,056✔
2162
            replace_in_linkset<Mixed>(m_origin_obj, m_origin_col_key, m_dest_orig.get_link(),
78✔
2163
                                      m_dest_replace.get_link());
78✔
2164
        }
78✔
2165
        void on_dictionary(Dictionary& dict) final
14,046✔
2166
        {
7,071✔
2167
            dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
108✔
2168
        }
108✔
2169
        void on_link_property(ColKey col) final
14,046✔
2170
        {
7,107✔
2171
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
180✔
2172
            m_origin_obj.set(col, m_dest_replace.get_key());
180✔
2173
        }
180✔
2174
        void on_mixed_property(ColKey col) final
14,046✔
2175
        {
7,047✔
2176
            auto val = m_origin_obj.get_any(col);
60✔
2177
            if (val.is_type(type_TypedLink)) {
60✔
2178
                REALM_ASSERT(val.get_link() == m_dest_orig.get_link());
36✔
2179
                m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
36✔
2180
            }
36✔
2181
            else if (val.is_type(type_Dictionary)) {
24✔
2182
                Dictionary dict(m_origin_obj, m_origin_col_key);
12✔
2183
                dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2184
            }
12✔
2185
            else if (val.is_type(type_List)) {
12✔
2186
                Lst<Mixed> list(m_origin_obj, m_origin_col_key);
12✔
2187
                list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2188
            }
12✔
UNCOV
2189
            else {
×
UNCOV
2190
                REALM_UNREACHABLE();
×
UNCOV
2191
            }
×
2192
        }
60✔
2193

7,017✔
2194
    private:
14,046✔
2195
        const Obj& m_dest_orig;
14,046✔
2196
        const Obj& m_dest_replace;
14,046✔
2197
    };
14,046✔
2198

7,017✔
2199
    REALM_ASSERT(get_table() == other.get_table());
14,046✔
2200
    if (auto col_pk = m_table->get_primary_key_column()) {
14,046✔
2201
        Mixed val = other.get_any(col_pk);
13,884✔
2202
        this->set_any(col_pk, val);
13,884✔
2203
    }
13,884✔
2204
    auto nb_tombstones = m_table->m_tombstones->size();
14,046✔
2205

7,017✔
2206
    auto copy_links = [this, &other, nb_tombstones](ColKey col) {
12,000✔
2207
        if (nb_tombstones != m_table->m_tombstones->size()) {
9,966✔
2208
            // Object has been deleted - we are done
6✔
2209
            return IteratorControl::Stop;
12✔
2210
        }
12✔
2211
        auto t = m_table->get_opposite_table(col);
9,954✔
2212
        auto c = m_table->get_opposite_column(col);
9,954✔
2213
        auto backlinks = other.get_all_backlinks(col);
9,954✔
2214
        for (auto bl : backlinks) {
9,915✔
2215
            auto linking_obj = t->get_object(bl);
9,876✔
2216
            LinkReplacer replacer{linking_obj, c, other, *this};
9,876✔
2217
            replacer.run();
9,876✔
2218
        }
9,876✔
2219
        return IteratorControl::AdvanceToNext;
9,954✔
2220
    };
9,954✔
2221
    m_table->for_each_backlink_column(copy_links);
14,046✔
2222
}
14,046✔
2223

2224
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
2225
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
2226
template float Obj::get<float>(ColKey col_key) const;
2227
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
2228
template double Obj::get<double>(ColKey col_key) const;
2229
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2230
template StringData Obj::get<StringData>(ColKey col_key) const;
2231
template BinaryData Obj::get<BinaryData>(ColKey col_key) const;
2232
template Timestamp Obj::get<Timestamp>(ColKey col_key) const;
2233
template ObjectId Obj::get<ObjectId>(ColKey col_key) const;
2234
template util::Optional<ObjectId> Obj::get<util::Optional<ObjectId>>(ColKey col_key) const;
2235
template ObjKey Obj::get<ObjKey>(ColKey col_key) const;
2236
template Decimal128 Obj::get<Decimal128>(ColKey col_key) const;
2237
template ObjLink Obj::get<ObjLink>(ColKey col_key) const;
2238
template Mixed Obj::get<Mixed>(realm::ColKey) const;
2239
template UUID Obj::get<UUID>(realm::ColKey) const;
2240
template util::Optional<UUID> Obj::get<util::Optional<UUID>>(ColKey col_key) const;
2241

2242
template <class T>
2243
inline void Obj::do_set_null(ColKey col_key)
2244
{
39,600✔
2245
    ColKey::Idx col_ndx = col_key.get_index();
39,600✔
2246
    Allocator& alloc = get_alloc();
39,600✔
2247
    alloc.bump_content_version();
39,600✔
2248
    Array fallback(alloc);
39,600✔
2249
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
39,600✔
2250

19,893✔
2251
    T values(alloc);
39,600✔
2252
    values.set_parent(&fields, col_ndx.val + 1);
39,600✔
2253
    values.init_from_parent();
39,600✔
2254
    values.set_null(m_row_ndx);
39,600✔
2255

19,893✔
2256
    sync(fields);
39,600✔
2257
}
39,600✔
2258

2259
template <>
2260
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2261
{
2,970✔
2262
    ColKey::Idx col_ndx = col_key.get_index();
2,970✔
2263
    size_t spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
2,970✔
2264
    Allocator& alloc = get_alloc();
2,970✔
2265
    alloc.bump_content_version();
2,970✔
2266
    Array fallback(alloc);
2,970✔
2267
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
2,970✔
2268

1,452✔
2269
    ArrayString values(alloc);
2,970✔
2270
    values.set_parent(&fields, col_ndx.val + 1);
2,970✔
2271
    values.set_spec(const_cast<Spec*>(&get_spec()), spec_ndx);
2,970✔
2272
    values.init_from_parent();
2,970✔
2273
    values.set_null(m_row_ndx);
2,970✔
2274

1,452✔
2275
    sync(fields);
2,970✔
2276
}
2,970✔
2277

2278
Obj& Obj::set_null(ColKey col_key, bool is_default)
2279
{
43,164✔
2280
    ColumnType col_type = col_key.get_type();
43,164✔
2281
    // Links need special handling
21,642✔
2282
    if (col_type == col_type_Link) {
43,164✔
2283
        set(col_key, null_key);
462✔
2284
    }
462✔
2285
    else if (col_type == col_type_Mixed) {
42,702✔
2286
        set(col_key, Mixed{});
66✔
2287
    }
66✔
2288
    else {
42,636✔
2289
        auto attrs = col_key.get_attrs();
42,636✔
2290
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
42,636✔
2291
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
66✔
2292
                              m_table->get_column_name(col_key));
66✔
2293
        }
66✔
2294

21,345✔
2295
        update_if_needed();
42,570✔
2296

21,345✔
2297
        SearchIndex* index = m_table->get_search_index(col_key);
42,570✔
2298
        if (index && !m_key.is_unresolved()) {
42,570✔
2299
            index->set(m_key, null{});
4,680✔
2300
        }
4,680✔
2301

21,345✔
2302
        switch (col_type) {
42,570✔
2303
            case col_type_Int:
6,393✔
2304
                do_set_null<ArrayIntNull>(col_key);
6,393✔
2305
                break;
6,393✔
2306
            case col_type_Bool:
5,577✔
2307
                do_set_null<ArrayBoolNull>(col_key);
5,577✔
2308
                break;
5,577✔
2309
            case col_type_Float:
6,366✔
2310
                do_set_null<ArrayFloatNull>(col_key);
6,366✔
2311
                break;
6,366✔
2312
            case col_type_Double:
6,285✔
2313
                do_set_null<ArrayDoubleNull>(col_key);
6,285✔
2314
                break;
6,285✔
2315
            case col_type_ObjectId:
3,288✔
2316
                do_set_null<ArrayObjectIdNull>(col_key);
3,288✔
2317
                break;
3,288✔
2318
            case col_type_String:
2,970✔
2319
                do_set_null<ArrayString>(col_key);
2,970✔
2320
                break;
2,970✔
2321
            case col_type_Binary:
1,248✔
2322
                do_set_null<ArrayBinary>(col_key);
1,248✔
2323
                break;
1,248✔
2324
            case col_type_Timestamp:
2,577✔
2325
                do_set_null<ArrayTimestamp>(col_key);
2,577✔
2326
                break;
2,577✔
2327
            case col_type_Decimal:
1,830✔
2328
                do_set_null<ArrayDecimal128>(col_key);
1,830✔
2329
                break;
1,830✔
2330
            case col_type_UUID:
6,036✔
2331
                do_set_null<ArrayUUIDNull>(col_key);
6,036✔
2332
                break;
6,036✔
UNCOV
2333
            case col_type_Mixed:
✔
UNCOV
2334
            case col_type_Link:
✔
UNCOV
2335
            case col_type_LinkList:
✔
UNCOV
2336
            case col_type_BackLink:
✔
UNCOV
2337
            case col_type_TypedLink:
✔
UNCOV
2338
                REALM_UNREACHABLE();
×
2339
        }
42,570✔
2340
    }
42,570✔
2341

21,642✔
2342
    if (Replication* repl = get_replication())
43,131✔
2343
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
22,944✔
2344
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
22,860✔
2345

21,609✔
2346
    return *this;
43,098✔
2347
}
43,164✔
2348

2349

2350
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2351
{
8,567,769✔
2352
    return get_table()->spec_ndx2colkey(col_ndx);
8,567,769✔
2353
}
8,567,769✔
2354

2355
size_t Obj::colkey2spec_ndx(ColKey key)
2356
{
777✔
2357
    return get_table()->colkey2spec_ndx(key);
777✔
2358
}
777✔
2359

2360
ColKey Obj::get_primary_key_column() const
2361
{
5,260,221✔
2362
    return m_table->get_primary_key_column();
5,260,221✔
2363
}
5,260,221✔
2364

2365
ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key)
2366
{
12,666✔
2367
    return to_ref(obj._get<int64_t>(col_key.get_index()));
12,666✔
2368
}
12,666✔
2369

2370
ref_type Obj::get_collection_ref(Index index, CollectionType type) const
2371
{
3,145,239✔
2372
    if (index.is_collection()) {
3,145,239✔
2373
        return to_ref(_get<int64_t>(index.get_index()));
3,136,980✔
2374
    }
3,136,980✔
2375
    if (check_index(index)) {
8,259✔
2376
        auto val = _get<Mixed>(index.get_index());
8,076✔
2377
        if (val.is_type(DataType(int(type)))) {
8,076✔
2378
            return val.get_ref();
8,070✔
2379
        }
8,070✔
2380
        throw realm::IllegalOperation(util::format("Not a %1", type));
6✔
2381
    }
6✔
2382
    throw StaleAccessor("This collection is no more");
183✔
2383
}
183✔
2384

2385
bool Obj::check_collection_ref(Index index, CollectionType type) const noexcept
2386
{
791,739✔
2387
    if (index.is_collection()) {
791,739✔
2388
        return true;
788,931✔
2389
    }
788,931✔
2390
    if (check_index(index)) {
2,808✔
2391
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
2,802✔
2392
    }
2,802✔
2393
    return false;
6✔
2394
}
6✔
2395

2396
void Obj::set_collection_ref(Index index, ref_type ref, CollectionType type)
2397
{
503,754✔
2398
    if (index.is_collection()) {
503,754✔
2399
        set_int(index.get_index(), from_ref(ref));
502,128✔
2400
        return;
502,128✔
2401
    }
502,128✔
2402
    set_ref(index.get_index(), ref, type);
1,626✔
2403
}
1,626✔
2404

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

© 2025 Coveralls, Inc