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

realm / realm-core / finn.schiermer-andersen_89

04 Jun 2024 02:04PM UTC coverage: 90.651% (-0.03%) from 90.685%
finn.schiermer-andersen_89

Pull #7654

Evergreen

finnschiermer
optimized string cache gc
Pull Request #7654: Fsa/string interning

102644 of 180648 branches covered (56.82%)

1005 of 1125 new or added lines in 15 files covered. (89.33%)

154 existing lines in 21 files now uncovered.

217953 of 240431 relevant lines covered (90.65%)

7671710.15 hits per line

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

74.65
/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,607✔
46
{
801,426✔
47
    if (m_values.size() > 1) {
801,426✔
48
        // Sort according to ColKey index
49
        std::sort(m_values.begin(), m_values.end(), [](const auto& a, const auto& b) {
53,301✔
50
            return a.col_key.get_index().val < b.col_key.get_index().val;
53,301✔
51
        });
53,301✔
52
    }
17,802✔
53
}
801,426✔
54

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

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

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

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

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

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

97

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

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

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

109

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

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

125
    auto column_initialize = [this](ColKey col_key) {
195,876✔
126
        auto col_ndx = col_key.get_index();
195,876✔
127
        while (size() <= col_ndx.val + 1)
391,752✔
128
            add(0);
195,876✔
129
        auto type = col_key.get_type();
195,876✔
130
        auto attr = col_key.get_attrs();
195,876✔
131
        if (attr.test(col_attr_Collection)) {
195,876✔
132
            ArrayRef arr(m_alloc);
6,156✔
133
            arr.create();
6,156✔
134
            arr.set_parent(this, col_ndx.val + s_first_col_index);
6,156✔
135
            arr.update_parent();
6,156✔
136
            return IteratorControl::AdvanceToNext;
6,156✔
137
        }
6,156✔
138
        switch (type) {
189,720✔
139
            case col_type_Int:
113,373✔
140
                if (attr.test(col_attr_Nullable)) {
113,373✔
141
                    do_create<ArrayIntNull>(col_key);
9,894✔
142
                }
9,894✔
143
                else {
103,479✔
144
                    do_create<ArrayInteger>(col_key);
103,479✔
145
                }
103,479✔
146
                break;
113,373✔
147
            case col_type_Bool:
684✔
148
                do_create<ArrayBoolNull>(col_key);
684✔
149
                break;
684✔
150
            case col_type_Float:
1,116✔
151
                do_create<ArrayFloatNull>(col_key);
1,116✔
152
                break;
1,116✔
153
            case col_type_Double:
5,793✔
154
                do_create<ArrayDoubleNull>(col_key);
5,793✔
155
                break;
5,793✔
156
            case col_type_String: {
49,767✔
157
                if (m_tree_top.is_string_enum_type(col_ndx)) {
49,767✔
158
                    do_create<ArrayInteger>(col_key);
5,340✔
159
                }
5,340✔
160
                else {
44,427✔
161
                    do_create<ArrayString>(col_key);
44,427✔
162
                }
44,427✔
163
                break;
49,767✔
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:
108✔
169
                do_create<ArrayMixed>(col_key);
108✔
170
                break;
108✔
171
            case col_type_Timestamp:
4,815✔
172
                do_create<ArrayTimestamp>(col_key);
4,815✔
173
                break;
4,815✔
174
            case col_type_Decimal:
276✔
175
                do_create<ArrayDecimal128>(col_key);
276✔
176
                break;
276✔
177
            case col_type_ObjectId:
2,901✔
178
                do_create<ArrayObjectIdNull>(col_key);
2,901✔
179
                break;
2,901✔
180
            case col_type_UUID:
408✔
181
                do_create<ArrayUUIDNull>(col_key);
408✔
182
                break;
408✔
183
            case col_type_Link:
2,169✔
184
                do_create<ArrayKey>(col_key);
2,169✔
185
                break;
2,169✔
186
            case col_type_TypedLink:
✔
187
                do_create<ArrayTypedLink>(col_key);
×
188
                break;
×
189
            case col_type_BackLink:
3,030✔
190
                do_create<ArrayBacklink>(col_key);
3,030✔
191
                break;
3,030✔
192
            default:
✔
193
                REALM_UNREACHABLE();
194
        }
189,720✔
195
        return IteratorControl::AdvanceToNext;
189,720✔
196
    };
189,720✔
197
    m_tree_top.m_owner->for_each_and_every_column(column_initialize);
103,323✔
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);
103,323✔
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();
103,323✔
205
}
103,323✔
206

207
void Cluster::init(MemRef mem)
208
{
323,834,916✔
209
    Array::init_from_mem(mem);
323,834,916✔
210
    auto rot = Array::get_as_ref_or_tagged(0);
323,834,916✔
211
    if (rot.is_tagged()) {
323,834,916✔
212
        m_keys.detach();
260,167,860✔
213
    }
260,167,860✔
214
    else {
63,667,056✔
215
        m_keys.init_from_ref(rot.get_as_ref());
63,667,056✔
216
    }
63,667,056✔
217
}
323,834,916✔
218

219
void Cluster::update_from_parent() noexcept
220
{
932,868✔
221
    Array::update_from_parent();
932,868✔
222
    auto rot = Array::get_as_ref_or_tagged(0);
932,868✔
223
    if (!rot.is_tagged()) {
932,868✔
224
        m_keys.update_from_parent();
31,419✔
225
    }
31,419✔
226
}
932,868✔
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
{
71,976,096✔
244
    auto rot = Array::get_as_ref_or_tagged(header, s_key_ref_or_size_index);
71,976,096✔
245
    if (rot.is_tagged()) {
71,976,096✔
246
        return size_t(rot.get_as_int());
71,645,454✔
247
    }
71,645,454✔
248
    else {
330,642✔
249
        return Array::get_size_from_header(alloc.translate(rot.get_as_ref()));
330,642✔
250
    }
330,642✔
251
}
71,976,096✔
252

253
template <class T>
254
inline void Cluster::set_string_interner(T&, ColKey) const
255
{
38,320,566✔
256
}
38,320,566✔
257

258
template <>
259
inline void Cluster::set_string_interner(ArrayString& arr, ColKey col_key) const
260
{
3,797,151✔
261
    m_tree_top.set_string_interner(arr, col_key);
3,797,151✔
262
}
3,797,151✔
263

264
template <class T>
265
inline void Cluster::set_spec(T&, ColKey::Idx) const
266
{
38,271,354✔
267
}
38,271,354✔
268

269
template <>
270
inline void Cluster::set_spec(ArrayString& arr, ColKey::Idx col_ndx) const
271
{
3,796,146✔
272
    m_tree_top.set_spec(arr, col_ndx);
3,796,146✔
273
}
3,796,146✔
274

275
template <class T>
276
inline void Cluster::do_insert_row(size_t ndx, ColKey col, Mixed init_val, bool nullable)
277
{
34,916,985✔
278
    using U = typename util::RemoveOptional<typename T::value_type>::type;
34,916,985✔
279

280
    T arr(m_alloc);
34,916,985✔
281
    auto col_ndx = col.get_index();
34,916,985✔
282
    arr.set_parent(this, col_ndx.val + s_first_col_index);
34,916,985✔
283
    set_spec<T>(arr, col_ndx);
34,916,985✔
284
    set_string_interner<T>(arr, col);
34,916,985✔
285
    arr.init_from_parent();
34,916,985✔
286
    if (init_val.is_null()) {
34,916,985✔
287
        arr.insert(ndx, T::default_value(nullable));
33,941,313✔
288
    }
33,941,313✔
289
    else {
975,672✔
290
        arr.insert(ndx, init_val.get<U>());
975,672✔
291
    }
975,672✔
292
}
34,916,985✔
293

294
inline void Cluster::do_insert_key(size_t ndx, ColKey col_key, Mixed init_val, ObjKey origin_key)
295
{
253,557✔
296
    ObjKey target_key = init_val.is_null() ? ObjKey{} : init_val.get<ObjKey>();
253,557✔
297
    ArrayKey arr(m_alloc);
253,557✔
298
    auto col_ndx = col_key.get_index();
253,557✔
299
    arr.set_parent(this, col_ndx.val + s_first_col_index);
253,557✔
300
    arr.init_from_parent();
253,557✔
301
    arr.insert(ndx, target_key);
253,557✔
302

303
    // Insert backlink if link is not null
304
    if (target_key) {
253,557✔
305
        const Table* origin_table = m_tree_top.get_owning_table();
12✔
306
        ColKey opp_col = origin_table->get_opposite_column(col_key);
12✔
307
        TableRef opp_table = origin_table->get_opposite_table(col_key);
12✔
308
        Obj target_obj = opp_table->get_object(target_key);
12✔
309
        target_obj.add_backlink(opp_col, origin_key);
12✔
310
    }
12✔
311
}
253,557✔
312

313
inline void Cluster::do_insert_mixed(size_t ndx, ColKey col_key, Mixed init_value, ObjKey origin_key)
314
{
22,314✔
315
    ArrayMixed arr(m_alloc);
22,314✔
316
    arr.set_parent(this, col_key.get_index().val + s_first_col_index);
22,314✔
317
    arr.init_from_parent();
22,314✔
318
    arr.insert(ndx, init_value);
22,314✔
319

320
    // Insert backlink if needed
321
    if (init_value.is_type(type_TypedLink)) {
22,314✔
322
        // In case we are inserting in a Dictionary cluster, the backlink will
323
        // be handled in Dictionary::insert function
324
        if (Table* origin_table = const_cast<Table*>(m_tree_top.get_owning_table())) {
24✔
325
            if (origin_table->is_asymmetric()) {
24✔
326
                throw IllegalOperation("Object value not supported in asymmetric table");
6✔
327
            }
6✔
328
            ObjLink link = init_value.get<ObjLink>();
18✔
329
            auto target_table = origin_table->get_parent_group()->get_table(link.get_table_key());
18✔
330
            if (target_table->is_asymmetric()) {
18✔
331
                throw IllegalOperation("Ephemeral object value not supported");
6✔
332
            }
6✔
333
            ColKey backlink_col_key = target_table->find_or_add_backlink_column(col_key, origin_table->get_key());
12✔
334
            target_table->get_object(link.get_obj_key()).add_backlink(backlink_col_key, origin_key);
12✔
335
        }
12✔
336
    }
24✔
337
}
22,314✔
338

339
inline void Cluster::do_insert_link(size_t ndx, ColKey col_key, Mixed init_val, ObjKey origin_key)
340
{
×
341
    ObjLink target_link = init_val.is_null() ? ObjLink{} : init_val.get<ObjLink>();
×
342
    ArrayTypedLink arr(m_alloc);
×
343
    auto col_ndx = col_key.get_index();
×
344
    arr.set_parent(this, col_ndx.val + s_first_col_index);
×
345
    arr.init_from_parent();
×
346
    arr.insert(ndx, target_link);
×
347

348
    // Insert backlink if link is not null
349
    if (target_link) {
×
350
        Table* origin_table = const_cast<Table*>(m_tree_top.get_owning_table());
×
351
        auto target_table = origin_table->get_parent_group()->get_table(target_link.get_table_key());
×
352

353
        ColKey backlink_col_key = target_table->find_or_add_backlink_column(col_key, origin_table->get_key());
×
354
        target_table->get_object(target_link.get_obj_key()).add_backlink(backlink_col_key, origin_key);
×
355
    }
×
356
}
×
357

