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

realm / realm-core / jorgen.edelbo_402

21 Aug 2024 11:10AM UTC coverage: 91.054% (-0.03%) from 91.085%
jorgen.edelbo_402

Pull #7803

Evergreen

jedelbo
Small fix to Table::typed_write

When writing the realm to a new file from a write transaction,
the Table may be COW so that the top ref is changed. So don't
use the ref that is present in the group when the operation starts.
Pull Request #7803: Feature/string compression

103494 of 181580 branches covered (57.0%)

1929 of 1999 new or added lines in 46 files covered. (96.5%)

695 existing lines in 51 files now uncovered.

220142 of 241772 relevant lines covered (91.05%)

7344461.76 hits per line

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

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

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

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

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

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

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

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

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

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

103
} // namespace
104

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

107
Obj::Obj(TableRef table, MemRef mem, ObjKey key, size_t row_ndx)
108
    : m_table(table)
120,746,760✔
109
    , m_key(key)
120,746,760✔
110
    , m_mem(mem)
120,746,760✔
111
    , m_row_ndx(row_ndx)
120,746,760✔
112
    , m_valid(true)
120,746,760✔
113
{
252,169,077✔
114
    m_storage_version = get_alloc().get_storage_version();
252,169,077✔
115
}
252,169,077✔
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
{
54,492✔
124
    return ObjLink(m_table->get_key(), m_key);
54,492✔
125
}
54,492✔
126

127
const ClusterTree* Obj::get_tree_top() const
128
{
41,962,086✔
129
    if (m_key.is_unresolved()) {
41,962,086✔
130
        return m_table.unchecked_ptr()->m_tombstones.get();
38,916✔
131
    }
38,916✔
132
    else {
41,923,170✔
133
        return &m_table.unchecked_ptr()->m_clusters;
41,923,170✔
134
    }
41,923,170✔
135
}
41,962,086✔
136

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

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

153
const Spec& Obj::get_spec() const
UNCOV
154
{
×
UNCOV
155
    return m_table.unchecked_ptr()->m_spec;
×
UNCOV
156
}
×
157

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

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

185
Replication* Obj::get_replication() const
186
{
31,021,002✔
187
    return m_table->get_repl();
31,021,002✔
188
}
31,021,002✔
189

190
bool Obj::compare_values(Mixed val1, Mixed val2, ColKey ck, Obj other, StringData col_name) const
191
{
8,226✔
192
    if (val1.is_null()) {
8,226✔
193
        if (!val2.is_null())
60✔
194
            return false;
×
195
    }
60✔
196
    else {
8,166✔
197
        if (val1.get_type() != val2.get_type())
8,166✔
198
            return false;
×
199
        if (val1.is_type(type_Link, type_TypedLink)) {
8,166✔
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
        }
228✔
209
        else {
7,938✔
210
            const auto type = val1.get_type();
7,938✔
211
            if (type == type_List) {
7,938✔
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) {
7,932✔
217
                Set<Mixed> set1(*this, ck);
×
218
                Set<Mixed> set2(other, other.get_column_key(col_name));
×
219
                return set1 == set2;
×
220
            }
×
221
            else if (type == type_Dictionary) {
7,932✔
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;
7,926✔
227
        }
7,938✔
228
    }
8,166✔
229
    return true;
60✔
230
}
8,226✔
231

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

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

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

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

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

266
    for (size_t i = 0; i < val1.size(); ++i) {
18✔
267

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

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

292
bool Obj::operator==(const Obj& other) const
293
{
522✔
294
    for (auto ck : m_table->get_column_keys()) {
1,914✔
295
        StringData col_name = m_table->get_column_name(ck);
1,914✔
296
        auto compare = [&](Mixed m1, Mixed m2) {
8,202✔
297
            return compare_values(m1, m2, ck, other, col_name);
8,202✔
298
        };
8,202✔
299

300
        if (!ck.is_collection()) {
1,914✔
301
            if (!compare(get_any(ck), other.get_any(col_name)))
1,476✔
302
                return false;
12✔
303
        }
1,476✔
304
        else {
438✔
305
            auto coll1 = get_collection_ptr(ck);
438✔
306
            auto coll2 = other.get_collection_ptr(col_name);
438✔
307
            size_t sz = coll1->size();
438✔
308
            if (coll2->size() != sz)
438✔
309
                return false;
×
310
            if (ck.is_list() || ck.is_set()) {
438✔
311
                for (size_t i = 0; i < sz; i++) {
6,774✔
312
                    if (!compare(coll1->get_any(i), coll2->get_any(i)))
6,522✔
313
                        return false;
×
314
                }
6,522✔
315
            }
252✔
316
            if (ck.is_dictionary()) {
438✔
317
                auto dict1 = dynamic_cast<Dictionary*>(coll1.get());
186✔
318
                auto dict2 = dynamic_cast<Dictionary*>(coll2.get());
186✔
319
                for (size_t i = 0; i < sz; i++) {
384✔
320
                    auto [key, value] = dict1->get_pair(i);
204✔
321
                    auto val2 = dict2->try_get(key);
204✔
322
                    if (!val2)
204✔
323
                        return false;
×
324
                    if (!compare(value, *val2))
204✔
325
                        return false;
6✔
326
                }
204✔
327
            }
186✔
328
        }
438✔
329
    }
1,914✔
330
    return true;
504✔
331
}
522✔
332

333
bool Obj::is_valid() const noexcept
334
{
1,888,335✔
335
    // Cache valid state. If once invalid, it can never become valid again
336
    if (m_valid)
1,888,335✔
337
        m_valid = bool(m_table) && (m_table.unchecked_ptr()->get_storage_version() == m_storage_version ||
1,886,946✔
338
                                    m_table.unchecked_ptr()->is_valid(m_key));
1,886,661✔
339

340
    return m_valid;
1,888,335✔
341
}
1,888,335✔
342

343
void Obj::remove()
344
{
197,187✔
345
    m_table.cast_away_const()->remove_object(m_key);
197,187✔
346
}
197,187✔
347

348
void Obj::invalidate()
349
{
8,034✔
350
    m_key = m_table.cast_away_const()->invalidate_object(m_key);
8,034✔
351
}
8,034✔
352

353
ColKey Obj::get_column_key(StringData col_name) const
354
{
3,753,270✔
355
    return get_table()->get_column_key(col_name);
3,753,270✔
356
}
3,753,270✔
357

358
TableKey Obj::get_table_key() const
359
{
×
360
    return get_table()->get_key();
×
361
}
×
362

363
TableRef Obj::get_target_table(ColKey col_key) const
364
{
7,165,914✔
365
    if (m_table) {
7,165,914✔
366
        return _impl::TableFriend::get_opposite_link_table(*m_table.unchecked_ptr(), col_key);
7,165,425✔
367
    }
7,165,425✔
368
    else {
489✔
369
        return TableRef();
489✔
370
    }
489✔
371
}
7,165,914✔
372

373
TableRef Obj::get_target_table(ObjLink link) const
374
{
×
375
    if (m_table) {
×
376
        return m_table.unchecked_ptr()->get_parent_group()->get_table(link.get_table_key());
×
377
    }
×
378
    else {
×
379
        return TableRef();
×
380
    }
×
381
}
×
382

383
bool Obj::update() const
384
{
166,317✔
385
    // Get a new object from key
386
    Obj new_obj = get_tree_top()->get(m_key); // Throws `KeyNotFound`
166,317✔
387

388
    bool changes = (m_mem.get_addr() != new_obj.m_mem.get_addr()) || (m_row_ndx != new_obj.m_row_ndx);
166,317✔
389
    if (changes) {
166,317✔
390
        m_mem = new_obj.m_mem;
23,535✔
391
        m_row_ndx = new_obj.m_row_ndx;
23,535✔
392
        ++m_version_counter;
23,535✔
393
    }
23,535✔
394
    // Always update versions
395
    m_storage_version = new_obj.m_storage_version;
166,317✔
396
    m_table = new_obj.m_table;
166,317✔
397
    return changes;
166,317✔
398
}
166,317✔
399

400
inline bool Obj::_update_if_needed() const
401
{
146,784,684✔
402
    auto current_version = _get_alloc().get_storage_version();
146,784,684✔
403
    if (current_version != m_storage_version) {
146,784,684✔
404
        return update();
19,842✔
405
    }
19,842✔
406
    return false;
146,764,842✔
407
}
146,784,684✔
408

409
UpdateStatus Obj::update_if_needed() const
410
{
88,250,037✔
411
    if (!m_table) {
88,250,037✔
412
        // Table deleted
413
        return UpdateStatus::Detached;
354✔
414
    }
354✔
415

416
    auto current_version = _get_alloc().get_storage_version();
88,249,683✔
417
    if (current_version != m_storage_version) {
88,249,683✔
418
        ClusterNode::State state = get_tree_top()->try_get(m_key);
2,203,431✔
419

420
        if (!state) {
2,203,431✔
421
            // Object deleted
422
            return UpdateStatus::Detached;
5,220✔
423
        }
5,220✔
424

425
        // Always update versions
426
        m_storage_version = current_version;
2,198,211✔
427
        if ((m_mem.get_addr() != state.mem.get_addr()) || (m_row_ndx != state.index)) {
2,198,211✔
428
            m_mem = state.mem;
196,971✔
429
            m_row_ndx = state.index;
196,971✔
430
            ++m_version_counter;
196,971✔
431
            return UpdateStatus::Updated;
196,971✔
432
        }
196,971✔
433
    }
2,198,211✔
434
    return UpdateStatus::NoChange;
88,047,492✔
435
}
88,249,683✔
436

437
void Obj::checked_update_if_needed() const
438
{
38,605,068✔
439
    if (update_if_needed() == UpdateStatus::Detached) {
38,605,068✔
440
        m_table.check();
18✔
441
        get_tree_top()->get(m_key); // should always throw
18✔
442
    }
18✔
443
}
38,605,068✔
444

445
template <class T>
446
T Obj::get(ColKey col_key) const
447
{
84,632,412✔
448
    m_table->check_column(col_key);
84,632,412✔
449
    ColumnType type = col_key.get_type();
84,632,412✔
450
    REALM_ASSERT(type == ColumnTypeTraits<T>::column_id);
84,632,412✔
451

452
    return _get<T>(col_key.get_index());
84,632,412✔
453
}
84,632,412✔
454

455
template UUID Obj::_get(ColKey::Idx col_ndx) const;
456
template util::Optional<UUID> Obj::_get(ColKey::Idx col_ndx) const;
457

458
#if REALM_ENABLE_GEOSPATIAL
459

460
template <>
461
Geospatial Obj::get(ColKey col_key) const
462
{
84✔
463
    m_table->check_column(col_key);
84✔
464
    ColumnType type = col_key.get_type();
84✔
465
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
84✔
466
    return Geospatial::from_link(get_linked_object(col_key));
84✔
467
}
84✔
468

469
template <>
470
std::optional<Geospatial> Obj::get(ColKey col_key) const
471
{
12✔
472
    m_table->check_column(col_key);
12✔
473
    ColumnType type = col_key.get_type();
12✔
474
    REALM_ASSERT(type == ColumnTypeTraits<Link>::column_id);
12✔
475

476
    auto geo = get_linked_object(col_key);
12✔
477
    if (!geo) {
12✔
478
        return {};
12✔
479
    }
12✔
480
    return Geospatial::from_link(geo);
×
481
}
12✔
482

483
#endif
484

485
template <class T>
486
T Obj::_get(ColKey::Idx col_ndx) const
487
{
83,670,537✔
488
    _update_if_needed();
83,670,537✔
489

490
    typename ColumnTypeTraits<T>::cluster_leaf_type values(_get_alloc());
83,670,537✔
491
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
83,670,537✔
492
    values.init_from_ref(ref);
83,670,537✔
493

494
    return values.get(m_row_ndx);
83,670,537✔
495
}
83,670,537✔
496

497
Mixed Obj::get_unfiltered_mixed(ColKey::Idx col_ndx) const
498
{
1,042,845✔
499
    ArrayMixed values(get_alloc());
1,042,845✔
500
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,042,845✔
501
    values.init_from_ref(ref);
1,042,845✔
502
    values.set_string_interner(m_table->get_string_interner(col_ndx));
1,042,845✔
503

504
    return values.get(m_row_ndx);
1,042,845✔
505
}
1,042,845✔
506

507
template <>
508
Mixed Obj::_get<Mixed>(ColKey::Idx col_ndx) const
509
{
881,007✔
510
    _update_if_needed();
881,007✔
511
    Mixed m = get_unfiltered_mixed(col_ndx);
881,007✔
512
    return m.is_unresolved_link() ? Mixed{} : m;
881,007✔
513
}
881,007✔
514

515
template <>
516
ObjKey Obj::_get<ObjKey>(ColKey::Idx col_ndx) const
517
{
343,866✔
518
    _update_if_needed();
343,866✔
519

520
    ArrayKey values(_get_alloc());
343,866✔
521
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
343,866✔
522
    values.init_from_ref(ref);
343,866✔
523

524
    ObjKey k = values.get(m_row_ndx);
343,866✔
525
    return k.is_unresolved() ? ObjKey{} : k;
343,866✔
526
}
343,866✔
527

528
bool Obj::is_unresolved(ColKey col_key) const
529
{
30✔
530
    m_table->check_column(col_key);
30✔
531
    ColumnType type = col_key.get_type();
30✔
532
    REALM_ASSERT(type == col_type_Link);
30✔
533

534
    _update_if_needed();
30✔
535

536
    return get_unfiltered_link(col_key).is_unresolved();
30✔
537
}
30✔
538

539
ObjKey Obj::get_unfiltered_link(ColKey col_key) const
540
{
340,581✔
541
    ArrayKey values(get_alloc());
340,581✔
542
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_key.get_index().val + 1));
340,581✔
543
    values.init_from_ref(ref);
340,581✔
544

545
    return values.get(m_row_ndx);
340,581✔
546
}
340,581✔
547

