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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

90.37
/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
{
195,585,984✔
114
    m_storage_version = get_alloc().get_storage_version();
195,585,984✔
115
}
195,585,984✔
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,415,564✔
129
    if (m_key.is_unresolved()) {
38,415,564✔
130
        return m_table.unchecked_ptr()->m_tombstones.get();
19,572✔
131
    }
19,572✔
132
    else {
38,395,992✔
133
        return &m_table.unchecked_ptr()->m_clusters;
38,395,992✔
134
    }
38,395,992✔
135
}
38,415,564✔
136

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

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

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

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

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

183
Replication* Obj::get_replication() const
184
{
29,242,578✔
185
    return m_table->get_repl();
29,242,578✔
186
}
29,242,578✔
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,944✔
324
    // Cache valid state. If once invalid, it can never become valid again
782,223✔
325
    if (m_valid)
1,459,944✔
326
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,458,597✔
327
                                    m_table.unchecked_ptr()->is_valid(m_key));
797,613✔
328

782,223✔
329
    return m_valid;
1,459,944✔
330
}
1,459,944✔
331

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

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

342
ColKey Obj::get_column_key(StringData col_name) const
343
{
3,849,708✔
344
    return get_table()->get_column_key(col_name);
3,849,708✔
345
}
3,849,708✔
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,076,700✔
354
    if (m_table) {
7,076,700✔
355
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,076,205✔
356
    }
7,076,205✔
357
    else {
495✔
358
        return TableRef();
495✔
359
    }
495✔
360
}
7,076,700✔
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
{
819,129✔
374
    // Get a new object from key
412,389✔
375
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
819,129✔
376

412,389✔
377
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
819,129✔
378
    if (changes) {
819,129✔
379
        m_mem = new_obj.m_mem;
134,124✔
380
        m_row_ndx = new_obj.m_row_ndx;
134,124✔
381
    }
134,124✔
382
    // Always update versions
412,389✔
383
    m_storage_version = new_obj.m_storage_version;
819,129✔
384
    m_table = new_obj.m_table;
819,129✔
385
    return changes;
819,129✔
386
}
819,129✔
387

388
inline bool Obj::_update_if_needed() const
389
{
78,870,483✔
390
    auto current_version = _get_alloc().get_storage_version();
78,870,483✔
391
    if (current_version != m_storage_version) {
78,870,483✔
392
        return update();
14,031✔
393
    }
14,031✔
394
    return false;
78,856,452✔
395
}
78,856,452✔
396

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

17,863,584✔
404
    auto current_version = get_alloc().get_storage_version();
35,718,786✔
405
    if (current_version != m_storage_version) {
35,718,786✔
406
        ClusterNode::State state = get_tree_top()->try_get(m_key);
639,570✔
407

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

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

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

38,769,417✔
431
    return _get<T>(col_key.get_index());
77,674,527✔
432
}
77,674,527✔
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,447,585✔
467
    _update_if_needed();
78,447,585✔
468

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

39,218,160✔
473
    return values.get(m_row_ndx);
78,447,585✔
474
}
78,447,585✔
475

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

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

157,416✔
485
    ObjKey k = values.get(m_row_ndx);
314,808✔
486
    return k.is_unresolved() ? ObjKey{} : k;
314,697✔
487
}
314,808✔
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,087✔
502
    ArrayKey values(get_alloc());
315,087✔
503
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
315,087✔
504
    values.init_from_ref(ref);
315,087✔
505

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

509
template <>
510
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
511
{
140,934,267✔
512
    // manual inline of _update_if_needed():
71,651,961✔
513
    auto& alloc = _get_alloc();
140,934,267✔
514
    auto current_version = alloc.get_storage_version();
140,934,267✔
515
    if (current_version != m_storage_version) {
140,934,267✔
516
        update();
143,523✔
517
    }
143,523✔
518

71,651,961✔
519
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
140,934,267✔
520
    char* header = alloc.translate(ref);
140,934,267✔
521
    int width = Array::get_width_from_header(header);
140,934,267✔
522
    char* data = Array::get_data_from_header(header);
140,934,267✔
523
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
140,934,267✔
UNCOV
524
}
×
525

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

