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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

89.33
/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
{
98,118✔
56
    coll.set_owner(obj, origin_col_key);
98,118✔
57
    return coll.find_first(link);
98,118✔
58
}
98,118✔
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
{
170,404,005✔
114
    m_storage_version = get_alloc().get_storage_version();
170,404,005✔
115
}
170,404,005✔
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
{
43,542✔
124
    return ObjLink(m_table->get_key(), m_key);
43,542✔
125
}
43,542✔
126

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

329
bool Obj::is_valid() const noexcept
330
{
1,458,126✔
331
    // Cache valid state. If once invalid, it can never become valid again
781,095✔
332
    if (m_valid)
1,458,126✔
333
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,456,746✔
334
                                    m_table.unchecked_ptr()->is_valid(m_key));
796,161✔
335

781,095✔
336
    return m_valid;
1,458,126✔
337
}
1,458,126✔
338

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

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

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

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

359
TableRef Obj::get_target_table(ColKey col_key) const
360
{
7,075,029✔
361
    if (m_table) {
7,075,029✔
362
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,074,195✔
363
    }
7,074,195✔
364
    else {
834✔
365
        return TableRef();
834✔
366
    }
834✔
367
}
7,075,029✔
368

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

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

409,320✔
384
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
816,279✔
385
    if (changes) {
816,279✔
386
        m_mem = new_obj.m_mem;
128,184✔
387
        m_row_ndx = new_obj.m_row_ndx;
128,184✔
388
    }
128,184✔
389
    // Always update versions
409,320✔
390
    m_storage_version = new_obj.m_storage_version;
816,279✔
391
    m_table = new_obj.m_table;
816,279✔
392
    return changes;
816,279✔
393
}
816,279✔
394

395
inline bool Obj::_update_if_needed() const
396
{
78,314,316✔
397
    auto current_version = _get_alloc().get_storage_version();
78,314,316✔
398
    if (current_version != m_storage_version) {
78,314,316✔
399
        return update();
13,233✔
400
    }
13,233✔
401
    return false;
78,301,083✔
402
}
78,301,083✔
403

404
UpdateStatus Obj::update_if_needed_with_status() const
405
{
35,685,795✔
406
    if (!m_table) {
35,685,795✔
407
        // Table deleted
171✔
408
        return UpdateStatus::Detached;
342✔
409
    }
342✔
410

17,858,799✔
411
    auto current_version = get_alloc().get_storage_version();
35,685,453✔
412
    if (current_version != m_storage_version) {
35,685,453✔
413
        ClusterNode::State state = get_tree_top()->try_get(m_key);
637,827✔
414

318,792✔
415
        if (!state) {
637,827✔
416
            // Object deleted
2,589✔
417
            return UpdateStatus::Detached;
5,178✔
418
        }
5,178✔
419

316,203✔
420
        // Always update versions
316,203✔
421
        m_storage_version = current_version;
632,649✔
422
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
632,649✔
423
            m_mem = state.mem;
143,313✔
424
            m_row_ndx = state.index;
143,313✔
425
            return UpdateStatus::Updated;
143,313✔
426
        }
143,313✔
427
    }
35,536,962✔
428
    return UpdateStatus::NoChange;
35,536,962✔
429
}
35,536,962✔
430

431
template <class T>
432
T Obj::get(ColKey col_key) const
433
{
77,883,432✔
434
    m_table->check_column(col_key);
77,883,432✔
435
    ColumnType type = col_key.get_type();
77,883,432✔
436
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
77,883,432!
437

38,852,358✔
438
    return _get<T>(col_key.get_index());
77,883,432✔
439
}
77,883,432✔
440

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

444
#if REALM_ENABLE_GEOSPATIAL
445

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

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

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

469
#endif
470

471
template <class T>
472
T Obj::_get(ColKey::Idx col_ndx) const
473
{
77,783,658✔
474
    _update_if_needed();
77,783,658✔
475

38,820,333✔
476
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
77,783,658✔
477
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
77,783,658✔
478
    values.init_from_ref(ref);
77,783,658✔
479

38,820,333✔
480
    return values.get(m_row_ndx);
77,783,658✔
481
}
77,783,658✔
482

483
template <>
484
ObjKey Obj::_get<ObjKey>(ColKey::Idx col_ndx) const
485
{
314,796✔
486
    _update_if_needed();
314,796✔
487

157,287✔
488
    ArrayKey values(_get_alloc());
314,796✔
489
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
314,796✔
490
    values.init_from_ref(ref);
314,796✔
491

157,287✔
492
    ObjKey k = values.get(m_row_ndx);
314,796✔
493
    return k.is_unresolved() ? ObjKey{} : k;
314,685✔
494
}
314,796✔
495

496
bool Obj::is_unresolved(ColKey col_key) const
497
{
30✔
498
    m_table->check_column(col_key);
30✔
499
    ColumnType type = col_key.get_type();
30✔
500
    REALM_ASSERT(type == col_type_Link);
30✔
501

15✔
502
    _update_if_needed();
30✔
503

15✔
504
    return get_unfiltered_link(col_key).is_unresolved();
30✔
505
}
30✔
506

507
ObjKey Obj::get_unfiltered_link(ColKey col_key) const
508
{
315,426✔
509
    ArrayKey values(get_alloc());
315,426✔
510
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
315,426✔
511
    values.init_from_ref(ref);
315,426✔
512

157,686✔
513
    return values.get(m_row_ndx);
315,426✔
514
}
315,426✔
515

516
template <>
517
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
518
{
126,671,520✔
519
    // manual inline of _update_if_needed():
67,073,775✔
520
    auto& alloc = _get_alloc();
126,671,520✔
521
    auto current_version = alloc.get_storage_version();
126,671,520✔
522
    if (current_version != m_storage_version) {
126,671,520✔
523
        update();
139,020✔
524
    }
139,020✔
525

67,073,775✔
526
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
126,671,520✔
527
    char* header = alloc.translate(ref);
126,671,520✔
528
    int width = Array::get_width_from_header(header);
126,671,520✔
529
    char* data = Array::get_data_from_header(header);
126,671,520✔
530
    REALM_TEMPEX(return get_direct, width, (data, m_row_ndx));
126,671,520✔
531
}
×
532

533
template <>
534
int64_t Obj::get<int64_t>(ColKey col_key) const
535
{
113,485,899✔
536
    m_table->check_column(col_key);
113,485,899✔
537
    ColumnType type = col_key.get_type();
113,485,899✔
538
    REALM_ASSERT(type == col_type_Int);
113,485,899✔
539

61,420,296✔
540
    if (col_key.get_attrs().test(col_attr_Nullable)) {
113,485,899✔
541
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
6,006✔
542
        if (!val) {
6,006✔
543
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
6✔
544
        }
6✔
545
        return *val;
6,000✔
546
    }
6,000✔
547
    else {
113,479,893✔
548
        return _get<int64_t>(col_key.get_index());
113,479,893✔
549
    }
113,479,893✔
550
}
113,485,899✔
551

552
template <>
553
bool Obj::get<bool>(ColKey col_key) const
554
{
492,900✔
555
    m_table->check_column(col_key);
492,900✔
556
    ColumnType type = col_key.get_type();
492,900✔
557
    REALM_ASSERT(type == col_type_Bool);
492,900✔
558

217,476✔
559
    if (col_key.get_attrs().test(col_attr_Nullable)) {
492,900✔
560
        auto val = _get<util::Optional<bool>>(col_key.get_index());
30✔
561
        if (!val) {
30✔
562
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
×
563
        }
×
564
        return *val;
30✔
565
    }
30✔
566
    else {
492,870✔
567
        return _get<bool>(col_key.get_index());
492,870✔
568
    }
492,870✔
569
}
492,900✔
570

571
template <>
572
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
573
{
5,443,656✔
574
    // manual inline of _update_if_needed():
2,683,260✔
575
    auto& alloc = _get_alloc();
5,443,656✔
576
    auto current_version = alloc.get_storage_version();
5,443,656✔
577
    if (current_version != m_storage_version) {
5,443,656✔
578
        update();
15,996✔
579
    }
15,996✔
580

2,683,260✔
581
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
5,443,656✔
582
    auto spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
5,443,656✔
583
    auto& spec = get_spec();
5,443,656✔
584
    if (spec.is_string_enum_type(spec_ndx)) {
5,443,656✔
585
        ArrayString values(get_alloc());
1,064,751✔
586
        values.set_spec(const_cast<Spec*>(&spec), spec_ndx);
1,064,751✔
587
        values.init_from_ref(ref);
1,064,751✔
588

531,279✔
589
        return values.get(m_row_ndx);
1,064,751✔
590
    }
1,064,751✔
591
    else {
4,378,905✔
592
        return ArrayString::get(alloc.translate(ref), m_row_ndx, alloc);
4,378,905✔
593
    }
4,378,905✔
594
}
5,443,656✔
595

596
template <>
597
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
598
{
156,696✔
599
    // manual inline of _update_if_needed():
78,342✔
600
    auto& alloc = _get_alloc();
156,696✔
601
    auto current_version = alloc.get_storage_version();
156,696✔
602
    if (current_version != m_storage_version) {
156,696✔
603
        update();
132✔
604
    }
132✔
605

78,342✔
606
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
156,696✔
607
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
156,696✔
608
}
156,696✔
609