548
template <>
549
int64_t Obj::_get<int64_t>(ColKey::Idx col_ndx) const
550
{
134,723,073✔
551
    // manual inline of _update_if_needed():
552
    auto& alloc = _get_alloc();
134,723,073✔
553
    auto current_version = alloc.get_storage_version();
134,723,073✔
554
    if (current_version != m_storage_version) {
134,723,073✔
555
        update();
143,547✔
556
    }
143,547✔
557
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
134,723,073✔
558
    char* header = alloc.translate(ref);
134,723,073✔
559
    return Array::get(header, m_row_ndx);
134,723,073✔
560
}
134,723,073✔
561

562
template <>
563
int64_t Obj::get<int64_t>(ColKey col_key) const
564
{
126,963,921✔
565
    m_table->check_column(col_key);
126,963,921✔
566
    ColumnType type = col_key.get_type();
126,963,921✔
567
    REALM_ASSERT(type == col_type_Int);
126,963,921✔
568

569
    if (col_key.get_attrs().test(col_attr_Nullable)) {
126,963,921✔
570
        auto val = _get<util::Optional<int64_t>>(col_key.get_index());
5,643,660✔
571
        if (!val) {
5,643,660✔
572
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
6✔
573
        }
6✔
574
        return *val;
5,643,654✔
575
    }
5,643,660✔
576
    else {
121,320,261✔
577
        return _get<int64_t>(col_key.get_index());
121,320,261✔
578
    }
121,320,261✔
579
}
126,963,921✔
580

581
template <>
582
bool Obj::get<bool>(ColKey col_key) const
583
{
522,126✔
584
    m_table->check_column(col_key);
522,126✔
585
    ColumnType type = col_key.get_type();
522,126✔
586
    REALM_ASSERT(type == col_type_Bool);
522,126✔
587

588
    if (col_key.get_attrs().test(col_attr_Nullable)) {
522,126✔
589
        auto val = _get<util::Optional<bool>>(col_key.get_index());
30✔
590
        if (!val) {
30✔
591
            throw IllegalOperation("Obj::get<int64_t> cannot return null");
×
592
        }
×
593
        return *val;
30✔
594
    }
30✔
595
    else {
522,096✔
596
        return _get<bool>(col_key.get_index());
522,096✔
597
    }
522,096✔
598
}
522,126✔
599

600
template <>
601
StringData Obj::_get<StringData>(ColKey::Idx col_ndx) const
602
{
64,141,713✔
603
    // manual inline of _update_if_needed():
604
    auto& alloc = _get_alloc();
64,141,713✔
605
    auto current_version = alloc.get_storage_version();
64,141,713✔
606
    if (current_version != m_storage_version) {
64,141,713✔
607
        update();
2,796✔
608
    }
2,796✔
609

610
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
64,141,713✔
611
    ArrayString values(get_alloc());
64,141,713✔
612
    auto col_key = m_table->leaf_ndx2colkey(col_ndx);
64,141,713✔
613
    values.set_string_interner(m_table->get_string_interner(col_key));
64,141,713✔
614
    values.init_from_ref(ref);
64,141,713✔
615
    return values.get(m_row_ndx);
64,141,713✔
616
}
64,141,713✔
617

618
template <>
619
BinaryData Obj::_get<BinaryData>(ColKey::Idx col_ndx) const
620
{
6,883,767✔
621
    // manual inline of _update_if_needed():
622
    auto& alloc = _get_alloc();
6,883,767✔
623
    auto current_version = alloc.get_storage_version();
6,883,767✔
624
    if (current_version != m_storage_version) {
6,883,767✔
625
        update();
132✔
626
    }
132✔
627

628
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
6,883,767✔
629
    return ArrayBinary::get(alloc.translate(ref), m_row_ndx, alloc);
6,883,767✔
630
}
6,883,767✔
631

632
std::optional<StringID> Obj::get_string_id(ColKey col_key) const
633
{
60,541,764✔
634
    // we return a string id only if the property is string or mixed.
635
    // And it got compressed.
636

637
    // only strings and mixed can have an interner
638
    if (col_key.get_type() != col_type_String && col_key.get_type() != col_type_Mixed)
60,541,764✔
NEW
639
        return {};
×
640

641
    m_table->check_column(col_key);
60,541,764✔
642
    _update_if_needed();
60,541,764✔
643

644
    const auto col_ndx = col_key.get_index();
60,541,764✔
645
    const auto interner = m_table->get_string_interner(col_ndx);
60,541,764✔
646
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
60,541,764✔
647

648
    if (col_key.get_type() == col_type_Mixed) {
60,541,764✔
649
        // mixed handling. Only strings in mixed may have a string id
650
        ArrayMixed values(get_alloc());
1,404✔
651
        values.set_string_interner(interner);
1,404✔
652
        values.init_from_ref(ref);
1,404✔
653
        return values.get_string_id(m_row_ndx);
1,404✔
654
    }
1,404✔
655
    // must be string.
656
    ArrayString values(get_alloc());
60,540,360✔
657
    values.set_string_interner(interner);
60,540,360✔
658
    values.init_from_ref(ref);
60,540,360✔
659
    return values.get_string_id(m_row_ndx);
60,540,360✔
660
}
60,541,764✔
661

662
Mixed Obj::get_any(ColKey col_key) const
663
{
74,027,913✔
664
    m_table->check_column(col_key);
74,027,913✔
665
    auto col_ndx = col_key.get_index();
74,027,913✔
666
    if (col_key.is_collection()) {
74,027,913✔
667
        ref_type ref = to_ref(_get<int64_t>(col_ndx));
6,924✔
668
        return Mixed(ref, get_table()->get_collection_type(col_key));
6,924✔
669
    }
6,924✔
670
    switch (col_key.get_type()) {
74,020,989✔
671
        case col_type_Int:
10,219,224✔
672
            if (col_key.get_attrs().test(col_attr_Nullable)) {
10,219,224✔
673
                return Mixed{_get<util::Optional<int64_t>>(col_ndx)};
473,175✔
674
            }
473,175✔
675
            else {
9,746,049✔
676
                return Mixed{_get<int64_t>(col_ndx)};
9,746,049✔
677
            }
9,746,049✔
678
        case col_type_Bool:
200,193✔
679
            return Mixed{_get<util::Optional<bool>>(col_ndx)};
200,193✔
680
        case col_type_Float:
8,007✔
681
            return Mixed{_get<util::Optional<float>>(col_ndx)};
8,007✔
682
        case col_type_Double:
19,617✔
683
            return Mixed{_get<util::Optional<double>>(col_ndx)};
19,617✔
684
        case col_type_String:
62,834,712✔
685
            return Mixed{_get<String>(col_ndx)};
62,834,712✔
686
        case col_type_Binary:
1,605✔
687
            return Mixed{_get<Binary>(col_ndx)};
1,605✔
688
        case col_type_Mixed:
23,709✔
689
            return _get<Mixed>(col_ndx);
23,709✔
690
        case col_type_Timestamp:
167,049✔
691
            return Mixed{_get<Timestamp>(col_ndx)};
167,049✔
692
        case col_type_Decimal:
7,860✔
693
            return Mixed{_get<Decimal128>(col_ndx)};
7,860✔
694
        case col_type_ObjectId:
481,074✔
695
            return Mixed{_get<util::Optional<ObjectId>>(col_ndx)};
481,074✔
696
        case col_type_UUID:
54,717✔
697
            return Mixed{_get<util::Optional<UUID>>(col_ndx)};
54,717✔
698
        case col_type_Link:
43,497✔
699
            return Mixed{_get<ObjKey>(col_ndx)};
43,497✔
700
        default:
✔
701
            REALM_UNREACHABLE();
702
            break;
×
703
    }
74,020,989✔
704
    return {};
×
705
}
74,020,989✔
706

707
Mixed Obj::get_primary_key() const
708
{
177,162✔
709
    auto col = m_table->get_primary_key_column();
177,162✔
710
    return col ? get_any(col) : Mixed{get_key()};
177,162✔
711
}
177,162✔
712

713
/* FIXME: Make this one fast too!
714
template <>
715
ObjKey Obj::_get(size_t col_ndx) const
716
{
717
    return ObjKey(_get<int64_t>(col_ndx));
718
}
719
*/
720

721
Obj Obj::_get_linked_object(ColKey link_col_key, Mixed link) const
722
{
27,930✔
723
    Obj obj;
27,930✔
724
    if (!link.is_null()) {
27,930✔
725
        TableRef target_table;
19,548✔
726
        if (link.is_type(type_TypedLink)) {
19,548✔
727
            target_table = m_table->get_parent_group()->get_table(link.get_link().get_table_key());
324✔
728
        }
324✔
729
        else {
19,224✔
730
            target_table = get_target_table(link_col_key);
19,224✔
731
        }
19,224✔
732
        obj = target_table->get_object(link.get<ObjKey>());
19,548✔
733
    }
19,548✔
734
    return obj;
27,930✔
735
}
27,930✔
736

737
Obj Obj::get_parent_object() const
738
{
12✔
739
    Obj obj;
12✔
740
    checked_update_if_needed();
12✔
741

742
    if (!m_table->is_embedded()) {
12✔
743
        throw LogicError(ErrorCodes::TopLevelObject, "Object is not embedded");
×
744
    }
×
745
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
18✔
746
        if (get_backlink_cnt(backlink_col_key) == 1) {
18✔
747
            auto obj_key = get_backlink(backlink_col_key, 0);
12✔
748
            obj = m_table->get_opposite_table(backlink_col_key)->get_object(obj_key);
12✔
749
            return IteratorControl::Stop;
12✔
750
        }
12✔
751
        return IteratorControl::AdvanceToNext;
6✔
752
    });
18✔
753

754
    return obj;
12✔
755
}
12✔
756

757
template <class T>
758
inline bool Obj::do_is_null(ColKey::Idx col_ndx) const
759
{
1,225,338✔
760
    T values(get_alloc());
1,225,338✔
761
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
1,225,338✔
762
    values.init_from_ref(ref);
1,225,338✔
763
    return values.is_null(m_row_ndx);
1,225,338✔
764
}
1,225,338✔
765

766
template <>
767
inline bool Obj::do_is_null<ArrayString>(ColKey::Idx col_ndx) const
UNCOV
768
{
×
NEW
769
    REALM_ASSERT(false); // Don't come here, you're falling from a cliff....
×
UNCOV
770
    ArrayString values(get_alloc());
×
UNCOV
771
    ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
×
NEW
772
    // TODO: Set string interner if needed
×
NEW
773
    // values.set_string_interner(m_table->get_string_interner(col_key));
×
UNCOV
774
    values.init_from_ref(ref);
×
UNCOV
775
    return values.is_null(m_row_ndx);
×
UNCOV
776
}
×
777

778
size_t Obj::get_link_count(ColKey col_key) const
779
{
108✔
780
    return get_list<ObjKey>(col_key).size();
108✔
781
}
108✔
782

783
bool Obj::is_null(ColKey col_key) const
784
{
5,420,169✔
785
    checked_update_if_needed();
5,420,169✔
786
    ColumnAttrMask attr = col_key.get_attrs();
5,420,169✔
787
    ColKey::Idx col_ndx = col_key.get_index();
5,420,169✔
788
    if (attr.test(col_attr_Nullable) && !attr.test(col_attr_Collection)) {
5,420,169✔
789
        switch (col_key.get_type()) {
1,625,601✔
790
            case col_type_Int:
458,367✔
791
                return do_is_null<ArrayIntNull>(col_ndx);
458,367✔
792
            case col_type_Bool:
333,924✔
793
                return do_is_null<ArrayBoolNull>(col_ndx);
333,924✔
794
            case col_type_Float:
9,165✔
795
                return do_is_null<ArrayFloatNull>(col_ndx);
9,165✔
796
            case col_type_Double:
3,267✔
797
                return do_is_null<ArrayDoubleNull>(col_ndx);
3,267✔
798
            case col_type_String: {
400,302✔
799
                ArrayString values(get_alloc());
400,302✔
800
                ref_type ref = to_ref(Array::get(m_mem.get_addr(), col_ndx.val + 1));
400,302✔
801
                // TODO: Set string interner if needed
802
                values.set_string_interner(m_table->get_string_interner(col_key));
400,302✔
803
                values.init_from_ref(ref);
400,302✔
804
                return values.is_null(m_row_ndx);
400,302✔
NEW
805
            }
×
806
                // return do_is_null<ArrayString>(col_ndx);
807
            case col_type_Binary:
267✔
808
                return do_is_null<ArrayBinary>(col_ndx);
267✔
809
            case col_type_Mixed:
1,995✔
810
                return do_is_null<ArrayMixed>(col_ndx);
1,995✔
811
            case col_type_Timestamp:
392,685✔
812
                return do_is_null<ArrayTimestamp>(col_ndx);
392,685✔
813
            case col_type_Link:
23,193✔
814
                return do_is_null<ArrayKey>(col_ndx);
23,193✔
815
            case col_type_ObjectId:
231✔
816
                return do_is_null<ArrayObjectIdNull>(col_ndx);
231✔
817
            case col_type_Decimal:
2,013✔
818
                return do_is_null<ArrayDecimal128>(col_ndx);
2,013✔
819
            case col_type_UUID:
231✔
820
                return do_is_null<ArrayUUIDNull>(col_ndx);
231✔
821
            default:
✔
822
                REALM_UNREACHABLE();
823
        }
1,625,601✔
824
    }
1,625,601✔
825
    return false;
3,794,568✔
826
}
5,420,169✔
827

828