53,679,852✔
533
    if (col_key.get_attrs().test(col_attr_Nullable)) {
103,052,043✔
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 {
103,046,037✔
541
        return _get<int64_t>(col_key.get_index());
103,046,037✔
542
    }
103,046,037✔
543
}
103,052,043✔
544

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

291,990✔
552
    if (col_key.get_attrs().test(col_attr_Nullable)) {
521,556✔
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 {
521,526✔
560
        return _get<bool>(col_key.get_index());
521,526✔
561
    }
521,526✔
562
}
521,556✔
563

564
template <>
565
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
566
{
11,932,989✔
567
    // manual inline of _update_if_needed():
5,915,301✔
568
    auto& alloc = _get_alloc();
11,932,989✔
569
    auto current_version = alloc.get_storage_version();
11,932,989✔
570
    if (current_version != m_storage_version) {
11,932,989✔
571
        update();
16,194✔
572
    }
16,194✔
573

5,915,301✔
574
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
11,932,989✔
575
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
11,932,989✔
576
    auto& spec = get_spec();
11,932,989✔
577
    if (spec.is_string_enum_type(spec_ndx)) {
11,932,989✔
578
        ArrayString values(get_alloc());
2,979,132✔
579
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
2,979,132✔
580
        values.init_from_ref(ref);
2,979,132✔
581

1,480,437✔
582
        return values.get(m_row_ndx);
2,979,132✔
583
    }
2,979,132✔
584
    else {
8,953,857✔
585
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
8,953,857✔
586
    }
8,953,857✔
587
}
11,932,989✔
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,361,408✔
605
    m_table->check_column(col_key);
47,361,408✔
606
    auto col_ndx = col_key.get_index();
47,361,408✔
607
    if (col_key.is_collection()) {
47,361,408✔
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,355,708✔
612
        case col_type_Int:
35,533,527✔
613
            if (col_key.get_attrs().test(col_attr_Nullable)) {
35,533,527✔
614
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
687,012✔
615
            }
687,012✔
616
            else {
34,846,515✔
617
                return Mixed{_get<int64_t>(col_ndx)};
34,846,515✔
618
            }
34,846,515✔
619
        case col_type_Bool:
365,475✔
620
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
365,475✔
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,720,401✔
626
            return Mixed{_get<String>(col_ndx)};
10,720,401✔
627
        case col_type_Binary:
1,605✔
628
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
629
        case col_type_Mixed:
6,489✔
630
            return _get<Mixed>(col_ndx);
6,489✔
631
        case col_type_Timestamp:
237,414✔
632
            return Mixed{_get<Timestamp>(col_ndx)};
237,414✔
633
        case col_type_Decimal:
7,860✔
634
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
635
        case col_type_ObjectId:
433,551✔
636
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
433,551✔
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,479✔
650
    auto col = m_table->get_primary_key_column();
100,479✔
651
    return col ? get_any(col) : Mixed{get_key()};
100,383✔
652
}
100,479✔
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,060,938✔
701
    T values(get_alloc());
1,060,938✔
702
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,060,938✔
703
    values.init_from_ref(ref);
1,060,938✔
704
    return values.is_null(m_row_ndx);
1,060,938✔
705
}
1,060,938✔
706

707
template <>
708
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
709
{
308,187✔
710
    ArrayString values(get_alloc());
308,187✔
711
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
308,187✔
712
    values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
308,187✔
713
    values.init_from_ref(ref);
308,187✔
714
    return values.is_null(m_row_ndx);
308,187✔
715
}
308,187✔
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,262,372✔
724
    update_if_needed();
5,262,372✔
725
    ColumnAttrMask attr = col_key.get_attrs();
5,262,372✔
726
    ColKey::Idx col_ndx = col_key.get_index();