610
Mixed Obj::get_any(ColKey col_key) const
611
{
15,294,918✔
612
    m_table->check_column(col_key);
15,294,918✔
613
    auto col_ndx = col_key.get_index();
15,294,918✔
614
    if (col_key.is_collection()) {
15,294,918✔
615
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
5,700✔
616
        return Mixed(ref, get_table()->get_collection_type(col_key));
5,700✔
617
    }
5,700✔
618
    switch (col_key.get_type()) {
15,289,218✔
619
        case col_type_Int:
10,413,354✔
620
            if (col_key.get_attrs().test(col_attr_Nullable)) {
10,413,354✔
621
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
391,974✔
622
            }
391,974✔
623
            else {
10,021,380✔
624
                return Mixed{_get<int64_t>(col_ndx)};
10,021,380✔
625
            }
10,021,380✔
626
        case col_type_Bool:
202,635✔
627
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
202,635✔
628
        case col_type_Float:
8,007✔
629
            return Mixed{_get<util::Optional<float>>(col_ndx)};
8,007✔
630
        case col_type_Double:
19,617✔
631
            return Mixed{_get<util::Optional<double>>(col_ndx)};
19,617✔
632
        case col_type_String:
4,157,094✔
633
            return Mixed{_get<String>(col_ndx)};
4,157,094✔
634
        case col_type_Binary:
1,605✔
635
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
636
        case col_type_Mixed:
13,863✔
637
            return _get<Mixed>(col_ndx);
13,863✔
638
        case col_type_Timestamp:
164,478✔
639
            return Mixed{_get<Timestamp>(col_ndx)};
164,478✔
640
        case col_type_Decimal:
7,860✔
641
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
642
        case col_type_ObjectId:
256,797✔
643
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
256,797✔
644
        case col_type_UUID:
52,353✔
645
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
52,353✔
646
        case col_type_Link:
40,596✔
647
            return Mixed{_get<ObjKey>(col_ndx)};
40,596✔
648
        default:
✔
649
            REALM_UNREACHABLE();
×
650
            break;
×
651
    }
×
652
    return {};
×
653
}
×
654

655
Mixed Obj::get_primary_key() const
656
{
99,561✔
657
    auto col = m_table->get_primary_key_column();
99,561✔
658
    return col ? get_any(col) : Mixed{get_key()};
99,465✔
659
}
99,561✔
660

661
/* FIXME: Make this one fast too!
662
template <>
663
ObjKey Obj::_get(size_t col_ndx) const
664
{
665
    return ObjKey(_get<int64_t>(col_ndx));
666
}
667
*/
668

669
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
670
{
25,287✔
671
    Obj obj;
25,287✔
672
    if (!link.is_null()) {
25,287✔
673
        TableRef target_table;
17,121✔
674
        if (link.is_type(type_TypedLink)) {
17,121✔
675
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
324✔
676
        }
324✔
677
        else {
16,797✔
678
            target_table = get_target_table(link_col_key);
16,797✔
679
        }
16,797✔
680
        obj = target_table->get_object(link.get<ObjKey>());
17,121✔
681
    }
17,121✔
682
    return obj;
25,287✔
683
}
25,287✔
684

685
Obj Obj::get_parent_object() const
686
{
12✔
687
    Obj obj;
12✔
688
    update_if_needed();
12✔
689

6✔
690
    if (!m_table->is_embedded()) {
12✔
691
        throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded");
×
692
    }
×
693
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
18✔
694
        if (get_backlink_cnt(backlink_col_key) == 1) {
18✔
695
            auto obj_key = get_backlink(backlink_col_key, 0);
12✔
696
            obj = m_table->get_opposite_table(backlink_col_key)->get_object(obj_key);
12✔
697
            return IteratorControl::Stop;
12✔
698
        }
12✔
699
        return IteratorControl::AdvanceToNext;
6✔
700
    });
6✔
701

6✔
702
    return obj;
12✔
703
}
12✔
704

705
template <class T>
706
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
707
{
1,182,414✔
708
    T values(get_alloc());
1,182,414✔
709
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,182,414✔
710
    values.init_from_ref(ref);
1,182,414✔
711
    return values.is_null(m_row_ndx);
1,182,414✔
712
}
1,182,414✔
713

714
template <>
715
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
716
{
314,043✔
717
    ArrayString values(get_alloc());
314,043✔
718
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
314,043✔
719
    values.set_spec(const_cast<Spec*>(&get_spec()), m_table->leaf_ndx2spec_ndx(col_ndx));
314,043✔
720
    values.init_from_ref(ref);
314,043✔
721
    return values.is_null(m_row_ndx);
314,043✔
722
}
314,043✔
723

724
size_t Obj::get_link_count(ColKey col_key) const
725
{
108✔
726
    return get_list<ObjKey>(col_key).size();
108✔
727
}
108✔
728

729
bool Obj::is_null(ColKey col_key) const
730
{
5,432,124✔
731
    update_if_needed();
5,432,124✔
732
    ColumnAttrMask attr = col_key.get_attrs();
5,432,124✔
733
    ColKey::Idx col_ndx = col_key.get_index();
5,432,124✔
734
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,432,124✔
735
        switch (col_key.get_type()) {
1,496,334✔
736
            case col_type_Int:
418,359✔
737
                return do_is_null<ArrayIntNull>(col_ndx);
418,359✔
738
            case col_type_Bool:
409,134✔
739
                return do_is_null<ArrayBoolNull>(col_ndx);
409,134✔
740
            case col_type_Float:
9,249✔
741
                return do_is_null<ArrayFloatNull>(col_ndx);
9,249✔
742
            case col_type_Double:
3,267✔
743
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
744
            case col_type_String:
314,043✔
745
                return do_is_null<ArrayString>(col_ndx);
314,043✔
746
            case col_type_Binary:
267✔
747
                return do_is_null<ArrayBinary>(col_ndx);
267✔
748
            case col_type_Mixed:
249✔
749
                return do_is_null<ArrayMixed>(col_ndx);
249✔
750
            case col_type_Timestamp:
318,822✔
751
                return do_is_null<ArrayTimestamp>(col_ndx);
318,822✔
752
            case col_type_Link:
20,592✔
753
                return do_is_null<ArrayKey>(col_ndx);
20,592✔
754
            case col_type_ObjectId:
231✔
755
                return do_is_null<ArrayObjectIdNull>(col_ndx);
231✔
756
            case col_type_Decimal:
2,013✔
757
                return do_is_null<ArrayDecimal128>(col_ndx);
2,013✔
758
            case col_type_UUID:
231✔
759
                return do_is_null<ArrayUUIDNull>(col_ndx);
231✔
760
            default:
✔
761
                REALM_UNREACHABLE();
×
762
        }
1,496,334✔
763
    }
1,496,334✔
764
    return false;
4,611,897✔
765
}
5,432,124✔
766

767

768
// Figure out if this object has any remaining backlinkss
769
bool Obj::has_backlinks(bool only_strong_links) const
770
{
80,841✔
771
    const Table& target_table = *m_table;
80,841✔
772

40,464✔
773
    // If we only look for strong links and the table is not embedded,
40,464✔
774
    // then there is no relevant backlinks to find.
40,464✔
775
    if (only_strong_links && !target_table.is_embedded()) {
80,841✔
776
        return false;
×
777
    }
×
778

40,464✔
779
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
80,841✔
780
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
24,117✔
781
    });
24,399✔
782
}
80,841✔
783

784
size_t Obj::get_backlink_count() const
785
{
141,636✔
786
    update_if_needed();
141,636✔
787

70,758✔
788
    size_t cnt = 0;
141,636✔
789
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
371,970✔
790
        cnt += get_backlink_cnt(backlink_col_key);
371,970✔
791
        return IteratorControl::AdvanceToNext;
371,970✔
792
    });
371,970✔
793
    return cnt;
141,636✔
794
}
141,636✔
795

796
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
797
{
14,037✔
798
    update_if_needed();
14,037✔
799

7,026✔
800
    size_t cnt = 0;
14,037✔
801
    if (TableKey origin_table_key = origin.get_key()) {
14,037✔
802
        ColKey backlink_col_key;
14,037✔
803
        auto type = origin_col_key.get_type();
14,037✔
804
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
14,037✔
805
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
12✔
806
        }
12✔
807
        else {
14,025✔
808
            backlink_col_key = origin.get_opposite_column(origin_col_key);
14,025✔
809
        }
14,025✔
810

7,026✔
811
        cnt = get_backlink_cnt(backlink_col_key);
14,037✔
812
    }
14,037✔
813
    return cnt;
14,037✔
814
}
14,037✔
815

816
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
817
{
14,571✔
818
    ColKey backlink_col_key;
14,571✔
819
    auto type = origin_col_key.get_type();
14,571✔
820
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
14,571✔
821
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
822
    }
36✔
823
    else {
14,535✔
824
        backlink_col_key = origin.get_opposite_column(origin_col_key);
14,535✔
825
    }
14,535✔
826
    return get_backlink(backlink_col_key, backlink_ndx);
14,571✔
827
}
14,571✔
828

829
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key) const
830
{
696✔
831
    TableView tv(src_table, src_col_key, *this);
696✔
832
    tv.do_sync();
696✔
833
    return tv;
696✔
834
}
696✔
835

836
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
837
{
14,583✔
838
    get_table()->check_column(backlink_col);
14,583✔
839
    Allocator& alloc = get_alloc();
14,583✔
840
    Array fields(alloc);
14,583✔
841
    fields.init_from_mem(m_mem);
14,583✔
842

7,299✔
843
    ArrayBacklink backlinks(alloc);
14,583✔
844
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
14,583✔
845
    backlinks.init_from_parent();
14,583✔
846
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
14,583✔
847
}
14,583✔
848