829
// Figure out if this object has any remaining backlinks
830
bool Obj::has_backlinks(bool only_strong_links) const
831
{
60,540✔
832
    const Table& target_table = *m_table;
60,540✔
833

834
    // If we only look for strong links and the table is not embedded,
835
    // then there is no relevant backlinks to find.
836
    if (only_strong_links && !target_table.is_embedded()) {
60,540✔
837
        return false;
×
838
    }
×
839

840
    return m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
60,540✔
841
        return get_backlink_cnt(backlink_col_key) != 0 ? IteratorControl::Stop : IteratorControl::AdvanceToNext;
59,520✔
842
    });
59,520✔
843
}
60,540✔
844

845
size_t Obj::get_backlink_count() const
846
{
768,486✔
847
    checked_update_if_needed();
768,486✔
848

849
    size_t cnt = 0;
768,486✔
850
    m_table->for_each_backlink_column([&](ColKey backlink_col_key) {
1,069,008✔
851
        cnt += get_backlink_cnt(backlink_col_key);
1,069,008✔
852
        return IteratorControl::AdvanceToNext;
1,069,008✔
853
    });
1,069,008✔
854
    return cnt;
768,486✔
855
}
768,486✔
856

857
size_t Obj::get_backlink_count(const Table& origin, ColKey origin_col_key) const
858
{
32,085✔
859
    checked_update_if_needed();
32,085✔
860

861
    size_t cnt = 0;
32,085✔
862
    if (TableKey origin_table_key = origin.get_key()) {
32,085✔
863
        ColKey backlink_col_key;
32,085✔
864
        auto type = origin_col_key.get_type();
32,085✔
865
        if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
32,085✔
866
            backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin_table_key);
12✔
867
        }
12✔
868
        else {
32,073✔
869
            backlink_col_key = origin.get_opposite_column(origin_col_key);
32,073✔
870
        }
32,073✔
871

872
        cnt = get_backlink_cnt(backlink_col_key);
32,085✔
873
    }
32,085✔
874
    return cnt;
32,085✔
875
}
32,085✔
876

877
ObjKey Obj::get_backlink(const Table& origin, ColKey origin_col_key, size_t backlink_ndx) const
878
{
9,023,613✔
879
    ColKey backlink_col_key;
9,023,613✔
880
    auto type = origin_col_key.get_type();
9,023,613✔
881
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
9,023,613✔
882
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
36✔
883
    }
36✔
884
    else {
9,023,577✔
885
        backlink_col_key = origin.get_opposite_column(origin_col_key);
9,023,577✔
886
    }
9,023,577✔
887
    return get_backlink(backlink_col_key, backlink_ndx);
9,023,613✔
888
}
9,023,613✔
889

890
TableView Obj::get_backlink_view(TableRef src_table, ColKey src_col_key) const
891
{
696✔
892
    TableView tv(src_table, src_col_key, *this);
696✔
893
    tv.do_sync();
696✔
894
    return tv;
696✔
895
}
696✔
896

897
ObjKey Obj::get_backlink(ColKey backlink_col, size_t backlink_ndx) const
898
{
9,023,604✔
899
    get_table()->check_column(backlink_col);
9,023,604✔
900
    Allocator& alloc = get_alloc();
9,023,604✔
901
    Array fields(alloc);
9,023,604✔
902
    fields.init_from_mem(m_mem);
9,023,604✔
903

904
    ArrayBacklink backlinks(alloc);
9,023,604✔
905
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
9,023,604✔
906
    backlinks.init_from_parent();
9,023,604✔
907
    return backlinks.get_backlink(m_row_ndx, backlink_ndx);
9,023,604✔
908
}
9,023,604✔
909

910
std::vector<ObjKey> Obj::get_all_backlinks(ColKey backlink_col) const
911
{
305,382✔
912
    checked_update_if_needed();
305,382✔
913

914
    get_table()->check_column(backlink_col);
305,382✔
915
    Allocator& alloc = get_alloc();
305,382✔
916
    Array fields(alloc);
305,382✔
917
    fields.init_from_mem(m_mem);
305,382✔
918

919
    ArrayBacklink backlinks(alloc);
305,382✔
920
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
305,382✔
921
    backlinks.init_from_parent();
305,382✔
922

923
    auto cnt = backlinks.get_backlink_count(m_row_ndx);
305,382✔
924
    std::vector<ObjKey> vec;
305,382✔
925
    vec.reserve(cnt);
305,382✔
926
    for (size_t i = 0; i < cnt; i++) {
572,544✔
927
        vec.push_back(backlinks.get_backlink(m_row_ndx, i));
267,162✔
928
    }
267,162✔
929
    return vec;
305,382✔
930
}
305,382✔
931

932
size_t Obj::get_backlink_cnt(ColKey backlink_col) const
933
{
1,160,628✔
934
    Allocator& alloc = get_alloc();
1,160,628✔
935
    Array fields(alloc);
1,160,628✔
936
    fields.init_from_mem(m_mem);
1,160,628✔
937

938
    ArrayBacklink backlinks(alloc);
1,160,628✔
939
    backlinks.set_parent(&fields, backlink_col.get_index().val + 1);
1,160,628✔
940
    backlinks.init_from_parent();
1,160,628✔
941

942
    return backlinks.get_backlink_count(m_row_ndx);
1,160,628✔
943
}
1,160,628✔
944

945
void Obj::verify_backlink(const Table& origin, ColKey origin_col_key, ObjKey origin_key) const
946
{
120,714✔
947
#ifdef REALM_DEBUG
120,714✔
948
    ColKey backlink_col_key;
120,714✔
949
    auto type = origin_col_key.get_type();
120,714✔
950
    if (type == col_type_TypedLink || type == col_type_Mixed || origin_col_key.is_dictionary()) {
120,714✔
951
        backlink_col_key = get_table()->find_backlink_column(origin_col_key, origin.get_key());
×
952
    }
×
953
    else {
120,714✔
954
        backlink_col_key = origin.get_opposite_column(origin_col_key);
120,714✔
955
    }
120,714✔
956

957
    Allocator& alloc = get_alloc();
120,714✔
958
    Array fields(alloc);
120,714✔
959
    fields.init_from_mem(m_mem);
120,714✔
960

961
    ArrayBacklink backlinks(alloc);
120,714✔
962
    backlinks.set_parent(&fields, backlink_col_key.get_index().val + 1);
120,714✔
963
    backlinks.init_from_parent();
120,714✔
964

965
    REALM_ASSERT(backlinks.verify_backlink(m_row_ndx, origin_key.value));
120,714✔
966
#else
967
    static_cast<void>(origin);
968
    static_cast<void>(origin_col_key);
969
    static_cast<void>(origin_key);
970
#endif
971
}
120,714✔
972

973
void Obj::traverse_path(Visitor v, PathSizer ps, size_t path_length) const
974
{
90✔
975
    struct BacklinkTraverser : public LinkTranslator {
90✔
976
        BacklinkTraverser(Obj origin, ColKey origin_col_key, Obj dest)
90✔
977
            : LinkTranslator(origin, origin_col_key)
90✔
978
            , m_dest_obj(dest)
90✔
979
        {
90✔
980
        }
48✔
981
        void on_list_of_links(LnkLst& ll) final
90✔
982
        {
90✔
983
            auto i = ll.find_first(m_dest_obj.get_key());
30✔
984
            REALM_ASSERT(i != realm::npos);
30✔
985
            m_index = Mixed(int64_t(i));
30✔
986
        }
30✔
987
        void on_dictionary(Dictionary& dict) final
90✔
988
        {
90✔
989
            for (auto it : dict) {
12✔
990
                if (it.second.is_type(type_TypedLink) && it.second.get_link() == m_dest_obj.get_link()) {
12✔
991
                    m_index = it.first;
6✔
992
                    break;
6✔
993
                }
6✔
994
            }
12✔
995
            REALM_ASSERT(!m_index.is_null());
6✔
996
        }
6✔
997
        void on_list_of_mixed(Lst<Mixed>&) final
90✔
998
        {
90✔
999
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
1000
        }
×
1001
        void on_set_of_links(LnkSet&) final
90✔
1002
        {
90✔
1003
            REALM_UNREACHABLE(); // sets of embedded objects are not allowed at the schema level
1004
        }
×
1005
        void on_set_of_mixed(Set<Mixed>&) final
90✔
1006
        {
90✔
1007
            REALM_UNREACHABLE(); // we don't support Mixed link to embedded object yet
1008
        }
×
1009
        void on_link_property(ColKey) final {}
90✔
1010
        void on_mixed_property(ColKey) final {}
90✔
1011
        Mixed result()
90✔
1012
        {
90✔
1013
            return m_index;
48✔
1014
        }
48✔
1015

1016
    private:
90✔
1017
        Mixed m_index;
90✔
1018
        Obj m_dest_obj;
90✔
1019
    };
90✔
1020

1021
    if (m_table->is_embedded()) {
90✔
1022
        REALM_ASSERT(get_backlink_count() == 1);
48✔
1023
        m_table->for_each_backlink_column([&](ColKey col_key) {
84✔
1024
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
84✔
1025
            if (backlinks.size() == 1) {
84✔
1026
                TableRef tr = m_table->get_opposite_table(col_key);
48✔
1027
                Obj obj = tr->get_object(backlinks[0]); // always the first (and only)
48✔
1028
                auto next_col_key = m_table->get_opposite_column(col_key);
48✔
1029
                BacklinkTraverser traverser{obj, next_col_key, *this};
48✔
1030
                traverser.run();
48✔
1031
                Mixed index = traverser.result();
48✔
1032
                obj.traverse_path(v, ps, path_length + 1);
48✔
1033
                v(obj, next_col_key, index);
48✔
1034
                return IteratorControl::Stop; // early out
48✔
1035
            }
48✔
1036
            return IteratorControl::AdvanceToNext; // try next column
36✔
1037
        });
84✔
1038
    }
48✔
1039
    else {
42✔
1040
        ps(path_length);
42✔
1041
    }
42✔
1042
}
90✔
1043

1044
Obj::FatPath Obj::get_fat_path() const
1045
{
30✔
1046
    FatPath result;
30✔
1047
    auto sizer = [&](size_t size) {
30✔
1048
        result.reserve(size);
30✔
1049
    };
30✔
1050
    auto step = [&](const Obj& o2, ColKey col, Mixed idx) -> void {
30✔
1051
        result.push_back({o2, col, idx});
30✔
1052
    };
30✔
1053
    traverse_path(step, sizer);
30✔
1054
    return result;
30✔
1055
}
30✔
1056

1057
FullPath Obj::get_path() const
1058
{
310,476✔
1059
    FullPath result;
310,476✔
1060
    if (m_table->is_embedded()) {
310,476✔
1061
        REALM_ASSERT(get_backlink_count() == 1);
177,114✔
1062
        m_table->for_each_backlink_column([&](ColKey col_key) {
293,244✔
1063
            std::vector<ObjKey> backlinks = get_all_backlinks(col_key);
293,244✔
1064
            if (backlinks.size() == 1) {
293,244✔
1065
                TableRef origin_table = m_table->get_opposite_table(col_key);
177,114✔
1066
                Obj obj = origin_table->get_object(backlinks[0]); // always the first (and only)
177,114✔
1067
                auto next_col_key = m_table->get_opposite_column(col_key);
177,114✔
1068

1069
                ColumnAttrMask attr = next_col_key.get_attrs();
177,114✔
1070
                Mixed index;
177,114✔
1071
                if (attr.test(col_attr_List)) {
177,114✔
1072
                    REALM_ASSERT(next_col_key.get_type() == col_type_Link);
62,016✔
1073
                    Lst<ObjKey> link_list(next_col_key);
62,016✔
1074
                    size_t i = find_link_value_in_collection(link_list, obj, next_col_key, get_key());
62,016✔
1075
                    REALM_ASSERT(i != realm::not_found);
62,016✔
1076
                    result = link_list.get_path();
62,016✔
1077
                    result.path_from_top.emplace_back(i);
62,016✔
1078
                }
62,016✔
1079
                else if (attr.test(col_attr_Dictionary)) {
115,098✔
1080
                    Dictionary dict(next_col_key);
49,056✔
1081
                    size_t ndx = find_link_value_in_collection(dict, obj, next_col_key, get_link());
49,056✔
1082
                    REALM_ASSERT(ndx != realm::not_found);
49,056✔
1083
                    result = dict.get_path();
49,056✔
1084
                    result.path_from_top.push_back(dict.get_key(ndx).get_string());
49,056✔
1085
                }
49,056✔
1086
                else {
66,042✔
1087
                    result = obj.get_path();
66,042✔
1088
                    if (result.path_from_top.empty()) {
66,042✔
1089
                        result.path_from_top.push_back(next_col_key);
15,540✔
1090
                    }
15,540✔
1091
                    else {
50,502✔
1092
                        result.path_from_top.push_back(obj.get_table()->get_column_name(next_col_key));
50,502✔
1093
                    }
50,502✔
1094
                }
66,042✔
1095

1096
                return IteratorControl::Stop; // early out
177,114✔
1097
            }
177,114✔
1098
            return IteratorControl::AdvanceToNext; // try next column
116,130✔
1099
        });
293,244✔
1100
    }
177,114✔
1101
    else {
133,362✔
1102
        result.top_objkey = get_key();
133,362✔
1103
        result.top_table = get_table()->get_key();
133,362✔
1104
    }
133,362✔
1105
    return result;
310,476✔
1106
}
310,476✔
1107