5,262,372✔
727
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,262,372✔
728
        switch (col_key.get_type()) {
1,369,116✔
729
            case col_type_Int:
286,230✔
730
                return do_is_null<ArrayIntNull>(col_ndx);
286,230✔
731
            case col_type_Bool:
363,168✔
732
                return do_is_null<ArrayBoolNull>(col_ndx);
363,168✔
733
            case col_type_Float:
9,561✔
734
                return do_is_null<ArrayFloatNull>(col_ndx);
9,561✔
735
            case col_type_Double:
3,267✔
736
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
737
            case col_type_String:
308,187✔
738
                return do_is_null<ArrayString>(col_ndx);
308,187✔
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:
375,105✔
744
                return do_is_null<ArrayTimestamp>(col_ndx);
375,105✔
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,369,116✔
756
    }
1,369,116✔
757
    return false;
4,508,829✔
758
}
5,262,372✔
759

760

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

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

40,656✔
772
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
81,693✔
773
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
24,045✔
774
    });
24,327✔
775
}
81,693✔
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,022✔
791
    update_if_needed();
14,022✔
792

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

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

809
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
810
{
14,556✔
811
    ColKey backlink_col_key;
14,556✔
812
    auto type = origin_col_key.get_type();
14,556✔
813
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
14,556✔
814
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
815
    }
36✔
816
    else {
14,520✔
817
        backlink_col_key = origin.get_opposite_column(origin_col_key);
14,520✔
818
    }
14,520✔
819
    return get_backlink(backlink_col_key, backlink_ndx);
14,556✔
820
}
14,556✔
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,568✔
831
    get_table()->check_column(backlink_col);
14,568✔
832
    Allocator& alloc = get_alloc();
14,568✔
833
    Array fields(alloc);
14,568✔
834
    fields.init_from_mem(m_mem);
14,568✔
835

7,302✔
836
    ArrayBacklink backlinks(alloc);
14,568✔
837
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
14,568✔
838
    backlinks.init_from_parent();
14,568✔
839
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
14,568✔
840
}
14,568✔
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,673✔
866
    Allocator& alloc = get_alloc();
500,673✔
867
    Array fields(alloc);
500,673✔
868
    fields.init_from_mem(m_mem);
500,673✔
869

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

249,930✔
874
    return backlinks.get_backlink_count(m_row_ndx);
500,673✔
875
}
500,673✔
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
{
379,716✔
1063
    return {};
379,716✔
1064
}
379,716✔
1065

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

1071
void Obj::add_index(Path& path, const Index& index) const
1072
{
494,622✔
1073
    if (path.empty()) {
494,622✔
1074
        path.emplace_back(get_table()->get_column_key(index));
491,376✔
1075
    }
491,376✔
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
}
494,622✔
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,602,787✔
1110
    auto ref = arr.get_ref();
36,602,787✔
1111
    if (arr.has_missing_parent_update()) {
36,602,787✔
1112
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
269,892✔
1113
    }
269,892✔
1114
    if (m_mem.get_ref() != ref) {
36,602,787✔
1115
        m_mem = arr.get_mem();
629,451✔
1116
        m_storage_version = arr.get_alloc().get_storage_version();
629,451✔
1117
    }
629,451✔
1118
}
36,602,787✔
1119

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

5,112✔
1130
    if (type != col_type_Mixed)
10,236✔
UNCOV
1131
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
×
1132
    if (value_is_null(value) && !col_key.is_nullable()) {
10,236✔
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,236✔
UNCOV
1136
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
×
UNCOV
1137
    }
×
1138

5,112✔
1139
    Mixed old_value = get<Mixed>(col_key);
10,236✔
1140
    ObjLink old_link{};
10,236✔
1141
    ObjLink new_link{};
10,236✔
1142
    if (old_value.is_type(type_TypedLink)) {
10,236✔
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,116✔
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,062✔
1151
        Lst<Mixed> list(*this, col_key);
54✔
1152
        list.remove_backlinks(state);
54✔
1153
    }
54✔
1154