849
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
850
{
236,493✔
851
    update_if_needed();
236,493✔
852

118,131✔
853
    get_table()->check_column(backlink_col);
236,493✔
854
    Allocator& alloc = get_alloc();
236,493✔
855
    Array fields(alloc);
236,493✔
856
    fields.init_from_mem(m_mem);
236,493✔
857

118,131✔
858
    ArrayBacklink backlinks(alloc);
236,493✔
859
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
236,493✔
860
    backlinks.init_from_parent();
236,493✔
861

118,131✔
862
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
236,493✔
863
    std::vector<ObjKey> vec;
236,493✔
864
    vec.reserve(cnt);
236,493✔
865
    for (size_t i = 0; i < cnt; i++) {
444,966✔
866
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
208,473✔
867
    }
208,473✔
868
    return vec;
236,493✔
869
}
236,493✔
870

871
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
872
{
410,424✔
873
    Allocator& alloc = get_alloc();
410,424✔
874
    Array fields(alloc);
410,424✔
875
    fields.init_from_mem(m_mem);
410,424✔
876

205,011✔
877
    ArrayBacklink backlinks(alloc);
410,424✔
878
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
410,424✔
879
    backlinks.init_from_parent();
410,424✔
880

205,011✔
881
    return backlinks.get_backlink_count(m_row_ndx);
410,424✔
882
}
410,424✔
883

884
void Obj::verify_backlink(const Table& origin, ColKey origin_col_key, ObjKey origin_key) const
885
{
120,666✔
886
#ifdef REALM_DEBUG
120,666✔
887
    ColKey backlink_col_key;
120,666✔
888
    auto type = origin_col_key.get_type();
120,666✔
889
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
120,666✔
NEW
890
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
×
NEW
891
    }
×
892
    else {
120,666✔
893
        backlink_col_key = origin.get_opposite_column(origin_col_key);
120,666✔
894
    }
120,666✔
895

60,333✔
896
    Allocator& alloc = get_alloc();
120,666✔
897
    Array fields(alloc);
120,666✔
898
    fields.init_from_mem(m_mem);
120,666✔
899

60,333✔
900
    ArrayBacklink backlinks(alloc);
120,666✔
901
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
120,666✔
902
    backlinks.init_from_parent();
120,666✔
903

60,333✔
904
    REALM_ASSERT(backlinks.verify_backlink(m_row_ndx, origin_key.value));
120,666✔
905
#else
906
    static_cast<void>(origin);
907
    static_cast<void>(origin_col_key);
908
    static_cast<void>(origin_key);
909
#endif
910
}
120,666✔
911

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

45✔
955
    private:
90✔
956
        Mixed m_index;
90✔
957
        Obj m_dest_obj;
90✔
958
    };
90✔
959

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

983
Obj::FatPath Obj::get_fat_path() const
984
{
30✔
985
    FatPath result;
30✔
986
    auto sizer = [&](size_t size) {
30✔
987
        result.reserve(size);
30✔
988
    };
30✔
989
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
30✔
990
        result.push_back({o2, col, idx});
30✔
991
    };
30✔
992
    traverse_path(step, sizer);
30✔
993
    return result;
30✔
994
}
30✔
995

996
FullPath Obj::get_path() const
997
{
230,286✔
998
    FullPath result;
230,286✔
999
    if (m_table->is_embedded()) {
230,286✔
1000
        REALM_ASSERT(get_backlink_count() == 1);
136,875✔
1001
        m_table->for_each_backlink_column([&](ColKey col_key) {
224,955✔
1002
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
224,955✔
1003
            if (backlinks.size() == 1) {
224,955✔
1004
                TableRef origin_table = m_table->get_opposite_table(col_key);
136,875✔
1005
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
136,875✔
1006
                auto next_col_key = m_table->get_opposite_column(col_key);
136,875✔
1007

68,376✔
1008
                ColumnAttrMask attr = next_col_key.get_attrs();
136,875✔
1009
                Mixed index;
136,875✔
1010
                if (attr.test(col_attr_List)) {
136,875✔
1011
                    REALM_ASSERT(next_col_key.get_type() == col_type_LinkList);
47,586✔
1012
                    Lst<ObjKey> link_list(next_col_key);
47,586✔
1013
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
47,586✔
1014
                    REALM_ASSERT(i != realm::not_found);
47,586✔
1015
                    result = link_list.get_path();
47,586✔
1016
                    result.path_from_top.emplace_back(i);
47,586✔
1017
                }
47,586✔
1018
                else if (attr.test(col_attr_Dictionary)) {
89,289✔
1019
                    Dictionary dict(next_col_key);
37,062✔
1020
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
37,062✔
1021
                    REALM_ASSERT(ndx != realm::not_found);
37,062✔
1022
                    result = dict.get_path();
37,062✔
1023
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
37,062✔
1024
                }
37,062✔
1025
                else {
52,227✔
1026
                    result = obj.get_path();
52,227✔
1027
                    if (result.path_from_top.empty()) {
52,227✔
1028
                        result.path_from_top.push_back(next_col_key);
11,715✔
1029
                    }
11,715✔
1030
                    else {
40,512✔
1031
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
40,512✔
1032
                    }
40,512✔
1033
                }
52,227✔
1034

68,376✔
1035
                return IteratorControl::Stop; // early out
136,875✔
1036
            }
136,875✔
1037
            return IteratorControl::AdvanceToNext; // try next column
88,080✔
1038
        });
88,080✔
1039
    }
136,875✔
1040
    else {
93,411✔
1041
        result.top_objkey = get_key();
93,411✔
1042
        result.top_table = get_table()->get_key();
93,411✔
1043
    }
93,411✔
1044
    return result;
230,286✔
1045
}
230,286✔
1046

1047
std::string Obj::get_id() const
UNCOV
1048
{
×
NEW
1049
    std::ostringstream ostr;
×
NEW
1050
    auto path = get_path();
×
NEW
1051
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
×
NEW
1052
    ostr << top_table->get_class_name() << '[';
×
NEW
1053
    if (top_table->get_primary_key_column()) {
×
NEW
1054
        ostr << top_table->get_primary_key(path.top_objkey);
×
UNCOV
1055
    }
×
NEW
1056
    else {
×
NEW
1057
        ostr << path.top_objkey;
×
UNCOV
1058
    }
×
NEW
1059
    ostr << ']';
×
NEW
1060
    if (!path.path_from_top.empty()) {
×
NEW
1061
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
×
NEW
1062
        path.path_from_top[0] = PathElement(prop_name);
×
NEW
1063
        ostr << path.path_from_top;
×
UNCOV
1064
    }
×
NEW
1065
    return ostr.str();
×
UNCOV
1066
}
×
1067

1068
Path Obj::get_short_path() const noexcept
1069
{
379,227✔
1070
    return {};
379,227✔
1071
}
379,227✔
1072

1073
StablePath Obj::get_stable_path() const noexcept
1074
{
1,902,936✔
1075
    return {};
1,902,936✔
1076
}
1,902,936✔
1077

1078
void Obj::add_index(Path& path, const Index& index) const
1079
{
463,893✔
1080
    if (path.empty()) {
463,893✔
1081
        path.emplace_back(get_table()->get_column_key(index));
460,923✔
1082
    }
460,923✔
1083
    else {
2,970✔
1084
        StringData col_name = get_table()->get_column_name(index);
2,970✔
1085
        path.emplace_back(col_name);
2,970✔
1086
    }
2,970✔
1087
}
463,893✔
1088

1089
std::string Obj::to_string() const
1090
{
24✔
1091
    std::ostringstream ostr;
24✔
1092
    to_json(ostr, 0, {});
24✔
1093
    return ostr.str();
24✔
1094
}
24✔
1095

1096
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
1097
{
×
1098
    obj.to_json(ostr, -1, {});
×
1099
    return ostr;
×
1100
}
×
1101

1102
/*********************************** Obj *************************************/
1103

1104
bool Obj::ensure_writeable()
1105
{
×
1106
    Allocator& alloc = get_alloc();
×
1107
    if (alloc.is_read_only(m_mem.get_ref())) {
×
1108
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
×
1109
        m_storage_version = alloc.get_storage_version();
×
1110
        return true;
×
1111
    }
×
1112
    return false;
×
1113
}
×
1114

1115
REALM_FORCEINLINE void Obj::sync(Node& arr)
1116
{
36,845,943✔
1117
    auto ref = arr.get_ref();
36,845,943✔
1118
    if (arr.has_missing_parent_update()) {
36,845,943✔
1119
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
269,568✔
1120
    }
269,568✔
1121
    if (m_mem.get_ref() != ref) {
36,845,943✔
1122
        m_mem = arr.get_mem();
632,901✔
1123
        m_storage_version = arr.get_alloc().get_storage_version();
632,901✔
1124
    }
632,901✔
1125
}
36,845,943✔
1126