1108
std::string Obj::get_id() const
1109
{
37,773✔
1110
    std::ostringstream ostr;
37,773✔
1111
    auto path = get_path();
37,773✔
1112
    auto top_table = m_table->get_parent_group()->get_table(path.top_table);
37,773✔
1113
    ostr << top_table->get_class_name() << '[';
37,773✔
1114
    if (top_table->get_primary_key_column()) {
37,773✔
1115
        ostr << top_table->get_primary_key(path.top_objkey);
37,605✔
1116
    }
37,605✔
1117
    else {
168✔
1118
        ostr << path.top_objkey;
168✔
1119
    }
168✔
1120
    ostr << ']';
37,773✔
1121
    if (!path.path_from_top.empty()) {
37,773✔
1122
        auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key());
27,795✔
1123
        path.path_from_top[0] = PathElement(prop_name);
27,795✔
1124
        ostr << path.path_from_top;
27,795✔
1125
    }
27,795✔
1126
    return ostr.str();
37,773✔
1127
}
37,773✔
1128

1129
Path Obj::get_short_path() const noexcept
1130
{
402,315✔
1131
    return {};
402,315✔
1132
}
402,315✔
1133

1134
ColKey Obj::get_col_key() const noexcept
1135
{
×
1136
    return {};
×
1137
}
×
1138

1139
StablePath Obj::get_stable_path() const noexcept
1140
{
2,013,681✔
1141
    return {};
2,013,681✔
1142
}
2,013,681✔
1143

1144
void Obj::add_index(Path& path, const CollectionParent::Index& index) const
1145
{
513,405✔
1146
    if (path.empty()) {
513,405✔
1147
        path.emplace_back(get_table()->get_column_key(index));
510,159✔
1148
    }
510,159✔
1149
    else {
3,246✔
1150
        StringData col_name = get_table()->get_column_name(index);
3,246✔
1151
        path.emplace_back(col_name);
3,246✔
1152
    }
3,246✔
1153
}
513,405✔
1154

1155
std::string Obj::to_string() const
1156
{
12✔
1157
    std::ostringstream ostr;
12✔
1158
    to_json(ostr);
12✔
1159
    return ostr.str();
12✔
1160
}
12✔
1161

1162
std::ostream& operator<<(std::ostream& ostr, const Obj& obj)
1163
{
×
1164
    obj.to_json(ostr);
×
1165
    return ostr;
×
1166
}
×
1167

1168
/*********************************** Obj *************************************/
1169

1170
bool Obj::ensure_writeable()
1171
{
×
1172
    Allocator& alloc = get_alloc();
×
1173
    if (alloc.is_read_only(m_mem.get_ref())) {
×
1174
        m_mem = const_cast<ClusterTree*>(get_tree_top())->ensure_writeable(m_key);
×
1175
        m_storage_version = alloc.get_storage_version();
×
1176
        return true;
×
1177
    }
×
1178
    return false;
×
1179
}
×
1180

1181
REALM_FORCEINLINE void Obj::sync(Node& arr)
1182
{
39,283,728✔
1183
    auto ref = arr.get_ref();
39,283,728✔
1184
    if (arr.has_missing_parent_update()) {
39,283,728✔
1185
        const_cast<ClusterTree*>(get_tree_top())->update_ref_in_parent(m_key, ref);
270,501✔
1186
    }
270,501✔
1187
    if (m_mem.get_ref() != ref) {
39,283,728✔
1188
        m_mem = arr.get_mem();
540,699✔
1189
        m_storage_version = arr.get_alloc().get_storage_version();
540,699✔
1190
    }
540,699✔
1191
}
39,283,728✔
1192

1193
// helper functions for filtering out calls to set_string_interner()
1194
template <class T>
1195
inline void Obj::set_string_interner(T&, ColKey)
1196
{
7,967,592✔
1197
}
7,967,592✔
1198
template <>
1199
inline void Obj::set_string_interner(ArrayString& values, ColKey col_key)
1200
{
1,795,107✔
1201
    values.set_string_interner(m_table->get_string_interner(col_key));
1,795,107✔
1202
}
1,795,107✔
1203
template <>
1204
inline void Obj::set_string_interner(ArrayMixed& values, ColKey col_key)
1205
{
160,416✔
1206
    values.set_string_interner(m_table->get_string_interner(col_key));
160,416✔
1207
}
160,416✔
1208

1209
template <>
1210
Obj& Obj::set<Mixed>(ColKey col_key, Mixed value, bool is_default)
1211
{
161,880✔
1212
    checked_update_if_needed();
161,880✔
1213
    get_table()->check_column(col_key);
161,880✔
1214
    auto type = col_key.get_type();
161,880✔
1215
    auto col_ndx = col_key.get_index();
161,880✔
1216
    bool recurse = false;
161,880✔
1217
    CascadeState state;
161,880✔
1218

1219
    if (type != col_type_Mixed)
161,880✔
1220
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a Mixed");
×
1221
    if (value_is_null(value) && !col_key.is_nullable()) {
161,880✔
1222
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
×
1223
    }
×
1224
    if (value.is_type(type_Link)) {
161,880✔
1225
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Link must be fully qualified");
×
1226
    }
×
1227

1228
    Mixed old_value = get_unfiltered_mixed(col_ndx);
161,880✔
1229
    if (!value.is_same_type(old_value) || value != old_value) {
161,880✔
1230
        if (old_value.is_type(type_TypedLink)) {
160,275✔
1231
            auto old_link = old_value.get<ObjLink>();
114✔
1232
            recurse = remove_backlink(col_key, old_link, state);
114✔
1233
        }
114✔
1234
        else if (old_value.is_type(type_Dictionary)) {
160,161✔
1235
            Dictionary dict(*this, col_key);
264✔
1236
            recurse = dict.remove_backlinks(state);
264✔
1237
        }
264✔
1238
        else if (old_value.is_type(type_List)) {
159,897✔
1239
            Lst<Mixed> list(*this, col_key);
426✔
1240
            recurse = list.remove_backlinks(state);
426✔
1241
        }
426✔
1242

1243
        if (value.is_type(type_TypedLink)) {
160,275✔
1244
            if (m_table->is_asymmetric()) {
966✔
1245
                throw IllegalOperation("Links not allowed in asymmetric tables");
12✔
1246
            }
12✔
1247
            auto new_link = value.get<ObjLink>();
954✔
1248
            m_table->get_parent_group()->validate(new_link);
954✔
1249
            set_backlink(col_key, new_link);
954✔
1250
        }
954✔
1251

1252
        SearchIndex* index = m_table->get_search_index(col_key);
160,263✔
1253
        // The following check on unresolved is just a precaution as it should not
1254
        // be possible to hit that while Mixed is not a supported primary key type.
1255
        if (index && !m_key.is_unresolved()) {
160,263✔
1256
            index->set(m_key, value.is_unresolved_link() ? Mixed() : value);
2,316✔
1257
        }
2,316✔
1258

1259
        Allocator& alloc = get_alloc();
160,263✔
1260
        alloc.bump_content_version();
160,263✔
1261
        Array fallback(alloc);
160,263✔
1262
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
160,263✔
1263
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
160,263✔
1264
        ArrayMixed values(alloc);
160,263✔
1265
        values.set_parent(&fields, col_ndx.val + 1);
160,263✔
1266
        set_string_interner(values, col_key);
160,263✔
1267
        values.init_from_parent();
160,263✔
1268
        values.set(m_row_ndx, value);
160,263✔
1269
        if (value.is_type(type_Dictionary, type_List)) {
160,263✔
1270
            values.set_key(m_row_ndx, CollectionParent::generate_key(0x10));
147,375✔
1271
        }
147,375✔
1272

1273
        sync(fields);
160,263✔
1274
    }
160,263✔
1275

1276
    if (Replication* repl = get_replication())
161,868✔
1277
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
11,196✔
1278
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
11,196✔
1279

1280
    if (recurse)
161,868✔
1281
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1282

1283
    return *this;
161,868✔
1284
}
161,880✔
1285

1286
Obj& Obj::set_any(ColKey col_key, Mixed value, bool is_default)
1287
{
942,978✔
1288
    if (value.is_null()) {
942,978✔
1289
        REALM_ASSERT(col_key.get_attrs().test(col_attr_Nullable));
312✔
1290
        set_null(col_key, is_default);
312✔
1291
    }
312✔
1292
    else {
942,666✔
1293
        switch (col_key.get_type()) {
942,666✔
1294
            case col_type_Int:
762,126✔
1295
                if (col_key.get_attrs().test(col_attr_Nullable)) {
762,126✔
1296
                    set(col_key, util::Optional<Int>(value.get_int()), is_default);
3,417✔
1297
                }
3,417✔
1298
                else {
758,709✔
1299
                    set(col_key, value.get_int(), is_default);
758,709✔
1300
                }
758,709✔
1301
                break;
762,126✔
1302
            case col_type_Bool:
108✔
1303
                set(col_key, value.get_bool(), is_default);
108✔
1304
                break;
108✔
1305
            case col_type_Float:
2,646✔
1306
                set(col_key, value.get_float(), is_default);
2,646✔
1307
                break;
2,646✔
1308
            case col_type_Double:
3,816✔
1309
                set(col_key, value.get_double(), is_default);
3,816✔
1310
                break;
3,816✔
1311
            case col_type_String:
102,036✔
1312
                set(col_key, value.get_string(), is_default);
102,036✔
1313
                break;
102,036✔
1314
            case col_type_Binary:
20,076✔
1315
                set(col_key, value.get<Binary>(), is_default);
20,076✔
1316
                break;
20,076✔
1317
            case col_type_Mixed:
3,066✔
1318
                set(col_key, value, is_default);
3,066✔
1319
                break;
3,066✔
1320
            case col_type_Timestamp:
8,919✔
1321
                set(col_key, value.get<Timestamp>(), is_default);
8,919✔
1322
                break;
8,919✔
1323
            case col_type_ObjectId:
33,612✔
1324
                set(col_key, value.get<ObjectId>(), is_default);
33,612✔
1325
                break;
33,612✔
1326
            case col_type_Decimal:
2,502✔
1327
                set(col_key, value.get<Decimal128>(), is_default);
2,502✔
1328
                break;
2,502✔
1329
            case col_type_UUID:
3,708✔
1330
                set(col_key, value.get<UUID>(), is_default);
3,708✔
1331
                break;
3,708✔
1332
            case col_type_Link:
72✔
1333
                set(col_key, value.get<ObjKey>(), is_default);
72✔
1334
                break;
72✔
1335
            case col_type_TypedLink:
✔
1336
                set(col_key, value.get<ObjLink>(), is_default);
×
1337
                break;
×
1338
            default:
✔
1339
                break;
×
1340
        }
942,666✔
1341
    }
942,666✔
1342
    return *this;
943,002✔
1343
}
942,978✔
1344

1345
template <>
1346
Obj& Obj::set<int64_t>(ColKey col_key, int64_t value, bool is_default)
1347
{
20,771,637✔
1348
    checked_update_if_needed();
20,771,637✔
1349
    get_table()->check_column(col_key);
20,771,637✔
1350
    auto col_ndx = col_key.get_index();
20,771,637✔
1351

1352
    if (col_key.get_type() != ColumnTypeTraits<int64_t>::column_id)
20,771,637✔
1353
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1354
                              util::format("Property not a %1", ColumnTypeTraits<int64_t>::column_id));
×
1355

1356
    SearchIndex* index = m_table->get_search_index(col_key);
20,771,637✔
1357
    if (index && !m_key.is_unresolved()) {
20,771,637✔
1358
        index->set(m_key, value);
184,491✔
1359
    }
184,491✔
1360

1361
    Allocator& alloc = get_alloc();
20,771,637✔
1362
    alloc.bump_content_version();
20,771,637✔
1363
    Array fallback(alloc);
20,771,637✔
1364
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
20,771,637✔
1365
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
20,771,637✔
1366
    auto attr = col_key.get_attrs();
20,771,637✔
1367
    if (attr.test(col_attr_Nullable)) {
20,771,637✔
1368
        ArrayIntNull values(alloc);
4,022,922✔
1369
        values.set_parent(&fields, col_ndx.val + 1);
4,022,922✔
1370
        values.init_from_parent();
4,022,922✔
1371
        values.set(m_row_ndx, value);
4,022,922✔
1372
    }
4,022,922✔
1373
    else {
16,748,715✔
1374
        ArrayInteger values(alloc);
16,748,715✔
1375
        values.set_parent(&fields, col_ndx.val + 1);
16,748,715✔
1376
        values.init_from_parent();
16,748,715✔
1377
        values.set(m_row_ndx, value);
16,748,715✔
1378
    }
16,748,715✔
1379

1380
    sync(fields);
20,771,637✔
1381

1382
    if (Replication* repl = get_replication()) {
20,771,637✔
1383
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
11,541,216✔
1384
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
11,541,216✔
1385
    }
11,541,216✔
1386

1387
    return *this;
20,771,637✔
1388
}
20,771,637✔
1389

1390
Obj& Obj::add_int(ColKey col_key, int64_t value)
1391
{
20,487✔
1392
    checked_update_if_needed();
20,487✔
1393
    get_table()->check_column(col_key);
20,487✔
1394
    auto col_ndx = col_key.get_index();
20,487✔
1395

1396
    auto add_wrap = [](int64_t a, int64_t b) -> int64_t {
20,487✔
1397
        uint64_t ua = uint64_t(a);
20,475✔
1398
        uint64_t ub = uint64_t(b);
20,475✔
1399
        return int64_t(ua + ub);
20,475✔
1400
    };
20,475✔
1401

1402
    Allocator& alloc = get_alloc();
20,487✔
1403
    alloc.bump_content_version();
20,487✔
1404
    Array fallback(alloc);
20,487✔
1405
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
20,487✔
1406
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
20,487✔
1407

1408
    if (col_key.get_type() == col_type_Mixed) {
20,487✔
1409
        ArrayMixed values(alloc);
150✔
1410
        values.set_parent(&fields, col_ndx.val + 1);
150✔
1411
        set_string_interner(values, col_key);
150✔
1412
        values.init_from_parent();
150✔
1413
        Mixed old = values.get(m_row_ndx);
150✔
1414
        if (old.is_type(type_Int)) {
150✔
1415
            Mixed new_val = Mixed(add_wrap(old.get_int(), value));
144✔
1416
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
144✔
1417
                index->set(m_key, new_val);
×
1418
            }
×
1419
            values.set(m_row_ndx, Mixed(new_val));
144✔
1420
        }
144✔
1421
        else {
6✔
1422
            throw IllegalOperation("Value not an int");
6✔
1423
        }
6✔
1424
    }