5,112✔
1155
    if (value.is_type(type_TypedLink)) {
10,236✔
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,112✔
1166
    SearchIndex* index = m_table->get_search_index(col_key);
10,221✔
1167
    // The following check on unresolved is just a precaution as it should not
5,097✔
1168
    // be possible to hit that while Mixed is not a supported primary key type.
5,097✔
1169
    if (index && !m_key.is_unresolved()) {
10,206✔
1170
        index->set(m_key, value);
882✔
1171
    }
882✔
1172

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

5,097✔
1183
    sync(fields);
10,206✔
1184

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

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

5,097✔
1192
    return *this;
10,206✔
1193
}
10,236✔
1194

1195
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1196
{
791,331✔
1197
    if (value.is_null()) {
791,331✔
1198
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
252✔
1199
        set_null(col_key);
252✔
1200
    }
252✔
1201
    else {
791,079✔
1202
        switch (col_key.get_type()) {
791,079✔
1203
            case col_type_Int:
727,119✔
1204
                if (col_key.get_attrs().test(col_attr_Nullable)) {
727,119✔
1205
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
4,122✔
1206
                }
4,122✔
1207
                else {
722,997✔
1208
                    set(col_key, value.get_int(), is_default);
722,997✔
1209
                }
722,997✔
1210
                break;
727,119✔
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:
37,656✔
1221
                set(col_key, value.get_string(), is_default);
37,656✔
1222
                break;
37,656✔
1223
            case col_type_Binary:
17,442✔
1224
                set(col_key, value.get<Binary>(), is_default);
17,442✔
1225
                break;
17,442✔
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
        }
791,169✔
1250
    }
791,169✔
1251
    return *this;
791,169✔
1252
}
791,169✔
1253

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

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

9,795,846✔
1265
    SearchIndex* index = m_table->get_search_index(col_key);
19,300,824✔
1266
    if (index && !m_key.is_unresolved()) {
19,300,824✔
1267
        index->set(m_key, value);
185,034✔
1268
    }
185,034✔
1269

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

9,795,846✔
1289
    sync(fields);
19,300,824✔
1290

9,795,846✔
1291
    if (Replication* repl = get_replication()) {
19,300,824✔
1292
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
10,266,234✔
1293
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
10,265,703✔
1294
    }
10,266,234✔
1295

9,795,846✔
1296
    return *this;
19,300,824✔
1297
}
19,300,824✔
1298

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

8,988✔
1305
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
18,018✔
1306
        uint64_t ua = uint64_t(a);
18,012✔
1307
        uint64_t ub = uint64_t(b);
18,012✔
1308
        return int64_t(ua + ub);
18,012✔
1309
    };
18,012✔
1310

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

8,988✔
1317
    if (col_key.get_type() == col_type_Mixed) {
18,024✔
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
    }
18,000✔
1333
    else {
18,000✔
1334
        if (col_key.get_type() != col_type_Int)
18,000✔
UNCOV
1335
            throw IllegalOperation("Property not an int");
×
1336

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

8,988✔
1367
    sync(fields);
18,018✔
1368

8,982✔
1369
    if (Replication* repl = get_replication()) {
18,012✔
1370
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
10,626✔
1371
    }
10,626✔
1372

8,982✔
1373
    return *this;
18,012✔
1374
}
18,024✔
1375