1127
template <>
1128
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
1129
{
9,588✔
1130
    update_if_needed();
9,588✔
1131
    get_table()->check_column(col_key);
9,588✔
1132
    auto type = col_key.get_type();
9,588✔
1133
    auto col_ndx = col_key.get_index();
9,588✔
1134
    bool recurse = false;
9,588✔
1135
    CascadeState state;
9,588✔
1136

4,788✔
1137
    if (type != col_type_Mixed)
9,588✔
1138
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
×
1139
    if (value_is_null(value) && !col_key.is_nullable()) {
9,588✔
1140
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
×
1141
    }
×
1142
    if (value.is_type(type_Link)) {
9,588✔
1143
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
×
1144
    }
×
1145

4,788✔
1146
    Mixed old_value = get<Mixed>(col_key);
9,588✔
1147
    ObjLink old_link{};
9,588✔
1148
    ObjLink new_link{};
9,588✔
1149
    if (old_value.is_type(type_TypedLink)) {
9,588✔
1150
        old_link = old_value.get<ObjLink>();
120✔
1151
        recurse = remove_backlink(col_key, old_link, state);
120✔
1152
    }
120✔
1153
    else if (old_value.is_type(type_Dictionary)) {
9,468✔
1154
        Dictionary dict(*this, col_key);
18✔
1155
        recurse = dict.remove_backlinks(state);
18✔
1156
    }
18✔
1157
    else if (old_value.is_type(type_List)) {
9,450✔
1158
        Lst<Mixed> list(*this, col_key);
12✔
1159
        recurse = list.remove_backlinks(state);
12✔
1160
    }
12✔
1161

4,788✔
1162
    if (value.is_type(type_TypedLink)) {
9,588✔
1163
        if (m_table->is_asymmetric()) {
930✔
1164
            throw IllegalOperation("Links not allowed in asymmetric tables");
12✔
1165
        }
12✔
1166
        new_link = value.template get<ObjLink>();
918✔
1167
        m_table->get_parent_group()->validate(new_link);
918✔
1168
        if (new_link == old_link)
918✔
1169
            return *this;
18✔
1170
        set_backlink(col_key, new_link);
900✔
1171
    }
900✔
1172

4,788✔
1173
    SearchIndex* index = m_table->get_search_index(col_key);
9,573✔
1174
    // The following check on unresolved is just a precaution as it should not
4,773✔
1175
    // be possible to hit that while Mixed is not a supported primary key type.
4,773✔
1176
    if (index && !m_key.is_unresolved()) {
9,558✔
1177
        index->set(m_key, value);
1,122✔
1178
    }
1,122✔
1179

4,773✔
1180
    Allocator& alloc = get_alloc();
9,558✔
1181
    alloc.bump_content_version();
9,558✔
1182
    Array fallback(alloc);
9,558✔
1183
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,558✔
1184
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,558✔
1185
    ArrayMixed values(alloc);
9,558✔
1186
    values.set_parent(&fields, col_ndx.val + 1);
9,558✔
1187
    values.init_from_parent();
9,558✔
1188
    values.set(m_row_ndx, value);
9,558✔
1189

4,773✔
1190
    sync(fields);
9,558✔
1191

4,773✔
1192
    if (Replication* repl = get_replication())
9,558✔
1193
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
5,490✔
1194
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
5,490✔
1195

4,773✔
1196
    if (recurse)
9,558✔
1197
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1198

4,773✔
1199
    return *this;
9,558✔
1200
}
9,588✔
1201

1202
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1203
{
793,128✔
1204
    if (value.is_null()) {
793,128✔
1205
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
252✔
1206
        set_null(col_key);
252✔
1207
    }
252✔
1208
    else {
792,876✔
1209
        switch (col_key.get_type()) {
792,876✔
1210
            case col_type_Int:
730,104✔
1211
                if (col_key.get_attrs().test(col_attr_Nullable)) {
730,104✔
1212
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
4,554✔
1213
                }
4,554✔
1214
                else {
725,550✔
1215
                    set(col_key, value.get_int(), is_default);
725,550✔
1216
                }
725,550✔
1217
                break;
730,104✔
1218
            case col_type_Bool:
108✔
1219
                set(col_key, value.get_bool(), is_default);
108✔
1220
                break;
108✔
1221
            case col_type_Float:
246✔
1222
                set(col_key, value.get_float(), is_default);
246✔
1223
                break;
246✔
1224
            case col_type_Double:
1,416✔
1225
                set(col_key, value.get_double(), is_default);
1,416✔
1226
                break;
1,416✔
1227
            case col_type_String:
36,510✔
1228
                set(col_key, value.get_string(), is_default);
36,510✔
1229
                break;
36,510✔
1230
            case col_type_Binary:
17,442✔
1231
                set(col_key, value.get<Binary>(), is_default);
17,442✔
1232
                break;
17,442✔
1233
            case col_type_Mixed:
288✔
1234
                set(col_key, value, is_default);
288✔
1235
                break;
288✔
1236
            case col_type_Timestamp:
5,307✔
1237
                set(col_key, value.get<Timestamp>(), is_default);
5,307✔
1238
                break;
5,307✔
1239
            case col_type_ObjectId:
1,134✔
1240
                set(col_key, value.get<ObjectId>(), is_default);
1,134✔
1241
                break;
1,134✔
1242
            case col_type_Decimal:
102✔
1243
                set(col_key, value.get<Decimal128>(), is_default);
102✔
1244
                break;
102✔
1245
            case col_type_UUID:
108✔
1246
                set(col_key, value.get<UUID>(), is_default);
108✔
1247
                break;
108✔
1248
            case col_type_Link:
72✔
1249
                set(col_key, value.get<ObjKey>(), is_default);
72✔
1250
                break;
72✔
UNCOV
1251
            case col_type_TypedLink:
✔
UNCOV
1252
                set(col_key, value.get<ObjLink>(), is_default);
×
UNCOV
1253
                break;
×
1254
            default:
✔
1255
                break;
×
1256
        }
793,020✔
1257
    }
793,020✔
1258
    return *this;
793,020✔
1259
}
793,020✔
1260

1261
template <>
1262
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1263
{
19,532,583✔
1264
    update_if_needed();
19,532,583✔
1265
    get_table()->check_column(col_key);
19,532,583✔
1266
    auto col_ndx = col_key.get_index();
19,532,583✔
1267

9,670,863✔
1268
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
19,532,583✔
1269
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1270
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1271

9,670,863✔
1272
    SearchIndex* index = m_table->get_search_index(col_key);
19,532,583✔
1273
    if (index && !m_key.is_unresolved()) {
19,532,583✔
1274
        index->set(m_key, value);
185,391✔
1275
    }
185,391✔
1276

9,670,863✔
1277
    Allocator& alloc = get_alloc();
19,532,583✔
1278
    alloc.bump_content_version();
19,532,583✔
1279
    Array fallback(alloc);
19,532,583✔
1280
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
19,532,583✔
1281
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
19,532,583✔
1282
    auto attr = col_key.get_attrs();
19,532,583✔
1283
    if (attr.test(col_attr_Nullable)) {
19,532,583✔
1284
        ArrayIntNull values(alloc);
2,528,904✔
1285
        values.set_parent(&fields, col_ndx.val + 1);
2,528,904✔
1286
        values.init_from_parent();
2,528,904✔
1287
        values.set(m_row_ndx, value);
2,528,904✔
1288
    }
2,528,904✔
1289
    else {
17,003,679✔
1290
        ArrayInteger values(alloc);
17,003,679✔
1291
        values.set_parent(&fields, col_ndx.val + 1);
17,003,679✔
1292
        values.init_from_parent();
17,003,679✔
1293
        values.set(m_row_ndx, value);
17,003,679✔
1294
    }
17,003,679✔
1295

9,670,863✔
1296
    sync(fields);
19,532,583✔
1297

9,670,863✔
1298
    if (Replication* repl = get_replication()) {
19,532,583✔
1299
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
10,280,007✔
1300
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
10,279,458✔
1301
    }
10,280,007✔
1302

9,670,863✔
1303
    return *this;
19,532,583✔
1304
}
19,532,583✔
1305

1306
Obj& Obj::add_int(ColKey col_key, int64_t value)
1307
{
18,141✔
1308
    update_if_needed();
18,141✔
1309
    get_table()->check_column(col_key);
18,141✔
1310
    auto col_ndx = col_key.get_index();
18,141✔
1311

8,973✔
1312
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
18,135✔
1313
        uint64_t ua = uint64_t(a);
18,129✔
1314
        uint64_t ub = uint64_t(b);
18,129✔
1315
        return int64_t(ua + ub);
18,129✔
1316
    };
18,129✔
1317

8,973✔
1318
    Allocator& alloc = get_alloc();
18,141✔
1319
    alloc.bump_content_version();
18,141✔
1320
    Array fallback(alloc);
18,141✔
1321
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
18,141✔
1322
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
18,141✔
1323

8,973✔
1324
    if (col_key.get_type() == col_type_Mixed) {
18,141✔
1325
        ArrayMixed values(alloc);
24✔
1326
        values.set_parent(&fields, col_ndx.val + 1);
24✔
1327
        values.init_from_parent();
24✔
1328
        Mixed old = values.get(m_row_ndx);
24✔
1329
        if (old.is_type(type_Int)) {
24✔
1330
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
18✔
1331
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
18✔
NEW
1332
                index->set(m_key, new_val);
×
UNCOV
1333
            }
×
1334
            values.set(m_row_ndx, Mixed(new_val));
18✔
1335
        }
18✔
1336
        else {
6✔
1337
            throw IllegalOperation("Value not an int");
6✔
1338
        }
6✔
1339
    }
18,117✔
1340
    else {
18,117✔
1341
        if (col_key.get_type() != col_type_Int)
18,117✔
1342
            throw IllegalOperation("Property not an int");
×
1343

8,961✔
1344
        auto attr = col_key.get_attrs();
18,117✔
1345
        if (attr.test(col_attr_Nullable)) {
18,117✔
1346
            ArrayIntNull values(alloc);
405✔
1347
            values.set_parent(&fields, col_ndx.val + 1);
405✔
1348
            values.init_from_parent();
405✔
1349
            util::Optional<int64_t> old = values.get(m_row_ndx);
405✔
1350
            if (old) {
405✔
1351
                auto new_val = add_wrap(*old, value);
399✔
1352
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
399✔
NEW
1353
                    index->set(m_key, new_val);
×
UNCOV
1354
                }
×
1355
                values.set(m_row_ndx, new_val);
399✔
1356
            }
399✔
1357
            else {
6✔
1358
                throw IllegalOperation("No prior value");
6✔
1359
            }
6✔
1360
        }