150✔
1425
    else {
20,337✔
1426
        if (col_key.get_type() != col_type_Int)
20,337✔
1427
            throw IllegalOperation("Property not an int");
×
1428

1429
        auto attr = col_key.get_attrs();
20,337✔
1430
        if (attr.test(col_attr_Nullable)) {
20,337✔
1431
            ArrayIntNull values(alloc);
93✔
1432
            values.set_parent(&fields, col_ndx.val + 1);
93✔
1433
            values.init_from_parent();
93✔
1434
            util::Optional<int64_t> old = values.get(m_row_ndx);
93✔
1435
            if (old) {
93✔
1436
                auto new_val = add_wrap(*old, value);
87✔
1437
                if (SearchIndex* index = m_table->get_search_index(col_key)) {
87✔
1438
                    index->set(m_key, new_val);
×
1439
                }
×
1440
                values.set(m_row_ndx, new_val);
87✔
1441
            }
87✔
1442
            else {
6✔
1443
                throw IllegalOperation("No prior value");
6✔
1444
            }
6✔
1445
        }
93✔
1446
        else {
20,244✔
1447
            ArrayInteger values(alloc);
20,244✔
1448
            values.set_parent(&fields, col_ndx.val + 1);
20,244✔
1449
            values.init_from_parent();
20,244✔
1450
            int64_t old = values.get(m_row_ndx);
20,244✔
1451
            auto new_val = add_wrap(old, value);
20,244✔
1452
            if (SearchIndex* index = m_table->get_search_index(col_key)) {
20,244✔
1453
                index->set(m_key, new_val);
6✔
1454
            }
6✔
1455
            values.set(m_row_ndx, new_val);
20,244✔
1456
        }
20,244✔
1457
    }
20,337✔
1458

1459
    sync(fields);
20,475✔
1460

1461
    if (Replication* repl = get_replication()) {
20,475✔
1462
        repl->add_int(m_table.unchecked_ptr(), col_key, m_key, value); // Throws
8,481✔
1463
    }
8,481✔
1464

1465
    return *this;
20,475✔
1466
}
20,487✔
1467

1468
template <>
1469
Obj& Obj::set<ObjKey>(ColKey col_key, ObjKey target_key, bool is_default)
1470
{
279,930✔
1471
    checked_update_if_needed();
279,930✔
1472
    get_table()->check_column(col_key);
279,930✔
1473
    ColKey::Idx col_ndx = col_key.get_index();
279,930✔
1474
    ColumnType type = col_key.get_type();
279,930✔
1475
    if (type != col_type_Link)
279,930✔
1476
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link");
×
1477
    TableRef target_table = get_target_table(col_key);
279,930✔
1478
    TableKey target_table_key = target_table->get_key();
279,930✔
1479
    if (target_key) {
279,930✔
1480
        ClusterTree* ct = target_key.is_unresolved() ? target_table->m_tombstones.get() : &target_table->m_clusters;
279,420✔
1481
        if (!ct->is_valid(target_key)) {
279,420✔
1482
            InvalidArgument(ErrorCodes::KeyNotFound, "Invalid object key");
12✔
1483
        }
12✔
1484
        if (target_table->is_embedded()) {
279,420✔
1485
            throw IllegalOperation(
×
1486
                util::format("Setting not allowed on embedded object: %1", m_table->get_column_name(col_key)));
×
1487
        }
×
1488
    }
279,420✔
1489
    ObjKey old_key = get_unfiltered_link(col_key); // Will update if needed
279,930✔
1490
    CascadeState state(CascadeState::Mode::Strong);
279,930✔
1491
    bool recurse = false;
279,930✔
1492

1493
    if (target_key != old_key) {
279,930✔
1494
        recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
279,180✔
1495
        _update_if_needed();
279,180✔
1496

1497
        Allocator& alloc = get_alloc();
279,180✔
1498
        alloc.bump_content_version();
279,180✔
1499
        Array fallback(alloc);
279,180✔
1500
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
279,180✔
1501
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
279,180✔
1502
        ArrayKey values(alloc);
279,180✔
1503
        values.set_parent(&fields, col_ndx.val + 1);
279,180✔
1504
        values.init_from_parent();
279,180✔
1505

1506
        values.set(m_row_ndx, target_key);
279,180✔
1507

1508
        sync(fields);
279,180✔
1509
    }
279,180✔
1510

1511
    if (Replication* repl = get_replication()) {
279,930✔
1512
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
32,796✔
1513
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
32,796✔
1514
    }
32,796✔
1515

1516
    if (recurse)
279,930✔
1517
        target_table->remove_recursive(state);
258✔
1518

1519
    return *this;
279,930✔
1520
}
279,930✔
1521

1522
template <>
1523
Obj& Obj::set<ObjLink>(ColKey col_key, ObjLink target_link, bool is_default)
1524
{
×
1525
    checked_update_if_needed();
×
1526
    get_table()->check_column(col_key);
×
1527
    ColKey::Idx col_ndx = col_key.get_index();
×
1528
    ColumnType type = col_key.get_type();
×
1529
    if (type != col_type_TypedLink)
×
1530
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a typed link");
×
1531
    m_table->get_parent_group()->validate(target_link);
×
1532

1533
    ObjLink old_link = get<ObjLink>(col_key); // Will update if needed
×
1534
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
1535
    bool recurse = false;
×
1536

1537
    if (target_link != old_link) {
×
1538
        recurse = replace_backlink(col_key, old_link, target_link, state);
×
1539
        _update_if_needed();
×
1540

1541
        Allocator& alloc = get_alloc();
×
1542
        alloc.bump_content_version();
×
1543
        Array fallback(alloc);
×
1544
        Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
×
1545
        REALM_ASSERT(col_ndx.val + 1 < fields.size());
×
1546
        ArrayTypedLink values(alloc);
×
1547
        values.set_parent(&fields, col_ndx.val + 1);
×
1548
        values.init_from_parent();
×
1549

1550
        values.set(m_row_ndx, target_link);
×
1551

1552
        sync(fields);
×
1553
    }
×
1554

1555
    if (Replication* repl = get_replication()) {
×
1556
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_link,
×
1557
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
×
1558
    }
×
1559

1560
    if (recurse)
×
1561
        const_cast<Table*>(m_table.unchecked_ptr())->remove_recursive(state);
×
1562

1563
    return *this;
×
1564
}
×
1565

1566
Obj Obj::create_and_set_linked_object(ColKey col_key, bool is_default)
1567
{
14,637✔
1568
    checked_update_if_needed();
14,637✔
1569
    get_table()->check_column(col_key);
14,637✔
1570
    ColKey::Idx col_ndx = col_key.get_index();
14,637✔
1571
    ColumnType type = col_key.get_type();
14,637✔
1572
    if (type != col_type_Link)
14,637✔
1573
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a link type");
×
1574
    TableRef target_table = get_target_table(col_key);
14,637✔
1575
    Table& t = *target_table;
14,637✔
1576
    // Only links to embedded objects are allowed.
1577
    REALM_ASSERT(t.is_embedded() || !get_table()->is_asymmetric());
14,637!
1578
    // Incoming links to asymmetric objects are disallowed.
1579
    REALM_ASSERT(!t.is_asymmetric());
14,637✔
1580
    TableKey target_table_key = t.get_key();
14,637✔
1581
    auto result = t.is_embedded() ? t.create_linked_object() : t.create_object();
14,637✔
1582
    auto target_key = result.get_key();
14,637✔
1583
    ObjKey old_key = get<ObjKey>(col_key); // Will update if needed
14,637✔
1584
    if (old_key != ObjKey()) {
14,637✔
1585
        if (t.is_embedded()) {
48✔
1586
            // If this is an embedded object and there was already an embedded object here, then we need to
1587
            // emit an instruction to set the old embedded object to null to clear the old object on other
1588
            // sync clients. Without this, you'll only see the Set ObjectValue instruction, which is idempotent,
1589
            // and then array operations will have a corrupted prior_size.
1590
            if (Replication* repl = get_replication()) {
48✔
1591
                repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
24✔
1592
                          is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
24✔
1593
            }
24✔
1594
        }
48✔
1595
    }
48✔
1596

1597
    REALM_ASSERT(target_key != old_key); // We will always create a new object
14,637✔
1598
    CascadeState state;
14,637✔
1599

1600
    bool recurse = replace_backlink(col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
14,637✔
1601
    _update_if_needed();
14,637✔
1602

1603
    Allocator& alloc = get_alloc();
14,637✔
1604
    alloc.bump_content_version();
14,637✔
1605
    Array fallback(alloc);
14,637✔
1606
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
14,637✔
1607
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
14,637✔
1608
    ArrayKey values(alloc);
14,637✔
1609
    values.set_parent(&fields, col_ndx.val + 1);
14,637✔
1610
    values.init_from_parent();
14,637✔
1611

1612
    values.set(m_row_ndx, target_key);
14,637✔
1613

1614
    sync(fields);
14,637✔
1615

1616
    if (Replication* repl = get_replication()) {
14,637✔
1617
        repl->set(m_table.unchecked_ptr(), col_key, m_key, target_key,
13,989✔
1618
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
13,989✔
1619
    }
13,989✔
1620

1621
    if (recurse)
14,637✔
1622
        target_table->remove_recursive(state);
48✔
1623

1624
    return result;
14,637✔
1625
}
14,637✔
1626

1627
namespace {
1628
template <class T>
1629
inline void check_range(const T&)
1630
{
2,795,142✔
1631
}
2,795,142✔
1632
template <>
1633
inline void check_range(const StringData& val)
1634
{
1,794,933✔
1635
    if (REALM_UNLIKELY(val.size() > Table::max_string_size))
1,794,933✔
1636
        throw LogicError(ErrorCodes::LimitExceeded, "String too big");
6✔
1637
}
1,794,933✔
1638
template <>
1639
inline void check_range(const BinaryData& val)
1640
{
5,172,138✔
1641
    if (REALM_UNLIKELY(val.size() > ArrayBlob::max_binary_size))
5,172,138✔
1642
        throw LogicError(ErrorCodes::LimitExceeded, "Binary too big");
6✔
1643
}
5,172,138✔
1644
} // namespace
1645

1646
#if REALM_ENABLE_GEOSPATIAL
1647

1648
template <>
1649
Obj& Obj::set(ColKey col_key, Geospatial value, bool is_default)
1650
{
246✔
1651
    checked_update_if_needed();
246✔
1652
    get_table()->check_column(col_key);
246✔
1653
    auto type = col_key.get_type();
246✔
1654

1655
    if (type != ColumnTypeTraits<Link>::column_id)
246✔
1656
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1657
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1658
                                           get_table()->get_column_name(col_key)));
6✔
1659

1660
    Obj geo = get_linked_object(col_key);
240✔
1661
    if (!geo) {
240✔
1662
        geo = create_and_set_linked_object(col_key, is_default);
216✔
1663
    }
216✔
1664
    value.assign_to(geo);
240✔
1665
    return *this;
240✔
1666
}
246✔
1667

1668
template <>
1669
Obj& Obj::set(ColKey col_key, std::optional<Geospatial> value, bool is_default)
1670
{
12✔
1671
    checked_update_if_needed();
12✔
1672
    auto table = get_table();
12✔
1673
    table->check_column(col_key);
12✔
1674
    auto type = col_key.get_type();
12✔
1675
    auto attrs = col_key.get_attrs();
12✔
1676

1677
    if (type != ColumnTypeTraits<Link>::column_id)
12✔
1678
        throw InvalidArgument(ErrorCodes::TypeMismatch,
6✔
1679
                              util::format("Property '%1' must be a link to set a Geospatial value",
6✔
1680
                                           get_table()->get_column_name(col_key)));
6✔
1681
    if (!value && !attrs.test(col_attr_Nullable))
6✔
1682
        throw NotNullable(Group::table_name_to_class_name(table->get_name()), table->get_column_name(col_key));
×
1683

1684
    if (!value) {
6✔
1685
        set_null(col_key, is_default);
6✔
1686
    }
6✔
1687
    else {
×
1688
        Obj geo = get_linked_object(col_key);
×
1689
        if (!geo) {
×
1690
            geo = create_and_set_linked_object(col_key, is_default);
×
1691
        }
×
1692
        value->assign_to(geo);
×
1693
    }
×
1694
    return *this;
6✔
1695
}
6✔
1696

1697
#endif
1698