1376
template <>
1377
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
1378
{
254,451✔
1379
    update_if_needed();
254,451✔
1380
    get_table()->check_column(col_key);
254,451✔
1381
    ColKey::Idx col_ndx = col_key.get_index();
254,451✔
1382
    ColumnType type = col_key.get_type();
254,451✔
1383
    if (type != col_type_Link)
254,451✔
UNCOV
1384
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
×
1385
    TableRef target_table = get_target_table(col_key);
254,451✔
1386
    TableKey target_table_key = target_table->get_key();
254,451✔
1387
    if (target_key) {
254,451✔
1388
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
253,809✔
1389
        if (!ct->is_valid(target_key)) {
253,890✔
1390
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
12✔
1391
        }
12✔
1392
        if (target_table->is_embedded()) {
253,890✔
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,451✔
1397
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
254,451✔
1398

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

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

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

126,726✔
1414
        values.set(m_row_ndx, target_key);
253,527✔
1415

126,726✔
1416
        sync(fields);
253,527✔
1417

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

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

127,224✔
1427
    return *this;
254,451✔
1428
}
254,451✔
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,148,357✔
1541
}
2,148,357✔
1542
template <>
1543
inline void check_range(const StringData& val)
1544
{
2,483,010✔
1545
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
2,483,010✔
1546
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
1,239,006✔
1547
}
2,483,010✔
1548
template <>
1549
inline void check_range(const BinaryData& val)
1550
{
5,114,751✔
1551
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
5,114,751✔
1552
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
2,556,813✔
1553
}
5,114,751✔
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,264,578✔
1560
}
7,264,578✔
1561
template <>
1562
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1563
{
2,483,391✔
1564
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
2,483,391✔
1565
    Spec* spec = const_cast<Spec*>(&get_spec());
2,483,391✔
1566
    values.set_spec(spec, spec_ndx);
2,483,391✔
1567
}
2,483,391✔
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,748,287✔
1625
    update_if_needed();
9,748,287✔
1626
    get_table()->check_column(col_key);
9,748,287✔
1627
    auto type = col_key.get_type();
9,748,287✔
1628
    auto attrs = col_key.get_attrs();
9,748,287✔
1629
    auto col_ndx = col_key.get_index();
9,748,287✔
1630

4,878,570✔
1631
    if (type != ColumnTypeTraits<T>::column_id)
9,748,287✔
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,748,287!
1635
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
6✔
1636

4,878,567✔
1637
    check_range(value);
9,748,281✔
1638

4,878,567✔
1639
    SearchIndex* index = m_table->get_search_index(col_key);
9,748,281✔
1640
    if (index && !m_key.is_unresolved()) {
9,748,281!
1641
        index->set(m_key, value);
471,729✔
1642
    }
471,729✔
1643

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

4,878,567✔
1656
    sync(fields);
9,748,281✔
1657

4,878,567✔
1658
    if (Replication* repl = get_replication())
9,748,281✔
1659
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
6,018,030✔
1660
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
6,017,673✔
1661

4,878,567✔
1662
    return *this;
9,748,281✔
1663
}
9,748,281✔
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
{
494,640✔
1678
    update_if_needed();
494,640✔
1679

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

249,423✔
1690
    sync(fields);
494,640✔
1691
}
494,640✔
1692

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

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

732✔
1707
    sync(fields);
1,464✔
1708
}
1,464✔
1709

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

3,364,638✔
1718
    ArrayBacklink backlinks(alloc);
6,730,332✔
1719
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
6,730,332✔
1720
    backlinks.init_from_parent();
6,730,332✔
1721

3,364,638✔
1722
    backlinks.add(m_row_ndx, origin_key);
6,730,332✔
1723

3,364,638✔
1724
    sync(fields);
6,730,332✔
1725
}
6,730,332✔
1726

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

101,964✔
1735
    ArrayBacklink backlinks(alloc);
204,036✔
1736
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
204,036✔
1737
    backlinks.init_from_parent();
204,036✔
1738

101,964✔
1739
    bool ret = backlinks.remove(m_row_ndx, origin_key);
204,036✔
1740

101,964✔
1741
    sync(fields);
204,036✔
1742

101,964✔
1743
    return ret;
204,036✔
1744
}
204,036✔
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
{
310,344✔
1930
    auto list = CollectionParent::get_listbase_ptr(col_key);
310,344✔
1931
    list->set_owner(*this, col_key);
310,344✔
1932
    return list;
310,344✔
1933
}
310,344✔
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
{
85,041✔
1944
    REALM_ASSERT(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed);
85,041✔
1945
    update_if_needed();
85,041✔
1946
    return Dictionary(Obj(*this), col_key);
85,041✔
1947
}
85,041✔
1948

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

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

549✔
1960
    if (old_val != new_val) {
1,098✔
1961
        set(col_key, Mixed(0, type));
888✔
1962
        // Update ref after write
444✔
1963
        ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
888✔
1964
        values.init_from_ref(ref);
888✔
1965
        values.set_key(m_row_ndx, generate_key(0x10));
888✔
1966
    }
