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

realm / realm-core / 2294

02 May 2024 08:09PM UTC coverage: 90.761% (+0.01%) from 90.747%
2294

push

Evergreen

web-flow
Fix a deadlock when accessing current user from inside an App listener (#7671)

App::switch_user() emitted changes without first releasing the lock on
m_user_mutex, leading to a deadlock if anyone inside the listener tried to
acquire the mutex. The rest of the places where we emitted changes were
correct.

The newly added wrapper catches this error when building with clang.

101982 of 180246 branches covered (56.58%)

14 of 17 new or added lines in 2 files covered. (82.35%)

66 existing lines in 15 files now uncovered.

212573 of 234212 relevant lines covered (90.76%)

5645071.85 hits per line

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

75.87
/src/realm/cluster.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/cluster.hpp"
20
#include "realm/array_integer.hpp"
21
#include "realm/array_basic.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_fixed_bytes.hpp"
29
#include "realm/array_key.hpp"
30
#include "realm/array_ref.hpp"
31
#include "realm/array_typed_link.hpp"
32
#include "realm/array_backlink.hpp"
33
#include "realm/column_type_traits.hpp"
34
#include "realm/replication.hpp"
35
#include "realm/dictionary.hpp"
36
#include "realm/list.hpp"
37
#include <iostream>
38
#include <cmath>
39

40
namespace realm {
41

42
/******************************* FieldValues *********************************/
43

44
FieldValues::FieldValues(std::initializer_list<FieldValue> init)
45
    : m_values(init)
389,868✔
46
{
801,309✔
47
    if (m_values.size() > 1) {
801,309✔
48
        // Sort according to ColKey index
49
        std::sort(m_values.begin(), m_values.end(), [](const auto& a, const auto& b) {
52,401✔
50
            return a.col_key.get_index().val < b.col_key.get_index().val;
52,401✔
51
        });
52,401✔
52
    }
17,502✔
53
}
801,309✔
54

55
void FieldValues::insert(ColKey k, Mixed val, bool is_default)
56
{
757,434✔
57
    if (m_values.empty()) {
757,434✔
58
        m_values.emplace_back(k, val, is_default);
745,254✔
59
        return;
745,254✔
60
    }
745,254✔
61
    unsigned int idx = k.get_index().val;
12,180✔
62
    auto it = std::lower_bound(m_values.begin(), m_values.end(), idx, [](const auto& a, unsigned int i) {
23,610✔
63
        return a.col_key.get_index().val < i;
23,610✔
64
    });
23,610✔
65
    m_values.insert(it, {k, val, is_default});
12,180✔
66
}
12,180✔
67

68
/******************************* ClusterNode *********************************/
69

70
void ClusterNode::IteratorState::clear()
71
{
2,162,565✔
72
    m_current_leaf.detach();
2,162,565✔
73
    m_key_offset = 0;
2,162,565✔
74
    m_current_index = size_t(-1);
2,162,565✔
75
}
2,162,565✔
76

77
void ClusterNode::IteratorState::init(State& s, ObjKey key)
78
{
55,809✔
79
    m_current_leaf.init(s.mem);
55,809✔
80
    m_current_index = s.index;
55,809✔
81
    m_key_offset = key.value - m_current_leaf.get_key_value(m_current_index);
55,809✔
82
    m_current_leaf.set_offset(m_key_offset);
55,809✔
83
}
55,809✔
84

85
const Table* ClusterNode::get_owning_table() const noexcept
86
{
104,910✔
87
    return m_tree_top.get_owning_table();
104,910✔
88
}
104,910✔
89

90
void ClusterNode::get(ObjKey k, ClusterNode::State& state) const
91
{
104,169,750✔
92
    if (!k || !try_get(k, state)) {
104,172,000✔
93
        throw KeyNotFound(util::format("No object with key '%1' in '%2'", k.value, get_owning_table()->get_name()));
210✔
94
    }
210✔
95
}
104,169,750✔
96

97

98
/********************************* Cluster ***********************************/
99

100
MemRef Cluster::create_empty_cluster(Allocator& alloc)
101
{
272,196✔
102
    Array arr(alloc);
272,196✔
103
    arr.create(Array::type_HasRefs); // Throws
272,196✔
104

105
    arr.add(RefOrTagged::make_tagged(0)); // Compact form
272,196✔
106
    return arr.get_mem();
272,196✔
107
}
272,196✔
108

109

110
template <class T>
111
inline void Cluster::do_create(ColKey col)
112
{
180,474✔
113
    T arr(m_alloc);
180,474✔
114
    arr.create();
180,474✔
115
    auto col_ndx = col.get_index();
180,474✔
116
    arr.set_parent(this, col_ndx.val + s_first_col_index);
180,474✔
117
    arr.update_parent();
180,474✔
118
}
180,474✔
119

120
void Cluster::create()
121
{
96,480✔
122
    Array::create(type_HasRefs, false, s_first_col_index);
96,480✔
123
    Array::set(0, RefOrTagged::make_tagged(0)); // Size = 0
96,480✔
124

125
    auto column_initialize = [this](ColKey col_key) {
186,621✔
126
        auto col_ndx = col_key.get_index();
186,621✔
127
        while (size() <= col_ndx.val + 1)
373,239✔
128
            add(0);
186,618✔
129
        auto type = col_key.get_type();
186,621✔
130
        auto attr = col_key.get_attrs();
186,621✔
131
        if (attr.test(col_attr_Collection)) {
186,621✔
132
            ArrayRef arr(m_alloc);
6,144✔
133
            arr.create();
6,144✔
134
            arr.set_parent(this, col_ndx.val + s_first_col_index);
6,144✔
135
            arr.update_parent();
6,144✔
136
            return IteratorControl::AdvanceToNext;
6,144✔
137
        }
6,144✔
138
        switch (type) {
180,477✔
139
            case col_type_Int:
109,155✔
140
                if (attr.test(col_attr_Nullable)) {
109,155✔
141
                    do_create<ArrayIntNull>(col_key);
7,434✔
142
                }
7,434✔
143
                else {
101,721✔
144
                    do_create<ArrayInteger>(col_key);
101,721✔
145
                }
101,721✔
146
                break;
109,155✔
147
            case col_type_Bool:
684✔
148
                do_create<ArrayBoolNull>(col_key);
684✔
149
                break;
684✔
150
            case col_type_Float:
1,092✔
151
                do_create<ArrayFloatNull>(col_key);
1,092✔
152
                break;
1,092✔
153
            case col_type_Double:
5,790✔
154
                do_create<ArrayDoubleNull>(col_key);
5,790✔
155
                break;
5,790✔
156
            case col_type_String: {
47,163✔
157
                if (m_tree_top.is_string_enum_type(col_ndx)) {
47,163✔
158
                    do_create<ArrayInteger>(col_key);
5,340✔
159
                }
5,340✔
160
                else {
41,823✔
161
                    do_create<ArrayString>(col_key);
41,823✔
162
                }
41,823✔
163
                break;
47,163✔
164
            }
×
165
            case col_type_Binary:
5,280✔
166
                do_create<ArrayBinary>(col_key);
5,280✔
167
                break;
5,280✔
168
            case col_type_Mixed:
72✔
169
                do_create<ArrayMixed>(col_key);
72✔
170
                break;
72✔
171
            case col_type_Timestamp:
2,457✔
172
                do_create<ArrayTimestamp>(col_key);
2,457✔
173
                break;
2,457✔
174
            case col_type_Decimal:
276✔
175
                do_create<ArrayDecimal128>(col_key);
276✔
176
                break;
276✔
177
            case col_type_ObjectId:
2,877✔
178
                do_create<ArrayObjectIdNull>(col_key);
2,877✔
179
                break;
2,877✔
180
            case col_type_UUID:
408✔
181
                do_create<ArrayUUIDNull>(col_key);
408✔
182
                break;
408✔
183
            case col_type_Link:
2,190✔
184
                do_create<ArrayKey>(col_key);
2,190✔
185
                break;
2,190✔
186
            case col_type_TypedLink:
✔
187
                do_create<ArrayTypedLink>(col_key);
×
188
                break;
×
189
            case col_type_BackLink:
3,033✔
190
                do_create<ArrayBacklink>(col_key);
3,033✔
191
                break;
3,033✔
192
            default:
✔
193
                REALM_UNREACHABLE();
194
        }
180,477✔
195
        return IteratorControl::AdvanceToNext;
180,477✔
196
    };
180,477✔
197
    m_tree_top.m_owner->for_each_and_every_column(column_initialize);
96,480✔
198

199
    // By specifying the minimum size, we ensure that the array has a capacity
200
    // to hold m_size 64 bit refs.
201
    ensure_size(m_size * 8);
96,480✔
202
    // "ensure_size" may COW, but as array is just created, it has no parents, so
203
    // failing to update parent is not an error.
204
    clear_missing_parent_update();
96,480✔
205
}
96,480✔
206

207
void Cluster::init(MemRef mem)
208
{
203,351,280✔
209
    Array::init_from_mem(mem);
203,351,280✔
210
    auto rot = Array::get_as_ref_or_tagged(0);
203,351,280✔
211
    if (rot.is_tagged()) {
203,351,280✔
212
        m_keys.detach();
141,909,201✔
213
    }
141,909,201✔
214
    else {
61,442,079✔
215
        m_keys.init_from_ref(rot.get_as_ref());
61,442,079✔
216
    }
61,442,079✔
217
}
203,351,280✔
218

219
void Cluster::update_from_parent() noexcept
220
{
624,915✔
221
    Array::update_from_parent();
624,915✔
222
    auto rot = Array::get_as_ref_or_tagged(0);
624,915✔
223
    if (!rot.is_tagged()) {
624,915✔
224
        m_keys.update_from_parent();
24,252✔
225
    }
24,252✔
226
}
624,915✔
227

228
MemRef Cluster::ensure_writeable(ObjKey)
229
{
×
230
    // By specifying the minimum size, we ensure that the array has a capacity
231
    // to hold m_size 64 bit refs.
232
    copy_on_write(8 * m_size);
×
233

234
    return get_mem();
×
235
}
×
236

237
void Cluster::update_ref_in_parent(ObjKey, ref_type)
238
{
×
239
    REALM_UNREACHABLE();
240
}
×
241

242
size_t Cluster::node_size_from_header(Allocator& alloc, const char* header)
243
{
70,785,090✔
244
    auto rot = Array::get_as_ref_or_tagged(header, s_key_ref_or_size_index);
70,785,090✔
245
    if (rot.is_tagged()) {
70,843,038✔
246
        return size_t(rot.get_as_int());
70,667,703✔
247
    }
70,667,703✔
248
    else {
2,147,658,982✔
249
        return Array::get_size_from_header(alloc.translate(rot.get_as_ref()));
2,147,658,982✔
250
    }
2,147,658,982✔
251
}
70,785,090✔
252

253
template <class T>
254
inline void Cluster::set_spec(T&, ColKey::Idx) const
255
{
36,636,582✔
256
}
36,636,582✔
257

258
template <>
259
inline void Cluster::set_spec(ArrayString& arr, ColKey::Idx col_ndx) const
260
{
3,145,143✔
261
    m_tree_top.set_spec(arr, col_ndx);
3,145,143✔
262
}
3,145,143✔
263

264
template <class T>
265
inline void Cluster::do_insert_row(size_t ndx, ColKey col, Mixed init_val, bool nullable)
266
{
32,603,493✔
267
    using U = typename util::RemoveOptional<typename T::value_type>::type;
32,603,493✔
268

269
    T arr(m_alloc);
32,603,493✔
270
    auto col_ndx = col.get_index();
32,603,493✔
271
    arr.set_parent(this, col_ndx.val + s_first_col_index);
32,603,493✔
272
    set_spec<T>(arr, col_ndx);
32,603,493✔
273
    arr.init_from_parent();
32,603,493✔
274
    if (init_val.is_null()) {
32,603,493✔
275
        arr.insert(ndx, T::default_value(nullable));
31,642,491✔
276
    }
31,642,491✔
277
    else {
961,002✔
278
        arr.insert(ndx, init_val.get<U>());
961,002✔
279
    }
961,002✔
280
}
32,603,493✔
281

282
inline void Cluster::do_insert_key(size_t ndx, ColKey col_key, Mixed init_val, ObjKey origin_key)
283
{
260,298✔
284
    ObjKey target_key = init_val.is_null() ? ObjKey{} : init_val.get<ObjKey>();
260,298✔
285
    ArrayKey arr(m_alloc);
260,298✔
286
    auto col_ndx = col_key.get_index();
260,298✔
287
    arr.set_parent(this, col_ndx.val + s_first_col_index);
260,298✔
288
    arr.init_from_parent();
260,298✔
289
    arr.insert(ndx, target_key);
260,298✔
290

291
    // Insert backlink if link is not null
292
    if (target_key) {
260,298✔
293
        const Table* origin_table = m_tree_top.get_owning_table();
12✔
294
        ColKey opp_col = origin_table->get_opposite_column(col_key);
12✔
295
        TableRef opp_table = origin_table->get_opposite_table(col_key);
12✔
296
        Obj target_obj = opp_table->get_object(target_key);
12✔
297
        target_obj.add_backlink(opp_col, origin_key);
12✔
298
    }
12✔
299
}
260,298✔
300

301
inline void Cluster::do_insert_mixed(size_t ndx, ColKey col_key, Mixed init_value, ObjKey origin_key)
302
{
19,272✔
303
    ArrayMixed arr(m_alloc);
19,272✔
304
    arr.set_parent(this, col_key.get_index().val + s_first_col_index);
19,272✔
305
    arr.init_from_parent();
19,272✔
306
    arr.insert(ndx, init_value);
19,272✔
307

308
    // Insert backlink if needed
309
    if (init_value.is_type(type_TypedLink)) {
19,272✔
310
        // In case we are inserting in a Dictionary cluster, the backlink will
311
        // be handled in Dictionary::insert function
312
        if (Table* origin_table = const_cast<Table*>(m_tree_top.get_owning_table())) {
24✔
313
            if (origin_table->is_asymmetric()) {
24✔
314
                throw IllegalOperation("Object value not supported in asymmetric table");
6✔
315
            }
6✔
316
            ObjLink link = init_value.get<ObjLink>();
18✔
317
            auto target_table = origin_table->get_parent_group()->get_table(link.get_table_key());
18✔
318
            if (target_table->is_asymmetric()) {
18✔
319
                throw IllegalOperation("Ephemeral object value not supported");
6✔
320
            }
6✔
321
            ColKey backlink_col_key = target_table->find_or_add_backlink_column(col_key, origin_table->get_key());
12✔
322
            target_table->get_object(link.get_obj_key()).add_backlink(backlink_col_key, origin_key);
12✔
323
        }
12✔
324
    }
24✔
325
}
19,272✔
326

327
inline void Cluster::do_insert_link(size_t ndx, ColKey col_key, Mixed init_val, ObjKey origin_key)
328
{
×
329
    ObjLink target_link = init_val.is_null() ? ObjLink{} : init_val.get<ObjLink>();
×
330
    ArrayTypedLink arr(m_alloc);
×
331
    auto col_ndx = col_key.get_index();
×
332
    arr.set_parent(this, col_ndx.val + s_first_col_index);
×
333
    arr.init_from_parent();
×
334
    arr.insert(ndx, target_link);
×
335

336
    // Insert backlink if link is not null
337
    if (target_link) {
×
338
        Table* origin_table = const_cast<Table*>(m_tree_top.get_owning_table());
×
339
        auto target_table = origin_table->get_parent_group()->get_table(target_link.get_table_key());
×
340

341
        ColKey backlink_col_key = target_table->find_or_add_backlink_column(col_key, origin_table->get_key());
×
342
        target_table->get_object(target_link.get_obj_key()).add_backlink(backlink_col_key, origin_key);
×
343
    }
×
344
}
×
345

346
void Cluster::insert_row(size_t ndx, ObjKey k, const FieldValues& init_values)
347
{
23,579,238✔
348
    // Ensure the cluster array is big enough to hold 64 bit values.
349
    copy_on_write(m_size * 8);
23,579,238✔
350

351
    if (m_keys.is_attached()) {
23,579,238✔
352
        m_keys.insert(ndx, k.value);
1,581,909✔
353
    }
1,581,909✔
354
    else {
21,997,329✔
355
        Array::set(s_key_ref_or_size_index, Array::get(s_key_ref_or_size_index) + 2); // Increments size by 1
21,997,329✔
356
    }
21,997,329✔
357

358
    auto val = init_values.begin();
23,579,238✔
359
    auto insert_in_column = [&](ColKey col_key) {
34,440,999✔
360
        auto col_ndx = col_key.get_index();
34,440,999✔
361
        auto attr = col_key.get_attrs();
34,440,999✔
362
        Mixed init_value;
34,440,999✔
363
        // init_values must be sorted in col_ndx order - this is ensured by ClustTree::insert()
364
        if (val != init_values.end() && val->col_key.get_index().val == col_ndx.val) {
34,440,999✔
365
            init_value = val->value;
822,867✔
366
            ++val;
822,867✔
367
        }
822,867✔
368

369
        auto type = col_key.get_type();
34,440,999✔
370
        if (attr.test(col_attr_Collection)) {
34,440,999✔
371
            REALM_ASSERT(init_value.is_null());
675,846✔
372
            ArrayRef arr(m_alloc);
675,846✔
373
            arr.set_parent(this, col_ndx.val + s_first_col_index);
675,846✔
374
            arr.init_from_parent();
675,846✔
375
            arr.insert(ndx, 0);
675,846✔
376
            return IteratorControl::AdvanceToNext;
675,846✔
377
        }
675,846✔
378

379
        bool nullable = attr.test(col_attr_Nullable);
33,765,153✔
380
        switch (type) {
33,765,153✔
381
            case col_type_Int:
25,733,625✔
382
                if (attr.test(col_attr_Nullable)) {
25,733,625✔
383
                    do_insert_row<ArrayIntNull>(ndx, col_key, init_value, nullable);
1,377,909✔
384
                }
1,377,909✔
385
                else {
24,355,716✔
386
                    do_insert_row<ArrayInteger>(ndx, col_key, init_value, nullable);
24,355,716✔
387
                }
24,355,716✔
388
                break;
25,733,625✔
389
            case col_type_Bool:
186,915✔
390
                do_insert_row<ArrayBoolNull>(ndx, col_key, init_value, nullable);
186,915✔
391
                break;
186,915✔
392
            case col_type_Float:
321,090✔
393
                do_insert_row<ArrayFloatNull>(ndx, col_key, init_value, nullable);
321,090✔
394
                break;
321,090✔
395
            case col_type_Double:
1,492,884✔
396
                do_insert_row<ArrayDoubleNull>(ndx, col_key, init_value, nullable);
1,492,884✔
397
                break;
1,492,884✔
398
            case col_type_String:
2,905,635✔
399
                do_insert_row<ArrayString>(ndx, col_key, init_value, nullable);
2,905,635✔
400
                break;
2,905,635✔
401
            case col_type_Binary:
1,364,196✔
402
                do_insert_row<ArrayBinary>(ndx, col_key, init_value, nullable);
1,364,196✔
403
                break;
1,364,196✔
404
            case col_type_Mixed: {
19,272✔
405
                do_insert_mixed(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
19,272✔
406
                break;
19,272✔
407
            }
×
408
            case col_type_Timestamp:
225,390✔
409
                do_insert_row<ArrayTimestamp>(ndx, col_key, init_value, nullable);
225,390✔
410
                break;
225,390✔
411
            case col_type_Decimal:
78,222✔
412
                do_insert_row<ArrayDecimal128>(ndx, col_key, init_value, nullable);
78,222✔
413
                break;
78,222✔
414
            case col_type_ObjectId:
172,200✔
415
                do_insert_row<ArrayObjectIdNull>(ndx, col_key, init_value, nullable);
172,200✔
416
                break;
172,200✔
417
            case col_type_UUID:
119,652✔
418
                do_insert_row<ArrayUUIDNull>(ndx, col_key, init_value, nullable);
119,652✔
419
                break;
119,652✔
420
            case col_type_Link:
260,301✔
421
                do_insert_key(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
260,301✔
422
                break;
260,301✔
423
            case col_type_TypedLink:
✔
424
                do_insert_link(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
×
425
                break;
×
426
            case col_type_BackLink: {
963,783✔
427
                ArrayBacklink arr(m_alloc);
963,783✔
428
                arr.set_parent(this, col_ndx.val + s_first_col_index);
963,783✔
429
                arr.init_from_parent();
963,783✔
430
                arr.insert(ndx, 0);
963,783✔
431
                break;
963,783✔
432
            }
×
433
            default:
✔
434
                REALM_ASSERT(false);
×
435
                break;
×
436
        }
33,765,153✔
437
        return IteratorControl::AdvanceToNext;
33,618,840✔
438
    };
33,765,153✔
439
    m_tree_top.m_owner->for_each_and_every_column(insert_in_column);
23,579,238✔
440
}
23,579,238✔
441

442
template <class T>
443
inline void Cluster::do_move(size_t ndx, ColKey col_key, Cluster* to)
444
{
21,636✔
445
    auto col_ndx = col_key.get_index().val + s_first_col_index;
21,636✔
446
    T src(m_alloc);
21,636✔
447
    src.set_parent(this, col_ndx);
21,636✔
448
    src.init_from_parent();
21,636✔
449

450
    T dst(m_alloc);
21,636✔
451
    dst.set_parent(to, col_ndx);
21,636✔
452
    dst.init_from_parent();
21,636✔
453

454
    src.move(dst, ndx);
21,636✔
455
}
21,636✔
456

457
void Cluster::move(size_t ndx, ClusterNode* new_node, int64_t offset)
458
{
11,388✔
459
    auto new_leaf = static_cast<Cluster*>(new_node);
11,388✔
460

461
    auto move_from_column = [&](ColKey col_key) {
21,636✔
462
        auto attr = col_key.get_attrs();
21,636✔
463
        auto type = col_key.get_type();
21,636✔
464

465
        if (attr.test(col_attr_Collection)) {
21,636✔
466
            do_move<ArrayRef>(ndx, col_key, new_leaf);
30✔
467
            return IteratorControl::AdvanceToNext;
30✔
468
        }
30✔
469

470
        switch (type) {
21,606✔
471
            case col_type_Int:
20,736✔
472
                if (attr.test(col_attr_Nullable)) {
20,736✔
473
                    do_move<ArrayIntNull>(ndx, col_key, new_leaf);
9,366✔
474
                }
9,366✔
475
                else {
11,370✔
476
                    do_move<ArrayInteger>(ndx, col_key, new_leaf);
11,370✔
477
                }
11,370✔
478
                break;
20,736✔
479
            case col_type_Bool:
36✔
480
                do_move<ArrayBoolNull>(ndx, col_key, new_leaf);
36✔
481
                break;
36✔
482
            case col_type_Float:
36✔
483
                do_move<ArrayFloat>(ndx, col_key, new_leaf);
36✔
484
                break;
36✔
485
            case col_type_Double:
36✔
486
                do_move<ArrayDouble>(ndx, col_key, new_leaf);
36✔
487
                break;
36✔
488
            case col_type_String: {
36✔
489
                if (m_tree_top.is_string_enum_type(col_key.get_index()))
36✔
490
                    do_move<ArrayInteger>(ndx, col_key, new_leaf);
×
491
                else
36✔
492
                    do_move<ArrayString>(ndx, col_key, new_leaf);
36✔
493
                break;
36✔
494
            }
×
495
            case col_type_Binary:
36✔
496
                do_move<ArrayBinary>(ndx, col_key, new_leaf);
36✔
497
                break;
36✔
498
            case col_type_Mixed:
✔
499
                do_move<ArrayMixed>(ndx, col_key, new_leaf);
×
500
                break;
×
501
            case col_type_Timestamp:
36✔
502
                do_move<ArrayTimestamp>(ndx, col_key, new_leaf);
36✔
503
                break;
36✔
504
            case col_type_Decimal:
36✔
505
                do_move<ArrayDecimal128>(ndx, col_key, new_leaf);
36✔
506
                break;
36✔
507
            case col_type_ObjectId:
36✔
508
                do_move<ArrayObjectIdNull>(ndx, col_key, new_leaf);
36✔
509
                break;
36✔
510
            case col_type_UUID:
120✔
511
                do_move<ArrayUUIDNull>(ndx, col_key, new_leaf);
120✔
512
                break;
120✔
513
            case col_type_Link:
6✔
514
                do_move<ArrayKey>(ndx, col_key, new_leaf);
6✔
515
                break;
6✔
516
            case col_type_TypedLink:
✔
517
                do_move<ArrayTypedLink>(ndx, col_key, new_leaf);
×
518
                break;
×
519
            case col_type_BackLink:
456✔
520
                do_move<ArrayBacklink>(ndx, col_key, new_leaf);
456✔
521
                break;
456✔
522
            default:
✔
523
                REALM_ASSERT(false);
×
524
                break;
×
525
        }
21,606✔
526
        return IteratorControl::AdvanceToNext;
21,606✔
527
    };
21,606✔
528
    m_tree_top.m_owner->for_each_and_every_column(move_from_column);
11,388✔
529
    for (size_t i = ndx; i < m_keys.size(); i++) {
1,231,701✔
530
        new_leaf->m_keys.add(m_keys.get(i) - offset);
1,220,313✔
531
    }
1,220,313✔
532
    m_keys.truncate(ndx);
11,388✔
533
}
11,388✔
534

535
Cluster::~Cluster() {}
207,452,358✔
536

537
ColKey Cluster::get_col_key(size_t ndx_in_parent) const
538
{
18,693✔
539
    ColKey::Idx col_ndx{unsigned(ndx_in_parent - 1)}; // <- leaf_index here. Opaque.
18,693✔
540
    auto col_key = get_owning_table()->leaf_ndx2colkey(col_ndx);
18,693✔
541
    REALM_ASSERT(col_key.get_index().val == col_ndx.val);
18,693✔
542
    return col_key;
18,693✔
543
}
18,693✔
544

545
void Cluster::ensure_general_form()
546
{
61,449✔
547
    if (!m_keys.is_attached()) {
61,449✔
548
        size_t current_size = get_size_in_compact_form();
48,252✔
549
        m_keys.create(current_size, 255);
48,252✔
550
        m_keys.update_parent();
48,252✔
551
        for (size_t i = 0; i < current_size; i++) {
5,519,088✔
552
            m_keys.set(i, i);
5,470,836✔
553
        }
5,470,836✔
554
    }
48,252✔
555
}
61,449✔
556

557
template <class T>
558
inline void Cluster::do_insert_column(ColKey col_key, bool nullable)
559
{
558,165✔
560
    size_t sz = node_size();
558,165✔
561

562
    T arr(m_alloc);
558,165✔
563
    arr.create();
558,165✔
564
    auto val = T::default_value(nullable);
558,165✔
565
    for (size_t i = 0; i < sz; i++) {
6,799,785✔
566
        arr.add(val);
6,241,620✔
567
    }
6,241,620✔
568
    auto col_ndx = col_key.get_index();
558,165✔
569
    unsigned ndx = col_ndx.val + s_first_col_index;
558,165✔
570

571
    // Fill up if indexes are not consecutive
572
    while (size() < ndx)
558,165✔
573
        Array::add(0);
×
574

575
    if (ndx == size())
558,165✔
576
        Array::insert(ndx, from_ref(arr.get_ref()));
558,075✔
577
    else
90✔
578
        Array::set(ndx, from_ref(arr.get_ref()));
90✔
579
}
558,165✔
580

581
void Cluster::insert_column(ColKey col_key)
582
{
750,444✔
583
    auto attr = col_key.get_attrs();
750,444✔
584
    auto type = col_key.get_type();
750,444✔
585
    if (attr.test(col_attr_Collection)) {
750,444✔
586
        size_t sz = node_size();
192,282✔
587

588
        ArrayRef arr(m_alloc);
192,282✔
589
        arr.create(sz);
192,282✔
590
        auto col_ndx = col_key.get_index();
192,282✔
591
        unsigned idx = col_ndx.val + s_first_col_index;
192,282✔
592
        if (idx == size())
192,282✔
593
            Array::insert(idx, from_ref(arr.get_ref()));
192,282✔
UNCOV
594
        else
×
UNCOV
595
            Array::set(idx, from_ref(arr.get_ref()));
×
596
        return;
192,282✔
597
    }
192,282✔
598
    bool nullable = attr.test(col_attr_Nullable);
558,162✔
599
    switch (type) {
558,162✔
600
        case col_type_Int:
249,426✔
601
            if (nullable) {
249,426✔
602
                do_insert_column<ArrayIntNull>(col_key, nullable);
21,081✔
603
            }
21,081✔
604
            else {
228,345✔
605
                do_insert_column<ArrayInteger>(col_key, nullable);
228,345✔
606
            }
228,345✔
607
            break;
249,426✔
608
        case col_type_Bool:
5,481✔
609
            do_insert_column<ArrayBoolNull>(col_key, nullable);
5,481✔
610
            break;
5,481✔
611
        case col_type_Float:
6,441✔
612
            do_insert_column<ArrayFloatNull>(col_key, nullable);
6,441✔
613
            break;
6,441✔
614
        case col_type_Double:
7,080✔
615
            do_insert_column<ArrayDoubleNull>(col_key, nullable);
7,080✔
616
            break;
7,080✔
617
        case col_type_String:
78,849✔
618
            do_insert_column<ArrayString>(col_key, nullable);
78,849✔
619
            break;
78,849✔
620
        case col_type_Binary:
6,858✔
621
            do_insert_column<ArrayBinary>(col_key, nullable);
6,858✔
622
            break;
6,858✔
623
        case col_type_Mixed:
6,924✔
624
            do_insert_column<ArrayMixed>(col_key, nullable);
6,924✔
625
            break;
6,924✔
626
        case col_type_Timestamp:
23,181✔
627
            do_insert_column<ArrayTimestamp>(col_key, nullable);
23,181✔
628
            break;
23,181✔
629
        case col_type_Decimal:
5,022✔
630
            do_insert_column<ArrayDecimal128>(col_key, nullable);
5,022✔
631
            break;
5,022✔
632
        case col_type_ObjectId:
48,807✔
633
            do_insert_column<ArrayObjectIdNull>(col_key, nullable);
48,807✔
634
            break;
48,807✔
635
        case col_type_UUID:
5,616✔
636
            do_insert_column<ArrayUUIDNull>(col_key, nullable);
5,616✔
637
            break;
5,616✔
638
        case col_type_Link:
31,035✔
639
            do_insert_column<ArrayKey>(col_key, nullable);
31,035✔
640
            break;
31,035✔
641
        case col_type_TypedLink:
✔
642
            do_insert_column<ArrayTypedLink>(col_key, nullable);
×
643
            break;
×
644
        case col_type_BackLink:
83,445✔
645
            do_insert_column<ArrayBacklink>(col_key, nullable);
83,445✔
646
            break;
83,445✔
647
        default:
✔
648
            REALM_UNREACHABLE();
649
            break;
×
650
    }
558,162✔
651
}
558,162✔
652

653
void Cluster::remove_column(ColKey col_key)
654
{
6,207✔
655
    auto col_ndx = col_key.get_index();
6,207✔
656
    unsigned idx = col_ndx.val + s_first_col_index;
6,207✔
657
    ref_type ref = to_ref(Array::get(idx));
6,207✔
658
    if (ref != 0) {
6,207✔
659
        Array::destroy_deep(ref, m_alloc);
6,207✔
660
    }
6,207✔
661
    if (idx == size() - 1)
6,207✔
662
        Array::erase(idx);
4,665✔
663
    else
1,542✔
664
        Array::set(idx, 0);
1,542✔
665
}
6,207✔
666

667
ref_type Cluster::insert(ObjKey k, const FieldValues& init_values, ClusterNode::State& state)
668
{
23,538,807✔
669
    int64_t current_key_value = -1;
23,538,807✔
670
    size_t sz;
23,538,807✔
671
    size_t ndx;
23,538,807✔
672
    ref_type ret = 0;
23,538,807✔
673

674
    auto on_error = [&] {
23,538,807✔
675
        throw KeyAlreadyUsed(
12✔
676
            util::format("When inserting key '%1' in '%2'", k.value, get_owning_table()->get_name()));
12✔
677
    };
12✔
678

679
    if (m_keys.is_attached()) {
23,538,807✔
680
        sz = m_keys.size();
1,570,686✔
681
        ndx = m_keys.lower_bound(uint64_t(k.value));
1,570,686✔
682
        if (ndx < sz) {
1,570,686✔
683
            current_key_value = m_keys.get(ndx);
616,908✔
684
            if (k.value == current_key_value) {
616,908✔
685
                on_error();
12✔
686
            }
12✔
687
        }
616,908✔
688
    }
1,570,686✔
689
    else {
21,968,121✔
690
        sz = size_t(Array::get(s_key_ref_or_size_index)) >> 1; // Size is stored as tagged integer
21,968,121✔
691
        if (uint64_t(k.value) < sz) {
21,968,121✔
692
            on_error();
×
693
        }
×
694
        // Key value is bigger than all other values, should be put last
695
        ndx = sz;
21,968,121✔
696
        if (uint64_t(k.value) > sz && sz < cluster_node_size) {
21,968,121✔
697
            ensure_general_form();
16,941✔
698
        }
16,941✔
699
    }
21,968,121✔
700

701
    REALM_ASSERT_DEBUG(sz <= cluster_node_size);
23,538,807✔
702
    if (REALM_LIKELY(sz < cluster_node_size)) {
23,538,807✔
703
        insert_row(ndx, k, init_values); // Throws
23,478,567✔
704
        state.mem = get_mem();
23,478,567✔
705
        state.index = ndx;
23,478,567✔
706
    }
23,478,567✔
707
    else {
60,240✔
708
        // Split leaf node
709
        Cluster new_leaf(0, m_alloc, m_tree_top);
60,240✔
710
        new_leaf.create();
60,240✔
711
        if (ndx == sz) {
85,779✔
712
            new_leaf.insert_row(0, ObjKey(0), init_values); // Throws
85,779✔
713
            state.split_key = k.value;
85,779✔
714
            state.mem = new_leaf.get_mem();
85,779✔
715
            state.index = 0;
85,779✔
716
        }
85,779✔
717
        else {
4,294,967,294✔
718
            // Current cluster must be in general form to get here
719
            REALM_ASSERT_DEBUG(m_keys.is_attached());
4,294,967,294✔
720
            new_leaf.ensure_general_form();
4,294,967,294✔
721
            move(ndx, &new_leaf, current_key_value);
4,294,967,294✔
722
            insert_row(ndx, k, init_values); // Throws
4,294,967,294✔
723
            state.mem = get_mem();
4,294,967,294✔
724
            state.split_key = current_key_value;
4,294,967,294✔
725
            state.index = ndx;
4,294,967,294✔
726
        }
4,294,967,294✔
727
        ret = new_leaf.get_ref();
60,240✔
728
    }
60,240✔
729

730
    return ret;
23,538,807✔
731
}
23,538,807✔
732

733
bool Cluster::try_get(ObjKey k, ClusterNode::State& state) const noexcept
734
{
155,330,901✔
735
    state.mem = get_mem();
155,330,901✔
736
    if (m_keys.is_attached()) {
155,330,901✔
737
        state.index = m_keys.lower_bound(uint64_t(k.value));
50,177,358✔
738
        return state.index != m_keys.size() && m_keys.get(state.index) == uint64_t(k.value);
50,177,358✔
739
    }
50,177,358✔
740
    else {
105,153,543✔
741
        if (uint64_t(k.value) < uint64_t(Array::get(s_key_ref_or_size_index) >> 1)) {
105,153,543✔
742
            state.index = size_t(k.value);
87,054,051✔
743
            return true;
87,054,051✔
744
        }
87,054,051✔
745
    }
105,153,543✔
746
    return false;
18,099,492✔
747
}
155,330,901✔
748

749
ObjKey Cluster::get(size_t ndx, ClusterNode::State& state) const
750
{
22,969,098✔
751
    state.index = ndx;
22,969,098✔
752
    state.mem = get_mem();
22,969,098✔
753
    return get_real_key(ndx);
22,969,098✔
754
}
22,969,098✔
755

756
template <class T>
757
inline void Cluster::do_erase(size_t ndx, ColKey col_key)
758
{
6,604,440✔
759
    auto col_ndx = col_key.get_index();
6,604,440✔
760
    T values(m_alloc);
6,604,440✔
761
    values.set_parent(this, col_ndx.val + s_first_col_index);
6,604,440✔
762
    set_spec<T>(values, col_ndx);
6,604,440✔
763
    values.init_from_parent();
6,604,440✔
764
    ObjLink link;
6,604,440✔
765
    if constexpr (std::is_same_v<T, ArrayTypedLink>) {
6,604,440✔
766
        link = values.get(ndx);
×
767
    }
×
768
    if constexpr (std::is_same_v<T, ArrayMixed>) {
6,604,440✔
769
        Mixed value = values.get(ndx);
777✔
770
        if (value.is_type(type_TypedLink)) {
777✔
771
            link = value.get<ObjLink>();
24✔
772
        }
24✔
773
    }
777✔
774
    if (link) {
6,604,440✔
775
        if (const Table* origin_table = m_tree_top.get_owning_table()) {
24!
776
            auto target_obj = origin_table->get_parent_group()->get_object(link);
24✔
777

778
            ColKey backlink_col_key = target_obj.get_table()->find_backlink_column(col_key, origin_table->get_key());
24✔
779
            REALM_ASSERT(backlink_col_key);
24!
780
            target_obj.remove_one_backlink(backlink_col_key, get_real_key(ndx)); // Throws
24✔
781
        }
24✔
782
    }
24✔
783
    values.erase(ndx);
6,604,440✔
784
}
6,604,440✔
785

786
inline void Cluster::do_erase_key(size_t ndx, ColKey col_key, CascadeState& state)
787
{
68,115✔
788
    ArrayKey values(m_alloc);
68,115✔
789
    auto col_ndx = col_key.get_index();
68,115✔
790
    values.set_parent(this, col_ndx.val + s_first_col_index);
68,115✔
791
    values.init_from_parent();
68,115✔
792

793
    ObjKey key = values.get(ndx);
68,115✔
794
    if (key != null_key) {
68,115✔
795
        do_remove_backlinks(get_real_key(ndx), col_key, std::vector<ObjKey>{key}, state);
65,361✔
796
    }
65,361✔
797
    values.erase(ndx);
68,115✔
798
}
68,115✔
799

800
size_t Cluster::get_ndx(ObjKey k, size_t ndx) const noexcept
801
{
9,938,547✔
802
    size_t index;
9,938,547✔
803
    if (m_keys.is_attached()) {
9,938,547✔
804
        index = m_keys.lower_bound(uint64_t(k.value));
9,823,845✔
805
        if (index == m_keys.size() || m_keys.get(index) != uint64_t(k.value)) {
9,825,165✔
806
            return realm::npos;
18✔
807
        }
18✔
808
    }
9,823,845✔
809
    else {
114,702✔
810
        index = size_t(k.value);
114,702✔
811
        if (index >= get_as_ref_or_tagged(s_key_ref_or_size_index).get_as_int()) {
114,702✔
812
            return realm::npos;
×
813
        }
×
814
    }
114,702✔
815
    return index + ndx;
9,938,529✔
816
}
9,938,547✔
817

818
size_t Cluster::erase(ObjKey key, CascadeState& state)
819
{
5,000,529✔
820
    size_t ndx = get_ndx(key, 0);
5,000,529✔
821
    if (ndx == realm::npos)
5,000,529✔
822
        throw KeyNotFound(util::format("When erasing key '%1' in '%2'", key.value, get_owning_table()->get_name()));
18✔
823
    std::vector<ColKey> backlink_column_keys;
5,000,511✔
824

825
    auto erase_in_column = [&](ColKey col_key) {
6,703,575✔
826
        auto col_type = col_key.get_type();
6,703,575✔
827
        auto attr = col_key.get_attrs();
6,703,575✔
828
        if (attr.test(col_attr_Collection)) {
6,703,575✔
829
            auto col_ndx = col_key.get_index();
31,593✔
830
            ArrayRef values(m_alloc);
31,593✔
831
            values.set_parent(this, col_ndx.val + s_first_col_index);
31,593✔
832
            values.init_from_parent();
31,593✔
833
            ref_type ref = values.get(ndx);
31,593✔
834

835
            if (ref) {
31,593✔
836
                const Table* origin_table = m_tree_top.get_owning_table();
11,622✔
837
                if (attr.test(col_attr_Dictionary)) {
11,622✔
838
                    if (col_type == col_type_Mixed || col_type == col_type_Link) {
2,655✔
839
                        Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
354✔
840
                        Dictionary dict(obj, col_key);
354✔
841
                        dict.remove_backlinks(state);
354✔
842
                    }
354✔
843
                }
2,655✔
844
                else if (col_type == col_type_Link) {
8,967✔
845
                    BPlusTree<ObjKey> links(m_alloc);
3,948✔
846
                    links.init_from_ref(ref);
3,948✔
847
                    if (links.size() > 0) {
3,948✔
848
                        do_remove_backlinks(ObjKey(key.value + m_offset), col_key, links.get_all(), state);
1,026✔
849
                    }
1,026✔
850
                }
3,948✔
851
                else if (col_type == col_type_TypedLink) {
5,019✔
852
                    BPlusTree<ObjLink> links(m_alloc);
×
853
                    links.init_from_ref(ref);
×
854
                    for (size_t i = 0; i < links.size(); i++) {
×
855
                        ObjLink link = links.get(i);
×
856
                        auto target_obj = origin_table->get_parent_group()->get_object(link);
×
857
                        ColKey backlink_col_key =
×
858
                            target_obj.get_table()->find_backlink_column(col_key, origin_table->get_key());
×
859
                        target_obj.remove_one_backlink(backlink_col_key, ObjKey(key.value + m_offset));
×
860
                    }
×
861
                }
×
862
                else if (col_type == col_type_Mixed) {
5,019✔
863
                    Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
216✔
864
                    Lst<Mixed> list(obj, col_key);
216✔
865
                    list.remove_backlinks(state);
216✔
866
                }
216✔
867
                Array::destroy_deep(ref, m_alloc);
11,622✔
868
            }
11,622✔
869

870
            values.erase(ndx);
31,593✔
871

872
            return IteratorControl::AdvanceToNext;
31,593✔
873
        }
31,593✔
874

875
        switch (col_type) {
6,671,982✔
876
            case col_type_Int:
5,984,127✔
877
                if (attr.test(col_attr_Nullable)) {
5,984,127✔
878
                    do_erase<ArrayIntNull>(ndx, col_key);
1,275,600✔
879
                }
1,275,600✔
880
                else {
4,708,527✔
881
                    do_erase<ArrayInteger>(ndx, col_key);
4,708,527✔
882
                }
4,708,527✔
883
                break;
5,984,127✔
884
            case col_type_Bool:
42,540✔
885
                do_erase<ArrayBoolNull>(ndx, col_key);
42,540✔
886
                break;
42,540✔
887
            case col_type_Float:
36,489✔
888
                do_erase<ArrayFloatNull>(ndx, col_key);
36,489✔
889
                break;
36,489✔
890
            case col_type_Double:
36,501✔
891
                do_erase<ArrayDoubleNull>(ndx, col_key);
36,501✔
892
                break;
36,501✔
893
            case col_type_String:
75,768✔
894
                do_erase<ArrayString>(ndx, col_key);
75,768✔
895
                break;
75,768✔
896
            case col_type_Binary:
39,675✔
897
                do_erase<ArrayBinary>(ndx, col_key);
39,675✔
898
                break;
39,675✔
899
            case col_type_Mixed:
777✔
900
                do_erase<ArrayMixed>(ndx, col_key);
777✔
901
                break;
777✔
902
            case col_type_Timestamp:
45,324✔
903
                do_erase<ArrayTimestamp>(ndx, col_key);
45,324✔
904
                break;
45,324✔
905
            case col_type_Decimal:
36,279✔
906
                do_erase<ArrayDecimal128>(ndx, col_key);
36,279✔
907
                break;
36,279✔
908
            case col_type_ObjectId:
38,814✔
909
                do_erase<ArrayObjectIdNull>(ndx, col_key);
38,814✔
910
                break;
38,814✔
911
            case col_type_UUID:
60,285✔
912
                do_erase<ArrayUUIDNull>(ndx, col_key);
60,285✔
913
                break;
60,285✔
914
            case col_type_Link:
68,115✔
915
                do_erase_key(ndx, col_key, state);
68,115✔
916
                break;
68,115✔
917
            case col_type_TypedLink:
✔
918
                do_erase<ArrayTypedLink>(ndx, col_key);
×
919
                break;
×
920
            case col_type_BackLink:
207,855✔
921
                if (state.m_mode == CascadeState::Mode::None) {
207,855✔
922
                    do_erase<ArrayBacklink>(ndx, col_key);
188,586✔
923
                }
188,586✔
924
                else {
19,269✔
925
                    // Postpone the deletion of backlink entries or else the
926
                    // checks for if there's any remaining backlinks will
927
                    // check the wrong row for columns which have already
928
                    // had values erased from them.
929
                    backlink_column_keys.push_back(col_key);
19,269✔
930
                }
19,269✔
931
                break;
207,855✔
932
            default:
✔
933
                REALM_ASSERT(false);
×
934
                break;
×
935
        }
6,671,982✔
936
        return IteratorControl::AdvanceToNext;
6,671,541✔
937
    };
6,671,982✔
938
    m_tree_top.m_owner->for_each_and_every_column(erase_in_column);
5,000,511✔
939

940
    // Any remaining backlink columns to erase from?
941
    for (auto k : backlink_column_keys)
5,000,511✔
942
        do_erase<ArrayBacklink>(ndx, k);
19,269✔
943

944
    if (m_keys.is_attached()) {
5,000,511✔
945
        m_keys.erase(ndx);
4,960,065✔
946
    }
4,960,065✔
947
    else {
40,446✔
948
        size_t current_size = get_size_in_compact_form();
40,446✔
949
        if (ndx == current_size - 1) {
40,446✔
950
            // When deleting last, we can still maintain compact form
951
            set(0, RefOrTagged::make_tagged(current_size - 1));
26,067✔
952
        }
26,067✔
953
        else {
14,379✔
954
            ensure_general_form();
14,379✔
955
            m_keys.erase(ndx);
14,379✔
956
        }
14,379✔
957
    }
40,446✔
958

959
    return node_size();
5,000,511✔
960
}
5,000,529✔
961

962
void Cluster::nullify_incoming_links(ObjKey key, CascadeState& state)
963
{
4,901,964✔
964
    size_t ndx = get_ndx(key, 0);
4,901,964✔
965
    if (ndx == realm::npos)
4,901,964✔
966
        throw KeyNotFound(util::format("Key '%1' not found in '%2' when nullifying incoming links", key.value,
×
967
                                       get_owning_table()->get_class_name()));
×
968

969
    // We must start with backlink columns in case the corresponding link
970
    // columns are in the same table so that we can nullify links before
971
    // erasing rows in the link columns.
972
    //
973
    // This phase also generates replication instructions documenting the side-
974
    // effects of deleting the object (i.e. link nullifications). These instructions
975
    // must come before the actual deletion of the object, but at the same time
976
    // the Replication object may need a consistent view of the row (not including
977
    // link columns). Therefore we first nullify links to this object, then
978
    // generate the instruction, and then delete the row in the remaining columns.
979

980
    auto nullify_fwd_links = [&](ColKey col_key) {
4,901,964✔
981
        ColKey::Idx leaf_ndx = col_key.get_index();
190,323✔
982
        auto type = col_key.get_type();
190,323✔
983
        REALM_ASSERT(type == col_type_BackLink);
190,323✔
984
        ArrayBacklink values(m_alloc);
190,323✔
985
        values.set_parent(this, leaf_ndx.val + s_first_col_index);
190,323✔
986
        values.init_from_parent();
190,323✔
987
        // Ensure that Cluster is writable and able to hold references to nodes in
988
        // the slab area before nullifying or deleting links. These operation may
989
        // both have the effect that other objects may be constructed and manipulated.
990
        // If those other object are in the same cluster that the object to be deleted
991
        // is in, then that will cause another accessor to this cluster to be created.
992
        // It would lead to an error if the cluster node was relocated without it being
993
        // reflected in the context here.
994
        values.copy_on_write();
190,323✔
995
        values.nullify_fwd_links(ndx, state);
190,323✔
996

997
        return IteratorControl::AdvanceToNext;
190,323✔
998
    };
190,323✔
999

1000
    m_tree_top.get_owning_table()->for_each_backlink_column(nullify_fwd_links);
4,901,964✔
1001
}
4,901,964✔
1002

1003
void Cluster::upgrade_string_to_enum(ColKey col_key, ArrayString& keys)
1004
{
1,089✔
1005
    auto col_ndx = col_key.get_index();
1,089✔
1006
    Array indexes(m_alloc);
1,089✔
1007
    indexes.create(Array::type_Normal, false);
1,089✔
1008
    ArrayString values(m_alloc);
1,089✔
1009
    ref_type ref = Array::get_as_ref(col_ndx.val + s_first_col_index);
1,089✔
1010
    values.init_from_ref(ref);
1,089✔
1011
    size_t sz = values.size();
1,089✔
1012
    for (size_t i = 0; i < sz; i++) {
118,545✔
1013
        auto v = values.get(i);
117,456✔
1014
        size_t pos = keys.lower_bound(v);
117,456✔
1015
        REALM_ASSERT_3(pos, !=, keys.size());
117,456✔
1016
        indexes.add(pos);
117,456✔
1017
    }
117,456✔
1018
    Array::set(col_ndx.val + s_first_col_index, indexes.get_ref());
1,089✔
1019
    Array::destroy_deep(ref, m_alloc);
1,089✔
1020
}
1,089✔
1021

1022
void Cluster::init_leaf(ColKey col_key, ArrayPayload* leaf) const
1023
{
8,381,715✔
1024
    auto col_ndx = col_key.get_index();
8,381,715✔
1025
    // FIXME: Move this validation into callers.
1026
    // Currently, the query subsystem may call with an unvalidated key.
1027
    // once fixed, reintroduce the noexcept declaration :-D
1028
    if (auto t = m_tree_top.get_owning_table())
8,381,715✔
1029
        t->check_column(col_key);
8,554,257✔
1030
    ref_type ref = to_ref(Array::get(col_ndx.val + 1));
8,381,715✔
1031
    if (leaf->need_spec()) {
8,381,715✔
1032
        m_tree_top.set_spec(*leaf, col_ndx);
137,715✔
1033
    }
137,715✔
1034
    leaf->init_from_ref(ref);
8,381,715✔
1035
    leaf->set_parent(const_cast<Cluster*>(this), col_ndx.val + 1);
8,381,715✔
1036
}
8,381,715✔
1037

1038
void Cluster::add_leaf(ColKey col_key, ref_type ref)
1039
{
×
1040
    auto col_ndx = col_key.get_index();
×
1041
    REALM_ASSERT((col_ndx.val + 1) == size());
×
1042
    Array::insert(col_ndx.val + 1, from_ref(ref));
×
1043
}
×
1044

1045
template <typename ArrayType>
1046
void Cluster::verify(ref_type ref, size_t index, util::Optional<size_t>& sz) const
1047
{
580,254✔
1048
    ArrayType arr(get_alloc());
580,254✔
1049
    set_spec(arr, ColKey::Idx{unsigned(index) - 1});
580,254✔
1050
    arr.set_parent(const_cast<Cluster*>(this), index);
580,254✔
1051
    arr.init_from_ref(ref);
580,254✔
1052
    arr.verify();
580,254✔
1053
    if (sz) {
580,254✔
1054
        REALM_ASSERT(arr.size() == *sz);
208,401!
1055
    }
208,401✔
1056
    else {
371,853✔
1057
        sz = arr.size();
371,853✔
1058
    }
371,853✔
1059
}
580,254✔
1060
namespace {
1061

1062
template <typename ArrayType>
1063
void verify_list(ArrayRef& arr, size_t sz)
1064
{
122,823✔
1065
    for (size_t n = 0; n < sz; n++) {
452,469!
1066
        if (ref_type bp_tree_ref = arr.get(n)) {
329,646!
1067
            BPlusTree<ArrayType> links(arr.get_alloc());
137,436✔
1068
            links.init_from_ref(bp_tree_ref);
137,436✔
1069
            links.set_parent(&arr, n);
137,436✔
1070
            links.verify();
137,436✔
1071
        }
137,436✔
1072
    }
329,646✔
1073
}
122,823✔
1074

1075
template <typename SetType>
1076
void verify_set(ArrayRef& arr, size_t sz)
1077
{
174✔
1078
    for (size_t n = 0; n < sz; ++n) {
402!
1079
        if (ref_type bp_tree_ref = arr.get(n)) {
228!
1080
            BPlusTree<SetType> elements(arr.get_alloc());
216✔
1081
            elements.init_from_ref(bp_tree_ref);
216✔
1082
            elements.set_parent(&arr, n);
216✔
1083
            elements.verify();
216✔
1084

1085
            // FIXME: Check uniqueness of elements.
1086
        }
216✔
1087
    }
228✔
1088
}
174✔
1089

1090
} // namespace
1091

1092
void Cluster::verify() const
1093
{
408,333✔
1094
#ifdef REALM_DEBUG
408,333✔
1095
    util::Optional<size_t> sz;
408,333✔
1096

1097
    auto verify_column = [this, &sz](ColKey col_key) {
715,524✔
1098
        size_t col = col_key.get_index().val + s_first_col_index;
715,524✔
1099
        ref_type ref = Array::get_as_ref(col);
715,524✔
1100
        auto attr = col_key.get_attrs();
715,524✔
1101
        auto col_type = col_key.get_type();
715,524✔
1102
        bool nullable = attr.test(col_attr_Nullable);
715,524✔
1103

1104
        if (attr.test(col_attr_List)) {
715,524✔
1105
            ArrayRef arr(get_alloc());
122,841✔
1106
            arr.set_parent(const_cast<Cluster*>(this), col);
122,841✔
1107
            arr.init_from_ref(ref);
122,841✔
1108
            arr.verify();
122,841✔
1109
            if (sz) {
122,841✔
1110
                REALM_ASSERT(arr.size() == *sz);
98,589✔
1111
            }
98,589✔
1112
            else {
24,252✔
1113
                sz = arr.size();
24,252✔
1114
            }
24,252✔
1115

1116
            switch (col_type) {
122,841✔
1117
                case col_type_Int:
41,856✔
1118
                    if (nullable) {
41,856✔
1119
                        verify_list<util::Optional<int64_t>>(arr, *sz);
×
1120
                    }
×
1121
                    else {
41,856✔
1122
                        verify_list<int64_t>(arr, *sz);
41,856✔
1123
                    }
41,856✔
1124
                    break;
41,856✔
1125
                case col_type_Bool:
✔
1126
                    verify_list<Bool>(arr, *sz);
×
1127
                    break;
×
1128
                case col_type_Float:
6✔
1129
                    verify_list<Float>(arr, *sz);
6✔
1130
                    break;
6✔
1131
                case col_type_Double:
✔
1132
                    verify_list<Double>(arr, *sz);
×
1133
                    break;
×
1134
                case col_type_String:
55,875✔
1135
                    verify_list<String>(arr, *sz);
55,875✔
1136
                    break;
55,875✔
1137
                case col_type_Binary:
24,000✔
1138
                    verify_list<Binary>(arr, *sz);
24,000✔
1139
                    break;
24,000✔
1140
                case col_type_Timestamp:
✔
1141
                    verify_list<Timestamp>(arr, *sz);
×
1142
                    break;
×
1143
                case col_type_Decimal:
✔
1144
                    verify_list<Decimal128>(arr, *sz);
×
1145
                    break;
×
1146
                case col_type_ObjectId:
✔
1147
                    verify_list<ObjectId>(arr, *sz);
×
1148
                    break;
×
1149
                case col_type_UUID:
✔
1150
                    verify_list<UUID>(arr, *sz);
×
1151
                    break;
×
1152
                case col_type_Link:
1,086✔
1153
                    verify_list<ObjKey>(arr, *sz);
1,086✔
1154
                    break;
1,086✔
1155
                default:
18✔
1156
                    // FIXME: Nullable primitives
1157
                    break;
18✔
1158
            }
122,841✔
1159
            return IteratorControl::AdvanceToNext;
122,841✔
1160
        }
122,841✔
1161
        else if (attr.test(col_attr_Dictionary)) {
592,683✔
1162
            ArrayRef arr(get_alloc());
12,168✔
1163
            arr.set_parent(const_cast<Cluster*>(this), col);
12,168✔
1164
            arr.init_from_ref(ref);
12,168✔
1165
            arr.verify();
12,168✔
1166
            if (sz) {
12,168✔
1167
                REALM_ASSERT(arr.size() == *sz);
138✔
1168
            }
138✔
1169
            else {
12,030✔
1170
                sz = arr.size();
12,030✔
1171
            }
12,030✔
1172
            for (size_t n = 0; n < sz; n++) {
24,504✔
1173
                if (auto ref = arr.get(n)) {
12,336✔
1174
                    Dictionary dict(get_alloc(), col_key, to_ref(ref));
12,219✔
1175
                    dict.verify();
12,219✔
1176
                }
12,219✔
1177
            }
12,336✔
1178
            return IteratorControl::AdvanceToNext;
12,168✔
1179
        }
12,168✔
1180
        else if (attr.test(col_attr_Set)) {
580,515✔
1181
            ArrayRef arr(get_alloc());
270✔
1182
            arr.set_parent(const_cast<Cluster*>(this), col);
270✔
1183
            arr.init_from_ref(ref);
270✔
1184
            arr.verify();
270✔
1185
            if (sz) {
270✔
1186
                REALM_ASSERT(arr.size() == *sz);
252✔
1187
            }
252✔
1188
            else {
18✔
1189
                sz = arr.size();
18✔
1190
            }
18✔
1191
            switch (col_type) {
270✔
1192
                case col_type_Int:
138✔
1193
                    if (nullable) {
138✔
1194
                        verify_set<util::Optional<int64_t>>(arr, *sz);
×
1195
                    }
×
1196
                    else {
138✔
1197
                        verify_set<int64_t>(arr, *sz);
138✔
1198
                    }
138✔
1199
                    break;
138✔
1200
                case col_type_Bool:
✔
1201
                    verify_set<Bool>(arr, *sz);
×
1202
                    break;
×
1203
                case col_type_Float:
✔
1204
                    verify_set<Float>(arr, *sz);
×
1205
                    break;
×
1206
                case col_type_Double:
✔
1207
                    verify_set<Double>(arr, *sz);
×
1208
                    break;
×
1209
                case col_type_String:
6✔
1210
                    verify_set<String>(arr, *sz);
6✔
1211
                    break;
6✔
1212
                case col_type_Binary:
18✔
1213
                    verify_set<Binary>(arr, *sz);
18✔
1214
                    break;
18✔
1215
                case col_type_Timestamp:
✔
1216
                    verify_set<Timestamp>(arr, *sz);
×
1217
                    break;
×
1218
                case col_type_Decimal:
✔
1219
                    verify_set<Decimal128>(arr, *sz);
×
1220
                    break;
×
1221
                case col_type_ObjectId:
✔
1222
                    verify_set<ObjectId>(arr, *sz);
×
1223
                    break;
×
1224
                case col_type_UUID:
✔
1225
                    verify_set<UUID>(arr, *sz);
×
1226
                    break;
×
1227
                case col_type_Link:
12✔
1228
                    verify_set<ObjKey>(arr, *sz);
12✔
1229
                    break;
12✔
1230
                default:
96✔
1231
                    // FIXME: Nullable primitives
1232
                    break;
96✔
1233
            }
270✔
1234
            return IteratorControl::AdvanceToNext;
270✔
1235
        }
270✔
1236

1237
        switch (col_type) {
580,245✔
1238
            case col_type_Int:
345,357✔
1239
                if (nullable) {
345,357✔
1240
                    verify<ArrayIntNull>(ref, col, sz);
29,328✔
1241
                }
29,328✔
1242
                else {
316,029✔
1243
                    verify<ArrayInteger>(ref, col, sz);
316,029✔
1244
                }
316,029✔
1245
                break;
345,357✔
1246
            case col_type_Bool:
7,158✔
1247
                verify<ArrayBoolNull>(ref, col, sz);
7,158✔
1248
                break;
7,158✔
1249
            case col_type_Float:
186✔
1250
                verify<ArrayFloatNull>(ref, col, sz);
186✔
1251
                break;
186✔
1252
            case col_type_Double:
213✔
1253
                verify<ArrayDoubleNull>(ref, col, sz);
213✔
1254
                break;
213✔
1255
            case col_type_String:
163,197✔
1256
                verify<ArrayString>(ref, col, sz);
163,197✔
1257
                break;
163,197✔
1258
            case col_type_Binary:
49,872✔
1259
                verify<ArrayBinary>(ref, col, sz);
49,872✔
1260
                break;
49,872✔
1261
            case col_type_Mixed:
2,688✔
1262
                verify<ArrayMixed>(ref, col, sz);
2,688✔
1263
                break;
2,688✔
1264
            case col_type_Timestamp:
7,059✔
1265
                verify<ArrayTimestamp>(ref, col, sz);
7,059✔
1266
                break;
7,059✔
1267
            case col_type_Decimal:
42✔
1268
                verify<ArrayDecimal128>(ref, col, sz);
42✔
1269
                break;
42✔
1270
            case col_type_ObjectId:
42✔
1271
                verify<ArrayObjectIdNull>(ref, col, sz);
42✔
1272
                break;
42✔
1273
            case col_type_UUID:
✔
1274
                verify<ArrayUUIDNull>(ref, col, sz);
×
1275
                break;
×
1276
            case col_type_Link:
1,773✔
1277
                verify<ArrayKey>(ref, col, sz);
1,773✔
1278
                break;
1,773✔
1279
            case col_type_BackLink:
2,667✔
1280
                verify<ArrayBacklink>(ref, col, sz);
2,667✔
1281
                break;
2,667✔
1282
            default:
✔
1283
                break;
×
1284
        }
580,245✔
1285
        return IteratorControl::AdvanceToNext;
580,251✔
1286
    };
580,245✔
1287

1288
    m_tree_top.m_owner->for_each_and_every_column(verify_column);
408,333✔
1289
#endif
408,333✔
1290
}
408,333✔
1291

1292
// LCOV_EXCL_START
1293
void Cluster::dump_objects(int64_t key_offset, std::string lead) const
1294
{
×
1295
    std::cout << lead << "leaf - size: " << node_size() << std::endl;
×
1296
    if (!m_keys.is_attached()) {
×
1297
        std::cout << lead << "compact form" << std::endl;
×
1298
    }
×
1299

1300
    for (unsigned i = 0; i < node_size(); i++) {
×
1301
        int64_t key_value;
×
1302
        if (m_keys.is_attached()) {
×
1303
            key_value = m_keys.get(i);
×
1304
        }
×
1305
        else {
×
1306
            key_value = int64_t(i);
×
1307
        }
×
1308
        std::cout << lead << "key: " << std::hex << key_value + key_offset << std::dec;
×
1309
        m_tree_top.m_owner->for_each_and_every_column([&](ColKey col) {
×
1310
            size_t j = col.get_index().val + 1;
×
1311
            if (col.get_attrs().test(col_attr_List)) {
×
1312
                ref_type ref = Array::get_as_ref(j);
×
1313
                ArrayRef refs(m_alloc);
×
1314
                refs.init_from_ref(ref);
×
1315
                std::cout << ", {";
×
1316
                ref = refs.get(i);
×
1317
                if (ref) {
×
1318
                    if (col.get_type() == col_type_Int) {
×
1319
                        // This is easy to handle
1320
                        Array ints(m_alloc);
×
1321
                        ints.init_from_ref(ref);
×
1322
                        for (size_t n = 0; n < ints.size(); n++) {
×
1323
                            std::cout << ints.get(n) << ", ";
×
1324
                        }
×
1325
                    }
×
1326
                    else {
×
1327
                        std::cout << col.get_type();
×
1328
                    }
×
1329
                }
×
1330
                std::cout << "}";
×
1331
                return IteratorControl::AdvanceToNext;
×
1332
            }
×
1333

1334
            switch (col.get_type()) {
×
1335
                case col_type_Int: {
×
1336
                    bool nullable = col.get_attrs().test(col_attr_Nullable);
×
1337
                    ref_type ref = Array::get_as_ref(j);
×
1338
                    if (nullable) {
×
1339
                        ArrayIntNull arr_int_null(m_alloc);
×
1340
                        arr_int_null.init_from_ref(ref);
×
1341
                        if (arr_int_null.is_null(i)) {
×
1342
                            std::cout << ", null";
×
1343
                        }
×
1344
                        else {
×
1345
                            std::cout << ", " << *arr_int_null.get(i);
×
1346
                        }
×
1347
                    }
×
1348
                    else {
×
1349
                        Array arr(m_alloc);
×
1350
                        arr.init_from_ref(ref);
×
1351
                        std::cout << ", " << arr.get(i);
×
1352
                    }
×
1353
                    break;
×
1354
                }
×
1355
                case col_type_Bool: {
×
1356
                    ArrayBoolNull arr(m_alloc);
×
1357
                    ref_type ref = Array::get_as_ref(j);
×
1358
                    arr.init_from_ref(ref);
×
1359
                    auto val = arr.get(i);
×
1360
                    std::cout << ", " << (val ? (*val ? "true" : "false") : "null");
×
1361
                    break;
×
1362
                }
×
1363
                case col_type_Float: {
×
1364
                    ArrayFloatNull arr(m_alloc);
×
1365
                    ref_type ref = Array::get_as_ref(j);
×
1366
                    arr.init_from_ref(ref);
×
1367
                    auto val = arr.get(i);
×
1368
                    if (val)
×
1369
                        std::cout << ", " << *val;
×
1370
                    else
×
1371
                        std::cout << ", null";
×
1372
                    break;
×
1373
                }
×
1374
                case col_type_Double: {
×
1375
                    ArrayDoubleNull arr(m_alloc);
×
1376
                    ref_type ref = Array::get_as_ref(j);
×
1377
                    arr.init_from_ref(ref);
×
1378
                    auto val = arr.get(i);
×
1379
                    if (val)
×
1380
                        std::cout << ", " << *val;
×
1381
                    else
×
1382
                        std::cout << ", null";
×
1383
                    break;
×
1384
                }
×
1385
                case col_type_String: {
×
1386
                    ArrayString arr(m_alloc);
×
1387
                    set_spec(arr, col.get_index());
×
1388
                    ref_type ref = Array::get_as_ref(j);
×
1389
                    arr.init_from_ref(ref);
×
1390
                    std::cout << ", " << arr.get(i);
×
1391
                    break;
×
1392
                }
×
1393
                case col_type_Binary: {
×
1394
                    ArrayBinary arr(m_alloc);
×
1395
                    ref_type ref = Array::get_as_ref(j);
×
1396
                    arr.init_from_ref(ref);
×
1397
                    std::cout << ", " << arr.get(i);
×
1398
                    break;
×
1399
                }
×
1400
                case col_type_Mixed: {
×
1401
                    ArrayMixed arr(m_alloc);
×
1402
                    ref_type ref = Array::get_as_ref(j);
×
1403
                    arr.init_from_ref(ref);
×
1404
                    std::cout << ", " << arr.get(i);
×
1405
                    break;
×
1406
                }
×
1407
                case col_type_Timestamp: {
×
1408
                    ArrayTimestamp arr(m_alloc);
×
1409
                    ref_type ref = Array::get_as_ref(j);
×
1410
                    arr.init_from_ref(ref);
×
1411
                    if (arr.is_null(i)) {
×
1412
                        std::cout << ", null";
×
1413
                    }
×
1414
                    else {
×
1415
                        std::cout << ", " << arr.get(i);
×
1416
                    }
×
1417
                    break;
×
1418
                }
×
1419
                case col_type_Decimal: {
×
1420
                    ArrayDecimal128 arr(m_alloc);
×
1421
                    ref_type ref = Array::get_as_ref(j);
×
1422
                    arr.init_from_ref(ref);
×
1423
                    if (arr.is_null(i)) {
×
1424
                        std::cout << ", null";
×
1425
                    }
×
1426
                    else {
×
1427
                        std::cout << ", " << arr.get(i);
×
1428
                    }
×
1429
                    break;
×
1430
                }
×
1431
                case col_type_ObjectId: {
×
1432
                    ArrayObjectIdNull arr(m_alloc);
×
1433
                    ref_type ref = Array::get_as_ref(j);
×
1434
                    arr.init_from_ref(ref);
×
1435
                    if (arr.is_null(i)) {
×
1436
                        std::cout << ", null";
×
1437
                    }
×
1438
                    else {
×
1439
                        std::cout << ", " << *arr.get(i);
×
1440
                    }
×
1441
                    break;
×
1442
                }
×
1443
                case col_type_UUID: {
×
1444
                    ArrayUUIDNull arr(m_alloc);
×
1445
                    ref_type ref = Array::get_as_ref(j);
×
1446
                    arr.init_from_ref(ref);
×
1447
                    if (arr.is_null(i)) {
×
1448
                        std::cout << ", null";
×
1449
                    }
×
1450
                    else {
×
1451
                        std::cout << ", " << arr.get(i);
×
1452
                    }
×
1453
                    break;
×
1454
                }
×
1455
                case col_type_Link: {
×
1456
                    ArrayKey arr(m_alloc);
×
1457
                    ref_type ref = Array::get_as_ref(j);
×
1458
                    arr.init_from_ref(ref);
×
1459
                    std::cout << ", " << arr.get(i);
×
1460
                    break;
×
1461
                }
×
1462
                case col_type_BackLink: {
×
1463
                    break;
×
1464
                }
×
1465
                default:
×
1466
                    std::cout << ", Error";
×
1467
                    break;
×
1468
            }
×
1469
            return IteratorControl::AdvanceToNext;
×
1470
        });
×
1471
        std::cout << std::endl;
×
1472
    }
×
1473
}
×
1474
// LCOV_EXCL_STOP
1475

1476
void Cluster::remove_backlinks(const Table* origin_table, ObjKey origin_key, ColKey origin_col_key,
1477
                               const std::vector<ObjKey>& keys, CascadeState& state)
1478
{
67,140✔
1479
    TableRef target_table = origin_table->get_opposite_table(origin_col_key);
67,140✔
1480
    ColKey backlink_col_key = origin_table->get_opposite_column(origin_col_key);
67,140✔
1481
    bool strong_links = target_table->is_embedded();
67,140✔
1482

1483
    for (auto key : keys) {
68,760✔
1484
        if (key != null_key) {
68,760✔
1485
            bool is_unres = key.is_unresolved();
68,760✔
1486
            Obj target_obj = is_unres ? target_table->m_tombstones->get(key) : target_table->m_clusters.get(key);
68,760✔
1487
            bool last_removed = target_obj.remove_one_backlink(backlink_col_key, origin_key); // Throws
68,760✔
1488
            if (is_unres) {
68,760✔
1489
                if (last_removed) {
30✔
1490
                    // Check is there are more backlinks
1491
                    if (!target_obj.has_backlinks(false)) {
24✔
1492
                        // Tombstones can be erased right away - there is no cascading effect
1493
                        target_table->m_tombstones->erase(key, state);
18✔
1494
                    }
18✔
1495
                }
24✔
1496
            }
30✔
1497
            else {
68,730✔
1498
                state.enqueue_for_cascade(target_obj, strong_links, last_removed);
68,730✔
1499
            }
68,730✔
1500
        }
68,760✔
1501
    }
68,760✔
1502
}
67,140✔
1503

1504
void Cluster::remove_backlinks(const Table* origin_table, ObjKey origin_key, ColKey origin_col_key,
1505
                               const std::vector<ObjLink>& links, CascadeState& state)
1506
{
144✔
1507
    Group* group = origin_table->get_parent_group();
144✔
1508
    TableKey origin_table_key = origin_table->get_key();
144✔
1509

1510
    for (auto link : links) {
240✔
1511
        if (link) {
240✔
1512
            bool is_unres = link.get_obj_key().is_unresolved();
240✔
1513
            Obj target_obj = group->get_object(link);
240✔
1514
            TableRef target_table = target_obj.get_table();
240✔
1515
            ColKey backlink_col_key = target_table->find_or_add_backlink_column(origin_col_key, origin_table_key);
240✔
1516

1517
            bool last_removed = target_obj.remove_one_backlink(backlink_col_key, origin_key); // Throws
240✔
1518
            if (is_unres) {
240✔
1519
                if (last_removed) {
6✔
1520
                    // Check is there are more backlinks
1521
                    if (!target_obj.has_backlinks(false)) {
6✔
1522
                        // Tombstones can be erased right away - there is no cascading effect
1523
                        target_table->m_tombstones->erase(link.get_obj_key(), state);
6✔
1524
                    }
6✔
1525
                }
6✔
1526
            }
6✔
1527
            else {
234✔
1528
                state.enqueue_for_cascade(target_obj, false, last_removed);
234✔
1529
            }
234✔
1530
        }
240✔
1531
    }
240✔
1532
}
144✔
1533

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