1699
template <class T>
1700
Obj& Obj::set(ColKey col_key, T value, bool is_default)
1701
{
9,762,030✔
1702
    checked_update_if_needed();
9,762,030✔
1703
    get_table()->check_column(col_key);
9,762,030✔
1704
    auto type = col_key.get_type();
9,762,030✔
1705
    auto attrs = col_key.get_attrs();
9,762,030✔
1706
    auto col_ndx = col_key.get_index();
9,762,030✔
1707

1708
    if (type != ColumnTypeTraits<T>::column_id)
9,762,030✔
1709
        throw InvalidArgument(ErrorCodes::TypeMismatch,
×
1710
                              util::format("Property not a %1", ColumnTypeTraits<T>::column_id));
×
1711
    if (value_is_null(value) && !attrs.test(col_attr_Nullable))
9,762,030!
1712
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
6✔
1713

1714
    check_range(value);
9,762,024✔
1715

1716
    SearchIndex* index = m_table->get_search_index(col_key);
9,762,024✔
1717
    if (index && !m_key.is_unresolved()) {
9,762,024✔
1718
        index->set(m_key, value);
333,210✔
1719
    }
333,210✔
1720

1721
    Allocator& alloc = get_alloc();
9,762,024✔
1722
    alloc.bump_content_version();
9,762,024✔
1723
    Array fallback(alloc);
9,762,024✔
1724
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
9,762,024✔
1725
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
9,762,024✔
1726
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
9,762,024✔
1727
    LeafType values(alloc);
9,762,024✔
1728
    values.set_parent(&fields, col_ndx.val + 1);
9,762,024✔
1729
    set_string_interner<LeafType>(values, col_key);
9,762,024✔
1730
    values.init_from_parent();
9,762,024✔
1731
    values.set(m_row_ndx, value);
9,762,024✔
1732

1733
    sync(fields);
9,762,024✔
1734

1735
    if (Replication* repl = get_replication())
9,762,024✔
1736
        repl->set(m_table.unchecked_ptr(), col_key, m_key, value,
7,292,622✔
1737
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
7,292,622✔
1738

1739
    return *this;
9,762,024✔
1740
}
9,762,030✔
1741

1742
#define INSTANTIATE_OBJ_SET(T) template Obj& Obj::set<T>(ColKey, T, bool)
1743
INSTANTIATE_OBJ_SET(bool);
1744
INSTANTIATE_OBJ_SET(StringData);
1745
INSTANTIATE_OBJ_SET(float);
1746
INSTANTIATE_OBJ_SET(double);
1747
INSTANTIATE_OBJ_SET(Decimal128);
1748
INSTANTIATE_OBJ_SET(Timestamp);
1749
INSTANTIATE_OBJ_SET(BinaryData);
1750
INSTANTIATE_OBJ_SET(ObjectId);
1751
INSTANTIATE_OBJ_SET(UUID);
1752

1753
void Obj::set_int(ColKey::Idx col_ndx, int64_t value)
1754
{
606,816✔
1755
    checked_update_if_needed();
606,816✔
1756

1757
    Allocator& alloc = get_alloc();
606,816✔
1758
    alloc.bump_content_version();
606,816✔
1759
    Array fallback(alloc);
606,816✔
1760
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
606,816✔
1761
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
606,816✔
1762
    Array values(alloc);
606,816✔
1763
    values.set_parent(&fields, col_ndx.val + 1);
606,816✔
1764
    values.init_from_parent();
606,816✔
1765
    values.set(m_row_ndx, value);
606,816✔
1766

1767
    sync(fields);
606,816✔
1768
}
606,816✔
1769

1770
void Obj::set_ref(ColKey::Idx col_ndx, ref_type value, CollectionType type)
1771
{
149,580✔
1772
    checked_update_if_needed();
149,580✔
1773

1774
    Allocator& alloc = get_alloc();
149,580✔
1775
    alloc.bump_content_version();
149,580✔
1776
    Array fallback(alloc);
149,580✔
1777
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
149,580✔
1778
    REALM_ASSERT(col_ndx.val + 1 < fields.size());
149,580✔
1779
    ArrayMixed values(alloc);
149,580✔
1780
    values.set_parent(&fields, col_ndx.val + 1);
149,580✔
1781
    values.init_from_parent();
149,580✔
1782
    values.set(m_row_ndx, Mixed(value, type));
149,580✔
1783

1784
    sync(fields);
149,580✔
1785
}
149,580✔
1786

1787
void Obj::add_backlink(ColKey backlink_col_key, ObjKey origin_key)
1788
{
7,067,547✔
1789
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
7,067,547✔
1790
    Allocator& alloc = get_alloc();
7,067,547✔
1791
    alloc.bump_content_version();
7,067,547✔
1792
    Array fallback(alloc);
7,067,547✔
1793
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
7,067,547✔
1794

1795
    ArrayBacklink backlinks(alloc);
7,067,547✔
1796
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
7,067,547✔
1797
    backlinks.init_from_parent();
7,067,547✔
1798

1799
    backlinks.add(m_row_ndx, origin_key);
7,067,547✔
1800

1801
    sync(fields);
7,067,547✔
1802
}
7,067,547✔
1803

1804
bool Obj::remove_one_backlink(ColKey backlink_col_key, ObjKey origin_key)
1805
{
514,239✔
1806
    ColKey::Idx backlink_col_ndx = backlink_col_key.get_index();
514,239✔
1807
    Allocator& alloc = get_alloc();
514,239✔
1808
    alloc.bump_content_version();
514,239✔
1809
    Array fallback(alloc);
514,239✔
1810
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
514,239✔
1811

1812
    ArrayBacklink backlinks(alloc);
514,239✔
1813
    backlinks.set_parent(&fields, backlink_col_ndx.val + 1);
514,239✔
1814
    backlinks.init_from_parent();
514,239✔
1815

1816
    bool ret = backlinks.remove(m_row_ndx, origin_key);
514,239✔
1817

1818
    sync(fields);
514,239✔
1819

1820
    return ret;
514,239✔
1821
}
514,239✔
1822

1823
template <class ValueType>
1824
inline void Obj::nullify_single_link(ColKey col, ValueType target)
1825
{
903✔
1826
    ColKey::Idx origin_col_ndx = col.get_index();
903✔
1827
    Allocator& alloc = get_alloc();
903✔
1828
    Array fallback(alloc);
903✔
1829
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
903✔
1830
    using ArrayType = typename ColumnTypeTraits<ValueType>::cluster_leaf_type;
903✔
1831
    ArrayType links(alloc);
903✔
1832
    links.set_parent(&fields, origin_col_ndx.val + 1);
903✔
1833
    links.init_from_parent();
903✔
1834
    // Ensure we are nullifying correct link
1835
    REALM_ASSERT(links.get(m_row_ndx) == target);
903✔
1836
    links.set(m_row_ndx, ValueType{});
903✔
1837
    sync(fields);
903✔
1838

1839
    if (Replication* repl = get_replication())
903✔
1840
        repl->nullify_link(m_table.unchecked_ptr(), col,
771✔
1841
                           m_key); // Throws
771✔
1842
}
903✔
1843

1844
template <>
1845
inline void Obj::nullify_single_link<Mixed>(ColKey col, Mixed target)
1846
{
30✔
1847
    ColKey::Idx origin_col_ndx = col.get_index();
30✔
1848
    Allocator& alloc = get_alloc();
30✔
1849
    Array fallback(alloc);
30✔
1850
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
30✔
1851
    ArrayMixed mixed(alloc);
30✔
1852
    mixed.set_parent(&fields, origin_col_ndx.val + 1);
30✔
1853
    mixed.init_from_parent();
30✔
1854
    auto val = mixed.get(m_row_ndx);
30✔
1855
    bool result = false;
30✔
1856
    if (val.is_type(type_TypedLink)) {
30✔
1857
        // Ensure we are nullifying correct link
1858
        result = (val == target);
12✔
1859
        mixed.set(m_row_ndx, Mixed{});
12✔
1860
        sync(fields);
12✔
1861

1862
        if (Replication* repl = get_replication())
12✔
1863
            repl->nullify_link(m_table.unchecked_ptr(), col,
×
1864
                               m_key); // Throws
×
1865
    }
12✔
1866
    else if (val.is_type(type_Dictionary)) {
18✔
1867
        Dictionary dict(*this, col);
18✔
1868
        result = dict.nullify(target.get_link());
18✔
1869
    }
18✔
1870
    else if (val.is_type(type_List)) {
×
1871
        Lst<Mixed> list(*this, col);
×
1872
        result = list.nullify(target.get_link());
×
1873
    }
×
1874
    REALM_ASSERT(result);
30✔
1875
    static_cast<void>(result);
30✔
1876
}
30✔
1877

1878
void Obj::nullify_link(ColKey origin_col_key, ObjLink target_link) &&
1879
{
6,999✔
1880
    REALM_ASSERT(get_alloc().get_storage_version() == m_storage_version);
6,999✔
1881

1882
    struct LinkNullifier : public LinkTranslator {
6,999✔
1883
        LinkNullifier(Obj origin_obj, ColKey origin_col, ObjLink target)
6,999✔
1884
            : LinkTranslator(origin_obj, origin_col)
6,999✔
1885
            , m_target_link(target)
6,999✔
1886
        {
6,999✔
1887
        }
6,999✔
1888
        void on_list_of_links(LnkLst&) final
6,999✔
1889
        {
6,999✔
1890
            nullify_linklist(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,310✔
1891
        }
2,310✔
1892
        void on_list_of_mixed(Lst<Mixed>& list) final
6,999✔
1893
        {
6,999✔
1894
            list.nullify(m_target_link);
474✔
1895
        }
474✔
1896
        void on_set_of_links(LnkSet&) final
6,999✔
1897
        {
6,999✔
1898
            nullify_set(m_origin_obj, m_origin_col_key, m_target_link.get_obj_key());
2,262✔
1899
        }
2,262✔
1900
        void on_set_of_mixed(Set<Mixed>&) final
6,999✔
1901
        {
6,999✔
1902
            nullify_set(m_origin_obj, m_origin_col_key, Mixed(m_target_link));
300✔
1903
        }
300✔
1904
        void on_dictionary(Dictionary& dict) final
6,999✔
1905
        {
6,999✔
1906
            dict.nullify(m_target_link);
720✔
1907
        }
720✔
1908
        void on_link_property(ColKey origin_col_key) final
6,999✔
1909
        {
6,999✔
1910
            m_origin_obj.nullify_single_link<ObjKey>(origin_col_key, m_target_link.get_obj_key());
903✔
1911
        }
903✔
1912
        void on_mixed_property(ColKey origin_col_key) final
6,999✔
1913
        {
6,999✔
1914
            m_origin_obj.nullify_single_link<Mixed>(origin_col_key, Mixed{m_target_link});
30✔
1915
        }
30✔
1916

1917
    private:
6,999✔
1918
        ObjLink m_target_link;
6,999✔
1919
    } nullifier{*this, origin_col_key, target_link};
6,999✔
1920

1921
    nullifier.run();
6,999✔
1922

1923
    get_alloc().bump_content_version();
6,999✔
1924
}
6,999✔
1925

1926

1927
struct EmbeddedObjectLinkMigrator : public LinkTranslator {
1928
    EmbeddedObjectLinkMigrator(Obj origin, ColKey origin_col, Obj dest_orig, Obj dest_replace)
1929
        : LinkTranslator(origin, origin_col)
30,159✔
1930
        , m_dest_orig(dest_orig)
30,159✔
1931
        , m_dest_replace(dest_replace)
30,159✔
1932
    {
60,318✔
1933
    }
60,318✔
1934
    void on_list_of_links(LnkLst& list) final
1935
    {
168✔
1936
        auto n = list.find_first(m_dest_orig.get_key());
168✔
1937
        REALM_ASSERT(n != realm::npos);
168✔
1938
        list.set(n, m_dest_replace.get_key());
168✔
1939
    }
168✔
1940
    void on_dictionary(Dictionary& dict) final
1941
    {
60✔
1942
        auto pos = dict.find_any(m_dest_orig.get_link());
60✔
1943
        REALM_ASSERT(pos != realm::npos);
60✔
1944
        Mixed key = dict.get_key(pos);
60✔
1945
        dict.insert(key, m_dest_replace.get_link());
60✔
1946
    }
60✔
1947
    void on_link_property(ColKey col) final
1948
    {
60,090✔
1949
        REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
60,090✔
1950
        m_origin_obj.set(col, m_dest_replace.get_key());
60,090✔
1951
    }
60,090✔
1952
    void on_set_of_links(LnkSet&) final
1953
    {
×
1954
        // this should never happen because sets of embedded objects are not allowed at the schema level
1955
        REALM_UNREACHABLE();
1956
    }
×
1957
    // The following cases have support here but are expected to fail later on in the
1958
    // migration due to core not yet supporting untyped Mixed links to embedded objects.
1959
    void on_set_of_mixed(Set<Mixed>& set) final
1960
    {
×
1961
        auto did_erase_pair = set.erase(m_dest_orig.get_link());
×
1962
        REALM_ASSERT(did_erase_pair.second);
×
1963
        set.insert(m_dest_replace.get_link());
×
1964
    }
×
1965
    void on_list_of_mixed(Lst<Mixed>& list) final
1966
    {
×
1967
        auto n = list.find_any(m_dest_orig.get_link());
×
1968
        REALM_ASSERT(n != realm::npos);
×
1969
        list.insert_any(n, m_dest_replace.get_link());
×
1970
    }
×
1971
    void on_mixed_property(ColKey col) final
1972
    {
×
1973
        REALM_ASSERT(m_origin_obj.get<Mixed>(col).is_null() ||
×
1974
                     m_origin_obj.get<Mixed>(col) == m_dest_orig.get_link());
×
1975
        m_origin_obj.set_any(col, m_dest_replace.get_link());
×
1976
    }
×
1977

1978
private:
1979
    Obj m_dest_orig;
1980
    Obj m_dest_replace;
1981
};
1982

1983
void Obj::handle_multiple_backlinks_during_schema_migration()
1984
{
54✔
1985
    REALM_ASSERT(!m_table->get_primary_key_column());
54✔
1986
    converters::EmbeddedObjectConverter embedded_obj_tracker;
54✔
1987
    auto copy_links = [&](ColKey col) {
108✔
1988
        auto opposite_table = m_table->get_opposite_table(col);
108✔
1989
        auto opposite_column = m_table->get_opposite_column(col);
108✔
1990
        auto backlinks = get_all_backlinks(col);
108✔
1991
        for (auto backlink : backlinks) {
60,318✔
1992
            // create a new obj
1993
            auto obj = m_table->create_object();
60,318✔
1994
            embedded_obj_tracker.track(*this, obj);
60,318✔
1995
            auto linking_obj = opposite_table->get_object(backlink);
60,318✔
1996
            // change incoming links to point to the newly created object
1997
            EmbeddedObjectLinkMigrator{linking_obj, opposite_column, *this, obj}.run();
60,318✔
1998
        }
60,318✔
1999
        embedded_obj_tracker.process_pending();
108✔
2000
        return IteratorControl::AdvanceToNext;
108✔
2001
    };
108✔
2002
    m_table->for_each_backlink_column(copy_links);
54✔
2003
}
54✔
2004

2005
LstBasePtr Obj::get_listbase_ptr(ColKey col_key) const
2006
{
328,914✔
2007
    auto list = CollectionParent::get_listbase_ptr(col_key, 0);
328,914✔
2008
    list->set_owner(*this, col_key);
328,914✔
2009
    return list;
328,914✔
2010
}
328,914✔
2011

2012
SetBasePtr Obj::get_setbase_ptr(ColKey col_key) const
2013
{
43,548✔
2014
    auto set = CollectionParent::get_setbase_ptr(col_key, 0);
43,548✔
2015
    set->set_owner(*this, col_key);
43,548✔
2016
    return set;
43,548✔
2017
}
43,548✔
2018

2019
Dictionary Obj::get_dictionary(ColKey col_key) const
2020
{
309,972✔
2021
    REALM_ASSERT(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed);
309,972✔
2022
    checked_update_if_needed();
309,972✔
2023
    return Dictionary(Obj(*this), col_key);
309,972✔
2024
}
309,972✔
2025

2026
Obj& Obj::set_collection(ColKey col_key, CollectionType type)
2027
{
148,500✔
2028
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
148,500✔
2029
    if ((col_key.is_dictionary() && type == CollectionType::Dictionary) ||
148,500✔
2030
        (col_key.is_list() && type == CollectionType::List)) {
148,500✔
2031
        return *this;
30✔
2032
    }
30✔
2033
    if (type == CollectionType::Set) {
148,470✔
2034
        throw IllegalOperation("Set nested in Mixed is not supported");
×
2035
    }
×
2036
    set(col_key, Mixed(0, type));
148,470✔
2037

2038
    return *this;
148,470✔
2039
}
148,470✔
2040

2041
DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const
2042
{
570✔
2043
    return std::make_shared<Dictionary>(get_dictionary(col_key));
570✔
2044
}
570✔
2045

2046
DictionaryPtr Obj::get_dictionary_ptr(const Path& path) const
2047
{
216✔
2048
    return std::dynamic_pointer_cast<Dictionary>(get_collection_ptr(path));
216✔
2049
}
216✔
2050

2051
Dictionary Obj::get_dictionary(StringData col_name) const
2052
{
16,632✔
2053
    return get_dictionary(get_column_key(col_name));
16,632✔
2054
}
16,632✔
2055

2056
CollectionPtr Obj::get_collection_ptr(const Path& path) const
2057
{
12,000✔
2058
    REALM_ASSERT(path.size() > 0);
12,000✔
2059
    // First element in path must be column name
2060
    auto col_key = path[0].is_col_key() ? path[0].get_col_key() : m_table->get_column_key(path[0].get_key());
12,000✔
2061
    REALM_ASSERT(col_key);
12,000✔
2062
    size_t level = 1;
12,000✔
2063
    CollectionBasePtr collection = get_collection_ptr(col_key);
12,000✔
2064

2065
    while (level < path.size()) {
13,182✔
2066
        auto& path_elem = path[level];
1,182✔
2067
        Mixed ref;
1,182✔
2068
        if (collection->get_collection_type() == CollectionType::List) {
1,182✔
2069
            ref = collection->get_any(path_elem.get_ndx());
462✔
2070
        }
462✔
2071
        else {
720✔
2072
            ref = dynamic_cast<Dictionary*>(collection.get())->get(path_elem.get_key());
720✔
2073
        }
720✔
2074
        if (ref.is_type(type_List)) {
1,182✔
2075
            collection = collection->get_list(path_elem);
762✔
2076
        }
762✔
2077
        else if (ref.is_type(type_Dictionary)) {
420✔
2078
            collection = collection->get_dictionary(path_elem);
420✔
2079
        }
420✔
2080
        else {
×
2081
            throw InvalidArgument("Wrong path");
×
2082
        }
×
2083
        level++;
1,182✔
2084
    }
1,182✔
2085

2086
    return collection;
12,000✔
2087
}
12,000✔
2088

2089
CollectionPtr Obj::get_collection_by_stable_path(const StablePath& path) const
2090
{
10,551✔
2091
    // First element in path is phony column key
2092
    ColKey col_key = m_table->get_column_key(path[0]);
10,551✔
2093
    size_t level = 1;
10,551✔
2094
    CollectionBasePtr collection = get_collection_ptr(col_key);
10,551✔
2095

2096
    while (level < path.size()) {
11,187✔
2097
        auto& index = path[level];
648✔
2098
        auto get_ref = [&]() -> std::pair<Mixed, PathElement> {
648✔
2099
            Mixed ref;
648✔
2100
            PathElement path_elem;
648✔
2101
            if (collection->get_collection_type() == CollectionType::List) {
648✔
2102
                auto list_of_mixed = dynamic_cast<Lst<Mixed>*>(collection.get());
396✔
2103
                size_t ndx = list_of_mixed->find_index(index);
396✔
2104
                if (ndx != realm::not_found) {
396✔
2105
                    ref = list_of_mixed->get(ndx);
396✔
2106
                    path_elem = ndx;
396✔
2107
                }
396✔
2108
            }
396✔
2109
            else {
252✔
2110
                auto dict = dynamic_cast<Dictionary*>(collection.get());
252✔
2111
                size_t ndx = dict->find_index(index);
252✔
2112
                if (ndx != realm::not_found) {
252✔
2113
                    ref = dict->get_any(ndx);
240✔
2114
                    path_elem = dict->get_key(ndx).get_string();
240✔
2115
                }
240✔
2116
            }
252✔
2117
            return {ref, path_elem};
648✔
2118
        };
648✔
2119
        auto [ref, path_elem] = get_ref();
648✔
2120
        if (ref.is_type(type_List)) {
648✔
2121
            collection = collection->get_list(path_elem);
480✔
2122
        }
480✔
2123
        else if (ref.is_type(type_Dictionary)) {
168✔
2124
            collection = collection->get_dictionary(path_elem);
156✔
2125
        }
156✔
2126
        else {
12✔
2127
            return nullptr;
12✔
2128
        }
12✔
2129
        level++;
636✔
2130
    }
636✔
2131

2132
    return collection;
10,539✔
2133
}
10,551✔
2134

2135
CollectionBasePtr Obj::get_collection_ptr(ColKey col_key) const
2136
{
131,796✔
2137
    if (col_key.is_collection()) {
131,796✔
2138
        auto collection = CollectionParent::get_collection_ptr(col_key, 0);
127,722✔
2139
        collection->set_owner(*this, col_key);
127,722✔
2140
        return collection;
127,722✔
2141
    }
127,722✔
2142
    REALM_ASSERT(col_key.get_type() == col_type_Mixed);
4,074✔
2143
    auto val = get<Mixed>(col_key);
4,074✔
2144
    if (val.is_type(type_List)) {
4,074✔
2145
        return std::make_shared<Lst<Mixed>>(*this, col_key);
1,974✔
2146
    }
1,974✔
2147
    REALM_ASSERT(val.is_type(type_Dictionary));
2,100✔
2148
    return std::make_shared<Dictionary>(*this, col_key);
2,100✔
2149
}
4,074✔
2150

2151
CollectionBasePtr Obj::get_collection_ptr(StringData col_name) const
2152
{
438✔
2153
    return get_collection_ptr(get_column_key(col_name));
438✔
2154
}
438✔
2155

2156
LinkCollectionPtr Obj::get_linkcollection_ptr(ColKey col_key) const
2157
{
3,114✔
2158
    if (col_key.is_list()) {
3,114✔
2159
        return get_linklist_ptr(col_key);
2,994✔
2160
    }
2,994✔
2161
    else if (col_key.is_set()) {
120✔
2162
        return get_linkset_ptr(col_key);
78✔
2163
    }
78✔
2164
    else if (col_key.is_dictionary()) {
42✔
2165
        auto dict = get_dictionary(col_key);
42✔
2166
        return std::make_unique<DictionaryLinkValues>(dict);
42✔
2167
    }
42✔
2168
    return {};
×
2169
}
3,114✔
2170

2171
template <class T>
2172
inline void replace_in_linkset(Obj& obj, ColKey origin_col_key, T target, T replacement)
2173
{
288✔
2174
    Set<T> link_set(origin_col_key);
288✔
2175
    size_t ndx = find_link_value_in_collection(link_set, obj, origin_col_key, target);
288✔
2176

2177
    REALM_ASSERT(ndx != realm::npos); // There has to be one
288✔
2178

2179
    link_set.erase(target);
288✔
2180
    link_set.insert(replacement);
288✔
2181
}
288✔
2182

2183
inline void replace_in_dictionary(Obj& obj, ColKey origin_col_key, Mixed target, Mixed replacement)
2184
{
×
2185
    Dictionary dict(origin_col_key);
×
2186
    size_t ndx = find_link_value_in_collection(dict, obj, origin_col_key, target);
×
2187

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

×
2190
    auto key = dict.get_key(ndx);
×
2191
    dict.insert(key, replacement);
×
2192
}
×
2193

2194
void Obj::assign_pk_and_backlinks(Obj& other)
2195
{
40,362✔
2196
    struct LinkReplacer : LinkTranslator {
40,362✔
2197
        LinkReplacer(Obj origin, ColKey origin_col_key, const Obj& dest_orig, const Obj& dest_replace)
40,362✔
2198
            : LinkTranslator(origin, origin_col_key)
40,362✔
2199
            , m_dest_orig(dest_orig)
40,362✔
2200
            , m_dest_replace(dest_replace)
40,362✔
2201
        {
40,362✔
2202
        }
28,326✔
2203
        void on_list_of_links(LnkLst&) final
40,362✔
2204
        {
40,362✔
2205
            auto linklist = m_origin_obj.get_linklist(m_origin_col_key);
27,414✔
2206
            linklist.replace_link(m_dest_orig.get_key(), m_dest_replace.get_key());
27,414✔
2207
        }
27,414✔
2208
        void on_list_of_mixed(Lst<Mixed>& list) final
40,362✔
2209
        {
40,362✔
2210
            list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
126✔
2211
        }
126✔
2212
        void on_set_of_links(LnkSet&) final
40,362✔
2213
        {
40,362✔
2214
            replace_in_linkset(m_origin_obj, m_origin_col_key, m_dest_orig.get_key(), m_dest_replace.get_key());
138✔
2215
        }
138✔
2216
        void on_set_of_mixed(Set<Mixed>&) final
40,362✔
2217
        {
40,362✔
2218
            replace_in_linkset<Mixed>(m_origin_obj, m_origin_col_key, m_dest_orig.get_link(),
150✔
2219
                                      m_dest_replace.get_link());
150✔
2220
        }
150✔
2221
        void on_dictionary(Dictionary& dict) final
40,362✔
2222
        {
40,362✔
2223
            dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
252✔
2224
        }
252✔
2225
        void on_link_property(ColKey col) final
40,362✔
2226
        {
40,362✔
2227
            REALM_ASSERT(!m_origin_obj.get<ObjKey>(col) || m_origin_obj.get<ObjKey>(col) == m_dest_orig.get_key());
180✔
2228
            // Handle links as plain integers. Backlinks has been taken care of.
2229
            // Be careful here - links are stored as value + 1 so that null link (-1) will be 0
2230
            auto new_key = m_dest_replace.get_key();
180✔
2231
            m_origin_obj.set_int(col.get_index(), new_key.value + 1);
180✔
2232
            if (Replication* repl = m_origin_obj.get_replication())
180✔
2233
                repl->set(m_origin_obj.get_table().unchecked_ptr(), col, m_origin_obj.get_key(), new_key);
132✔
2234
        }
180✔
2235
        void on_mixed_property(ColKey col) final
40,362✔
2236
        {
40,362✔
2237
            auto val = m_origin_obj.get_any(col);
66✔
2238
            if (val.is_type(type_Dictionary)) {
66✔
2239
                Dictionary dict(m_origin_obj, m_origin_col_key);
12✔
2240
                dict.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2241
            }
12✔
2242
            else if (val.is_type(type_List)) {
54✔
2243
                Lst<Mixed> list(m_origin_obj, m_origin_col_key);
12✔
2244
                list.replace_link(m_dest_orig.get_link(), m_dest_replace.get_link());
12✔
2245
            }
12✔
2246
            else {
42✔
2247
                REALM_ASSERT(val.is_null() || val.get_link().get_obj_key() == m_dest_orig.get_key());
42✔
2248
                m_origin_obj.set(col, Mixed{m_dest_replace.get_link()});
42✔
2249
            }
42✔
2250
        }
66✔
2251

2252
    private:
40,362✔
2253
        const Obj& m_dest_orig;
40,362✔
2254
        const Obj& m_dest_replace;
40,362✔
2255
    };
40,362✔
2256

2257
    REALM_ASSERT(get_table() == other.get_table());
40,362✔
2258
    if (auto col_pk = m_table->get_primary_key_column()) {
40,362✔
2259
        Mixed val = other.get_any(col_pk);
40,200✔
2260
        this->set_any(col_pk, val);
40,200✔
2261
    }
40,200✔
2262
    auto nb_tombstones = m_table->m_tombstones->size();
40,362✔
2263

2264
    auto copy_links = [this, &other, nb_tombstones](ColKey col) {
40,362✔
2265
        if (nb_tombstones != m_table->m_tombstones->size()) {
10,554✔
2266
            // Object has been deleted - we are done
2267
            return IteratorControl::Stop;
×
2268
        }
×
2269

2270
        auto t = m_table->get_opposite_table(col);
10,554✔
2271
        auto c = m_table->get_opposite_column(col);
10,554✔
2272
        auto backlinks = other.get_all_backlinks(col);
10,554✔
2273

2274
        if (c.get_type() == col_type_Link && !(c.is_dictionary() || c.is_set())) {
10,554✔
2275
            auto idx = col.get_index();
9,882✔
2276
            // Transfer the backlinks from tombstone to live object
2277
            REALM_ASSERT(_get<int64_t>(idx) == 0);
9,882✔
2278
            auto other_val = other._get<int64_t>(idx);
9,882✔
2279
            set_int(idx, other_val);
9,882✔
2280
            other.set_int(idx, 0);
9,882✔
2281
        }
9,882✔
2282

2283
        for (auto bl : backlinks) {
28,326✔
2284
            auto linking_obj = t->get_object(bl);
28,326✔
2285
            LinkReplacer replacer{linking_obj, c, other, *this};
28,326✔
2286
            replacer.run();
28,326✔
2287
        }
28,326✔
2288
        return IteratorControl::AdvanceToNext;
10,554✔
2289
    };
10,554✔
2290
    m_table->for_each_backlink_column(copy_links);
40,362✔
2291
}
40,362✔
2292

2293
template util::Optional<int64_t> Obj::get<util::Optional<int64_t>>(ColKey col_key) const;
2294
template util::Optional<Bool> Obj::get<util::Optional<Bool>>(ColKey col_key) const;
2295
template float Obj::get<float>(ColKey col_key) const;
2296
template util::Optional<float> Obj::get<util::Optional<float>>(ColKey col_key) const;
2297
template double Obj::get<double>(ColKey col_key) const;
2298
template util::Optional<double> Obj::get<util::Optional<double>>(ColKey col_key) const;
2299
template StringData Obj::get<StringData>(ColKey col_key) const;
2300
template BinaryData Obj::get<BinaryData>(ColKey col_key) const;
2301
template Timestamp Obj::get<Timestamp>(ColKey col_key) const;
2302
template ObjectId Obj::get<ObjectId>(ColKey col_key) const;
2303
template util::Optional<ObjectId> Obj::get<util::Optional<ObjectId>>(ColKey col_key) const;
2304
template ObjKey Obj::get<ObjKey>(ColKey col_key) const;
2305
template Decimal128 Obj::get<Decimal128>(ColKey col_key) const;
2306
template ObjLink Obj::get<ObjLink>(ColKey col_key) const;
2307
template Mixed Obj::get<Mixed>(realm::ColKey) const;
2308
template UUID Obj::get<UUID>(realm::ColKey) const;
2309
template util::Optional<UUID> Obj::get<util::Optional<UUID>>(ColKey col_key) const;
2310

2311
template <class T>
2312
inline void Obj::do_set_null(ColKey col_key)
2313
{
39,237✔
2314
    ColKey::Idx col_ndx = col_key.get_index();
39,237✔
2315
    Allocator& alloc = get_alloc();
39,237✔
2316
    alloc.bump_content_version();
39,237✔
2317
    Array fallback(alloc);
39,237✔
2318
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
39,237✔
2319

2320
    T values(alloc);
39,237✔
2321
    values.set_parent(&fields, col_ndx.val + 1);
39,237✔
2322
    values.init_from_parent();
39,237✔
2323
    values.set_null(m_row_ndx);
39,237✔
2324

2325
    sync(fields);
39,237✔
2326
}
39,237✔
2327

2328
template <>
2329
inline void Obj::do_set_null<ArrayString>(ColKey col_key)
2330
{
2,493✔
2331
    ColKey::Idx col_ndx = col_key.get_index();
2,493✔
2332
    Allocator& alloc = get_alloc();
2,493✔
2333
    alloc.bump_content_version();
2,493✔
2334
    Array fallback(alloc);
2,493✔
2335
    Array& fields = get_tree_top()->get_fields_accessor(fallback, m_mem);
2,493✔
2336

2337
    ArrayString values(alloc);
2,493✔
2338
    values.set_parent(&fields, col_ndx.val + 1);
2,493✔
2339
    values.set_string_interner(m_table->get_string_interner(col_key));
2,493✔
2340
    values.init_from_parent();
2,493✔
2341
    values.set_null(m_row_ndx);
2,493✔
2342

2343
    sync(fields);
2,493✔
2344
}
2,493✔
2345

2346
Obj& Obj::set_null(ColKey col_key, bool is_default)
2347
{
42,282✔
2348
    ColumnType col_type = col_key.get_type();
42,282✔
2349
    // Links need special handling
2350
    if (col_type == col_type_Link) {
42,282✔
2351
        return set(col_key, null_key, is_default);
408✔
2352
    }
408✔
2353
    if (col_type == col_type_Mixed) {
41,874✔
2354
        return set(col_key, Mixed{}, is_default);
78✔
2355
    }
78✔
2356

2357
    auto attrs = col_key.get_attrs();
41,796✔
2358
    if (REALM_UNLIKELY(!attrs.test(col_attr_Nullable))) {
41,796✔
2359
        throw NotNullable(Group::table_name_to_class_name(m_table->get_name()), m_table->get_column_name(col_key));
66✔
2360
    }
66✔
2361

2362
    checked_update_if_needed();
41,730✔
2363

2364
    SearchIndex* index = m_table->get_search_index(col_key);
41,730✔
2365
    if (index && !m_key.is_unresolved()) {
41,730✔
2366
        index->set(m_key, null{});
4,674✔
2367
    }
4,674✔
2368

2369
    switch (col_type) {
41,730✔
2370
        case col_type_Int:
5,838✔
2371
            do_set_null<ArrayIntNull>(col_key);
5,838✔
2372
            break;
5,838✔
2373
        case col_type_Bool:
5,616✔
2374
            do_set_null<ArrayBoolNull>(col_key);
5,616✔
2375
            break;
5,616✔
2376
        case col_type_Float:
6,390✔
2377
            do_set_null<ArrayFloatNull>(col_key);
6,390✔
2378
            break;
6,390✔
2379
        case col_type_Double:
6,330✔
2380
            do_set_null<ArrayDoubleNull>(col_key);
6,330✔
2381
            break;
6,330✔
2382
        case col_type_ObjectId:
3,390✔
2383
            do_set_null<ArrayObjectIdNull>(col_key);
3,390✔
2384
            break;
3,390✔
2385
        case col_type_String:
2,493✔
2386
            do_set_null<ArrayString>(col_key);
2,493✔
2387
            break;
2,493✔
2388
        case col_type_Binary:
1,248✔
2389
            do_set_null<ArrayBinary>(col_key);
1,248✔
2390
            break;
1,248✔
2391
        case col_type_Timestamp:
2,424✔
2392
            do_set_null<ArrayTimestamp>(col_key);
2,424✔
2393
            break;
2,424✔
2394
        case col_type_Decimal:
1,830✔
2395
            do_set_null<ArrayDecimal128>(col_key);
1,830✔
2396
            break;
1,830✔
2397
        case col_type_UUID:
6,171✔
2398
            do_set_null<ArrayUUIDNull>(col_key);
6,171✔
2399
            break;
6,171✔
2400
        case col_type_Mixed:
✔
2401
        case col_type_Link:
✔
2402
        case col_type_BackLink:
✔
2403
        case col_type_TypedLink:
✔
2404
            REALM_UNREACHABLE();
2405
    }
41,730✔
2406

2407
    if (Replication* repl = get_replication())
41,730✔
2408
        repl->set(m_table.unchecked_ptr(), col_key, m_key, util::none,
21,537✔
2409
                  is_default ? _impl::instr_SetDefault : _impl::instr_Set); // Throws
21,537✔
2410

2411
    return *this;
41,730✔
2412
}
41,730✔
2413

2414

2415
ColKey Obj::spec_ndx2colkey(size_t col_ndx)
2416
{
7,978,062✔
2417
    return get_table()->spec_ndx2colkey(col_ndx);
7,978,062✔
2418
}
7,978,062✔
2419

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

2425
ColKey Obj::get_primary_key_column() const
2426
{
5,266,719✔
2427
    return m_table->get_primary_key_column();
5,266,719✔
2428
}
5,266,719✔
2429

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

2435
ref_type Obj::get_collection_ref(StableIndex index, CollectionType type) const
2436
{
4,367,358✔
2437
    if (index.is_collection()) {
4,367,358✔
2438
        return to_ref(_get<int64_t>(index.get_index()));
3,557,061✔
2439
    }
3,557,061✔
2440
    if (check_index(index)) {
810,963✔
2441
        auto val = _get<Mixed>(index.get_index());
810,852✔
2442
        if (val.is_type(DataType(int(type)))) {
810,969✔
2443
            return val.get_ref();
810,948✔
2444
        }
810,948✔
2445
        throw realm::IllegalOperation(util::format("Not a %1", type));
2,147,483,668✔
2446
    }
810,852✔
2447
    throw StaleAccessor("This collection is no more");
2,147,483,758✔
2448
}
810,297✔
2449

2450
bool Obj::check_collection_ref(StableIndex index, CollectionType type) const noexcept
2451
{
1,165,254✔
2452
    if (index.is_collection()) {
1,165,254✔
2453
        return true;
1,160,382✔
2454
    }
1,160,382✔
2455
    if (check_index(index)) {
4,872✔
2456
        return _get<Mixed>(index.get_index()).is_type(DataType(int(type)));
4,866✔
2457
    }
4,866✔
2458
    return false;
6✔
2459
}
4,872✔
2460

2461
void Obj::set_collection_ref(StableIndex index, ref_type ref, CollectionType type)
2462
{
736,407✔
2463
    if (index.is_collection()) {
736,407✔
2464
        set_int(index.get_index(), from_ref(ref));
586,893✔
2465
        return;
586,893✔
2466
    }
586,893✔
2467
    set_ref(index.get_index(), ref, type);
149,514✔
2468
}
149,514✔
2469

2470
void Obj::set_backlink(ColKey col_key, ObjLink new_link) const
2471
{
7,110,339✔
2472
    if (!new_link) {
7,110,339✔
2473
        return;
42,126✔
2474
    }
42,126✔
2475

2476
    auto target_table = m_table->get_parent_group()->get_table(new_link.get_table_key());
7,068,213✔
2477
    ColKey backlink_col_key;
7,068,213✔
2478
    auto type = col_key.get_type();
7,068,213✔
2479
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
7,068,213✔
2480
        // This may modify the target table
2481
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
322,926✔
2482
        // it is possible that this was a link to the same table and that adding a backlink column has
2483
        // caused the need to update this object as well.
2484
        update_if_needed();
322,926✔
2485
    }
322,926✔
2486
    else {
6,745,287✔
2487
        backlink_col_key = m_table->get_opposite_column(col_key);
6,745,287✔
2488
    }
6,745,287✔
2489
    auto obj_key = new_link.get_obj_key();
7,068,213✔
2490
    auto target_obj =
7,068,213✔
2491
        obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key) : target_table->try_get_object(obj_key);
7,068,213✔
2492
    if (!target_obj) {
7,068,213✔
2493
        throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
12✔
2494
    }
12✔
2495
    target_obj.add_backlink(backlink_col_key, m_key);
7,068,201✔
2496
}
7,068,201✔
2497