888✔
1967
    return *this;
1,098✔
1968
}
1,098✔
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,068✔
2023
    // First element in path is phony column key
5,034✔
2024
    ColKey col_key = m_table->get_column_key(path[0]);
10,068✔
2025
    size_t level = 1;
10,068✔
2026
    CollectionBasePtr collection = get_collection_ptr(col_key);
10,068✔
2027

5,034✔
2028
    while (level < path.size()) {
10,392✔
2029
        auto& index = path[level];
324✔
2030
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
324✔
2031
            if (collection->get_collection_type() == CollectionType::List) {
324✔
2032
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
216✔
2033
                size_t ndx = list_of_mixed->find_index(index);
216✔
2034
                return {list_of_mixed->get(ndx), PathElement(ndx)};
216✔
2035
            }
216✔
2036
            else {
108✔
2037
                auto dict = dynamic_cast<Dictionary*>(collection.get());
108✔
2038
                size_t ndx = dict->find_index(index);
108✔
2039
                return {dict->get_any(ndx), PathElement(dict->get_key(ndx).get_string())};
108✔
2040
            }
108✔
2041
        };
324✔
2042
        auto [ref, path_elem] = get_ref();
324✔
2043
        if (ref.is_type(type_List)) {
324✔
2044
            collection = collection->get_list(path_elem);
306✔
2045
        }
306✔
2046
        else if (ref.is_type(type_Set)) {
18✔
UNCOV
2047
            collection = collection->get_set(path_elem);
×
UNCOV
2048
        }
×
2049
        else if (ref.is_type(type_Dictionary)) {
18✔
2050
            collection = collection->get_dictionary(path_elem);
18✔
2051
        }
18✔
UNCOV
2052
        else {
×
UNCOV
2053
            return nullptr;
×
UNCOV
2054
        }
×
2055
        level++;
324✔
2056
    }
324✔
2057

5,034✔
2058
    return collection;
10,068✔
2059
}
10,068✔
2060

2061
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2062
{
128,757✔
2063
    if (col_key.is_collection()) {
128,757✔
2064
        auto collection = CollectionParent::get_collection_ptr(col_key);
127,221✔
2065
        collection->set_owner(*this, col_key);
127,221✔
2066
        return collection;
127,221✔
2067
    }
127,221✔
2068
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
1,536✔
2069
    auto val = get<Mixed>(col_key);
1,536✔
2070
    if (val.is_type(type_List)) {
1,536✔
2071
        return std::make_shared<Lst<Mixed>>(*this, col_key);
1,056✔
2072
    }
1,056✔
2073
    else if (val.is_type(type_Set)) {
480✔
2074
        return std::make_shared<Set<Mixed>>(*this, col_key);
42✔
2075
    }
42✔
2076
    REALM_ASSERT(val.is_type(type_Dictionary));
438✔
2077
    return std::make_shared<Dictionary>(*this, col_key);
438✔
2078
}
438✔
2079

2080
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
2081
{
396✔
2082
    return get_collection_ptr(get_column_key(col_name));
396✔
2083
}
396✔
2084

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

2100
template <class T>
2101
inline void replace_in_linklist(Obj& obj, ColKey origin_col_key, T target, T replacement)
2102
{
9,342✔
2103
    Lst<T> link_list(origin_col_key);
9,342✔
2104
    size_t ndx = find_link_value_in_collection(link_list, obj, origin_col_key, target);
9,342✔
2105

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

4,671✔
2108
    link_list.set(ndx, replacement);
9,342✔
2109
}
9,342✔
2110

2111
template <class T>
2112
inline void replace_in_linkset(Obj& obj, ColKey origin_col_key, T target, T replacement)
2113
{
144✔
2114
    Set<T> link_set(origin_col_key);
144✔
2115
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
144✔
2116

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

72✔
2119
    link_set.erase(target);
144✔
2120
    link_set.insert(replacement);
144✔
2121
}
144✔
2122