17,712✔
1361
        else {
17,712✔
1362
            ArrayInteger values(alloc);
17,712✔
1363
            values.set_parent(&fields, col_ndx.val + 1);
17,712✔
1364
            values.init_from_parent();
17,712✔
1365
            int64_t old = values.get(m_row_ndx);
17,712✔
1366
            auto new_val = add_wrap(old, value);
17,712✔
1367
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
17,712✔
1368
                index->set(m_key, new_val);
6✔
1369
            }
6✔
1370
            values.set(m_row_ndx, new_val);
17,712✔
1371
        }
17,712✔
1372
    }
18,117✔
1373

8,973✔
1374
    sync(fields);
18,135✔
1375

8,967✔
1376
    if (Replication* repl = get_replication()) {
18,129✔
1377
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
10,743✔
1378
    }
10,743✔
1379

8,967✔
1380
    return *this;
18,129✔
1381
}
18,141✔
1382

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

127,350✔
1406
    if (target_key != old_key) {
254,751✔
1407
        CascadeState state(CascadeState::Mode::Strong);
253,854✔
1408

126,903✔
1409
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
253,854✔
1410
        _update_if_needed();
253,854✔
1411

126,903✔
1412
        Allocator& alloc = get_alloc();
253,854✔
1413
        alloc.bump_content_version();
253,854✔
1414
        Array fallback(alloc);
253,854✔
1415
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
253,854✔
1416
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
253,854✔
1417
        ArrayKey values(alloc);
253,854✔
1418
        values.set_parent(&fields, col_ndx.val + 1);
253,854✔
1419
        values.init_from_parent();
253,854✔
1420

126,903✔
1421
        values.set(m_row_ndx, target_key);
253,854✔
1422

126,903✔
1423
        sync(fields);
253,854✔
1424

126,903✔
1425
        if (Replication* repl = get_replication()) {
253,854✔
1426
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
31,962✔
1427
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
31,962✔
1428
        }
31,962✔
1429

126,903✔
1430
        if (recurse)
253,854✔
1431
            target_table->remove_recursive(state);
258✔
1432
    }
253,854✔
1433

127,350✔
1434
    return *this;
254,751✔
1435
}
254,751✔
1436

1437
template <>
1438
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
UNCOV
1439
{
×
UNCOV
1440
    update_if_needed();
×
UNCOV
1441
    get_table()->check_column(col_key);
×
UNCOV
1442
    ColKey::Idx col_ndx = col_key.get_index();
×
UNCOV
1443
    ColumnType type = col_key.get_type();
×
UNCOV
1444
    if (type != col_type_TypedLink)
×
1445
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
×
UNCOV
1446
    m_table->get_parent_group()->validate(target_link);
×
1447

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

UNCOV
1450
    if (target_link != old_link) {
×
UNCOV
1451
        CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All
×
UNCOV
1452
                                                                  : CascadeState::Mode::Strong);
×
1453

UNCOV
1454
        bool recurse = replace_backlink(col_key, old_link, target_link, state);
×
UNCOV
1455
        _update_if_needed();
×
1456

UNCOV
1457
        Allocator& alloc = get_alloc();
×
UNCOV
1458
        alloc.bump_content_version();
×
UNCOV
1459
        Array fallback(alloc);
×
UNCOV
1460
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
×
UNCOV
1461
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
UNCOV
1462
        ArrayTypedLink values(alloc);
×
UNCOV
1463
        values.set_parent(&fields, col_ndx.val + 1);
×
UNCOV
1464
        values.init_from_parent();
×
1465

UNCOV
1466
        values.set(m_row_ndx, target_link);
×
1467

UNCOV
1468
        sync(fields);
×
1469

UNCOV
1470
        if (Replication* repl = get_replication()) {
×
UNCOV
1471
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
×
UNCOV
1472
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
×
UNCOV
1473
        }
×
1474

UNCOV
1475
        if (recurse)
×
1476
            const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
UNCOV
1477
    }
×
1478

UNCOV
1479
    return *this;
×
UNCOV
1480
}
×
1481

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

6,372✔
1513
    if (target_key != old_key) {
12,792✔
1514
        CascadeState state;
12,792✔
1515

6,372✔
1516
        bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
12,792✔
1517
        _update_if_needed();
12,792✔
1518

6,372✔
1519
        Allocator& alloc = get_alloc();
12,792✔
1520
        alloc.bump_content_version();
12,792✔
1521
        Array fallback(alloc);
12,792✔
1522
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
12,792✔
1523
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
12,792✔
1524
        ArrayKey values(alloc);
12,792✔
1525
        values.set_parent(&fields, col_ndx.val + 1);
12,792✔
1526
        values.init_from_parent();
12,792✔
1527

6,372✔
1528
        values.set(m_row_ndx, target_key);
12,792✔
1529

6,372✔
1530
        sync(fields);
12,792✔
1531

6,372✔
1532
        if (Replication* repl = get_replication()) {
12,792✔
1533
            repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
12,144✔
1534
                      is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
12,144✔
1535
        }
12,144✔
1536

6,372✔
1537
        if (recurse)
12,792✔
1538
            target_table->remove_recursive(state);
48✔
1539
    }
12,792✔
1540

6,372✔
1541
    return result;
12,792✔
1542
}
12,792✔
1543

1544
namespace {
1545
template <class T>
1546
inline void check_range(const T&)
1547
{
2,137,497✔
1548
}
2,137,497✔
1549
template <>
1550
inline void check_range(const StringData& val)
1551
{
2,483,163✔
1552
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
2,483,163✔
1553
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
1,231,803✔
1554
}
2,483,163✔
1555
template <>
1556
inline void check_range(const BinaryData& val)
1557
{
5,120,073✔
1558
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
5,120,073✔
1559
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
2,558,889✔
1560
}
5,120,073✔
1561
} // namespace
1562

1563
// helper functions for filtering out calls to set_spec()
1564
template <class T>
1565
inline void Obj::set_spec(T&, ColKey)
1566
{
7,259,337✔
1567
}
7,259,337✔
1568
template <>
1569
inline void Obj::set_spec<ArrayString>(ArrayString& values, ColKey col_key)
1570
{
2,483,121✔
1571
    size_t spec_ndx = m_table->colkey2spec_ndx(col_key);
2,483,121✔
1572
    Spec* spec = const_cast<Spec*>(&get_spec());
2,483,121✔
1573
    values.set_spec(spec, spec_ndx);
2,483,121✔
1574
}
2,483,121✔
1575

1576
#if REALM_ENABLE_GEOSPATIAL
1577

1578
template <>
1579
Obj& Obj::set(ColKey col_key, Geospatial value, bool)
1580
{
246✔
1581
    update_if_needed();
246✔
1582
    get_table()->check_column(col_key);
246✔
1583
    auto type = col_key.get_type();
246✔
1584

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

120✔
1590
    Obj geo = get_linked_object(col_key);
240✔
1591
    if (!geo) {
240✔
1592
        geo = create_and_set_linked_object(col_key);
216✔
1593
    }
216✔
1594
    value.assign_to(geo);
240✔
1595
    return *this;
240✔
1596
}
240✔
1597

1598
template <>
1599
Obj& Obj::set(ColKey col_key, std::optional<Geospatial> value, bool)
1600
{
12✔
1601
    update_if_needed();
12✔
1602
    auto table = get_table();
12✔
1603
    table->check_column(col_key);
12✔
1604
    auto type = col_key.get_type();
12✔
1605
    auto attrs = col_key.get_attrs();
12✔
1606

6✔
1607
    if (type != ColumnTypeTraits<Link>::column_id)
12✔
1608
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1609
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1610
                                           get_table()->get_column_name(col_key)));
6✔
1611
    if (!value && !attrs.test(col_attr_Nullable))
6✔
1612
        throw NotNullable(Group::table_name_to_class_name(table->get_name()), table->get_column_name(col_key));
×
1613

3✔
1614
    if (!value) {
6✔
1615
        set_null(col_key);
6✔
1616
    }
6✔
1617
    else {
×
1618
        Obj geo = get_linked_object(col_key);
×
1619
        if (!geo) {
×
1620
            geo = create_and_set_linked_object(col_key);
×
1621
        }
×
1622
        value->assign_to(geo);
×
1623
    }
×
1624
    return *this;
6✔
1625
}
6✔
1626

1627
#endif
1628

1629
template <class T>
1630
Obj& Obj::set(ColKey col_key, T value, bool is_default)
1631
{
9,740,982✔
1632
    update_if_needed();
9,740,982✔
1633
    get_table()->check_column(col_key);
9,740,982✔
1634
    auto type = col_key.get_type();
9,740,982✔
1635
    auto attrs = col_key.get_attrs();
9,740,982✔
1636
    auto col_ndx = col_key.get_index();
9,740,982✔
1637

4,859,064✔
1638
    if (type != ColumnTypeTraits<T>::column_id)
9,740,982✔
1639
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1640
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1641
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
9,740,982!
1642
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
6✔
1643

4,859,061✔
1644
    check_range(value);
9,740,976✔
1645

4,859,061✔
1646
    SearchIndex* index = m_table->get_search_index(col_key);
9,740,976✔
1647
    if (index && !m_key.is_unresolved()) {
9,740,976!
1648
        index->set(m_key, value);
472,389✔
1649
    }
472,389✔
1650

4,859,061✔
1651
    Allocator& alloc = get_alloc();
9,740,976✔
1652
    alloc.bump_content_version();
9,740,976✔
1653
    Array fallback(alloc);
9,740,976✔
1654
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,740,976✔
1655
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,740,976✔
1656
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
9,740,976✔
1657
    LeafType values(alloc);
9,740,976✔
1658
    values.set_parent(&fields, col_ndx.val + 1);
9,740,976✔
1659
    set_spec<LeafType>(values, col_key);
9,740,976✔
1660
    values.init_from_parent();
9,740,976✔
1661
    values.set(m_row_ndx, value);
9,740,976✔
1662

4,859,061✔
1663
    sync(fields);
9,740,976✔
1664

4,859,061✔
1665
    if (Replication* repl = get_replication())
9,740,976✔
1666
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
6,033,441✔
1667
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
6,033,117✔
1668

4,859,061✔
1669
    return *this;
9,740,976✔
1670
}
9,740,976✔
1671