358
void Cluster::insert_row(size_t ndx, ObjKey k, const FieldValues& init_values)
359
{
25,314,009✔
360
    // Ensure the cluster array is big enough to hold 64 bit values.
361
    copy_on_write(m_size * 8);
25,314,009✔
362

363
    if (m_keys.is_attached()) {
25,314,009✔
364
        m_keys.insert(ndx, k.value);
1,581,126✔
365
    }
1,581,126✔
366
    else {
23,732,883✔
367
        Array::set(s_key_ref_or_size_index, Array::get(s_key_ref_or_size_index) + 2); // Increments size by 1
23,732,883✔
368
    }
23,732,883✔
369

370
    auto val = init_values.begin();
25,314,009✔
371
    auto insert_in_column = [&](ColKey col_key) {
36,756,363✔
372
        auto col_ndx = col_key.get_index();
36,756,363✔
373
        auto attr = col_key.get_attrs();
36,756,363✔
374
        Mixed init_value;
36,756,363✔
375
        // init_values must be sorted in col_ndx order - this is ensured by ClustTree::insert()
376
        if (val != init_values.end() && val->col_key.get_index().val == col_ndx.val) {
36,756,363✔
377
            init_value = val->value;
824,514✔
378
            ++val;
824,514✔
379
        }
824,514✔
380

381
        auto type = col_key.get_type();
36,756,363✔
382
        if (attr.test(col_attr_Collection)) {
36,756,363✔
383
            REALM_ASSERT(init_value.is_null());
678,663✔
384
            ArrayRef arr(m_alloc);
678,663✔
385
            arr.set_parent(this, col_ndx.val + s_first_col_index);
678,663✔
386
            arr.init_from_parent();
678,663✔
387
            arr.insert(ndx, 0);
678,663✔
388
            return IteratorControl::AdvanceToNext;
678,663✔
389
        }
678,663✔
390

391
        bool nullable = attr.test(col_attr_Nullable);
36,077,700✔
392
        switch (type) {
36,077,700✔
393
            case col_type_Int:
26,780,517✔
394
                if (attr.test(col_attr_Nullable)) {
26,780,517✔
395
                    do_insert_row<ArrayIntNull>(ndx, col_key, init_value, nullable);
1,978,146✔
396
                }
1,978,146✔
397
                else {
24,802,371✔
398
                    do_insert_row<ArrayInteger>(ndx, col_key, init_value, nullable);
24,802,371✔
399
                }
24,802,371✔
400
                break;
26,780,517✔
401
            case col_type_Bool:
186,909✔
402
                do_insert_row<ArrayBoolNull>(ndx, col_key, init_value, nullable);
186,909✔
403
                break;
186,909✔
404
            case col_type_Float:
325,074✔
405
                do_insert_row<ArrayFloatNull>(ndx, col_key, init_value, nullable);
325,074✔
406
                break;
325,074✔
407
            case col_type_Double:
1,493,079✔
408
                do_insert_row<ArrayDoubleNull>(ndx, col_key, init_value, nullable);
1,493,079✔
409
                break;
1,493,079✔
410
            case col_type_String:
3,570,612✔
411
                do_insert_row<ArrayString>(ndx, col_key, init_value, nullable);
3,570,612✔
412
                break;
3,570,612✔
413
            case col_type_Binary:
1,364,211✔
414
                do_insert_row<ArrayBinary>(ndx, col_key, init_value, nullable);
1,364,211✔
415
                break;
1,364,211✔
416
            case col_type_Mixed: {
22,314✔
417
                do_insert_mixed(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
22,314✔
418
                break;
22,314✔
419
            }
×
420
            case col_type_Timestamp:
824,616✔
421
                do_insert_row<ArrayTimestamp>(ndx, col_key, init_value, nullable);
824,616✔
422
                break;
824,616✔
423
            case col_type_Decimal:
78,222✔
424
                do_insert_row<ArrayDecimal128>(ndx, col_key, init_value, nullable);
78,222✔
425
                break;
78,222✔
426
            case col_type_ObjectId:
173,424✔
427
                do_insert_row<ArrayObjectIdNull>(ndx, col_key, init_value, nullable);
173,424✔
428
                break;
173,424✔
429
            case col_type_UUID:
119,652✔
430
                do_insert_row<ArrayUUIDNull>(ndx, col_key, init_value, nullable);
119,652✔
431
                break;
119,652✔
432
            case col_type_Link:
253,557✔
433
                do_insert_key(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
253,557✔
434
                break;
253,557✔
435
            case col_type_TypedLink:
✔
436
                do_insert_link(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
×
437
                break;
×
438
            case col_type_BackLink: {
962,619✔
439
                ArrayBacklink arr(m_alloc);
962,619✔
440
                arr.set_parent(this, col_ndx.val + s_first_col_index);
962,619✔
441
                arr.init_from_parent();
962,619✔
442
                arr.insert(ndx, 0);
962,619✔
443
                break;
962,619✔
444
            }
×
445
            default:
✔
446
                REALM_ASSERT(false);
×
447
                break;
×
448
        }
36,077,700✔
449
        return IteratorControl::AdvanceToNext;
35,933,202✔
450
    };
36,077,700✔
451
    m_tree_top.m_owner->for_each_and_every_column(insert_in_column);
25,314,009✔
452
}
25,314,009✔
453

454
template <class T>
455
inline void Cluster::do_move(size_t ndx, ColKey col_key, Cluster* to)
456
{
22,128✔
457
    auto col_ndx = col_key.get_index().val + s_first_col_index;
22,128✔
458
    T src(m_alloc);
22,128✔
459
    src.set_parent(this, col_ndx);
22,128✔
460
    src.init_from_parent();
22,128✔
461
    set_string_interner<T>(src, col_key);
22,128✔
462

463
    T dst(m_alloc);
22,128✔
464
    dst.set_parent(to, col_ndx);
22,128✔
465
    dst.init_from_parent();
22,128✔
466
    set_string_interner<T>(dst, col_key);
22,128✔
467

468
    src.move(dst, ndx);
22,128✔
469
}
22,128✔
470

471
void Cluster::move(size_t ndx, ClusterNode* new_node, int64_t offset)
472
{
11,640✔
473
    auto new_leaf = static_cast<Cluster*>(new_node);
11,640✔
474

475
    auto move_from_column = [&](ColKey col_key) {
22,128✔
476
        auto attr = col_key.get_attrs();
22,128✔
477
        auto type = col_key.get_type();
22,128✔
478

479
        if (attr.test(col_attr_Collection)) {
22,128✔
480
            do_move<ArrayRef>(ndx, col_key, new_leaf);
30✔
481
            return IteratorControl::AdvanceToNext;
30✔
482
        }
30✔
483

484
        switch (type) {
22,098✔
485
            case col_type_Int:
21,228✔
486
                if (attr.test(col_attr_Nullable)) {
21,228✔
487
                    do_move<ArrayIntNull>(ndx, col_key, new_leaf);
9,606✔
488
                }
9,606✔
489
                else {
11,622✔
490
                    do_move<ArrayInteger>(ndx, col_key, new_leaf);
11,622✔
491
                }
11,622✔
492
                break;
21,228✔
493
            case col_type_Bool:
36✔
494
                do_move<ArrayBoolNull>(ndx, col_key, new_leaf);
36✔
495
                break;
36✔
496
            case col_type_Float:
36✔
497
                do_move<ArrayFloat>(ndx, col_key, new_leaf);
36✔
498
                break;
36✔
499
            case col_type_Double:
36✔
500
                do_move<ArrayDouble>(ndx, col_key, new_leaf);
36✔
501
                break;
36✔
502
            case col_type_String: {
36✔
503
                if (m_tree_top.is_string_enum_type(col_key.get_index()))
36✔
504
                    do_move<ArrayInteger>(ndx, col_key, new_leaf);
×
505
                else
36✔
506
                    do_move<ArrayString>(ndx, col_key, new_leaf);
36✔
507
                break;
36✔
508
            }
×
509
            case col_type_Binary:
36✔
510
                do_move<ArrayBinary>(ndx, col_key, new_leaf);
36✔
511
                break;
36✔
512
            case col_type_Mixed:
✔
513
                do_move<ArrayMixed>(ndx, col_key, new_leaf);
×
514
                break;
×
515
            case col_type_Timestamp:
36✔
516
                do_move<ArrayTimestamp>(ndx, col_key, new_leaf);
36✔
517
                break;
36✔
518
            case col_type_Decimal:
36✔
519
                do_move<ArrayDecimal128>(ndx, col_key, new_leaf);
36✔
520
                break;
36✔
521
            case col_type_ObjectId:
36✔
522
                do_move<ArrayObjectIdNull>(ndx, col_key, new_leaf);
36✔
523
                break;
36✔
524
            case col_type_UUID:
120✔
525
                do_move<ArrayUUIDNull>(ndx, col_key, new_leaf);
120✔
526
                break;
120✔
527
            case col_type_Link:
6✔
528
                do_move<ArrayKey>(ndx, col_key, new_leaf);
6✔
529
                break;
6✔
530
            case col_type_TypedLink:
✔
531
                do_move<ArrayTypedLink>(ndx, col_key, new_leaf);
×
532
                break;
×
533
            case col_type_BackLink:
456✔
534
                do_move<ArrayBacklink>(ndx, col_key, new_leaf);
456✔
535
                break;
456✔
536
            default:
✔
537
                REALM_ASSERT(false);
×
538
                break;
×
539
        }
22,098✔
540
        return IteratorControl::AdvanceToNext;
22,098✔
541
    };
22,098✔
542
    m_tree_top.m_owner->for_each_and_every_column(move_from_column);
11,640✔
543
    for (size_t i = ndx; i < m_keys.size(); i++) {
1,245,903✔
544
        new_leaf->m_keys.add(m_keys.get(i) - offset);
1,234,263✔
545
    }
1,234,263✔
546
    m_keys.truncate(ndx);
11,640✔
547
}
11,640✔
548

549
Cluster::~Cluster() {}
328,102,245✔
550

551
ColKey Cluster::get_col_key(size_t ndx_in_parent) const
552
{
18,387✔
553
    ColKey::Idx col_ndx{unsigned(ndx_in_parent - 1)}; // <- leaf_index here. Opaque.
18,387✔
554
    auto col_key = get_owning_table()->leaf_ndx2colkey(col_ndx);
18,387✔
555
    REALM_ASSERT(col_key.get_index().val == col_ndx.val);
18,387✔
556
    return col_key;
18,387✔
557
}
18,387✔
558

559
void Cluster::ensure_general_form()
560
{
61,803✔
561
    if (!m_keys.is_attached()) {
61,803✔
562
        size_t current_size = get_size_in_compact_form();
48,345✔
563
        m_keys.create(current_size, 255);
48,345✔
564
        m_keys.update_parent();
48,345✔
565
        for (size_t i = 0; i < current_size; i++) {
5,519,400✔
566
            m_keys.set(i, i);
5,471,055✔
567
        }
5,471,055✔
568
    }
48,345✔
569
}
61,803✔
570

571
template <class T>
572
inline void Cluster::do_insert_column(ColKey col_key, bool nullable)
573
{
565,221✔
574
    size_t sz = node_size();
565,221✔
575

576
    T arr(m_alloc);
565,221✔
577
    arr.create();
565,221✔
578
    auto val = T::default_value(nullable);
565,221✔
579
    for (size_t i = 0; i < sz; i++) {
6,802,929✔
580
        arr.add(val);
6,237,708✔
581
    }
6,237,708✔
582
    auto col_ndx = col_key.get_index();
565,221✔
583
    unsigned ndx = col_ndx.val + s_first_col_index;
565,221✔
584

585
    // Fill up if indexes are not consecutive
586
    while (size() < ndx)
565,221✔
587
        Array::add(0);
×
588

589
    if (ndx == size())
565,221✔
590
        Array::insert(ndx, from_ref(arr.get_ref()));
565,131✔
591
    else
90✔
592
        Array::set(ndx, from_ref(arr.get_ref()));
90✔
593
}
565,221✔
594

595
void Cluster::insert_column(ColKey col_key)
596
{
760,098✔
597
    auto attr = col_key.get_attrs();
760,098✔
598
    auto type = col_key.get_type();
760,098✔
599
    if (attr.test(col_attr_Collection)) {
760,098✔
600
        size_t sz = node_size();
194,877✔
601

602
        ArrayRef arr(m_alloc);
194,877✔
603
        arr.create(sz);
194,877✔
604
        auto col_ndx = col_key.get_index();
194,877✔
605
        unsigned idx = col_ndx.val + s_first_col_index;
194,877✔
606
        if (idx == size())
194,877✔
607
            Array::insert(idx, from_ref(arr.get_ref()));
194,877✔
608
        else
×
609
            Array::set(idx, from_ref(arr.get_ref()));
×
610
        return;
194,877✔
611
    }
194,877✔
612
    bool nullable = attr.test(col_attr_Nullable);
565,221✔
613
    switch (type) {
565,221✔
614
        case col_type_Int:
251,580✔
615
            if (nullable) {
251,580✔
616
                do_insert_column<ArrayIntNull>(col_key, nullable);
20,988✔
617
            }
20,988✔
618
            else {
230,592✔
619
                do_insert_column<ArrayInteger>(col_key, nullable);
230,592✔
620
            }
230,592✔
621
            break;
251,580✔
622
        case col_type_Bool:
5,481✔
623
            do_insert_column<ArrayBoolNull>(col_key, nullable);
5,481✔
624
            break;
5,481✔
625
        case col_type_Float:
6,438✔
626
            do_insert_column<ArrayFloatNull>(col_key, nullable);
6,438✔
627
            break;
6,438✔
628
        case col_type_Double:
7,086✔
629
            do_insert_column<ArrayDoubleNull>(col_key, nullable);
7,086✔
630
            break;
7,086✔
631
        case col_type_String:
79,248✔
632
            do_insert_column<ArrayString>(col_key, nullable);
79,248✔
633
            break;
79,248✔
634
        case col_type_Binary:
6,876✔
635
            do_insert_column<ArrayBinary>(col_key, nullable);
6,876✔
636
            break;
6,876✔
637
        case col_type_Mixed:
9,045✔
638
            do_insert_column<ArrayMixed>(col_key, nullable);
9,045✔
639
            break;
9,045✔
640
        case col_type_Timestamp:
23,532✔
641
            do_insert_column<ArrayTimestamp>(col_key, nullable);
23,532✔
642
            break;
23,532✔
643
        case col_type_Decimal:
5,025✔
644
            do_insert_column<ArrayDecimal128>(col_key, nullable);
5,025✔
645
            break;
5,025✔
646
        case col_type_ObjectId:
50,484✔
647
            do_insert_column<ArrayObjectIdNull>(col_key, nullable);
50,484✔
648
            break;
50,484✔
649
        case col_type_UUID:
5,619✔
650
            do_insert_column<ArrayUUIDNull>(col_key, nullable);
5,619✔
651
            break;
5,619✔
652
        case col_type_Link:
31,095✔
653
            do_insert_column<ArrayKey>(col_key, nullable);
31,095✔
654
            break;
31,095✔
655
        case col_type_TypedLink:
✔
656
            do_insert_column<ArrayTypedLink>(col_key, nullable);
×
657
            break;
×
658
        case col_type_BackLink:
83,712✔
659
            do_insert_column<ArrayBacklink>(col_key, nullable);
83,712✔
660
            break;
83,712✔
661
        default:
✔
662
            REALM_UNREACHABLE();
663
            break;
×
664
    }
565,221✔
665
}
565,221✔
666

667
void Cluster::remove_column(ColKey col_key)
668
{
6,705✔
669
    auto col_ndx = col_key.get_index();
6,705✔
670
    unsigned idx = col_ndx.val + s_first_col_index;
6,705✔
671
    ref_type ref = to_ref(Array::get(idx));
6,705✔
672
    if (ref != 0) {
6,705✔
673
        Array::destroy_deep(ref, m_alloc);
6,705✔
674
    }
6,705✔
675
    if (idx == size() - 1)
6,705✔
676
        Array::erase(idx);
5,166✔
677
    else
1,539✔
678
        Array::set(idx, 0);
1,539✔
679
}
6,705✔
680

681
ref_type Cluster::insert(ObjKey k, const FieldValues& init_values, ClusterNode::State& state)
682
{
25,292,700✔
683
    int64_t current_key_value = -1;
25,292,700✔
684
    size_t sz;
25,292,700✔
685
    size_t ndx;
25,292,700✔
686
    ref_type ret = 0;
25,292,700✔
687

688
    auto on_error = [&] {
25,292,700✔
689
        throw KeyAlreadyUsed(
12✔
690
            util::format("When inserting key '%1' in '%2'", k.value, get_owning_table()->get_name()));
12✔
691
    };
12✔
692

693
    if (m_keys.is_attached()) {
25,292,700✔
694
        sz = m_keys.size();
1,569,840✔
695
        ndx = m_keys.lower_bound(uint64_t(k.value));
1,569,840✔
696
        if (ndx < sz) {
1,569,840✔
697
            current_key_value = m_keys.get(ndx);
616,710✔
698
            if (k.value == current_key_value) {
616,710✔
699
                on_error();
12✔
700
            }
12✔
701
        }
616,710✔
702
    }
1,569,840✔
703
    else {
23,722,860✔
704
        sz = size_t(Array::get(s_key_ref_or_size_index)) >> 1; // Size is stored as tagged integer
23,722,860✔
705
        if (uint64_t(k.value) < sz) {
23,722,860✔
706
            on_error();
×
707
        }
×
708
        // Key value is bigger than all other values, should be put last
709
        ndx = sz;
23,722,860✔
710
        if (uint64_t(k.value) > sz && sz < cluster_node_size) {
23,722,860✔
711
            ensure_general_form();
16,920✔
712
        }
16,920✔
713
    }
23,722,860✔
714

715
    REALM_ASSERT_DEBUG(sz <= cluster_node_size);
25,292,700✔
716
    if (REALM_LIKELY(sz < cluster_node_size)) {
25,292,700✔
717
        insert_row(ndx, k, init_values); // Throws
25,212,702✔
718
        state.mem = get_mem();
25,212,702✔
719
        state.index = ndx;
25,212,702✔
720
    }
25,212,702✔
721
    else {
79,998✔
722
        // Split leaf node
723
        Cluster new_leaf(0, m_alloc, m_tree_top);
79,998✔
724
        new_leaf.create();
79,998✔
725
        if (ndx == sz) {
92,475✔
726
            new_leaf.insert_row(0, ObjKey(0), init_values); // Throws
92,475✔
727
            state.split_key = k.value;
92,475✔
728
            state.mem = new_leaf.get_mem();
92,475✔
729
            state.index = 0;
92,475✔
730
        }
92,475✔
731
        else {
4,294,967,294✔
732
            // Current cluster must be in general form to get here
733
            REALM_ASSERT_DEBUG(m_keys.is_attached());
4,294,967,294✔
734
            new_leaf.ensure_general_form();
4,294,967,294✔
735
            move(ndx, &new_leaf, current_key_value);
4,294,967,294✔
736
            insert_row(ndx, k, init_values); // Throws
4,294,967,294✔
737
            state.mem = get_mem();
4,294,967,294✔
738
            state.split_key = current_key_value;
4,294,967,294✔
739
            state.index = ndx;
4,294,967,294✔
740
        }
4,294,967,294✔
741
        ret = new_leaf.get_ref();
79,998✔
742
    }
79,998✔
743

744
    return ret;
25,292,700✔
745
}
25,292,700✔
746

747
bool Cluster::try_get(ObjKey k, ClusterNode::State& state) const noexcept
748
{
273,686,160✔
749
    state.mem = get_mem();
273,686,160✔
750
    if (m_keys.is_attached()) {
273,686,160✔
751
        state.index = m_keys.lower_bound(uint64_t(k.value));
50,230,440✔
752
        return state.index != m_keys.size() && m_keys.get(state.index) == uint64_t(k.value);
50,230,440✔
753
    }
50,230,440✔
754
    else {
223,455,720✔
755
        if (uint64_t(k.value) < uint64_t(Array::get(s_key_ref_or_size_index) >> 1)) {
223,455,720✔
756
            state.index = size_t(k.value);
205,750,038✔
757
            return true;
205,750,038✔
758
        }
205,750,038✔
759
    }
223,455,720✔
760
    return false;
17,705,682✔
761
}
273,686,160✔
762

763
ObjKey Cluster::get(size_t ndx, ClusterNode::State& state) const
764
{
11,454,372✔
765
    state.index = ndx;
11,454,372✔
766
    state.mem = get_mem();
11,454,372✔
767
    return get_real_key(ndx);
11,454,372✔
768
}
11,454,372✔
769

770
template <class T>
771
inline void Cluster::do_erase(size_t ndx, ColKey col_key)
772
{
6,603,828✔
773
    auto col_ndx = col_key.get_index();
6,603,828✔
774
    T values(m_alloc);
6,603,828✔
775
    values.set_parent(this, col_ndx.val + s_first_col_index);
6,603,828✔
776
    set_spec<T>(values, col_ndx);
6,603,828✔
777
    set_string_interner<T>(values, col_key);
6,603,828✔
778
    values.init_from_parent();
6,603,828✔
779
    if constexpr (std::is_same_v<T, ArrayTypedLink>) {
6,603,828✔
780
        if (ObjLink link = values.get(ndx)) {
×
781
            if (const Table* origin_table = m_tree_top.get_owning_table()) {
×
782
                auto target_obj = origin_table->get_parent_group()->get_object(link);
×
783

784
                ColKey backlink_col_key =
×
785
                    target_obj.get_table()->find_backlink_column(col_key, origin_table->get_key());
×
786
                REALM_ASSERT(backlink_col_key);
×
787
                target_obj.remove_one_backlink(backlink_col_key, get_real_key(ndx)); // Throws
×
788
            }
×
789
        }
×
790
    }
×
791
    values.erase(ndx);
6,603,828✔
792
}
6,603,828✔
793

794
inline void Cluster::do_erase_mixed(size_t ndx, ColKey col_key, ObjKey key, CascadeState& state)
795
{
1,431✔
796
    const Table* origin_table = m_tree_top.get_owning_table();
1,431✔
797
    auto col_ndx = col_key.get_index();
1,431✔
798

799
    ArrayMixed values(m_alloc);
1,431✔
800
    values.set_parent(this, col_ndx.val + s_first_col_index);
1,431✔
801
    values.init_from_parent();
1,431✔
802

803
    Mixed value = values.get(ndx);
1,431✔
804
    if (value.is_type(type_TypedLink)) {
1,431✔
805
        ObjLink link = value.get<ObjLink>();
24✔
806
        auto target_obj = origin_table->get_parent_group()->get_object(link);
24✔
807

808
        ColKey backlink_col_key = target_obj.get_table()->find_backlink_column(col_key, origin_table->get_key());
24✔
809
        REALM_ASSERT(backlink_col_key);
24✔
810
        target_obj.remove_one_backlink(backlink_col_key, get_real_key(ndx)); // Throws
24✔
811
    }
24✔
812
    if (value.is_type(type_List)) {
1,431✔
813
        Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
42✔
814
        Lst<Mixed> list(obj, col_key);
42✔
815
        list.remove_backlinks(state);
42✔
816
    }
42✔
817
    if (value.is_type(type_Dictionary)) {
1,431✔
818
        Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
18✔
819
        Dictionary dict(obj, col_key);
18✔
820
        dict.remove_backlinks(state);
18✔
821
    }
18✔
822
    values.erase(ndx);
1,431✔
823
}
1,431✔
824

825
inline void Cluster::do_erase_key(size_t ndx, ColKey col_key, CascadeState& state)
826
{
68,136✔
827
    ArrayKey values(m_alloc);
68,136✔
828
    auto col_ndx = col_key.get_index();
68,136✔
829
    values.set_parent(this, col_ndx.val + s_first_col_index);
68,136✔
830
    values.init_from_parent();
68,136✔
831

832
    ObjKey key = values.get(ndx);
68,136✔
833
    if (key != null_key) {
68,136✔
834
        do_remove_backlinks(get_real_key(ndx), col_key, std::vector<ObjKey>{key}, state);
65,376✔
835
    }
65,376✔
836
    values.erase(ndx);
68,136✔
837
}
68,136✔
838

839
size_t Cluster::get_ndx(ObjKey k, size_t ndx) const noexcept
840
{
9,939,666✔
841
    size_t index;
9,939,666✔
842
    if (m_keys.is_attached()) {
9,939,666✔
843
        index = m_keys.lower_bound(uint64_t(k.value));
9,825,378✔
844
        if (index == m_keys.size() || m_keys.get(index) != uint64_t(k.value)) {
9,825,405✔
845
            return realm::npos;
18✔
846
        }
18✔
847
    }
9,825,378✔
848
    else {
114,288✔
849
        index = size_t(k.value);
114,288✔
850
        if (index >= get_as_ref_or_tagged(s_key_ref_or_size_index).get_as_int()) {
114,288✔
851
            return realm::npos;
×
852
        }
×
853
    }
114,288✔
854
    return index + ndx;
9,939,648✔
855
}
9,939,666✔
856

857
size_t Cluster::erase(ObjKey key, CascadeState& state)
858
{
5,000,541✔
859
    size_t ndx = get_ndx(key, 0);
5,000,541✔
860
    if (ndx == realm::npos)
5,000,541✔
861
        throw KeyNotFound(util::format("When erasing key '%1' in '%2'", key.value, get_owning_table()->get_name()));
18✔
862
    std::vector<ColKey> backlink_column_keys;
5,000,523✔
863

864
    auto erase_in_column = [&](ColKey col_key) {
6,704,970✔
865
        auto col_type = col_key.get_type();
6,704,970✔
866
        auto attr = col_key.get_attrs();
6,704,970✔
867
        if (attr.test(col_attr_Collection)) {
6,704,970✔
868
            auto col_ndx = col_key.get_index();
31,644✔
869
            ArrayRef values(m_alloc);
31,644✔
870
            values.set_parent(this, col_ndx.val + s_first_col_index);
31,644✔
871
            values.init_from_parent();
31,644✔
872
            ref_type ref = values.get(ndx);
31,644✔
873

874
            if (ref) {
31,644✔
875
                const Table* origin_table = m_tree_top.get_owning_table();
11,436✔
876
                if (attr.test(col_attr_Dictionary)) {
11,436✔
877
                    if (col_type == col_type_Mixed || col_type == col_type_Link) {
2,655✔
878
                        Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
354✔
879
                        Dictionary dict(obj, col_key);
354✔
880
                        dict.remove_backlinks(state);
354✔
881
                    }
354✔
882
                }
2,655✔
883
                else if (col_type == col_type_Link) {
8,781✔
884
                    BPlusTree<ObjKey> links(m_alloc);
3,957✔
885
                    links.init_from_ref(ref);
3,957✔
886
                    if (links.size() > 0) {
3,957✔
887
                        do_remove_backlinks(ObjKey(key.value + m_offset), col_key, links.get_all(), state);
1,023✔
888
                    }
1,023✔
889
                }
3,957✔
890
                else if (col_type == col_type_TypedLink) {
4,824✔
891
                    BPlusTree<ObjLink> links(m_alloc);
×
892
                    links.init_from_ref(ref);
×
893
                    for (size_t i = 0; i < links.size(); i++) {
×
894
                        ObjLink link = links.get(i);
×
895
                        auto target_obj = origin_table->get_parent_group()->get_object(link);
×
896
                        ColKey backlink_col_key =
×
897
                            target_obj.get_table()->find_backlink_column(col_key, origin_table->get_key());
×
898
                        target_obj.remove_one_backlink(backlink_col_key, ObjKey(key.value + m_offset));
×
899
                    }
×
900
                }
×
901
                else if (col_type == col_type_Mixed) {
4,824✔
902
                    Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
228✔
903
                    Lst<Mixed> list(obj, col_key);
228✔
904
                    list.remove_backlinks(state);
228✔
905
                }
228✔
906
                Array::destroy_deep(ref, m_alloc);
11,436✔
907
            }
11,436✔
908

909
            values.erase(ndx);
31,644✔
910

911
            return IteratorControl::AdvanceToNext;
31,644✔
912
        }
31,644✔
913

914
        switch (col_type) {
6,673,326✔
915
            case col_type_Int:
5,984,958✔
916
                if (attr.test(col_attr_Nullable)) {
5,984,958✔
917
                    do_erase<ArrayIntNull>(ndx, col_key);
1,275,885✔
918
                }
1,275,885✔
919
                else {
4,709,073✔
920
                    do_erase<ArrayInteger>(ndx, col_key);
4,709,073✔
921
                }
4,709,073✔
922
                break;
5,984,958✔
923
            case col_type_Bool:
42,420✔
924
                do_erase<ArrayBoolNull>(ndx, col_key);
42,420✔
925
                break;
42,420✔
926
            case col_type_Float:
36,489✔
927
                do_erase<ArrayFloatNull>(ndx, col_key);
36,489✔
928
                break;
36,489✔
929
            case col_type_Double:
36,501✔
930
                do_erase<ArrayDoubleNull>(ndx, col_key);
36,501✔
931
                break;
36,501✔
932
            case col_type_String:
75,339✔
933
                do_erase<ArrayString>(ndx, col_key);
75,339✔
934
                break;
75,339✔
935
            case col_type_Binary:
39,627✔
936
                do_erase<ArrayBinary>(ndx, col_key);
39,627✔
937
                break;
39,627✔
938
            case col_type_Mixed:
1,431✔
939
                do_erase_mixed(ndx, col_key, key, state);
1,431✔
940
                break;
1,431✔
941
            case col_type_Timestamp:
45,297✔
942
                do_erase<ArrayTimestamp>(ndx, col_key);
45,297✔
943
                break;
45,297✔
944
            case col_type_Decimal:
36,279✔
945
                do_erase<ArrayDecimal128>(ndx, col_key);
36,279✔
946
                break;
36,279✔
947
            case col_type_ObjectId:
38,793✔
948
                do_erase<ArrayObjectIdNull>(ndx, col_key);
38,793✔
949
                break;
38,793✔
950
            case col_type_UUID:
60,285✔
951
                do_erase<ArrayUUIDNull>(ndx, col_key);
60,285✔
952
                break;
60,285✔
953
            case col_type_Link:
68,136✔
954
                do_erase_key(ndx, col_key, state);
68,136✔
955
                break;
68,136✔
956
            case col_type_TypedLink:
✔
957
                do_erase<ArrayTypedLink>(ndx, col_key);
×
958
                break;
×
959
            case col_type_BackLink:
207,843✔
960
                if (state.m_mode == CascadeState::Mode::None) {
207,843✔
961
                    do_erase<ArrayBacklink>(ndx, col_key);
188,598✔
962
                }
188,598✔
963
                else {
19,245✔
964
                    // Postpone the deletion of backlink entries or else the
965
                    // checks for if there's any remaining backlinks will
966
                    // check the wrong row for columns which have already
967
                    // had values erased from them.
968
                    backlink_column_keys.push_back(col_key);
19,245✔
969
                }
19,245✔
970
                break;
207,843✔
971
            default:
✔
972
                REALM_ASSERT(false);
×
973
                break;
×
974
        }
6,673,326✔
975
        return IteratorControl::AdvanceToNext;
6,673,281✔
976
    };
6,673,326✔
977
    m_tree_top.m_owner->for_each_and_every_column(erase_in_column);
5,000,523✔
978

979
    // Any remaining backlink columns to erase from?
980
    for (auto k : backlink_column_keys)
5,000,523✔
981
        do_erase<ArrayBacklink>(ndx, k);
19,245✔
982

983
    if (m_keys.is_attached()) {
5,000,523✔
984
        m_keys.erase(ndx);
4,959,987✔
985
    }
4,959,987✔
986
    else {
40,536✔
987
        size_t current_size = get_size_in_compact_form();
40,536✔
988
        if (ndx == current_size - 1) {
40,536✔
989
            // When deleting last, we can still maintain compact form
990
            set(0, RefOrTagged::make_tagged(current_size - 1));
26,160✔
991
        }
26,160✔
992
        else {
14,376✔
993
            ensure_general_form();
14,376✔
994
            m_keys.erase(ndx);
14,376✔
995
        }
14,376✔
996
    }
40,536✔
997

998
    return node_size();
5,000,523✔
999
}
5,000,541✔
1000

1001
void Cluster::nullify_incoming_links(ObjKey key, CascadeState& state)
1002
{
4,902,234✔
1003
    size_t ndx = get_ndx(key, 0);
4,902,234✔
1004
    if (ndx == realm::npos)
4,902,234✔
1005
        throw KeyNotFound(util::format("Key '%1' not found in '%2' when nullifying incoming links", key.value,
×
1006
                                       get_owning_table()->get_class_name()));
×
1007

1008
    // We must start with backlink columns in case the corresponding link
1009
    // columns are in the same table so that we can nullify links before
1010
    // erasing rows in the link columns.
1011
    //
1012
    // This phase also generates replication instructions documenting the side-
1013
    // effects of deleting the object (i.e. link nullifications). These instructions
1014
    // must come before the actual deletion of the object, but at the same time
1015
    // the Replication object may need a consistent view of the row (not including
1016
    // link columns). Therefore we first nullify links to this object, then
1017
    // generate the instruction, and then delete the row in the remaining columns.
1018

1019
    auto nullify_fwd_links = [&](ColKey col_key) {
4,902,234✔
1020
        ColKey::Idx leaf_ndx = col_key.get_index();
190,365✔
1021
        auto type = col_key.get_type();
190,365✔
1022
        REALM_ASSERT(type == col_type_BackLink);
190,365✔
1023
        ArrayBacklink values(m_alloc);
190,365✔
1024
        values.set_parent(this, leaf_ndx.val + s_first_col_index);
190,365✔
1025
        values.init_from_parent();
190,365✔
1026
        // Ensure that Cluster is writable and able to hold references to nodes in
1027
        // the slab area before nullifying or deleting links. These operation may
1028
        // both have the effect that other objects may be constructed and manipulated.
1029
        // If those other object are in the same cluster that the object to be deleted
1030
        // is in, then that will cause another accessor to this cluster to be created.
1031
        // It would lead to an error if the cluster node was relocated without it being
1032
        // reflected in the context here.
1033
        values.copy_on_write();
190,365✔
1034
        values.nullify_fwd_links(ndx, state);
190,365✔
1035

1036
        return IteratorControl::AdvanceToNext;
190,365✔
1037
    };
190,365✔
1038

1039
    m_tree_top.get_owning_table()->for_each_backlink_column(nullify_fwd_links);
4,902,234✔
1040
}
4,902,234✔
1041

1042
void Cluster::upgrade_string_to_enum(ColKey col_key, ArrayString& keys)
1043
{
1,086✔
1044
    auto col_ndx = col_key.get_index();
1,086✔
1045
    Array indexes(m_alloc);
1,086✔
1046
    indexes.create(Array::type_Normal, false);
1,086✔
1047
    ArrayString values(m_alloc);
1,086✔
1048
    ref_type ref = Array::get_as_ref(col_ndx.val + s_first_col_index);
1,086✔
1049
    set_string_interner(values, col_key);
1,086✔
1050
    values.init_from_ref(ref);
1,086✔
1051
    size_t sz = values.size();
1,086✔
1052
    for (size_t i = 0; i < sz; i++) {
118,542✔
1053
        auto v = values.get(i);
117,456✔
1054
        size_t pos = keys.lower_bound(v);
117,456✔
1055
        REALM_ASSERT_3(pos, !=, keys.size());
117,456✔
1056
        indexes.add(pos);
117,456✔
1057
    }
117,456✔
1058
    Array::set(col_ndx.val + s_first_col_index, indexes.get_ref());
1,086✔
1059
    Array::destroy_deep(ref, m_alloc);
1,086✔
1060
}
1,086✔
1061

1062
void Cluster::init_leaf(ColKey col_key, ArrayPayload* leaf) const
1063
{
8,643,519✔
1064
    auto col_ndx = col_key.get_index();
8,643,519✔
1065
    // FIXME: Move this validation into callers.
1066
    // Currently, the query subsystem may call with an unvalidated key.
1067
    // once fixed, reintroduce the noexcept declaration :-D
1068
    if (auto t = m_tree_top.get_owning_table())
8,643,519✔
1069
        t->check_column(col_key);
8,692,650✔
1070
    ref_type ref = to_ref(Array::get(col_ndx.val + 1));
8,643,519✔
1071
    if (leaf->need_string_interner()) {
8,643,519✔
1072
        m_tree_top.set_string_interner(*leaf, col_key);
138,351✔
1073
    }
138,351✔
1074
    if (leaf->need_spec()) {
8,643,519✔
1075
        m_tree_top.set_spec(*leaf, col_ndx);
138,354✔
1076
    }
138,354✔
1077
    leaf->init_from_ref(ref);
8,643,519✔
1078
    leaf->set_parent(const_cast<Cluster*>(this), col_ndx.val + 1);
8,643,519✔
1079
}
8,643,519✔
1080

1081
void Cluster::add_leaf(ColKey col_key, ref_type ref)
1082
{
×
1083
    auto col_ndx = col_key.get_index();
×
1084
    REALM_ASSERT((col_ndx.val + 1) == size());
×
1085
    Array::insert(col_ndx.val + 1, from_ref(ref));
×
1086
}
×
1087

1088
template <typename ArrayType>
1089
void Cluster::verify(ref_type ref, size_t index, util::Optional<size_t>& sz) const
1090
{
557,571✔
1091
    ArrayType arr(get_alloc());
557,571✔
1092
    set_spec(arr, ColKey::Idx{unsigned(index) - 1});
557,571✔
1093
    auto table = get_owning_table();
557,571✔
1094
    REALM_ASSERT(index <= table->m_leaf_ndx2colkey.size());
557,571✔
1095
    auto col_key = table->m_leaf_ndx2colkey[index - 1];
557,571✔
1096
    set_string_interner(arr, col_key);
557,571✔
1097
    arr.set_parent(const_cast<Cluster*>(this), index);
557,571✔
1098
    arr.init_from_ref(ref);
557,571✔
1099
    arr.verify();
557,571✔
1100
    if (sz) {
557,571✔
1101
        REALM_ASSERT(arr.size() == *sz);
211,056✔
1102
    }
211,056✔
1103
    else {
346,515✔
1104
        sz = arr.size();
346,515✔
1105
    }
346,515✔
1106
}
557,571✔
1107
namespace {
1108

1109
template <typename ArrayType>
1110
void verify_list(ArrayRef& arr, size_t sz)
1111
{
89,925✔
1112
    for (size_t n = 0; n < sz; n++) {
324,087!
1113
        if (ref_type bp_tree_ref = arr.get(n)) {
234,162!
1114
            BPlusTree<ArrayType> links(arr.get_alloc());
109,776✔
1115
            links.init_from_ref(bp_tree_ref);
109,776✔
1116
            links.set_parent(&arr, n);
109,776✔
1117
            links.verify();
109,776✔
1118
        }
109,776✔
1119
    }
234,162✔
1120
}
89,925✔
1121

1122
template <typename SetType>
1123
void verify_set(ArrayRef& arr, size_t sz)
1124
{
174✔
1125
    for (size_t n = 0; n < sz; ++n) {
402!
1126
        if (ref_type bp_tree_ref = arr.get(n)) {
228!
1127
            BPlusTree<SetType> elements(arr.get_alloc());
216✔
1128
            elements.init_from_ref(bp_tree_ref);
216✔
1129
            elements.set_parent(&arr, n);
216✔
1130
            elements.verify();
216✔
1131

1132
            // FIXME: Check uniqueness of elements.
1133
        }
216✔
1134
    }
228✔
1135
}
174✔
1136

1137
} // namespace
1138

1139
void Cluster::verify() const
1140
{
383,100✔
1141
#ifdef REALM_DEBUG
383,100✔
1142
    util::Optional<size_t> sz;
383,100✔
1143

1144
    auto verify_column = [this, &sz](ColKey col_key) {
659,940✔
1145
        size_t col = col_key.get_index().val + s_first_col_index;
659,940✔
1146
        ref_type ref = Array::get_as_ref(col);
659,940✔
1147
        auto attr = col_key.get_attrs();
659,940✔
1148
        auto col_type = col_key.get_type();
659,940✔
1149
        bool nullable = attr.test(col_attr_Nullable);
659,940✔
1150

1151
        if (attr.test(col_attr_List)) {
659,940✔
1152
            ArrayRef arr(get_alloc());
89,943✔
1153
            arr.set_parent(const_cast<Cluster*>(this), col);
89,943✔
1154
            arr.init_from_ref(ref);
89,943✔
1155
            arr.verify();
89,943✔
1156
            if (sz) {
89,943✔
1157
                REALM_ASSERT(arr.size() == *sz);
65,607✔
1158
            }
65,607✔
1159
            else {
24,336✔
1160
                sz = arr.size();
24,336✔
1161
            }
24,336✔
1162

1163
            switch (col_type) {
89,943✔
1164
                case col_type_Int:
41,712✔
1165
                    if (nullable) {
41,712✔
1166
                        verify_list<util::Optional<int64_t>>(arr, *sz);
×
1167
                    }
×
1168
                    else {
41,712✔
1169
                        verify_list<int64_t>(arr, *sz);
41,712✔
1170
                    }
41,712✔
1171
                    break;
41,712✔
1172
                case col_type_Bool:
✔
1173
                    verify_list<Bool>(arr, *sz);
×
1174
                    break;
×
1175
                case col_type_Float:
6✔
1176
                    verify_list<Float>(arr, *sz);
6✔
1177
                    break;
6✔
1178
                case col_type_Double:
✔
1179
                    verify_list<Double>(arr, *sz);
×
1180
                    break;
×
1181
                case col_type_String:
23,085✔
1182
                    verify_list<String>(arr, *sz);
23,085✔
1183
                    break;
23,085✔
1184
                case col_type_Binary:
24,000✔
1185
                    verify_list<Binary>(arr, *sz);
24,000✔
1186
                    break;
24,000✔
1187
                case col_type_Timestamp:
✔
1188
                    verify_list<Timestamp>(arr, *sz);
×
1189
                    break;
×
1190
                case col_type_Decimal:
✔
1191
                    verify_list<Decimal128>(arr, *sz);
×
1192
                    break;
×
1193
                case col_type_ObjectId:
✔
1194
                    verify_list<ObjectId>(arr, *sz);
×
1195
                    break;
×
1196
                case col_type_UUID:
✔
1197
                    verify_list<UUID>(arr, *sz);
×
1198
                    break;
×
1199
                case col_type_Link:
1,122✔
1200
                    verify_list<ObjKey>(arr, *sz);
1,122✔
1201
                    break;
1,122✔
1202
                default:
18✔
1203
                    // FIXME: Nullable primitives
1204
                    break;
18✔
1205
            }
89,943✔
1206
            return IteratorControl::AdvanceToNext;
89,943✔
1207
        }
89,943✔
1208
        else if (attr.test(col_attr_Dictionary)) {
569,997✔
1209
            ArrayRef arr(get_alloc());
12,168✔
1210
            arr.set_parent(const_cast<Cluster*>(this), col);
12,168✔
1211
            arr.init_from_ref(ref);
12,168✔
1212
            arr.verify();
12,168✔
1213
            if (sz) {
12,168✔
1214
                REALM_ASSERT(arr.size() == *sz);
138✔
1215
            }
138✔
1216
            else {
12,030✔
1217
                sz = arr.size();
12,030✔
1218
            }
12,030✔
1219
            for (size_t n = 0; n < sz; n++) {
24,504✔
1220
                if (auto ref = arr.get(n)) {
12,336✔
1221
                    Dictionary dict(get_alloc(), col_key, to_ref(ref));
12,219✔
1222
                    dict.verify();
12,219✔
1223
                }
12,219✔
1224
            }
12,336✔
1225
            return IteratorControl::AdvanceToNext;
12,168✔
1226
        }
12,168✔
1227
        else if (attr.test(col_attr_Set)) {
557,829✔
1228
            ArrayRef arr(get_alloc());
270✔
1229
            arr.set_parent(const_cast<Cluster*>(this), col);
270✔
1230
            arr.init_from_ref(ref);
270✔
1231
            arr.verify();
270✔
1232
            if (sz) {
270✔
1233
                REALM_ASSERT(arr.size() == *sz);
252✔
1234
            }
252✔
1235
            else {
18✔
1236
                sz = arr.size();
18✔
1237
            }
18✔
1238
            switch (col_type) {
270✔
1239
                case col_type_Int:
138✔
1240
                    if (nullable) {
138✔
1241
                        verify_set<util::Optional<int64_t>>(arr, *sz);
×
1242
                    }
×
1243
                    else {
138✔
1244
                        verify_set<int64_t>(arr, *sz);
138✔
1245
                    }
138✔
1246
                    break;
138✔
1247
                case col_type_Bool:
✔
1248
                    verify_set<Bool>(arr, *sz);
×
1249
                    break;
×
1250
                case col_type_Float:
✔
1251
                    verify_set<Float>(arr, *sz);
×
1252
                    break;
×
1253
                case col_type_Double:
✔
1254
                    verify_set<Double>(arr, *sz);
×
1255
                    break;
×
1256
                case col_type_String:
6✔
1257
                    verify_set<String>(arr, *sz);
6✔
1258
                    break;
6✔
1259
                case col_type_Binary:
18✔
1260
                    verify_set<Binary>(arr, *sz);
18✔
1261
                    break;
18✔
1262
                case col_type_Timestamp:
✔
1263
                    verify_set<Timestamp>(arr, *sz);
×
1264
                    break;
×
1265
                case col_type_Decimal:
✔
1266
                    verify_set<Decimal128>(arr, *sz);
×
1267
                    break;
×
1268
                case col_type_ObjectId:
✔
1269
                    verify_set<ObjectId>(arr, *sz);
×
1270
                    break;
×
1271
                case col_type_UUID:
✔
1272
                    verify_set<UUID>(arr, *sz);
×
1273
                    break;
×
1274
                case col_type_Link:
12✔
1275
                    verify_set<ObjKey>(arr, *sz);
12✔
1276
                    break;
12✔
1277
                default:
96✔
1278
                    // FIXME: Nullable primitives
1279
                    break;
96✔
1280
            }
270✔
1281
            return IteratorControl::AdvanceToNext;
270✔
1282
        }
270✔
1283

1284
        switch (col_type) {
557,559✔
1285
            case col_type_Int:
336,078✔
1286
                if (nullable) {
336,078✔
1287
                    verify<ArrayIntNull>(ref, col, sz);
24,447✔
1288
                }
24,447✔
1289
                else {
311,631✔
1290
                    verify<ArrayInteger>(ref, col, sz);
311,631✔
1291
                }
311,631✔
1292
                break;
336,078✔
1293
            case col_type_Bool:
7,158✔
1294
                verify<ArrayBoolNull>(ref, col, sz);
7,158✔
1295
                break;
7,158✔
1296
            case col_type_Float:
198✔
1297
                verify<ArrayFloatNull>(ref, col, sz);
198✔
1298
                break;
198✔
1299
            case col_type_Double:
198✔
1300
                verify<ArrayDoubleNull>(ref, col, sz);
198✔
1301
                break;
198✔
1302
            case col_type_String:
150,177✔
1303
                verify<ArrayString>(ref, col, sz);
150,177✔
1304
                break;
150,177✔
1305
            case col_type_Binary:
49,872✔
1306
                verify<ArrayBinary>(ref, col, sz);
49,872✔
1307
                break;
49,872✔
1308
            case col_type_Mixed:
2,724✔
1309
                verify<ArrayMixed>(ref, col, sz);
2,724✔
1310
                break;
2,724✔
1311
            case col_type_Timestamp:
6,948✔
1312
                verify<ArrayTimestamp>(ref, col, sz);
6,948✔
1313
                break;
6,948✔
1314
            case col_type_Decimal:
42✔
1315
                verify<ArrayDecimal128>(ref, col, sz);
42✔
1316
                break;
42✔
1317
            case col_type_ObjectId:
42✔
1318
                verify<ArrayObjectIdNull>(ref, col, sz);
42✔
1319
                break;
42✔
1320
            case col_type_UUID:
✔
1321
                verify<ArrayUUIDNull>(ref, col, sz);
×
1322
                break;
×
1323
            case col_type_Link:
1,593✔
1324
                verify<ArrayKey>(ref, col, sz);
1,593✔
1325
                break;
1,593✔
1326
            case col_type_BackLink:
2,541✔
1327
                verify<ArrayBacklink>(ref, col, sz);
2,541✔
1328
                break;
2,541✔
1329
            default:
✔
1330
                break;
×
1331
        }
557,559✔
1332
        return IteratorControl::AdvanceToNext;
557,568✔
1333
    };
557,559✔
1334

1335
    m_tree_top.m_owner->for_each_and_every_column(verify_column);
383,100✔
1336
#endif
383,100✔
1337
}
383,100✔
1338

1339
// LCOV_EXCL_START
1340
void Cluster::dump_objects(int64_t key_offset, std::string lead) const
1341
{
×
1342
    std::cout << lead << "leaf - size: " << node_size() << std::endl;
×
1343
    if (!m_keys.is_attached()) {
×
1344
        std::cout << lead << "compact form" << std::endl;
×
1345
    }
×
1346

1347
    for (unsigned i = 0; i < node_size(); i++) {
×
1348
        int64_t key_value;
×
1349
        if (m_keys.is_attached()) {
×
1350
            key_value = m_keys.get(i);
×
1351
        }
×
1352
        else {
×
1353
            key_value = int64_t(i);
×
1354
        }
×
1355
        std::cout << lead << "key: " << std::hex << key_value + key_offset << std::dec;
×
1356
        m_tree_top.m_owner->for_each_and_every_column([&](ColKey col) {
×
1357
            size_t j = col.get_index().val + 1;
×
1358
            if (col.get_attrs().test(col_attr_List)) {
×
1359
                ref_type ref = Array::get_as_ref(j);
×
1360
                ArrayRef refs(m_alloc);
×
1361
                refs.init_from_ref(ref);
×
1362
                std::cout << ", {";
×
1363
                ref = refs.get(i);
×
1364
                if (ref) {
×
1365
                    if (col.get_type() == col_type_Int) {
×
1366
                        // This is easy to handle
1367
                        Array ints(m_alloc);
×
1368
                        ints.init_from_ref(ref);
×
1369
                        for (size_t n = 0; n < ints.size(); n++) {
×
1370
                            std::cout << ints.get(n) << ", ";
×
1371
                        }
×
1372
                    }
×
1373
                    else {
×
1374
                        std::cout << col.get_type();
×
1375
                    }
×
1376
                }
×
1377
                std::cout << "}";
×
1378
                return IteratorControl::AdvanceToNext;
×
1379
            }
×
1380

1381
            switch (col.get_type()) {
×
1382
                case col_type_Int: {
×
1383
                    bool nullable = col.get_attrs().test(col_attr_Nullable);
×
1384
                    ref_type ref = Array::get_as_ref(j);
×
1385
                    if (nullable) {
×
1386
                        ArrayIntNull arr_int_null(m_alloc);
×
1387
                        arr_int_null.init_from_ref(ref);
×
1388
                        if (arr_int_null.is_null(i)) {
×
1389
                            std::cout << ", null";
×
1390
                        }
×
1391
                        else {
×
1392
                            std::cout << ", " << *arr_int_null.get(i);
×
1393
                        }
×
1394
                    }
×
1395
                    else {
×
1396
                        Array arr(m_alloc);
×
1397
                        arr.init_from_ref(ref);
×
1398
                        std::cout << ", " << arr.get(i);
×
1399
                    }
×
1400
                    break;
×
1401
                }
×
1402
                case col_type_Bool: {
×
1403
                    ArrayBoolNull arr(m_alloc);
×
1404
                    ref_type ref = Array::get_as_ref(j);
×
1405
                    arr.init_from_ref(ref);
×
1406
                    auto val = arr.get(i);
×
1407
                    std::cout << ", " << (val ? (*val ? "true" : "false") : "null");
×
1408
                    break;
×
1409
                }
×
1410
                case col_type_Float: {
×
1411
                    ArrayFloatNull arr(m_alloc);
×
1412
                    ref_type ref = Array::get_as_ref(j);
×
1413
                    arr.init_from_ref(ref);
×
1414
                    auto val = arr.get(i);
×
1415
                    if (val)
×
1416
                        std::cout << ", " << *val;
×
1417
                    else
×
1418
                        std::cout << ", null";
×
1419
                    break;
×
1420
                }
×
1421
                case col_type_Double: {
×
1422
                    ArrayDoubleNull arr(m_alloc);
×
1423
                    ref_type ref = Array::get_as_ref(j);
×
1424
                    arr.init_from_ref(ref);
×
1425
                    auto val = arr.get(i);
×
1426
                    if (val)
×
1427
                        std::cout << ", " << *val;
×
1428
                    else
×
1429
                        std::cout << ", null";
×
1430
                    break;
×
1431
                }
×
1432
                case col_type_String: {
×
1433
                    ArrayString arr(m_alloc);
×
1434
                    set_spec(arr, col.get_index());
×
NEW
1435
                    set_string_interner(arr, col);
×
1436
                    ref_type ref = Array::get_as_ref(j);
×
1437
                    arr.init_from_ref(ref);
×
1438
                    std::cout << ", " << arr.get(i);
×
1439
                    break;
×
1440
                }
×
1441
                case col_type_Binary: {
×
1442
                    ArrayBinary arr(m_alloc);
×
1443
                    ref_type ref = Array::get_as_ref(j);
×
1444
                    arr.init_from_ref(ref);
×
1445
                    std::cout << ", " << arr.get(i);
×
1446
                    break;
×
1447
                }
×
1448
                case col_type_Mixed: {
×
1449
                    ArrayMixed arr(m_alloc);
×
1450
                    ref_type ref = Array::get_as_ref(j);
×
1451
                    arr.init_from_ref(ref);
×
1452
                    std::cout << ", " << arr.get(i);
×
1453
                    break;
×
1454
                }
×
1455
                case col_type_Timestamp: {
×
1456
                    ArrayTimestamp arr(m_alloc);
×
1457
                    ref_type ref = Array::get_as_ref(j);
×
1458
                    arr.init_from_ref(ref);
×
1459
                    if (arr.is_null(i)) {
×
1460
                        std::cout << ", null";
×
1461
                    }
×
1462
                    else {
×
1463
                        std::cout << ", " << arr.get(i);
×
1464
                    }
×
1465
                    break;
×
1466
                }
×
1467
                case col_type_Decimal: {
×
1468
                    ArrayDecimal128 arr(m_alloc);
×
1469
                    ref_type ref = Array::get_as_ref(j);
×
1470
                    arr.init_from_ref(ref);
×
1471
                    if (arr.is_null(i)) {
×
1472
                        std::cout << ", null";
×
1473
                    }
×
1474
                    else {
×
1475
                        std::cout << ", " << arr.get(i);
×
1476
                    }
×
1477
                    break;
×
1478
                }
×
1479
                case col_type_ObjectId: {
×
1480
                    ArrayObjectIdNull arr(m_alloc);
×
1481
                    ref_type ref = Array::get_as_ref(j);
×
1482
                    arr.init_from_ref(ref);
×
1483
                    if (arr.is_null(i)) {
×
1484
                        std::cout << ", null";
×
1485
                    }
×
1486
                    else {
×
1487
                        std::cout << ", " << *arr.get(i);
×
1488
                    }
×
1489
                    break;
×
1490
                }
×
1491
                case col_type_UUID: {
×
1492
                    ArrayUUIDNull arr(m_alloc);
×
1493
                    ref_type ref = Array::get_as_ref(j);
×
1494
                    arr.init_from_ref(ref);
×
1495
                    if (arr.is_null(i)) {
×
1496
                        std::cout << ", null";
×
1497
                    }
×
1498
                    else {
×
1499
                        std::cout << ", " << arr.get(i);
×
1500
                    }
×
1501
                    break;
×
1502
                }
×
1503
                case col_type_Link: {
×
1504
                    ArrayKey arr(m_alloc);
×
1505
                    ref_type ref = Array::get_as_ref(j);
×
1506
                    arr.init_from_ref(ref);
×
1507
                    std::cout << ", " << arr.get(i);
×
1508
                    break;
×
1509
                }
×
1510
                case col_type_BackLink: {
×
1511
                    break;
×
1512
                }
×
1513
                default:
×
1514
                    std::cout << ", Error";
×
1515
                    break;
×
1516
            }
×
1517
            return IteratorControl::AdvanceToNext;
×
1518
        });
×
1519
        std::cout << std::endl;
×
1520
    }
×
1521
}
×
1522
// LCOV_EXCL_STOP
1523

1524
void Cluster::remove_backlinks(const Table* origin_table, ObjKey origin_key, ColKey origin_col_key,
1525
                               const std::vector<ObjKey>& keys, CascadeState& state)
1526
{
67,149✔
1527
    TableRef target_table = origin_table->get_opposite_table(origin_col_key);
67,149✔
1528
    ColKey backlink_col_key = origin_table->get_opposite_column(origin_col_key);
67,149✔
1529
    bool strong_links = target_table->is_embedded();
67,149✔
1530

1531
    for (auto key : keys) {
68,787✔
1532
        if (key != null_key) {
68,787✔
1533
            bool is_unres = key.is_unresolved();
68,787✔
1534
            Obj target_obj = is_unres ? target_table->m_tombstones->get(key) : target_table->m_clusters.get(key);
68,787✔
1535
            bool last_removed = target_obj.remove_one_backlink(backlink_col_key, origin_key); // Throws
68,787✔
1536
            if (is_unres) {
68,787✔
1537
                if (last_removed) {
30✔
1538
                    // Check is there are more backlinks
1539
                    if (!target_obj.has_backlinks(false)) {
24✔
1540
                        // Tombstones can be erased right away - there is no cascading effect
1541
                        target_table->m_tombstones->erase(key, state);
18✔
1542
                    }
18✔
1543
                }
24✔
1544
            }
30✔
1545
            else {
68,757✔
1546
                state.enqueue_for_cascade(target_obj, strong_links, last_removed);
68,757✔
1547
            }
68,757✔
1548
        }
68,787✔
1549
    }
68,787✔
1550
}
67,149✔
1551

1552
void Cluster::remove_backlinks(const Table* origin_table, ObjKey origin_key, ColKey origin_col_key,
1553
                               const std::vector<ObjLink>& links, CascadeState& state)
1554
{
144✔
1555
    Group* group = origin_table->get_parent_group();
144✔
1556
    TableKey origin_table_key = origin_table->get_key();
144✔
1557

1558
    for (auto link : links) {
240✔
1559
        if (link) {
240✔
1560
            bool is_unres = link.get_obj_key().is_unresolved();
240✔
1561
            Obj target_obj = group->get_object(link);
240✔
1562
            TableRef target_table = target_obj.get_table();
240✔
1563
            ColKey backlink_col_key = target_table->find_or_add_backlink_column(origin_col_key, origin_table_key);
240✔
1564

1565
            bool last_removed = target_obj.remove_one_backlink(backlink_col_key, origin_key); // Throws
240✔
1566
            if (is_unres) {
240✔
1567
                if (last_removed) {
6✔
1568
                    // Check is there are more backlinks
1569
                    if (!target_obj.has_backlinks(false)) {
6✔
1570
                        // Tombstones can be erased right away - there is no cascading effect
1571
                        target_table->m_tombstones->erase(link.get_obj_key(), state);
6✔
1572
                    }
6✔
1573
                }
6✔
1574
            }
6✔
1575
            else {
234✔
1576
                state.enqueue_for_cascade(target_obj, false, last_removed);
234✔
1577
            }
234✔
1578
        }
240✔
1579
    }
240✔
1580
}
144✔
1581

1582
namespace {
1583

1584
template <typename Fn>
1585
static void switch_on_type(ColKey ck, Fn&& fn)
1586
{
1,276,239✔
1587
    bool is_optional = ck.is_nullable();
1,276,239✔
1588
    auto type = ck.get_type();
1,276,239✔
1589
    switch (type) {
1,276,239✔
1590
        case col_type_Int:
777,645✔
1591
            return is_optional ? fn((util::Optional<int64_t>*)0) : fn((int64_t*)0);
777,645✔
1592
        case col_type_Bool:
9,324✔
1593
            return is_optional ? fn((util::Optional<bool>*)0) : fn((bool*)0);
9,324✔
1594
        case col_type_Float:
9,528✔
1595
            return is_optional ? fn((util::Optional<float>*)0) : fn((float*)0);
9,528✔
1596
        case col_type_Double:
13,533✔
1597
            return is_optional ? fn((util::Optional<double>*)0) : fn((double*)0);
13,533✔
UNCOV
1598
        case col_type_String:
✔
UNCOV
1599
            return fn((StringData*)0);
×
1600
        case col_type_Binary:
247,980✔
1601
            return fn((BinaryData*)0);
247,980✔
1602
        case col_type_Timestamp:
39,069✔
1603
            return fn((Timestamp*)0);
39,069✔
1604
        case col_type_Link:
56,145✔
1605
            return fn((ObjKey*)0);
56,145✔
1606
        case col_type_ObjectId:
86,631✔
1607
            return is_optional ? fn((util::Optional<ObjectId>*)0) : fn((ObjectId*)0);
86,631✔
1608
        case col_type_Decimal:
8,562✔
1609
            return fn((Decimal128*)0);
8,562✔
1610
        case col_type_UUID:
9,342✔
1611
            return is_optional ? fn((util::Optional<UUID>*)0) : fn((UUID*)0);
9,342✔
1612
        case col_type_Mixed:
18,474✔
1613
            return fn((Mixed*)0);
18,474✔
1614
        default:
✔
1615
            REALM_COMPILER_HINT_UNREACHABLE();
×
1616
    }
1,276,239✔
1617
}
1,276,239✔
1618

1619
} // namespace
1620

1621
ref_type Cluster::typed_write(ref_type ref, _impl::ArrayWriterBase& out) const
1622
{
1,049,943✔
1623
    REALM_ASSERT_DEBUG(ref == get_mem().get_ref());
1,049,943✔
1624
    bool only_modified = out.only_modified;
1,049,943✔
1625
    if (only_modified && m_alloc.is_read_only(ref))
1,049,943✔
1626
        return ref;
1,332✔
1627
    REALM_ASSERT_DEBUG(!get_is_inner_bptree_node_from_header(get_header()));
1,048,611✔
1628
    REALM_ASSERT_DEBUG(!get_context_flag_from_header(get_header()));
1,048,611✔
1629
    TempArray written_cluster(size());
1,048,611✔
1630
    for (size_t j = 0; j < size(); ++j) {
4,560,720✔
1631
        RefOrTagged leaf_rot = get_as_ref_or_tagged(j);
3,512,097✔
1632
        // Handle nulls
1633
        if (!leaf_rot.is_ref() || !leaf_rot.get_as_ref()) {
3,512,097✔
1634
            written_cluster.set(j, leaf_rot);
987,387✔
1635
            continue;
987,387✔
1636
        }
987,387✔
1637
        // prune subtrees which should not be written:
1638
        if (only_modified && m_alloc.is_read_only(leaf_rot.get_as_ref())) {
2,524,710✔
1639
            written_cluster.set(j, leaf_rot);
343,506✔
1640
            continue;
343,506✔
1641
        }
343,506✔
1642
        // from here: this leaf exists and needs to be written.
1643
        ref = leaf_rot.get_as_ref();
2,181,204✔
1644
        ref_type new_ref = ref;
2,181,204✔
1645
        if (j == 0) {
2,181,204✔
1646
            // Keys  (ArrayUnsigned me thinks, so don't compress)
1647
            Array leaf(m_alloc);
48,864✔
1648
            leaf.init_from_ref(ref);
48,864✔
1649
            new_ref = leaf.write(out, false, only_modified, false);
48,864✔
1650
        }
48,864✔
1651
        else {
2,132,340✔
1652
            // Columns
1653
            auto col_key = out.table->m_leaf_ndx2colkey[j - 1];
2,132,340✔
1654
            auto col_type = col_key.get_type();
2,132,340✔
1655
            // String columns are interned at this point
1656
            if (out.compress && col_type == col_type_String && !col_key.is_collection()) {
2,132,439✔
1657
                ArrayRef leaf(m_alloc);
232,572✔
1658
                leaf.init_from_ref(ref);
232,572✔
1659
                auto header = leaf.get_header();
232,572✔
1660
                if (NodeHeader::get_hasrefs_from_header(header) ||
232,572✔
1661
                    NodeHeader::get_wtype_from_header(header) == wtype_Multiply) {
232,572✔
1662
                    // We're interning these strings
1663
                    ArrayString as(m_alloc);
114,813✔
1664
                    as.init_from_ref(leaf_rot.get_as_ref());
114,813✔
1665
                    written_cluster.set_as_ref(j, as.write(out, out.table->get_string_interner(col_key)));
114,813✔
1666
                    // in a transactional setting:
1667
                    // Destroy all sub-arrays if present, in order to release memory in file
1668
                    // This is contrary to the rest of the handling in this function, but needed
1669
                    // here since sub-arrays may not have been COW'ed and therefore not freed in file.
1670
                    // We rely on 'only_modified' to indicate that we're in a transactional setting.
1671
                    if (only_modified)
114,813✔
1672
                        leaf.destroy_deep(true);
113,004✔
1673
                    continue;
114,813✔
1674
                }
114,813✔
1675
                // whether it's the old enum strings or the new interned strings,
1676
                // just write out the array using integer leaf compression
1677
                written_cluster.set_as_ref(j, leaf.write(out, false, false, false));
117,759✔
1678
                continue;
117,759✔
1679
            }
232,572✔
1680
            if (col_key.is_collection()) {
1,899,768✔
1681
                ArrayRef arr_ref(m_alloc);
451,140✔
1682
                arr_ref.init_from_ref(ref);
451,140✔
1683
                auto sz = arr_ref.size();
451,140✔
1684
                TempArray written_ref_leaf(sz);
451,140✔
1685

1686
                for (size_t k = 0; k < sz; k++) {
4,782,642✔
1687
                    ref_type new_sub_ref = 0;
4,331,490✔
1688
                    // Now we have to find out if the nested collection is a
1689
                    // dictionary or a list. If the top array has a size of 2
1690
                    // and it is not a BplusTree inner node, then it is a dictionary
1691
                    if (auto sub_ref = arr_ref.get(k)) {
4,331,490✔
1692
                        if (col_key.is_dictionary()) {
2,592,822✔
1693
                            new_sub_ref = Dictionary::typed_write(sub_ref, out, m_alloc);
43,794✔
1694
                        }
43,794✔
1695
                        else {
2,549,028✔
1696
                            // List or set - Can be handled the same way
1697
                            // For some reason, switch_on_type() would not compile on Windows
1698
                            // switch_on_type(col_key, [&](auto t) {
1699
                            //     using U = std::decay_t<decltype(*t)>;
1700
                            //     new_sub_ref = BPlusTree<U>::typed_write(sub_ref, out, m_alloc);
1701
                            // });
1702
                            switch (col_type) {
2,549,028✔
1703
                                case col_type_Int:
23,847✔
1704
                                    new_sub_ref = BPlusTree<int64_t>::typed_write(sub_ref, out, m_alloc);
23,847✔
1705
                                    break;
23,847✔
1706
                                case col_type_Bool:
2,118✔
1707
                                    new_sub_ref = BPlusTree<bool>::typed_write(sub_ref, out, m_alloc);
2,118✔
1708
                                    break;
2,118✔
1709
                                case col_type_Float:
2,208✔
1710
                                    new_sub_ref = BPlusTree<float>::typed_write(sub_ref, out, m_alloc);
2,208✔
1711
                                    break;
2,208✔
1712
                                case col_type_Double:
2,400✔
1713
                                    new_sub_ref = BPlusTree<double>::typed_write(sub_ref, out, m_alloc);
2,400✔
1714
                                    break;
2,400✔
1715
                                case col_type_String:
10,746✔
1716
                                    new_sub_ref = BPlusTree<StringData>::typed_write(sub_ref, out, m_alloc);
10,746✔
1717
                                    break;
10,746✔
1718
                                case col_type_Binary:
19,794✔
1719
                                    new_sub_ref = BPlusTree<BinaryData>::typed_write(sub_ref, out, m_alloc);
19,794✔
1720
                                    break;
19,794✔
1721
                                case col_type_Timestamp:
2,148✔
1722
                                    new_sub_ref = BPlusTree<Timestamp>::typed_write(sub_ref, out, m_alloc);
2,148✔
1723
                                    break;
2,148✔
1724
                                case col_type_Link:
2,460,957✔
1725
                                    new_sub_ref = BPlusTree<ObjKey>::typed_write(sub_ref, out, m_alloc);
2,460,957✔
1726
                                    break;
2,460,957✔
1727
                                case col_type_ObjectId:
13,512✔
1728
                                    new_sub_ref = BPlusTree<ObjectId>::typed_write(sub_ref, out, m_alloc);
13,512✔
1729
                                    break;
13,512✔
1730
                                case col_type_Decimal:
2,328✔
1731
                                    new_sub_ref = BPlusTree<Decimal128>::typed_write(sub_ref, out, m_alloc);
2,328✔
1732
                                    break;
2,328✔
1733
                                case col_type_UUID:
2,148✔
1734
                                    new_sub_ref = BPlusTree<UUID>::typed_write(sub_ref, out, m_alloc);
2,148✔
1735
                                    break;
2,148✔
1736
                                case col_type_Mixed:
6,828✔
1737
                                    new_sub_ref = BPlusTree<Mixed>::typed_write(sub_ref, out, m_alloc);
6,828✔
1738
                                    break;
6,828✔
1739
                                default:
✔
1740
                                    REALM_COMPILER_HINT_UNREACHABLE();
×
1741
                            }
2,549,028✔
1742
                        }
2,549,028✔
1743
                    }
2,592,822✔
1744
                    written_ref_leaf.set_as_ref(k, new_sub_ref);
4,331,502✔
1745
                }
4,331,502✔
1746
                new_ref = written_ref_leaf.write(out);
451,152✔
1747
            }
451,152✔
1748
            else if (col_type == col_type_BackLink) {
1,448,628✔
1749
                Array leaf(m_alloc);
172,518✔
1750
                leaf.init_from_ref(ref);
172,518✔
1751
                new_ref = leaf.write(out, true, only_modified, false);
172,518✔
1752
            }
172,518✔
1753
            else {
1,276,110✔
1754
                switch_on_type(col_key, [&](auto t) {
1,276,233✔
1755
                    using U = std::decay_t<decltype(*t)>;
1,276,233✔
1756
                    new_ref = ColumnTypeTraits<U>::cluster_leaf_type::typed_write(ref, out, m_alloc);
1,276,233✔
1757
                });
1,276,233✔
1758
            }
1,276,110✔
1759
        }
1,899,768✔
1760
        written_cluster.set_as_ref(j, new_ref);
1,948,644✔
1761
    }
1,948,644✔
1762
    return written_cluster.write(out);
1,048,623✔
1763
}
1,048,611✔
1764

1765
void Cluster::typed_print(std::string prefix) const
1766
{
×
1767
    REALM_ASSERT_DEBUG(!get_is_inner_bptree_node_from_header(get_header()));
×
1768
    std::cout << "Cluster of size " << size() << " " << header_to_string(get_header()) << std::endl;
×
1769
    const auto table = get_owning_table();
×
1770
    for (unsigned j = 0; j < size(); ++j) {
×
1771
        RefOrTagged rot = get_as_ref_or_tagged(j);
×
1772
        auto pref = prefix + "  " + std::to_string(j) + ":\t";
×
1773
        if (rot.is_ref() && rot.get_as_ref()) {
×
1774
            if (j == 0) {
×
1775
                std::cout << pref << "Keys as ArrayUnsigned as ";
×
1776
                Array a(m_alloc);
×
1777
                a.init_from_ref(rot.get_as_ref());
×
1778
                a.typed_print(pref);
×
1779
            }
×
1780
            else {
×
1781
                auto col_key = table->m_leaf_ndx2colkey[j - 1];
×
1782
                auto col_type = col_key.get_type();
×
1783
                auto col_attr = col_key.get_attrs();
×
1784
                std::string attr_string;
×
1785
                if (col_attr.test(col_attr_Dictionary))
×
1786
                    attr_string = "Dict:";
×
1787
                if (col_attr.test(col_attr_List))
×
1788
                    attr_string = "List:";
×
1789
                if (col_attr.test(col_attr_Set))
×
1790
                    attr_string = "Set:";
×
1791
                if (col_attr.test(col_attr_Nullable))
×
1792
                    attr_string += "Null:";
×
1793
                std::cout << pref << "Column[" << attr_string << col_type << "] as ";
×
1794
                // special cases for the types we want to compress
1795
                if (col_attr.test(col_attr_List) || col_attr.test(col_attr_Set)) {
×
1796
                    // That is a single bplustree
1797
                    // propagation of nullable missing here?
1798
                    // handling of mixed missing here?
1799
                    BPlusTreeBase::typed_print(pref, m_alloc, rot.get_as_ref(), col_type);
×
1800
                }
×
1801
                else if (col_attr.test(col_attr_Dictionary)) {
×
1802
                    Array dict_top(m_alloc);
×
1803
                    dict_top.init_from_ref(rot.get_as_ref());
×
1804
                    if (dict_top.size() == 0) {
×
1805
                        std::cout << "{ empty }" << std::endl;
×
1806
                        continue;
×
1807
                    }
×
1808
                    std::cout << "{" << std::endl;
×
1809
                    auto ref0 = dict_top.get_as_ref(0);
×
1810
                    if (ref0) {
×
1811
                        auto p = pref + "  0:\t";
×
1812
                        std::cout << p;
×
1813
                        BPlusTreeBase::typed_print(p, m_alloc, ref0, col_type);
×
1814
                    }
×
1815
                    if (dict_top.size() == 1) {
×
1816
                        continue; // is this really possible? or should all dicts have both trees?
×
1817
                    }
×
1818
                    auto ref1 = dict_top.get_as_ref(1);
×
1819
                    if (ref1) {
×
1820
                        auto p = pref + "  1:\t";
×
1821
                        std::cout << p;
×
1822
                        BPlusTreeBase::typed_print(p, m_alloc, dict_top.get_as_ref(1), col_type);
×
1823
                    }
×
1824
                }
×
1825
                else {
×
1826
                    // handle all other cases as generic arrays
1827
                    Array a(m_alloc);
×
1828
                    a.init_from_ref(rot.get_as_ref());
×
1829
                    a.typed_print(pref);
×
1830
                }
×
1831
            }
×
1832
        }
×
1833
    }
×
1834
}
×
1835

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

© 2026 Coveralls, Inc