2123
inline void replace_in_dictionary(Obj& obj, ColKey origin_col_key, Mixed target, Mixed replacement)
2124
{
×
UNCOV
2125
    Dictionary dict(origin_col_key);
×
2126
    size_t ndx = find_link_value_in_collection(dict, obj, origin_col_key, target);
×
2127

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

×
2130
    auto key = dict.get_key(ndx);
×
UNCOV
2131
    dict.insert(key, replacement);
×
2132
}
×
2133

2134

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

6,888✔
2190
    private:
13,629✔
2191
        const Obj& m_dest_orig;
13,629✔
2192
        const Obj& m_dest_replace;
13,629✔
2193
    };
13,629✔
2194

6,888✔
2195
    REALM_ASSERT(get_table() == other.get_table());
13,629✔
2196
    if (auto col_pk = m_table->get_primary_key_column()) {
13,629✔
2197
        Mixed val = other.get_any(col_pk);
13,467✔
2198
        this->set_any(col_pk, val);
13,467✔
2199
    }
13,467✔
2200
    auto nb_tombstones = m_table->m_tombstones->size();
13,629✔
2201

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

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

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

19,836✔
2247
    T values(alloc);
39,675✔
2248
    values.set_parent(&fields, col_ndx.val + 1);
39,675✔
2249
    values.init_from_parent();
39,675✔
2250
    values.set_null(m_row_ndx);
39,675✔
2251

19,836✔
2252
    sync(fields);
39,675✔
2253
}
39,675✔
2254

2255
template <>
2256
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2257
{
3,321✔
2258
    ColKey::Idx col_ndx = col_key.get_index();
3,321✔
2259
    size_t spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
3,321✔
2260
    Allocator& alloc = get_alloc();
3,321✔
2261
    alloc.bump_content_version();
3,321✔
2262
    Array fallback(alloc);
3,321✔
2263
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
3,321✔
2264

1,722✔
2265
    ArrayString values(alloc);
3,321✔
2266
    values.set_parent(&fields, col_ndx.val + 1);
3,321✔
2267
    values.set_spec(const_cast<Spec*>(&get_spec()), spec_ndx);
3,321✔
2268
    values.init_from_parent();
3,321✔
2269
    values.set_null(m_row_ndx);
3,321✔
2270

1,722✔
2271
    sync(fields);
3,321✔
2272
}
3,321✔
2273

2274
Obj& Obj::set_null(ColKey col_key, bool is_default)
2275
{
43,590✔
2276
    ColumnType col_type = col_key.get_type();
43,590✔
2277
    // Links need special handling
21,855✔
2278
    if (col_type == col_type_Link) {
43,590✔
2279
        set(col_key, null_key);
462✔
2280
    }
462✔
2281
    else if (col_type == col_type_Mixed) {
43,128✔
2282
        set(col_key, Mixed{});
66✔
2283
    }
66✔
2284
    else {
43,062✔
2285
        auto attrs = col_key.get_attrs();
43,062✔
2286
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
43,062✔
2287
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
66✔
2288
                              m_table->get_column_name(col_key));
66✔
2289
        }
66✔
2290

21,558✔
2291
        update_if_needed();
42,996✔
2292

21,558✔
2293
        SearchIndex* index = m_table->get_search_index(col_key);
42,996✔
2294
        if (index && !m_key.is_unresolved()) {
42,996✔
2295
            index->set(m_key, null{});
4,617✔
2296
        }
4,617✔
2297