1672
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
1673
INSTANTIATE_OBJ_SET(bool);
1674
INSTANTIATE_OBJ_SET(StringData);
1675
INSTANTIATE_OBJ_SET(float);
1676
INSTANTIATE_OBJ_SET(double);
1677
INSTANTIATE_OBJ_SET(Decimal128);
1678
INSTANTIATE_OBJ_SET(Timestamp);
1679
INSTANTIATE_OBJ_SET(BinaryData);
1680
INSTANTIATE_OBJ_SET(ObjectId);
1681
INSTANTIATE_OBJ_SET(UUID);
1682

1683
void Obj::set_int(ColKey::Idx col_ndx, int64_t value)
1684
{
498,984✔
1685
    update_if_needed();
498,984✔
1686

251,220✔
1687
    Allocator& alloc = get_alloc();
498,984✔
1688
    alloc.bump_content_version();
498,984✔
1689
    Array fallback(alloc);
498,984✔
1690
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
498,984✔
1691
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
498,984✔
1692
    Array values(alloc);
498,984✔
1693
    values.set_parent(&fields, col_ndx.val + 1);
498,984✔
1694
    values.init_from_parent();
498,984✔
1695
    values.set(m_row_ndx, value);
498,984✔
1696

251,220✔
1697
    sync(fields);
498,984✔
1698
}
498,984✔
1699

1700
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
1701
{
486✔
1702
    update_if_needed();
486✔
1703

243✔
1704
    Allocator& alloc = get_alloc();
486✔
1705
    alloc.bump_content_version();
486✔
1706
    Array fallback(alloc);
486✔
1707
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
486✔
1708
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
486✔
1709
    ArrayMixed values(alloc);
486✔
1710
    values.set_parent(&fields, col_ndx.val + 1);
486✔
1711
    values.init_from_parent();
486✔
1712
    values.set(m_row_ndx, Mixed(value, type));
486✔
1713

243✔
1714
    sync(fields);
486✔
1715
}
486✔
1716

1717
void Obj::add_backlink(ColKey backlink_col_key, ObjKey origin_key)
1718
{
6,729,912✔
1719
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
6,729,912✔
1720
    Allocator& alloc = get_alloc();
6,729,912✔
1721
    alloc.bump_content_version();
6,729,912✔
1722
    Array fallback(alloc);
6,729,912✔
1723
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
6,729,912✔
1724

3,364,836✔
1725
    ArrayBacklink backlinks(alloc);
6,729,912✔
1726
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
6,729,912✔
1727
    backlinks.init_from_parent();
6,729,912✔
1728

3,364,836✔
1729
    backlinks.add(m_row_ndx, origin_key);
6,729,912✔
1730

3,364,836✔
1731
    sync(fields);
6,729,912✔
1732
}
6,729,912✔
1733

1734
bool Obj::remove_one_backlink(ColKey backlink_col_key, ObjKey origin_key)
1735
{
204,147✔
1736
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
204,147✔
1737
    Allocator& alloc = get_alloc();
204,147✔
1738
    alloc.bump_content_version();
204,147✔
1739
    Array fallback(alloc);
204,147✔
1740
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
204,147✔
1741

102,057✔
1742
    ArrayBacklink backlinks(alloc);
204,147✔
1743
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
204,147✔
1744
    backlinks.init_from_parent();
204,147✔
1745

102,057✔
1746
    bool ret = backlinks.remove(m_row_ndx, origin_key);
204,147✔
1747

102,057✔
1748
    sync(fields);
204,147✔
1749

102,057✔
1750
    return ret;
204,147✔
1751
}
204,147✔
1752

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

444✔
1769
    if (Replication* repl = get_replication())
891✔
1770
        repl->nullify_link(m_table.unchecked_ptr(), col,
759✔
1771
                           m_key); // Throws
759✔
1772
}
891✔
1773

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

3✔
1792
        if (Replication* repl = get_replication())
6✔
NEW
1793
            repl->nullify_link(m_table.unchecked_ptr(), col,
×
NEW
1794
                               m_key); // Throws
×
1795
    }
6✔
1796
    else if (val.is_type(type_Dictionary)) {
6✔
1797
        Dictionary dict(*this, col);
6✔
1798
        result = dict.nullify(target.get_link());
6✔
1799
    }
6✔
NEW
1800
    else if (val.is_type(type_List)) {
×
NEW
1801
        Lst<Mixed> list(*this, col);
×
NEW
1802
        result = list.nullify(target.get_link());
×
NEW
1803
    }
×
1804
    REALM_ASSERT(result);
12✔
1805
    static_cast<void>(result);
12✔
1806
}
12✔
1807

1808
void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) &&
1809
{
5,253✔
1810
    REALM_ASSERT(get_alloc().get_storage_version() == m_storage_version);
5,253✔
1811

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

2,625✔
1847
    private:
5,253✔
1848
        ObjLink m_target_link;
5,253✔
1849
    } nullifier{*this, origin_col_key, target_link};
5,253✔
1850

2,625✔
1851
    nullifier.run();
5,253✔
1852

2,625✔
1853
    get_alloc().bump_content_version();
5,253✔
1854
}
5,253✔
1855

1856

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

1908
private:
1909
    Obj m_dest_orig;
1910
    Obj m_dest_replace;
1911
};
1912

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

1935
LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const
1936
{
313,149✔
1937
    auto list = CollectionParent::get_listbase_ptr(col_key);
313,149✔
1938
    list->set_owner(*this, col_key);
313,149✔
1939
    return list;
313,149✔
1940
}
313,149✔
1941

1942
SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const
1943
{
42,753✔
1944
    auto set = CollectionParent::get_setbase_ptr(col_key);
42,753✔
1945
    set->set_owner(*this, col_key);
42,753✔
1946
    return set;
42,753✔
1947
}
42,753✔
1948

1949
Dictionary Obj::get_dictionary(ColKey col_key) const
1950
{
84,891✔
1951
    REALM_ASSERT(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed);
84,891✔
1952
    update_if_needed();
84,891✔
1953
    return Dictionary(Obj(*this), col_key);
84,891✔
1954
}
84,891✔
1955

1956
Obj& Obj::set_collection(ColKey col_key, CollectionType type)
1957
{
300✔
1958
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
300✔
1959
    update_if_needed();
300✔
1960
    Mixed new_val(0, type);
300✔
1961

150✔
1962
    ArrayMixed arr(_get_alloc());
300✔
1963
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
300✔
1964
    arr.init_from_ref(ref);
300✔
1965
    auto old_val = arr.get(m_row_ndx);
300✔
1966

150✔
1967
    if (old_val != new_val) {
300✔
1968
        CascadeState state;
288✔
1969
        if (old_val.is_type(type_TypedLink)) {
288✔
NEW
1970
            remove_backlink(col_key, old_val.get<ObjLink>(), state);
×
NEW
1971
        }
×
1972
        else if (old_val.is_type(type_Dictionary)) {
288✔
1973
            Dictionary dict(*this, col_key);
6✔
1974
            dict.remove_backlinks(state);
6✔
1975
        }
6✔
1976
        else if (old_val.is_type(type_List)) {
282✔
1977
            Lst<Mixed> list(*this, col_key);
6✔
1978
            list.remove_backlinks(state);
6✔
1979
        }
6✔
1980

144✔
1981
        Allocator& alloc = _get_alloc();
288✔
1982
        alloc.bump_content_version();
288✔
1983

144✔
1984
        Array fallback(alloc);
288✔
1985
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
288✔
1986
        ArrayMixed values(alloc);
288✔
1987
        values.set_parent(&fields, col_key.get_index().val + 1);
288✔
1988
        values.init_from_parent();
288✔
1989

144✔
1990
        values.set(m_row_ndx, new_val);
288✔
1991
        values.set_key(m_row_ndx, generate_key(0x10));
288✔
1992

144✔
1993
        sync(fields);
288✔
1994

144✔
1995
        if (Replication* repl = get_replication())
288✔
1996
            repl->set(m_table.unchecked_ptr(), col_key, m_key, new_val); // Throws
234✔
1997
    }
288✔
1998

150✔
1999
    return *this;
300✔
2000
}
300✔
2001

2002
DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const
2003
{
318✔
2004
    return std::make_shared<Dictionary>(get_dictionary(col_key));
318✔
2005
}
318✔
2006

2007
DictionaryPtr Obj::get_dictionary_ptr(const Path& path) const
NEW
2008
{
×
NEW
2009
    return std::dynamic_pointer_cast<Dictionary>(get_collection_ptr(path));
×
UNCOV
2010
}
×
2011

2012
Dictionary Obj::get_dictionary(StringData col_name) const
2013
{
16,062✔
2014
    return get_dictionary(get_column_key(col_name));
16,062✔
2015
}
16,062✔
2016