2498
bool Obj::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
2499
{
502,254✔
2500
    bool recurse = remove_backlink(col_key, old_link, state);
502,254✔
2501
    set_backlink(col_key, new_link);
502,254✔
2502
    return recurse;
502,254✔
2503
}
502,254✔
2504

2505
bool Obj::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
2506
{
810,159✔
2507
    if (!old_link) {
810,159✔
2508
        return false;
396,471✔
2509
    }
396,471✔
2510

2511
    REALM_ASSERT(m_table->valid_column(col_key));
413,688✔
2512
    ObjKey old_key = old_link.get_obj_key();
413,688✔
2513
    auto target_obj = m_table->get_parent_group()->get_object(old_link);
413,688✔
2514
    TableRef target_table = target_obj.get_table();
413,688✔
2515
    ColKey backlink_col_key;
413,688✔
2516
    auto type = col_key.get_type();
413,688✔
2517
    if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
413,700✔
2518
        backlink_col_key = target_table->find_or_add_backlink_column(col_key, m_table->get_key());
300,492✔
2519
    }
300,492✔
2520
    else {
113,196✔
2521
        backlink_col_key = m_table->get_opposite_column(col_key);
113,196✔
2522
    }
113,196✔
2523

2524
    bool strong_links = target_table->is_embedded();
413,688✔
2525
    bool is_unres = old_key.is_unresolved();
413,688✔
2526

2527
    bool last_removed = target_obj.remove_one_backlink(backlink_col_key, m_key); // Throws
413,688✔
2528
    if (is_unres) {
413,688✔
2529
        if (last_removed) {
276✔
2530
            // Check is there are more backlinks
2531
            if (!target_obj.has_backlinks(false)) {
258✔
2532
                // Tombstones can be erased right away - there is no cascading effect
2533
                target_table->m_tombstones->erase(old_key, state);
258✔
2534
            }
258✔
2535
        }
258✔
2536
    }
276✔
2537
    else {
413,412✔
2538
        return state.enqueue_for_cascade(target_obj, strong_links, last_removed);
413,412✔
2539
    }
413,412✔
2540

2541
    return false;
276✔
2542
}
413,688✔
2543

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