21,558✔
2298
        switch (col_type) {
42,996✔
2299
            case col_type_Int:
6,429✔
2300
                do_set_null<ArrayIntNull>(col_key);
6,429✔
2301
                break;
6,429✔
2302
            case col_type_Bool:
5,619✔
2303
                do_set_null<ArrayBoolNull>(col_key);
5,619✔
2304
                break;
5,619✔
2305
            case col_type_Float:
6,363✔
2306
                do_set_null<ArrayFloatNull>(col_key);
6,363✔
2307
                break;
6,363✔
2308
            case col_type_Double:
6,192✔
2309
                do_set_null<ArrayDoubleNull>(col_key);
6,192✔
2310
                break;
6,192✔
2311
            case col_type_ObjectId:
3,363✔
2312
                do_set_null<ArrayObjectIdNull>(col_key);
3,363✔
2313
                break;
3,363✔
2314
            case col_type_String:
3,321✔
2315
                do_set_null<ArrayString>(col_key);
3,321✔
2316
                break;
3,321✔
2317
            case col_type_Binary:
1,248✔
2318
                do_set_null<ArrayBinary>(col_key);
1,248✔
2319
                break;
1,248✔
2320
            case col_type_Timestamp:
2,445✔
2321
                do_set_null<ArrayTimestamp>(col_key);
2,445✔
2322
                break;
2,445✔
2323
            case col_type_Decimal:
1,830✔
2324
                do_set_null<ArrayDecimal128>(col_key);
1,830✔
2325
                break;
1,830✔
2326
            case col_type_UUID:
6,186✔
2327
                do_set_null<ArrayUUIDNull>(col_key);
6,186✔
2328
                break;
6,186✔
UNCOV
2329
            case col_type_Mixed:
✔
UNCOV
2330
            case col_type_Link:
✔
UNCOV
2331
            case col_type_LinkList:
✔
UNCOV
2332
            case col_type_BackLink:
✔
UNCOV
2333
            case col_type_TypedLink:
✔
UNCOV
2334
                REALM_UNREACHABLE();
×
2335
        }
42,996✔
2336
    }
42,996✔
2337

21,855✔
2338
    if (Replication* repl = get_replication())
43,557✔
2339
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
23,220✔
2340
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
23,088✔
2341

21,822✔
2342
    return *this;
43,524✔
2343
}
43,590✔
2344

2345

2346
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2347
{
8,577,567✔
2348
    return get_table()->spec_ndx2colkey(col_ndx);
8,577,567✔
2349
}
8,577,567✔
2350

2351
size_t Obj::colkey2spec_ndx(ColKey key)
2352
{
777✔
2353
    return get_table()->colkey2spec_ndx(key);
777✔
2354
}
777✔
2355

2356
ColKey Obj::get_primary_key_column() const
2357
{
5,263,971✔
2358
    return m_table->get_primary_key_column();
5,263,971✔
2359
}
5,263,971✔
2360

2361
ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key)
2362
{
12,666✔
2363
    return to_ref(obj._get<int64_t>(col_key.get_index()));
12,666✔
2364
}
12,666✔
2365

2366
ref_type Obj::get_collection_ref(Index index, CollectionType type) const
2367
{
3,134,655✔
2368
    if (index.is_collection()) {
3,134,655✔
2369
        return to_ref(_get<int64_t>(index.get_index()));
3,127,272✔
2370
    }
3,127,272✔
2371
    if (check_index(index)) {
7,383✔
2372
        auto val = _get<Mixed>(index.get_index());
7,254✔
2373
        if (val.is_type(DataType(int(type)))) {
7,254✔
2374
            return val.get_ref();
7,248✔
2375
        }
7,248✔
2376
        throw realm::IllegalOperation(util::format("Not a %1", type));
6✔
2377
    }
6✔
2378
    throw StaleAccessor("This collection is no more");
129✔
2379
}
129✔
2380

2381
bool Obj::check_collection_ref(Index index, CollectionType type) const noexcept
2382
{
791,145✔
2383
    if (index.is_collection()) {
791,145✔
2384
        return true;
788,931✔
2385
    }
788,931✔
2386
    if (check_index(index)) {
2,214✔
2387
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
2,208✔
2388
    }
2,208✔
2389
    return false;
6✔
2390
}
6✔
2391

2392
void Obj::set_collection_ref(Index index, ref_type ref, CollectionType type)
2393
{
496,104✔
2394
    if (index.is_collection()) {
496,104✔
2395
        set_int(index.get_index(), from_ref(ref));
494,643✔
2396
        return;
494,643✔
2397
    }
494,643✔
2398
    set_ref(index.get_index(), ref, type);
1,461✔
2399
}
1,461✔
2400

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