2017
CollectionPtr Obj::get_collection_ptr(const Path& path) const
2018
{
11,148✔
2019
    REALM_ASSERT(path.size() > 0);
11,148✔
2020
    // First element in path must be column name
5,574✔
2021
    auto col_key = path[0].is_col_key() ? path[0].get_col_key() : m_table->get_column_key(path[0].get_key());
11,094✔
2022
    REALM_ASSERT(col_key);
11,148✔
2023
    size_t level = 1;
11,148✔
2024
    CollectionBasePtr collection = get_collection_ptr(col_key);
11,148✔
2025

5,574✔
2026
    while (level < path.size()) {
11,280✔
2027
        auto& path_elem = path[level];
132✔
2028
        Mixed ref;
132✔
2029
        if (collection->get_collection_type() == CollectionType::List) {
132✔
2030
            ref = collection->get_any(path_elem.get_ndx());
90✔
2031
        }
90✔
2032
        else {
42✔
2033
            ref = dynamic_cast<Dictionary*>(collection.get())->get(path_elem.get_key());
42✔
2034
        }
42✔
2035
        if (ref.is_type(type_List)) {
132✔
2036
            collection = collection->get_list(path_elem);
108✔
2037
        }
108✔
2038
        else if (ref.is_type(type_Set)) {
24✔
NEW
2039
            collection = collection->get_set(path_elem);
×
NEW
2040
        }
×
2041
        else if (ref.is_type(type_Dictionary)) {
24✔
2042
            collection = collection->get_dictionary(path_elem);
24✔
2043
        }
24✔
NEW
2044
        else {
×
NEW
2045
            throw InvalidArgument("Wrong path");
×
NEW
2046
        }
×
2047
        level++;
132✔
2048
    }
132✔
2049

5,574✔
2050
    return collection;
11,148✔
2051
}
11,148✔
2052

2053
CollectionPtr Obj::get_collection_by_stable_path(const StablePath& path) const
2054
{
9,960✔
2055
    // First element in path is phony column key
4,980✔
2056
    ColKey col_key = m_table->get_column_key(path[0]);
9,960✔
2057
    size_t level = 1;
9,960✔
2058
    CollectionBasePtr collection = get_collection_ptr(col_key);
9,960✔
2059

4,980✔
2060
    while (level < path.size()) {
10,212✔
2061
        auto& index = path[level];
252✔
2062
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
252✔
2063
            Mixed ref;
252✔
2064
            PathElement path_elem;
252✔
2065
            if (collection->get_collection_type() == CollectionType::List) {
252✔
2066
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
144✔
2067
                size_t ndx = list_of_mixed->find_index(index);
144✔
2068
                if (ndx != realm::not_found) {
144✔
2069
                    ref = list_of_mixed->get(ndx);
144✔
2070
                    path_elem = ndx;
144✔
2071
                }
144✔
2072
            }
144✔
2073
            else {
108✔
2074
                auto dict = dynamic_cast<Dictionary*>(collection.get());
108✔
2075
                size_t ndx = dict->find_index(index);
108✔
2076
                if (ndx != realm::not_found) {
108✔
2077
                    ref = dict->get_any(ndx);
108✔
2078
                    path_elem = dict->get_key(ndx).get_string();
108✔
2079
                }
108✔
2080
            }
108✔
2081
            return {ref, path_elem};
252✔
2082
        };
252✔
2083
        auto [ref, path_elem] = get_ref();
252✔
2084
        if (ref.is_type(type_List)) {
252✔
2085
            collection = collection->get_list(path_elem);
234✔
2086
        }
234✔
2087
        else if (ref.is_type(type_Set)) {
18✔
NEW
2088
            collection = collection->get_set(path_elem);
×
NEW
2089
        }
×
2090
        else if (ref.is_type(type_Dictionary)) {
18✔
2091
            collection = collection->get_dictionary(path_elem);
18✔
2092
        }
18✔
NEW
2093
        else {
×
NEW
2094
            return nullptr;
×
NEW
2095
        }
×
2096
        level++;
252✔
2097
    }
252✔
2098

4,980✔
2099
    return collection;
9,960✔
2100
}
9,960✔
2101

2102
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2103
{
127,935✔
2104
    if (col_key.is_collection()) {
127,935✔
2105
        auto collection = CollectionParent::get_collection_ptr(col_key);
127,317✔
2106
        collection->set_owner(*this, col_key);
127,317✔
2107
        return collection;
127,317✔
2108
    }
127,317✔
2109
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
618✔
2110
    auto val = get<Mixed>(col_key);
618✔
2111
    if (val.is_type(type_List)) {
618✔
2112
        return std::make_shared<Lst<Mixed>>(*this, col_key);
276✔
2113
    }
276✔
2114
    else if (val.is_type(type_Set)) {
342✔
2115
        return std::make_shared<Set<Mixed>>(*this, col_key);
24✔
2116
    }
24✔
2117
    REALM_ASSERT(val.is_type(type_Dictionary));
318✔
2118
    return std::make_shared<Dictionary>(*this, col_key);
318✔
2119
}
318✔
2120

2121
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
2122
{
396✔
2123
    return get_collection_ptr(get_column_key(col_name));
396✔
2124
}
396✔
2125

2126
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
2127
{
3,114✔
2128
    if (col_key.is_list()) {
3,114✔
2129
        return get_linklist_ptr(col_key);
2,994✔
2130
    }
2,994✔
2131
    else if (col_key.is_set()) {
120✔
2132
        return get_linkset_ptr(col_key);
78✔
2133
    }
78✔
2134
    else if (col_key.is_dictionary()) {
42✔
2135
        auto dict = get_dictionary(col_key);
42✔
2136
        return std::make_unique<DictionaryLinkValues>(dict);
42✔
2137
    }
42✔
2138
    return {};
×
2139
}
×
2140

2141
template <class T>
2142
inline void replace_in_linklist(Obj& obj, ColKey origin_col_key, T target, T replacement)
2143
{
9,342✔
2144
    Lst<T> link_list(origin_col_key);
9,342✔
2145
    size_t ndx = find_link_value_in_collection(link_list, obj, origin_col_key, target);
9,342✔
2146

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

4,671✔
2149
    link_list.set(ndx, replacement);
9,342✔
2150
}
9,342✔
2151

2152
template <class T>
2153
inline void replace_in_linkset(Obj& obj, ColKey origin_col_key, T target, T replacement)
2154
{
144✔
2155
    Set<T> link_set(origin_col_key);
144✔
2156
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
144✔
2157

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

72✔
2160
    link_set.erase(target);
144✔
2161
    link_set.insert(replacement);
144✔
2162
}
144✔
2163

2164
inline void replace_in_dictionary(Obj& obj, ColKey origin_col_key, Mixed target, Mixed replacement)
NEW
2165
{
×
NEW
2166
    Dictionary dict(origin_col_key);
×
NEW
2167
    size_t ndx = find_link_value_in_collection(dict, obj, origin_col_key, target);
×
NEW
2168

×
NEW
2169
    REALM_ASSERT(ndx != realm::npos); // There has to be one
×
NEW
2170

×
NEW
2171
    auto key = dict.get_key(ndx);
×
NEW
2172
    dict.insert(key, replacement);
×
NEW
2173
}
×
2174

2175

2176
void Obj::assign_pk_and_backlinks(const Obj& other)
2177
{
13,722✔
2178
    struct LinkReplacer : LinkTranslator {
13,722✔
2179
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
13,722✔
2180
            : LinkTranslator(origin, origin_col_key)
13,722✔
2181
            , m_dest_orig(dest_orig)
13,722✔
2182
            , m_dest_replace(dest_replace)
13,722✔
2183
        {
11,844✔
2184
        }
9,876✔
2185
        void on_list_of_links(LnkLst&) final
13,722✔
2186
        {
11,577✔
2187
            replace_in_linklist(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
9,342✔
2188
        }
9,342✔
2189
        void on_list_of_mixed(Lst<Mixed>& list) final
13,722✔
2190
        {
6,927✔
2191
            list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
42✔
2192
        }
42✔
2193
        void on_set_of_links(LnkSet&) final
13,722✔
2194
        {
6,939✔
2195
            replace_in_linkset(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
66✔
2196
        }
66✔
2197
        void on_set_of_mixed(Set<Mixed>&) final
13,722✔
2198
        {
6,945✔
2199
            replace_in_linkset<Mixed>(m_origin_obj, m_origin_col_key, m_dest_orig.get_link(),
78✔
2200
                                      m_dest_replace.get_link());
78✔
2201
        }
78✔
2202
        void on_dictionary(Dictionary& dict) final
13,722✔
2203
        {
6,960✔
2204
            dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
108✔
2205
        }
108✔
2206
        void on_link_property(ColKey col) final
13,722✔
2207
        {
6,996✔
2208
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
180✔
2209
            m_origin_obj.set(col, m_dest_replace.get_key());
180✔
2210
        }
180✔
2211
        void on_mixed_property(ColKey col) final
13,722✔
2212
        {
6,936✔
2213
            auto val = m_origin_obj.get_any(col);
60✔
2214
            if (val.is_type(type_TypedLink)) {
60✔
2215
                REALM_ASSERT(val.get_link() == m_dest_orig.get_link());
36✔
2216
                m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
36✔
2217
            }
36✔
2218
            else if (val.is_type(type_Dictionary)) {
24✔
2219
                Dictionary dict(m_origin_obj, m_origin_col_key);
12✔
2220
                dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2221
            }
12✔
2222
            else if (val.is_type(type_List)) {
12✔
2223
                Lst<Mixed> list(m_origin_obj, m_origin_col_key);
12✔
2224
                list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2225
            }
12✔
NEW
2226
            else {
×
NEW
2227
                REALM_UNREACHABLE();
×
NEW
2228
            }
×
2229
        }
60✔
2230

6,906✔
2231
    private:
13,722✔
2232
        const Obj& m_dest_orig;
13,722✔
2233
        const Obj& m_dest_replace;
13,722✔
2234
    };
13,722✔
2235

6,906✔
2236
    REALM_ASSERT(get_table() == other.get_table());
13,722✔
2237
    if (auto col_pk = m_table->get_primary_key_column()) {
13,722✔
2238
        Mixed val = other.get_any(col_pk);
13,560✔
2239
        this->set_any(col_pk, val);
13,560✔
2240
    }
13,560✔
2241
    auto nb_tombstones = m_table->m_tombstones->size();
13,722✔
2242

6,906✔
2243
    auto copy_links = [this, &other, nb_tombstones](ColKey col) {
11,889✔
2244
        if (nb_tombstones != m_table->m_tombstones->size()) {
9,966✔
2245
            // Object has been deleted - we are done
6✔
2246
            return IteratorControl::Stop;
12✔
2247
        }
12✔
2248
        auto t = m_table->get_opposite_table(col);
9,954✔
2249
        auto c = m_table->get_opposite_column(col);
9,954✔
2250
        auto backlinks = other.get_all_backlinks(col);
9,954✔
2251
        for (auto bl : backlinks) {
9,915✔
2252
            auto linking_obj = t->get_object(bl);
9,876✔
2253
            LinkReplacer replacer{linking_obj, c, other, *this};
9,876✔
2254
            replacer.run();
9,876✔
2255
        }
9,876✔
2256
        return IteratorControl::AdvanceToNext;
9,954✔
2257
    };
9,954✔
2258
    m_table->for_each_backlink_column(copy_links);
13,722✔
2259
}
13,722✔
2260

2261
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
2262
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
2263
template float Obj::get<float>(ColKey col_key) const;
2264
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
2265
template double Obj::get<double>(ColKey col_key) const;
2266
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2267
template StringData Obj::get<StringData>(ColKey col_key) const;
2268
template BinaryData Obj::get<BinaryData>(ColKey col_key) const;
2269
template Timestamp Obj::get<Timestamp>(ColKey col_key) const;
2270
template ObjectId Obj::get<ObjectId>(ColKey col_key) const;
2271
template util::Optional<ObjectId> Obj::get<util::Optional<ObjectId>>(ColKey col_key) const;
2272
template ObjKey Obj::get<ObjKey>(ColKey col_key) const;
2273
template Decimal128 Obj::get<Decimal128>(ColKey col_key) const;
2274
template ObjLink Obj::get<ObjLink>(ColKey col_key) const;
2275
template Mixed Obj::get<Mixed>(realm::ColKey) const;
2276
template UUID Obj::get<UUID>(realm::ColKey) const;
2277
template util::Optional<UUID> Obj::get<util::Optional<UUID>>(ColKey col_key) const;
2278

2279
template <class T>
2280
inline void Obj::do_set_null(ColKey col_key)
2281
{
40,392✔
2282
    ColKey::Idx col_ndx = col_key.get_index();
40,392✔
2283
    Allocator& alloc = get_alloc();
40,392✔
2284
    alloc.bump_content_version();
40,392✔
2285
    Array fallback(alloc);
40,392✔
2286
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
40,392✔
2287

20,346✔
2288
    T values(alloc);
40,392✔
2289
    values.set_parent(&fields, col_ndx.val + 1);
40,392✔
2290
    values.init_from_parent();
40,392✔
2291
    values.set_null(m_row_ndx);
40,392✔
2292

20,346✔
2293
    sync(fields);
40,392✔
2294
}
40,392✔
2295

2296
template <>
2297
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2298
{
3,222✔
2299
    ColKey::Idx col_ndx = col_key.get_index();
3,222✔
2300
    size_t spec_ndx = m_table->leaf_ndx2spec_ndx(col_ndx);
3,222✔
2301
    Allocator& alloc = get_alloc();
3,222✔
2302
    alloc.bump_content_version();
3,222✔
2303
    Array fallback(alloc);
3,222✔
2304
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
3,222✔
2305

1,641✔
2306
    ArrayString values(alloc);
3,222✔
2307
    values.set_parent(&fields, col_ndx.val + 1);
3,222✔
2308
    values.set_spec(const_cast<Spec*>(&get_spec()), spec_ndx);
3,222✔
2309
    values.init_from_parent();
3,222✔
2310
    values.set_null(m_row_ndx);
3,222✔
2311

1,641✔
2312
    sync(fields);
3,222✔
2313
}
3,222✔
2314

2315
Obj& Obj::set_null(ColKey col_key, bool is_default)
2316
{
44,208✔
2317
    ColumnType col_type = col_key.get_type();
44,208✔
2318
    // Links need special handling
22,284✔
2319
    if (col_type == col_type_Link) {
44,208✔
2320
        set(col_key, null_key);
462✔
2321
    }
462✔
2322
    else if (col_type == col_type_Mixed) {
43,746✔
2323
        set(col_key, Mixed{});
66✔
2324
    }
66✔
2325
    else {
43,680✔
2326
        auto attrs = col_key.get_attrs();
43,680✔
2327
        if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
43,680✔
2328
            throw NotNullable(Group::table_name_to_class_name(m_table->get_name()),
66✔
2329
                              m_table->get_column_name(col_key));
66✔
2330
        }
66✔
2331

21,987✔
2332
        update_if_needed();
43,614✔
2333

21,987✔
2334
        SearchIndex* index = m_table->get_search_index(col_key);
43,614✔
2335
        if (index && !m_key.is_unresolved()) {
43,614✔
2336
            index->set(m_key, null{});
4,620✔
2337
        }
4,620✔
2338

21,987✔
2339
        switch (col_type) {
43,614✔
2340
            case col_type_Int:
6,555✔
2341
                do_set_null<ArrayIntNull>(col_key);
6,555✔
2342
                break;
6,555✔
2343
            case col_type_Bool:
5,838✔
2344
                do_set_null<ArrayBoolNull>(col_key);
5,838✔
2345
                break;
5,838✔
2346
            case col_type_Float:
6,363✔
2347
                do_set_null<ArrayFloatNull>(col_key);
6,363✔
2348
                break;
6,363✔
2349
            case col_type_Double:
6,375✔
2350
                do_set_null<ArrayDoubleNull>(col_key);
6,375✔
2351
                break;
6,375✔
2352
            case col_type_ObjectId:
3,363✔
2353
                do_set_null<ArrayObjectIdNull>(col_key);
3,363✔
2354
                break;
3,363✔
2355
            case col_type_String:
3,222✔
2356
                do_set_null<ArrayString>(col_key);
3,222✔
2357
                break;
3,222✔
2358
            case col_type_Binary:
1,248✔
2359
                do_set_null<ArrayBinary>(col_key);
1,248✔
2360
                break;
1,248✔
2361
            case col_type_Timestamp:
2,460✔
2362
                do_set_null<ArrayTimestamp>(col_key);
2,460✔
2363
                break;
2,460✔
2364
            case col_type_Decimal:
1,830✔
2365
                do_set_null<ArrayDecimal128>(col_key);
1,830✔
2366
                break;
1,830✔
2367
            case col_type_UUID:
6,360✔
2368
                do_set_null<ArrayUUIDNull>(col_key);
6,360✔
2369
                break;
6,360✔
2370
            case col_type_Mixed:
✔
2371
            case col_type_Link:
✔
2372
            case col_type_LinkList:
✔
2373
            case col_type_BackLink:
✔
2374
            case col_type_TypedLink:
✔
2375
                REALM_UNREACHABLE();
×
2376
        }
43,614✔
2377
    }
43,614✔
2378

22,284✔
2379
    if (Replication* repl = get_replication())
44,175✔
2380
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
23,664✔
2381
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
23,529✔
2382

22,251✔
2383
    return *this;
44,142✔
2384
}
44,208✔
2385

2386

2387
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2388
{
8,579,526✔
2389
    return get_table()->spec_ndx2colkey(col_ndx);
8,579,526✔
2390
}
8,579,526✔
2391

2392
size_t Obj::colkey2spec_ndx(ColKey key)
2393
{
777✔
2394
    return get_table()->colkey2spec_ndx(key);
777✔
2395
}
777✔
2396

2397
ColKey Obj::get_primary_key_column() const
2398
{
5,264,781✔
2399
    return m_table->get_primary_key_column();
5,264,781✔
2400
}
5,264,781✔
2401

2402
ref_type Obj::Internal::get_ref(const Obj& obj, ColKey col_key)
2403
{
12,774✔
2404
    return to_ref(obj._get<int64_t>(col_key.get_index()));
12,774✔
2405
}
12,774✔
2406

2407
ref_type Obj::get_collection_ref(Index index, CollectionType type) const
2408
{
3,102,291✔
2409
    if (index.is_collection()) {
3,102,291✔
2410
        return to_ref(_get<int64_t>(index.get_index()));
3,099,630✔
2411
    }
3,099,630✔
2412
    if (check_index(index)) {
2,661✔
2413
        auto val = _get<Mixed>(index.get_index());
2,364✔
2414
        if (val.is_type(DataType(int(type)))) {
2,364✔
2415
            return val.get_ref();
2,358✔
2416
        }
2,358✔
2417
        throw realm::IllegalOperation(util::format("Not a %1", type));
6✔
2418
    }
6✔
2419
    throw StaleAccessor("This collection is no more");
297✔
2420
}
297✔
2421

2422
bool Obj::check_collection_ref(Index index, CollectionType type) const noexcept
2423
{
789,411✔
2424
    if (index.is_collection()) {
789,411✔
2425
        return true;
788,595✔
2426
    }
788,595✔
2427
    if (check_index(index)) {
816✔
2428
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
810✔
2429
    }
810✔
2430
    return false;
6✔
2431
}
6✔
2432

2433
void Obj::set_collection_ref(Index index, ref_type ref, CollectionType type)
2434
{
499,482✔
2435
    if (index.is_collection()) {
499,482✔
2436
        set_int(index.get_index(), from_ref(ref));
498,990✔
2437
        return;
498,990✔
2438
    }
498,990✔
2439
    set_ref(index.get_index(), ref, type);
492✔
2440
}
492✔
2441

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