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

realm / realm-core / jorgen.edelbo_334

01 Jul 2024 07:22AM UTC coverage: 90.829% (-0.04%) from 90.865%
jorgen.edelbo_334

Pull #7803

Evergreen

jedelbo
Merge branch 'next-major' into feature/string-compression
Pull Request #7803: Feature/string compression

102912 of 180568 branches covered (56.99%)

1141 of 1267 new or added lines in 33 files covered. (90.06%)

172 existing lines in 24 files now uncovered.

218291 of 240332 relevant lines covered (90.83%)

7818396.4 hits per line

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

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

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

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

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

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

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

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

97

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

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

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

109

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

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

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

207
void Cluster::init(MemRef mem)
208
{
323,588,139✔
209
    Array::init_from_mem(mem);
323,588,139✔
210
    auto rot = Array::get_as_ref_or_tagged(0);
323,588,139✔
211
    if (rot.is_tagged()) {
323,588,139✔
212
        m_keys.detach();
261,052,254✔
213
    }
261,052,254✔
214
    else {
62,535,885✔
215
        m_keys.init_from_ref(rot.get_as_ref());
62,535,885✔
216
    }
62,535,885✔
217
}
323,588,139✔
218

219
void Cluster::update_from_parent() noexcept
220
{
1,005,756✔
221
    Array::update_from_parent();
1,005,756✔
222
    auto rot = Array::get_as_ref_or_tagged(0);
1,005,756✔
223
    if (!rot.is_tagged()) {
1,005,756✔
224
        m_keys.update_from_parent();
32,721✔
225
    }
32,721✔
226
}
1,005,756✔
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,152,623✔
244
    auto rot = Array::get_as_ref_or_tagged(header, s_key_ref_or_size_index);
71,152,623✔
245
    if (rot.is_tagged()) {
71,152,623✔
246
        return size_t(rot.get_as_int());
70,829,418✔
247
    }
70,829,418✔
248
    else {
323,205✔
249
        return Array::get_size_from_header(alloc.translate(rot.get_as_ref()));
323,205✔
250
    }
323,205✔
251
}
71,152,623✔
252

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

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

264
template <>
265
inline void Cluster::set_string_interner(ArrayMixed& arr, ColKey col_key) const
266
{
26,505✔
267
    m_tree_top.set_string_interner(arr, col_key);
26,505✔
268
}
26,505✔
269

270
template <class T>
271
inline void Cluster::set_spec(T&, ColKey::Idx) const
272
{
39,001,770✔
273
}
39,001,770✔
274

275
template <>
276
inline void Cluster::set_spec(ArrayString& arr, ColKey::Idx col_ndx) const
277
{
3,495,066✔
278
    m_tree_top.set_spec(arr, col_ndx);
3,495,066✔
279
}
3,495,066✔
280

281
template <class T>
282
inline void Cluster::do_insert_row(size_t ndx, ColKey col, Mixed init_val, bool nullable)
283
{
35,350,731✔
284
    using U = typename util::RemoveOptional<typename T::value_type>::type;
35,350,731✔
285

286
    T arr(m_alloc);
35,350,731✔
287
    auto col_ndx = col.get_index();
35,350,731✔
288
    arr.set_parent(this, col_ndx.val + s_first_col_index);
35,350,731✔
289
    set_spec<T>(arr, col_ndx);
35,350,731✔
290
    set_string_interner<T>(arr, col);
35,350,731✔
291
    arr.init_from_parent();
35,350,731✔
292
    if (init_val.is_null()) {
35,350,731✔
293
        arr.insert(ndx, T::default_value(nullable));
34,724,202✔
294
    }
34,724,202✔
295
    else {
626,529✔
296
        arr.insert(ndx, init_val.get<U>());
626,529✔
297
    }
626,529✔
298
}
35,350,731✔
299

300
inline void Cluster::do_insert_key(size_t ndx, ColKey col_key, Mixed init_val, ObjKey origin_key)
301
{
254,205✔
302
    ObjKey target_key = init_val.is_null() ? ObjKey{} : init_val.get<ObjKey>();
254,205✔
303
    ArrayKey arr(m_alloc);
254,205✔
304
    auto col_ndx = col_key.get_index();
254,205✔
305
    arr.set_parent(this, col_ndx.val + s_first_col_index);
254,205✔
306
    arr.init_from_parent();
254,205✔
307
    arr.insert(ndx, target_key);
254,205✔
308

309
    // Insert backlink if link is not null
310
    if (target_key) {
254,205✔
311
        const Table* origin_table = m_tree_top.get_owning_table();
12✔
312
        ColKey opp_col = origin_table->get_opposite_column(col_key);
12✔
313
        TableRef opp_table = origin_table->get_opposite_table(col_key);
12✔
314
        Obj target_obj = opp_table->get_object(target_key);
12✔
315
        target_obj.add_backlink(opp_col, origin_key);
12✔
316
    }
12✔
317
}
254,205✔
318

319
inline void Cluster::do_insert_mixed(size_t ndx, ColKey col_key, Mixed init_value, ObjKey origin_key)
320
{
22,350✔
321
    ArrayMixed arr(m_alloc);
22,350✔
322
    arr.set_parent(this, col_key.get_index().val + s_first_col_index);
22,350✔
323
    set_string_interner(arr, col_key);
22,350✔
324
    arr.init_from_parent();
22,350✔
325
    arr.insert(ndx, init_value);
22,350✔
326

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

346
inline void Cluster::do_insert_link(size_t ndx, ColKey col_key, Mixed init_val, ObjKey origin_key)
347
{
×
348
    ObjLink target_link = init_val.is_null() ? ObjLink{} : init_val.get<ObjLink>();
×
349
    ArrayTypedLink arr(m_alloc);
×
350
    auto col_ndx = col_key.get_index();
×
351
    arr.set_parent(this, col_ndx.val + s_first_col_index);
×
352
    arr.init_from_parent();
×
353
    arr.insert(ndx, target_link);
×
354

355
    // Insert backlink if link is not null
356
    if (target_link) {
×
357
        Table* origin_table = const_cast<Table*>(m_tree_top.get_owning_table());
×
358
        auto target_table = origin_table->get_parent_group()->get_table(target_link.get_table_key());
×
359

360
        ColKey backlink_col_key = target_table->find_or_add_backlink_column(col_key, origin_table->get_key());
×
361
        target_table->get_object(target_link.get_obj_key()).add_backlink(backlink_col_key, origin_key);
×
362
    }
×
363
}
×
364

365
void Cluster::insert_row(size_t ndx, ObjKey k, const FieldValues& init_values)
366
{
25,250,079✔
367
    // Ensure the cluster array is big enough to hold 64 bit values.
368
    copy_on_write(m_size * 8);
25,250,079✔
369

370
    if (m_keys.is_attached()) {
25,250,079✔
371
        m_keys.insert(ndx, k.value);
1,584,294✔
372
    }
1,584,294✔
373
    else {
23,665,785✔
374
        Array::set(s_key_ref_or_size_index, Array::get(s_key_ref_or_size_index) + 2); // Increments size by 1
23,665,785✔
375
    }
23,665,785✔
376

377
    auto val = init_values.begin();
25,250,079✔
378
    auto insert_in_column = [&](ColKey col_key) {
37,239,066✔
379
        auto col_ndx = col_key.get_index();
37,239,066✔
380
        auto attr = col_key.get_attrs();
37,239,066✔
381
        Mixed init_value;
37,239,066✔
382
        // init_values must be sorted in col_ndx order - this is ensured by ClustTree::insert()
383
        if (val != init_values.end() && val->col_key.get_index().val == col_ndx.val) {
37,239,066✔
384
            init_value = val->value;
506,541✔
385
            ++val;
506,541✔
386
        }
506,541✔
387

388
        auto type = col_key.get_type();
37,239,066✔
389
        if (attr.test(col_attr_Collection)) {
37,239,066✔
390
            REALM_ASSERT(init_value.is_null());
678,855✔
391
            ArrayRef arr(m_alloc);
678,855✔
392
            arr.set_parent(this, col_ndx.val + s_first_col_index);
678,855✔
393
            arr.init_from_parent();
678,855✔
394
            arr.insert(ndx, 0);
678,855✔
395
            return IteratorControl::AdvanceToNext;
678,855✔
396
        }
678,855✔
397

398
        bool nullable = attr.test(col_attr_Nullable);
36,560,211✔
399
        switch (type) {
36,560,211✔
400
            case col_type_Int:
27,486,834✔
401
                if (attr.test(col_attr_Nullable)) {
27,486,834✔
402
                    do_insert_row<ArrayIntNull>(ndx, col_key, init_value, nullable);
2,484,882✔
403
                }
2,484,882✔
404
                else {
25,001,952✔
405
                    do_insert_row<ArrayInteger>(ndx, col_key, init_value, nullable);
25,001,952✔
406
                }
25,001,952✔
407
                break;
27,486,834✔
408
            case col_type_Bool:
186,885✔
409
                do_insert_row<ArrayBoolNull>(ndx, col_key, init_value, nullable);
186,885✔
410
                break;
186,885✔
411
            case col_type_Float:
341,823✔
412
                do_insert_row<ArrayFloatNull>(ndx, col_key, init_value, nullable);
341,823✔
413
                break;
341,823✔
414
            case col_type_Double:
1,493,376✔
415
                do_insert_row<ArrayDoubleNull>(ndx, col_key, init_value, nullable);
1,493,376✔
416
                break;
1,493,376✔
417
            case col_type_String:
3,280,185✔
418
                do_insert_row<ArrayString>(ndx, col_key, init_value, nullable);
3,280,185✔
419
                break;
3,280,185✔
420
            case col_type_Binary:
1,364,160✔
421
                do_insert_row<ArrayBinary>(ndx, col_key, init_value, nullable);
1,364,160✔
422
                break;
1,364,160✔
423
            case col_type_Mixed: {
22,350✔
424
                do_insert_mixed(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
22,350✔
425
                break;
22,350✔
426
            }
×
427
            case col_type_Timestamp:
825,603✔
428
                do_insert_row<ArrayTimestamp>(ndx, col_key, init_value, nullable);
825,603✔
429
                break;
825,603✔
430
            case col_type_Decimal:
78,219✔
431
                do_insert_row<ArrayDecimal128>(ndx, col_key, init_value, nullable);
78,219✔
432
                break;
78,219✔
433
            case col_type_ObjectId:
173,478✔
434
                do_insert_row<ArrayObjectIdNull>(ndx, col_key, init_value, nullable);
173,478✔
435
                break;
173,478✔
436
            case col_type_UUID:
119,652✔
437
                do_insert_row<ArrayUUIDNull>(ndx, col_key, init_value, nullable);
119,652✔
438
                break;
119,652✔
439
            case col_type_Link:
254,205✔
440
                do_insert_key(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
254,205✔
441
                break;
254,205✔
442
            case col_type_TypedLink:
✔
443
                do_insert_link(ndx, col_key, init_value, ObjKey(k.value + get_offset()));
×
444
                break;
×
445
            case col_type_BackLink: {
964,335✔
446
                ArrayBacklink arr(m_alloc);
964,335✔
447
                arr.set_parent(this, col_ndx.val + s_first_col_index);
964,335✔
448
                arr.init_from_parent();
964,335✔
449
                arr.insert(ndx, 0);
964,335✔
450
                break;
964,335✔
451
            }
×
452
            default:
✔
453
                REALM_ASSERT(false);
×
454
                break;
×
455
        }
36,560,211✔
456
        return IteratorControl::AdvanceToNext;
36,472,929✔
457
    };
36,560,211✔
458
    m_tree_top.m_owner->for_each_and_every_column(insert_in_column);
25,250,079✔
459
}
25,250,079✔
460

461
template <class T>
462
inline void Cluster::do_move(size_t ndx, ColKey col_key, Cluster* to)
463
{
22,233✔
464
    auto col_ndx = col_key.get_index().val + s_first_col_index;
22,233✔
465
    T src(m_alloc);
22,233✔
466
    src.set_parent(this, col_ndx);
22,233✔
467
    src.init_from_parent();
22,233✔
468
    set_string_interner<T>(src, col_key);
22,233✔
469

470
    T dst(m_alloc);
22,233✔
471
    dst.set_parent(to, col_ndx);
22,233✔
472
    dst.init_from_parent();
22,233✔
473
    set_string_interner<T>(dst, col_key);
22,233✔
474

475
    src.move(dst, ndx);
22,233✔
476
}
22,233✔
477

478
void Cluster::move(size_t ndx, ClusterNode* new_node, int64_t offset)
479
{
11,694✔
480
    auto new_leaf = static_cast<Cluster*>(new_node);
11,694✔
481

482
    auto move_from_column = [&](ColKey col_key) {
22,233✔
483
        auto attr = col_key.get_attrs();
22,233✔
484
        auto type = col_key.get_type();
22,233✔
485

486
        if (attr.test(col_attr_Collection)) {
22,233✔
487
            do_move<ArrayRef>(ndx, col_key, new_leaf);
30✔
488
            return IteratorControl::AdvanceToNext;
30✔
489
        }
30✔
490

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

556
Cluster::~Cluster() {}
328,042,209✔
557

558
ColKey Cluster::get_col_key(size_t ndx_in_parent) const
559
{
18,345✔
560
    ColKey::Idx col_ndx{unsigned(ndx_in_parent - 1)}; // <- leaf_index here. Opaque.
18,345✔
561
    auto col_key = get_owning_table()->leaf_ndx2colkey(col_ndx);
18,345✔
562
    REALM_ASSERT(col_key.get_index().val == col_ndx.val);
18,345✔
563
    return col_key;
18,345✔
564
}
18,345✔
565

566
void Cluster::ensure_general_form()
567
{
61,944✔
568
    if (!m_keys.is_attached()) {
61,944✔
569
        size_t current_size = get_size_in_compact_form();
48,444✔
570
        m_keys.create(current_size, 255);
48,444✔
571
        m_keys.update_parent();
48,444✔
572
        for (size_t i = 0; i < current_size; i++) {
5,513,607✔
573
            m_keys.set(i, i);
5,465,163✔
574
        }
5,465,163✔
575
    }
48,444✔
576
}
61,944✔
577

578
template <class T>
579
inline void Cluster::do_insert_column(ColKey col_key, bool nullable)
580
{
610,833✔
581
    size_t sz = node_size();
610,833✔
582

583
    T arr(m_alloc);
610,833✔
584
    arr.create();
610,833✔
585
    auto val = T::default_value(nullable);
610,833✔
586
    for (size_t i = 0; i < sz; i++) {
6,848,697✔
587
        arr.add(val);
6,237,864✔
588
    }
6,237,864✔
589
    auto col_ndx = col_key.get_index();
610,833✔
590
    unsigned ndx = col_ndx.val + s_first_col_index;
610,833✔
591

592
    // Fill up if indexes are not consecutive
593
    while (size() < ndx)
610,833!
594
        Array::add(0);
×
595

596
    if (ndx == size())
610,833✔
597
        Array::insert(ndx, from_ref(arr.get_ref()));
610,734✔
598
    else
99✔
599
        Array::set(ndx, from_ref(arr.get_ref()));
99✔
600
}
610,833✔
601

602
void Cluster::insert_column(ColKey col_key)
603
{
805,893✔
604
    auto attr = col_key.get_attrs();
805,893✔
605
    auto type = col_key.get_type();
805,893✔
606
    if (attr.test(col_attr_Collection)) {
805,893✔
607
        size_t sz = node_size();
195,057✔
608

609
        ArrayRef arr(m_alloc);
195,057✔
610
        arr.create(sz);
195,057✔
611
        auto col_ndx = col_key.get_index();
195,057✔
612
        unsigned idx = col_ndx.val + s_first_col_index;
195,057✔
613
        if (idx == size())
195,057✔
614
            Array::insert(idx, from_ref(arr.get_ref()));
195,057✔
615
        else
×
616
            Array::set(idx, from_ref(arr.get_ref()));
×
617
        return;
195,057✔
618
    }
195,057✔
619
    bool nullable = attr.test(col_attr_Nullable);
610,836✔
620
    switch (type) {
610,836✔
621
        case col_type_Int:
275,130✔
622
            if (nullable) {
275,130✔
623
                do_insert_column<ArrayIntNull>(col_key, nullable);
32,901✔
624
            }
32,901✔
625
            else {
242,229✔
626
                do_insert_column<ArrayInteger>(col_key, nullable);
242,229✔
627
            }
242,229✔
628
            break;
275,130✔
629
        case col_type_Bool:
5,484✔
630
            do_insert_column<ArrayBoolNull>(col_key, nullable);
5,484✔
631
            break;
5,484✔
632
        case col_type_Float:
6,441✔
633
            do_insert_column<ArrayFloatNull>(col_key, nullable);
6,441✔
634
            break;
6,441✔
635
        case col_type_Double:
7,080✔
636
            do_insert_column<ArrayDoubleNull>(col_key, nullable);
7,080✔
637
            break;
7,080✔
638
        case col_type_String:
100,992✔
639
            do_insert_column<ArrayString>(col_key, nullable);
100,992✔
640
            break;
100,992✔
641
        case col_type_Binary:
6,894✔
642
            do_insert_column<ArrayBinary>(col_key, nullable);
6,894✔
643
            break;
6,894✔
644
        case col_type_Mixed:
9,060✔
645
            do_insert_column<ArrayMixed>(col_key, nullable);
9,060✔
646
            break;
9,060✔
647
        case col_type_Timestamp:
23,634✔
648
            do_insert_column<ArrayTimestamp>(col_key, nullable);
23,634✔
649
            break;
23,634✔
650
        case col_type_Decimal:
5,022✔
651
            do_insert_column<ArrayDecimal128>(col_key, nullable);
5,022✔
652
            break;
5,022✔
653
        case col_type_ObjectId:
50,577✔
654
            do_insert_column<ArrayObjectIdNull>(col_key, nullable);
50,577✔
655
            break;
50,577✔
656
        case col_type_UUID:
5,616✔
657
            do_insert_column<ArrayUUIDNull>(col_key, nullable);
5,616✔
658
            break;
5,616✔
659
        case col_type_Link:
31,116✔
660
            do_insert_column<ArrayKey>(col_key, nullable);
31,116✔
661
            break;
31,116✔
662
        case col_type_TypedLink:
✔
663
            do_insert_column<ArrayTypedLink>(col_key, nullable);
×
664
            break;
×
665
        case col_type_BackLink:
83,790✔
666
            do_insert_column<ArrayBacklink>(col_key, nullable);
83,790✔
667
            break;
83,790✔
668
        default:
✔
669
            REALM_UNREACHABLE();
670
            break;
×
671
    }
610,836✔
672
}
610,836✔
673

674
void Cluster::remove_column(ColKey col_key)
675
{
6,474✔
676
    auto col_ndx = col_key.get_index();
6,474✔
677
    unsigned idx = col_ndx.val + s_first_col_index;
6,474✔
678
    ref_type ref = to_ref(Array::get(idx));
6,474✔
679
    if (ref != 0) {
6,474✔
680
        Array::destroy_deep(ref, m_alloc);
6,474✔
681
    }
6,474✔
682
    if (idx == size() - 1)
6,474✔
683
        Array::erase(idx);
4,941✔
684
    else
1,533✔
685
        Array::set(idx, 0);
1,533✔
686
}
6,474✔
687

688
ref_type Cluster::insert(ObjKey k, const FieldValues& init_values, ClusterNode::State& state)
689
{
25,222,362✔
690
    int64_t current_key_value = -1;
25,222,362✔
691
    size_t sz;
25,222,362✔
692
    size_t ndx;
25,222,362✔
693
    ref_type ret = 0;
25,222,362✔
694

695
    auto on_error = [&] {
25,222,362✔
696
        throw KeyAlreadyUsed(
27✔
697
            util::format("When inserting key '%1' in '%2'", k.value, get_owning_table()->get_name()));
27✔
698
    };
27✔
699

700
    if (m_keys.is_attached()) {
25,222,362✔
701
        sz = m_keys.size();
1,572,909✔
702
        ndx = m_keys.lower_bound(uint64_t(k.value));
1,572,909✔
703
        if (ndx < sz) {
1,572,909✔
704
            current_key_value = m_keys.get(ndx);
616,923✔
705
            if (k.value == current_key_value) {
616,923✔
706
                on_error();
27✔
707
            }
27✔
708
        }
616,923✔
709
    }
1,572,909✔
710
    else {
23,649,453✔
711
        sz = size_t(Array::get(s_key_ref_or_size_index)) >> 1; // Size is stored as tagged integer
23,649,453✔
712
        if (uint64_t(k.value) < sz) {
23,649,453✔
713
            on_error();
×
714
        }
×
715
        // Key value is bigger than all other values, should be put last
716
        ndx = sz;
23,649,453✔
717
        if (uint64_t(k.value) > sz && sz < cluster_node_size) {
23,649,453✔
718
            ensure_general_form();
16,989✔
719
        }
16,989✔
720
    }
23,649,453✔
721

722
    REALM_ASSERT_DEBUG(sz <= cluster_node_size);
25,222,362✔
723
    if (REALM_LIKELY(sz < cluster_node_size)) {
25,222,362✔
724
        insert_row(ndx, k, init_values); // Throws
25,149,600✔
725
        state.mem = get_mem();
25,149,600✔
726
        state.index = ndx;
25,149,600✔
727
    }
25,149,600✔
728
    else {
72,762✔
729
        // Split leaf node
730
        Cluster new_leaf(0, m_alloc, m_tree_top);
72,762✔
731
        new_leaf.create();
72,762✔
732
        if (ndx == sz) {
92,319✔
733
            new_leaf.insert_row(0, ObjKey(0), init_values); // Throws
92,319✔
734
            state.split_key = k.value;
92,319✔
735
            state.mem = new_leaf.get_mem();
92,319✔
736
            state.index = 0;
92,319✔
737
        }
92,319✔
738
        else {
4,294,967,294✔
739
            // Current cluster must be in general form to get here
740
            REALM_ASSERT_DEBUG(m_keys.is_attached());
4,294,967,294✔
741
            new_leaf.ensure_general_form();
4,294,967,294✔
742
            move(ndx, &new_leaf, current_key_value);
4,294,967,294✔
743
            insert_row(ndx, k, init_values); // Throws
4,294,967,294✔
744
            state.mem = get_mem();
4,294,967,294✔
745
            state.split_key = current_key_value;
4,294,967,294✔
746
            state.index = ndx;
4,294,967,294✔
747
        }
4,294,967,294✔
748
        ret = new_leaf.get_ref();
72,762✔
749
    }
72,762✔
750

751
    return ret;
25,222,362✔
752
}
25,222,362✔
753

754
bool Cluster::try_get(ObjKey k, ClusterNode::State& state) const noexcept
755
{
273,467,397✔
756
    state.mem = get_mem();
273,467,397✔
757
    if (m_keys.is_attached()) {
273,467,397✔
758
        state.index = m_keys.lower_bound(uint64_t(k.value));
50,169,042✔
759
        return state.index != m_keys.size() && m_keys.get(state.index) == uint64_t(k.value);
50,169,042✔
760
    }
50,169,042✔
761
    else {
223,298,355✔
762
        if (uint64_t(k.value) < uint64_t(Array::get(s_key_ref_or_size_index) >> 1)) {
223,298,355✔
763
            state.index = size_t(k.value);
205,727,337✔
764
            return true;
205,727,337✔
765
        }
205,727,337✔
766
    }
223,298,355✔
767
    return false;
17,571,018✔
768
}
273,467,397✔
769

770
ObjKey Cluster::get(size_t ndx, ClusterNode::State& state) const
771
{
11,676,408✔
772
    state.index = ndx;
11,676,408✔
773
    state.mem = get_mem();
11,676,408✔
774
    return get_real_key(ndx);
11,676,408✔
775
}
11,676,408✔
776

777
template <class T>
778
inline void Cluster::do_erase(size_t ndx, ColKey col_key)
779
{
6,603,573✔
780
    auto col_ndx = col_key.get_index();
6,603,573✔
781
    T values(m_alloc);
6,603,573✔
782
    values.set_parent(this, col_ndx.val + s_first_col_index);
6,603,573✔
783
    set_spec<T>(values, col_ndx);
6,603,573✔
784
    set_string_interner<T>(values, col_key);
6,603,573✔
785
    values.init_from_parent();
6,603,573✔
786
    if constexpr (std::is_same_v<T, ArrayTypedLink>) {
6,603,573✔
787
        if (ObjLink link = values.get(ndx)) {
×
788
            if (const Table* origin_table = m_tree_top.get_owning_table()) {
×
789
                auto target_obj = origin_table->get_parent_group()->get_object(link);
×
790

791
                ColKey backlink_col_key =
×
792
                    target_obj.get_table()->find_backlink_column(col_key, origin_table->get_key());
×
793
                REALM_ASSERT(backlink_col_key);
×
794
                target_obj.remove_one_backlink(backlink_col_key, get_real_key(ndx)); // Throws
×
795
            }
×
796
        }
×
797
    }
×
798
    values.erase(ndx);
6,603,573✔
799
}
6,603,573✔
800

801
inline void Cluster::do_erase_mixed(size_t ndx, ColKey col_key, ObjKey key, CascadeState& state)
802
{
1,431✔
803
    const Table* origin_table = m_tree_top.get_owning_table();
1,431✔
804
    auto col_ndx = col_key.get_index();
1,431✔
805

806
    ArrayMixed values(m_alloc);
1,431✔
807
    values.set_parent(this, col_ndx.val + s_first_col_index);
1,431✔
808
    set_string_interner(values, col_key);
1,431✔
809
    values.init_from_parent();
1,431✔
810

811
    Mixed value = values.get(ndx);
1,431✔
812
    if (value.is_type(type_TypedLink)) {
1,431✔
813
        ObjLink link = value.get<ObjLink>();
24✔
814
        auto target_obj = origin_table->get_parent_group()->get_object(link);
24✔
815

816
        ColKey backlink_col_key = target_obj.get_table()->find_backlink_column(col_key, origin_table->get_key());
24✔
817
        REALM_ASSERT(backlink_col_key);
24✔
818
        target_obj.remove_one_backlink(backlink_col_key, get_real_key(ndx)); // Throws
24✔
819
    }
24✔
820
    if (value.is_type(type_List)) {
1,431✔
821
        Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
42✔
822
        Lst<Mixed> list(obj, col_key);
42✔
823
        list.remove_backlinks(state);
42✔
824
    }
42✔
825
    if (value.is_type(type_Dictionary)) {
1,431✔
826
        Obj obj(origin_table->m_own_ref, get_mem(), key, ndx);
18✔
827
        Dictionary dict(obj, col_key);
18✔
828
        dict.remove_backlinks(state);
18✔
829
    }
18✔
830
    values.erase(ndx);
1,431✔
831
}
1,431✔
832

833
inline void Cluster::do_erase_key(size_t ndx, ColKey col_key, CascadeState& state)
834
{
68,169✔
835
    ArrayKey values(m_alloc);
68,169✔
836
    auto col_ndx = col_key.get_index();
68,169✔
837
    values.set_parent(this, col_ndx.val + s_first_col_index);
68,169✔
838
    values.init_from_parent();
68,169✔
839

840
    ObjKey key = values.get(ndx);
68,169✔
841
    if (key != null_key) {
68,169✔
842
        do_remove_backlinks(get_real_key(ndx), col_key, std::vector<ObjKey>{key}, state);
65,412✔
843
    }
65,412✔
844
    values.erase(ndx);
68,169✔
845
}
68,169✔
846

847
size_t Cluster::get_ndx(ObjKey k, size_t ndx) const noexcept
848
{
9,939,288✔
849
    size_t index;
9,939,288✔
850
    if (m_keys.is_attached()) {
9,939,288✔
851
        index = m_keys.lower_bound(uint64_t(k.value));
9,824,562✔
852
        if (index == m_keys.size() || m_keys.get(index) != uint64_t(k.value)) {
9,824,760✔
853
            return realm::npos;
18✔
854
        }
18✔
855
    }
9,824,562✔
856
    else {
114,726✔
857
        index = size_t(k.value);
114,726✔
858
        if (index >= get_as_ref_or_tagged(s_key_ref_or_size_index).get_as_int()) {
114,726✔
859
            return realm::npos;
×
860
        }
×
861
    }
114,726✔
862
    return index + ndx;
9,939,270✔
863
}
9,939,288✔
864

865
size_t Cluster::erase(ObjKey key, CascadeState& state)
866
{
5,000,442✔
867
    size_t ndx = get_ndx(key, 0);
5,000,442✔
868
    if (ndx == realm::npos)
5,000,442✔
869
        throw KeyNotFound(util::format("When erasing key '%1' in '%2'", key.value, get_owning_table()->get_name()));
18✔
870
    std::vector<ColKey> backlink_column_keys;
5,000,424✔
871

872
    auto erase_in_column = [&](ColKey col_key) {
6,704,901✔
873
        auto col_type = col_key.get_type();
6,704,901✔
874
        auto attr = col_key.get_attrs();
6,704,901✔
875
        if (attr.test(col_attr_Collection)) {
6,704,901✔
876
            auto col_ndx = col_key.get_index();
31,776✔
877
            ArrayRef values(m_alloc);
31,776✔
878
            values.set_parent(this, col_ndx.val + s_first_col_index);
31,776✔
879
            values.init_from_parent();
31,776✔
880
            ref_type ref = values.get(ndx);
31,776✔
881

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

917
            values.erase(ndx);
31,776✔
918

919
            return IteratorControl::AdvanceToNext;
31,776✔
920
        }
31,776✔
921

922
        switch (col_type) {
6,673,125✔
923
            case col_type_Int:
5,984,898✔
924
                if (attr.test(col_attr_Nullable)) {
5,984,898✔
925
                    do_erase<ArrayIntNull>(ndx, col_key);
1,275,918✔
926
                }
1,275,918✔
927
                else {
4,708,980✔
928
                    do_erase<ArrayInteger>(ndx, col_key);
4,708,980✔
929
                }
4,708,980✔
930
                break;
5,984,898✔
931
            case col_type_Bool:
42,423✔
932
                do_erase<ArrayBoolNull>(ndx, col_key);
42,423✔
933
                break;
42,423✔
934
            case col_type_Float:
36,486✔
935
                do_erase<ArrayFloatNull>(ndx, col_key);
36,486✔
936
                break;
36,486✔
937
            case col_type_Double:
36,498✔
938
                do_erase<ArrayDoubleNull>(ndx, col_key);
36,498✔
939
                break;
36,498✔
940
            case col_type_String:
75,102✔
941
                do_erase<ArrayString>(ndx, col_key);
75,102✔
942
                break;
75,102✔
943
            case col_type_Binary:
39,633✔
944
                do_erase<ArrayBinary>(ndx, col_key);
39,633✔
945
                break;
39,633✔
946
            case col_type_Mixed:
1,431✔
947
                do_erase_mixed(ndx, col_key, key, state);
1,431✔
948
                break;
1,431✔
949
            case col_type_Timestamp:
45,291✔
950
                do_erase<ArrayTimestamp>(ndx, col_key);
45,291✔
951
                break;
45,291✔
952
            case col_type_Decimal:
36,279✔
953
                do_erase<ArrayDecimal128>(ndx, col_key);
36,279✔
954
                break;
36,279✔
955
            case col_type_ObjectId:
38,781✔
956
                do_erase<ArrayObjectIdNull>(ndx, col_key);
38,781✔
957
                break;
38,781✔
958
            case col_type_UUID:
60,285✔
959
                do_erase<ArrayUUIDNull>(ndx, col_key);
60,285✔
960
                break;
60,285✔
961
            case col_type_Link:
68,169✔
962
                do_erase_key(ndx, col_key, state);
68,169✔
963
                break;
68,169✔
964
            case col_type_TypedLink:
✔
965
                do_erase<ArrayTypedLink>(ndx, col_key);
×
966
                break;
×
967
            case col_type_BackLink:
207,897✔
968
                if (state.m_mode == CascadeState::Mode::None) {
207,897✔
969
                    do_erase<ArrayBacklink>(ndx, col_key);
188,604✔
970
                }
188,604✔
971
                else {
19,293✔
972
                    // Postpone the deletion of backlink entries or else the
973
                    // checks for if there's any remaining backlinks will
974
                    // check the wrong row for columns which have already
975
                    // had values erased from them.
976
                    backlink_column_keys.push_back(col_key);
19,293✔
977
                }
19,293✔
978
                break;
207,897✔
979
            default:
✔
980
                REALM_ASSERT(false);
×
981
                break;
×
982
        }
6,673,125✔
983
        return IteratorControl::AdvanceToNext;
6,673,101✔
984
    };
6,673,125✔
985
    m_tree_top.m_owner->for_each_and_every_column(erase_in_column);
5,000,424✔
986

987
    // Any remaining backlink columns to erase from?
988
    for (auto k : backlink_column_keys)
5,000,424✔
989
        do_erase<ArrayBacklink>(ndx, k);
19,293✔
990

991
    if (m_keys.is_attached()) {
5,000,424✔
992
        m_keys.erase(ndx);
4,959,753✔
993
    }
4,959,753✔
994
    else {
40,671✔
995
        size_t current_size = get_size_in_compact_form();
40,671✔
996
        if (ndx == current_size - 1) {
40,671✔
997
            // When deleting last, we can still maintain compact form
998
            set(0, RefOrTagged::make_tagged(current_size - 1));
26,304✔
999
        }
26,304✔
1000
        else {
14,367✔
1001
            ensure_general_form();
14,367✔
1002
            m_keys.erase(ndx);
14,367✔
1003
        }
14,367✔
1004
    }
40,671✔
1005

1006
    return node_size();
5,000,424✔
1007
}
5,000,442✔
1008

1009
void Cluster::nullify_incoming_links(ObjKey key, CascadeState& state)
1010
{
4,902,201✔
1011
    size_t ndx = get_ndx(key, 0);
4,902,201✔
1012
    if (ndx == realm::npos)
4,902,201✔
1013
        throw KeyNotFound(util::format("Key '%1' not found in '%2' when nullifying incoming links", key.value,
×
1014
                                       get_owning_table()->get_class_name()));
×
1015

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

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

1044
        return IteratorControl::AdvanceToNext;
190,413✔
1045
    };
190,413✔
1046

1047
    m_tree_top.get_owning_table()->for_each_backlink_column(nullify_fwd_links);
4,902,201✔
1048
}
4,902,201✔
1049

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

1070
void Cluster::init_leaf(ColKey col_key, ArrayPayload* leaf) const
1071
{
8,711,478✔
1072
    auto col_ndx = col_key.get_index();
8,711,478✔
1073
    // FIXME: Move this validation into callers.
1074
    // Currently, the query subsystem may call with an unvalidated key.
1075
    // once fixed, reintroduce the noexcept declaration :-D
1076
    if (auto t = m_tree_top.get_owning_table())
8,711,478✔
1077
        t->check_column(col_key);
8,213,964✔
1078
    ref_type ref = to_ref(Array::get(col_ndx.val + 1));
8,711,478✔
1079
    if (leaf->need_string_interner()) {
8,711,478✔
1080
        m_tree_top.set_string_interner(*leaf, col_key);
160,986✔
1081
    }
160,986✔
1082
    if (leaf->need_spec()) {
8,711,478✔
1083
        m_tree_top.set_spec(*leaf, col_ndx);
148,608✔
1084
    }
148,608✔
1085
    leaf->init_from_ref(ref);
8,711,478✔
1086
    leaf->set_parent(const_cast<Cluster*>(this), col_ndx.val + 1);
8,711,478✔
1087
}
8,711,478✔
1088

1089
void Cluster::add_leaf(ColKey col_key, ref_type ref)
1090
{
×
1091
    auto col_ndx = col_key.get_index();
×
1092
    REALM_ASSERT((col_ndx.val + 1) == size());
×
1093
    Array::insert(col_ndx.val + 1, from_ref(ref));
×
1094
}
×
1095

1096
template <typename ArrayType>
1097
void Cluster::verify(ref_type ref, size_t index, util::Optional<size_t>& sz) const
1098
{
553,539✔
1099
    ArrayType arr(get_alloc());
553,539✔
1100
    set_spec(arr, ColKey::Idx{unsigned(index) - 1});
553,539✔
1101
    auto table = get_owning_table();
553,539✔
1102
    REALM_ASSERT(index <= table->m_leaf_ndx2colkey.size());
553,539✔
1103
    auto col_key = table->m_leaf_ndx2colkey[index - 1];
553,539✔
1104
    set_string_interner(arr, col_key);
553,539✔
1105
    arr.set_parent(const_cast<Cluster*>(this), index);
553,539✔
1106
    arr.init_from_ref(ref);
553,539✔
1107
    arr.verify();
553,539✔
1108
    if (sz) {
553,539✔
1109
        REALM_ASSERT(arr.size() == *sz);
199,059✔
1110
    }
199,059✔
1111
    else {
354,480✔
1112
        sz = arr.size();
354,480✔
1113
    }
354,480✔
1114
}
553,539✔
1115
namespace {
1116

1117
template <typename ArrayType>
1118
void verify_list(ArrayRef& arr, size_t sz)
1119
{
96,264✔
1120
    for (size_t n = 0; n < sz; n++) {
355,485!
1121
        if (ref_type bp_tree_ref = arr.get(n)) {
259,221!
1122
            BPlusTree<ArrayType> links(arr.get_alloc());
107,610✔
1123
            links.init_from_ref(bp_tree_ref);
107,610✔
1124
            links.set_parent(&arr, n);
107,610✔
1125
            links.verify();
107,610✔
1126
        }
107,610✔
1127
    }
259,221✔
1128
}
96,264✔
1129

1130
template <typename SetType>
1131
void verify_set(ArrayRef& arr, size_t sz)
1132
{
174✔
1133
    for (size_t n = 0; n < sz; ++n) {
402!
1134
        if (ref_type bp_tree_ref = arr.get(n)) {
228!
1135
            BPlusTree<SetType> elements(arr.get_alloc());
216✔
1136
            elements.init_from_ref(bp_tree_ref);
216✔
1137
            elements.set_parent(&arr, n);
216✔
1138
            elements.verify();
216✔
1139

1140
            // FIXME: Check uniqueness of elements.
1141
        }
216✔
1142
    }
228✔
1143
}
174✔
1144

1145
} // namespace
1146

1147
void Cluster::verify() const
1148
{
391,089✔
1149
#ifdef REALM_DEBUG
391,089✔
1150
    util::Optional<size_t> sz;
391,089✔
1151

1152
    auto verify_column = [this, &sz](ColKey col_key) {
662,238✔
1153
        size_t col = col_key.get_index().val + s_first_col_index;
662,238✔
1154
        ref_type ref = Array::get_as_ref(col);
662,238✔
1155
        auto attr = col_key.get_attrs();
662,238✔
1156
        auto col_type = col_key.get_type();
662,238✔
1157
        bool nullable = attr.test(col_attr_Nullable);
662,238✔
1158

1159
        if (attr.test(col_attr_List)) {
662,238✔
1160
            ArrayRef arr(get_alloc());
96,282✔
1161
            arr.set_parent(const_cast<Cluster*>(this), col);
96,282✔
1162
            arr.init_from_ref(ref);
96,282✔
1163
            arr.verify();
96,282✔
1164
            if (sz) {
96,282✔
1165
                REALM_ASSERT(arr.size() == *sz);
71,982✔
1166
            }
71,982✔
1167
            else {
24,300✔
1168
                sz = arr.size();
24,300✔
1169
            }
24,300✔
1170

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

1292
        switch (col_type) {
553,518✔
1293
            case col_type_Int:
342,411✔
1294
                if (nullable) {
342,411✔
1295
                    verify<ArrayIntNull>(ref, col, sz);
40,767✔
1296
                }
40,767✔
1297
                else {
301,644✔
1298
                    verify<ArrayInteger>(ref, col, sz);
301,644✔
1299
                }
301,644✔
1300
                break;
342,411✔
1301
            case col_type_Bool:
7,158✔
1302
                verify<ArrayBoolNull>(ref, col, sz);
7,158✔
1303
                break;
7,158✔
1304
            case col_type_Float:
168✔
1305
                verify<ArrayFloatNull>(ref, col, sz);
168✔
1306
                break;
168✔
1307
            case col_type_Double:
156✔
1308
                verify<ArrayDoubleNull>(ref, col, sz);
156✔
1309
                break;
156✔
1310
            case col_type_String:
139,911✔
1311
                verify<ArrayString>(ref, col, sz);
139,911✔
1312
                break;
139,911✔
1313
            case col_type_Binary:
49,890✔
1314
                verify<ArrayBinary>(ref, col, sz);
49,890✔
1315
                break;
49,890✔
1316
            case col_type_Mixed:
2,724✔
1317
                verify<ArrayMixed>(ref, col, sz);
2,724✔
1318
                break;
2,724✔
1319
            case col_type_Timestamp:
6,948✔
1320
                verify<ArrayTimestamp>(ref, col, sz);
6,948✔
1321
                break;
6,948✔
1322
            case col_type_Decimal:
42✔
1323
                verify<ArrayDecimal128>(ref, col, sz);
42✔
1324
                break;
42✔
1325
            case col_type_ObjectId:
42✔
1326
                verify<ArrayObjectIdNull>(ref, col, sz);
42✔
1327
                break;
42✔
1328
            case col_type_UUID:
✔
1329
                verify<ArrayUUIDNull>(ref, col, sz);
×
1330
                break;
×
1331
            case col_type_Link:
1,572✔
1332
                verify<ArrayKey>(ref, col, sz);
1,572✔
1333
                break;
1,572✔
1334
            case col_type_BackLink:
2,514✔
1335
                verify<ArrayBacklink>(ref, col, sz);
2,514✔
1336
                break;
2,514✔
1337
            default:
✔
1338
                break;
×
1339
        }
553,518✔
1340
        return IteratorControl::AdvanceToNext;
553,536✔
1341
    };
553,518✔
1342

1343
    m_tree_top.m_owner->for_each_and_every_column(verify_column);
391,089✔
1344
#endif
391,089✔
1345
}
391,089✔
1346

1347
// LCOV_EXCL_START
1348
void Cluster::dump_objects(int64_t key_offset, std::string lead) const
1349
{
×
1350
    std::cout << lead << "leaf - size: " << node_size() << std::endl;
×
1351
    if (!m_keys.is_attached()) {
×
1352
        std::cout << lead << "compact form" << std::endl;
×
1353
    }
×
1354

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

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

1533
void Cluster::remove_backlinks(const Table* origin_table, ObjKey origin_key, ColKey origin_col_key,
1534
                               const std::vector<ObjKey>& keys, CascadeState& state)
1535
{
67,191✔
1536
    TableRef target_table = origin_table->get_opposite_table(origin_col_key);
67,191✔
1537
    ColKey backlink_col_key = origin_table->get_opposite_column(origin_col_key);
67,191✔
1538
    bool strong_links = target_table->is_embedded();
67,191✔
1539

1540
    for (auto key : keys) {
68,829✔
1541
        if (key != null_key) {
68,829✔
1542
            bool is_unres = key.is_unresolved();
68,829✔
1543
            Obj target_obj = is_unres ? target_table->m_tombstones->get(key) : target_table->m_clusters.get(key);
68,829✔
1544
            bool last_removed = target_obj.remove_one_backlink(backlink_col_key, origin_key); // Throws
68,829✔
1545
            if (is_unres) {
68,829✔
1546
                if (last_removed) {
30✔
1547
                    // Check is there are more backlinks
1548
                    if (!target_obj.has_backlinks(false)) {
24✔
1549
                        // Tombstones can be erased right away - there is no cascading effect
1550
                        target_table->m_tombstones->erase(key, state);
18✔
1551
                    }
18✔
1552
                }
24✔
1553
            }
30✔
1554
            else {
68,799✔
1555
                state.enqueue_for_cascade(target_obj, strong_links, last_removed);
68,799✔
1556
            }
68,799✔
1557
        }
68,829✔
1558
    }
68,829✔
1559
}
67,191✔
1560

1561
void Cluster::remove_backlinks(const Table* origin_table, ObjKey origin_key, ColKey origin_col_key,
1562
                               const std::vector<ObjLink>& links, CascadeState& state)
1563
{
144✔
1564
    Group* group = origin_table->get_parent_group();
144✔
1565
    TableKey origin_table_key = origin_table->get_key();
144✔
1566

1567
    for (auto link : links) {
240✔
1568
        if (link) {
240✔
1569
            bool is_unres = link.get_obj_key().is_unresolved();
240✔
1570
            Obj target_obj = group->get_object(link);
240✔
1571
            TableRef target_table = target_obj.get_table();
240✔
1572
            ColKey backlink_col_key = target_table->find_or_add_backlink_column(origin_col_key, origin_table_key);
240✔
1573

1574
            bool last_removed = target_obj.remove_one_backlink(backlink_col_key, origin_key); // Throws
240✔
1575
            if (is_unres) {
240✔
1576
                if (last_removed) {
6✔
1577
                    // Check is there are more backlinks
1578
                    if (!target_obj.has_backlinks(false)) {
6✔
1579
                        // Tombstones can be erased right away - there is no cascading effect
1580
                        target_table->m_tombstones->erase(link.get_obj_key(), state);
6✔
1581
                    }
6✔
1582
                }
6✔
1583
            }
6✔
1584
            else {
234✔
1585
                state.enqueue_for_cascade(target_obj, false, last_removed);
234✔
1586
            }
234✔
1587
        }
240✔
1588
    }
240✔
1589
}
144✔
1590

1591
namespace {
1592

1593
template <typename Fn>
1594
static void switch_on_type(ColKey ck, Fn&& fn)
1595
{
1,583,130✔
1596
    bool is_optional = ck.is_nullable();
1,583,130✔
1597
    auto type = ck.get_type();
1,583,130✔
1598
    switch (type) {
1,583,130✔
1599
        case col_type_Int:
815,865✔
1600
            return is_optional ? fn((util::Optional<int64_t>*)0) : fn((int64_t*)0);
815,865✔
1601
        case col_type_Bool:
9,318✔
1602
            return is_optional ? fn((util::Optional<bool>*)0) : fn((bool*)0);
9,318✔
1603
        case col_type_Float:
9,519✔
1604
            return is_optional ? fn((util::Optional<float>*)0) : fn((float*)0);
9,519✔
1605
        case col_type_Double:
13,524✔
1606
            return is_optional ? fn((util::Optional<double>*)0) : fn((double*)0);
13,524✔
1607
        case col_type_String:
267,945✔
1608
            return fn((StringData*)0);
267,945✔
1609
        case col_type_Binary:
247,965✔
1610
            return fn((BinaryData*)0);
247,965✔
1611
        case col_type_Timestamp:
39,369✔
1612
            return fn((Timestamp*)0);
39,369✔
1613
        case col_type_Link:
56,322✔
1614
            return fn((ObjKey*)0);
56,322✔
1615
        case col_type_ObjectId:
86,907✔
1616
            return is_optional ? fn((util::Optional<ObjectId>*)0) : fn((ObjectId*)0);
86,907✔
1617
        case col_type_Decimal:
8,559✔
1618
            return fn((Decimal128*)0);
8,559✔
1619
        case col_type_UUID:
9,339✔
1620
            return is_optional ? fn((util::Optional<UUID>*)0) : fn((UUID*)0);
9,339✔
1621
        case col_type_Mixed:
18,519✔
1622
            return fn((Mixed*)0);
18,519✔
1623
        default:
✔
1624
            REALM_COMPILER_HINT_UNREACHABLE();
×
1625
    }
1,583,130✔
1626
}
1,583,130✔
1627

1628
} // namespace
1629

1630
ref_type Cluster::typed_write(ref_type ref, _impl::ArrayWriterBase& out) const
1631
{
1,070,976✔
1632
    REALM_ASSERT_DEBUG(ref == get_mem().get_ref());
1,070,976✔
1633
    bool only_modified = out.only_modified;
1,070,976✔
1634
    if (only_modified && m_alloc.is_read_only(ref))
1,070,976✔
1635
        return ref;
1,272✔
1636
    REALM_ASSERT_DEBUG(!get_is_inner_bptree_node_from_header(get_header()));
1,069,704✔
1637
    REALM_ASSERT_DEBUG(!get_context_flag_from_header(get_header()));
1,069,704✔
1638
    TempArray written_cluster(size());
1,069,704✔
1639
    for (size_t j = 0; j < size(); ++j) {
4,676,880✔
1640
        RefOrTagged leaf_rot = get_as_ref_or_tagged(j);
3,607,164✔
1641
        // Handle nulls
1642
        if (!leaf_rot.is_ref() || !leaf_rot.get_as_ref()) {
3,607,164✔
1643
            written_cluster.set(j, leaf_rot);
1,009,122✔
1644
            continue;
1,009,122✔
1645
        }
1,009,122✔
1646
        // prune subtrees which should not be written:
1647
        if (only_modified && m_alloc.is_read_only(leaf_rot.get_as_ref())) {
2,598,042✔
1648
            written_cluster.set(j, leaf_rot);
341,871✔
1649
            continue;
341,871✔
1650
        }
341,871✔
1651
        // from here: this leaf exists and needs to be written.
1652
        ref = leaf_rot.get_as_ref();
2,256,171✔
1653
        ref_type new_ref = ref;
2,256,171✔
1654
        if (j == 0) {
2,256,171✔
1655
            // Keys  (ArrayUnsigned me thinks, so don't compress)
1656
            Array leaf(m_alloc);
49,032✔
1657
            leaf.init_from_ref(ref);
49,032✔
1658
            new_ref = leaf.write(out, false, only_modified, false);
49,032✔
1659
        }
49,032✔
1660
        else {
2,207,139✔
1661
            // Columns
1662
            auto col_key = out.table->m_leaf_ndx2colkey[j - 1];
2,207,139✔
1663
            out.col_key = col_key;
2,207,139✔
1664
            auto col_type = col_key.get_type();
2,207,139✔
1665
            if (col_key.is_collection()) {
2,207,139✔
1666
                ArrayRef arr_ref(m_alloc);
451,314✔
1667
                arr_ref.init_from_ref(ref);
451,314✔
1668
                auto sz = arr_ref.size();
451,314✔
1669
                TempArray written_ref_leaf(sz);
451,314✔
1670

1671
                for (size_t k = 0; k < sz; k++) {
4,781,454✔
1672
                    ref_type new_sub_ref = 0;
4,330,128✔
1673
                    // Now we have to find out if the nested collection is a
1674
                    // dictionary or a list. If the top array has a size of 2
1675
                    // and it is not a BplusTree inner node, then it is a dictionary
1676
                    if (auto sub_ref = arr_ref.get(k)) {
4,330,128✔
1677
                        if (col_key.is_dictionary()) {
2,595,432✔
1678
                            new_sub_ref = Dictionary::typed_write(sub_ref, out, m_alloc);
43,809✔
1679
                        }
43,809✔
1680
                        else {
2,551,623✔
1681
                            // List or set - Can be handled the same way
1682
                            // For some reason, switch_on_type() would not compile on Windows
1683
                            // switch_on_type(col_key, [&](auto t) {
1684
                            //     using U = std::decay_t<decltype(*t)>;
1685
                            //     new_sub_ref = BPlusTree<U>::typed_write(sub_ref, out, m_alloc);
1686
                            // });
1687
                            switch (col_type) {
2,551,623✔
1688
                                case col_type_Int:
22,848✔
1689
                                    new_sub_ref = BPlusTree<int64_t>::typed_write(sub_ref, out, m_alloc);
22,848✔
1690
                                    break;
22,848✔
1691
                                case col_type_Bool:
2,124✔
1692
                                    new_sub_ref = BPlusTree<bool>::typed_write(sub_ref, out, m_alloc);
2,124✔
1693
                                    break;
2,124✔
1694
                                case col_type_Float:
2,208✔
1695
                                    new_sub_ref = BPlusTree<float>::typed_write(sub_ref, out, m_alloc);
2,208✔
1696
                                    break;
2,208✔
1697
                                case col_type_Double:
2,406✔
1698
                                    new_sub_ref = BPlusTree<double>::typed_write(sub_ref, out, m_alloc);
2,406✔
1699
                                    break;
2,406✔
1700
                                case col_type_String:
9,867✔
1701
                                    new_sub_ref = BPlusTree<StringData>::typed_write(sub_ref, out, m_alloc);
9,867✔
1702
                                    break;
9,867✔
1703
                                case col_type_Binary:
19,761✔
1704
                                    new_sub_ref = BPlusTree<BinaryData>::typed_write(sub_ref, out, m_alloc);
19,761✔
1705
                                    break;
19,761✔
1706
                                case col_type_Timestamp:
2,154✔
1707
                                    new_sub_ref = BPlusTree<Timestamp>::typed_write(sub_ref, out, m_alloc);
2,154✔
1708
                                    break;
2,154✔
1709
                                case col_type_Link:
2,465,349✔
1710
                                    new_sub_ref = BPlusTree<ObjKey>::typed_write(sub_ref, out, m_alloc);
2,465,349✔
1711
                                    break;
2,465,349✔
1712
                                case col_type_ObjectId:
13,512✔
1713
                                    new_sub_ref = BPlusTree<ObjectId>::typed_write(sub_ref, out, m_alloc);
13,512✔
1714
                                    break;
13,512✔
1715
                                case col_type_Decimal:
2,328✔
1716
                                    new_sub_ref = BPlusTree<Decimal128>::typed_write(sub_ref, out, m_alloc);
2,328✔
1717
                                    break;
2,328✔
1718
                                case col_type_UUID:
2,148✔
1719
                                    new_sub_ref = BPlusTree<UUID>::typed_write(sub_ref, out, m_alloc);
2,148✔
1720
                                    break;
2,148✔
1721
                                case col_type_Mixed:
6,912✔
1722
                                    new_sub_ref = BPlusTree<Mixed>::typed_write(sub_ref, out, m_alloc);
6,912✔
1723
                                    break;
6,912✔
1724
                                default:
✔
1725
                                    REALM_COMPILER_HINT_UNREACHABLE();
×
1726
                            }
2,551,623✔
1727
                        }
2,551,623✔
1728
                    }
2,595,432✔
1729
                    written_ref_leaf.set_as_ref(k, new_sub_ref);
4,330,140✔
1730
                }
4,330,140✔
1731
                new_ref = written_ref_leaf.write(out);
451,326✔
1732
            }
451,326✔
1733
            else if (col_type == col_type_BackLink) {
1,755,825✔
1734
                Array leaf(m_alloc);
172,809✔
1735
                leaf.init_from_ref(ref);
172,809✔
1736
                new_ref = leaf.write(out, true, only_modified, false);
172,809✔
1737
            }
172,809✔
1738
            else {
1,583,016✔
1739
                switch_on_type(col_key, [&](auto t) {
1,583,157✔
1740
                    using U = std::decay_t<decltype(*t)>;
1,583,157✔
1741
                    new_ref = ColumnTypeTraits<U>::cluster_leaf_type::typed_write(ref, out, m_alloc);
1,583,157✔
1742
                });
1,583,157✔
1743
            }
1,583,016✔
1744
        }
2,207,139✔
1745
        written_cluster.set_as_ref(j, new_ref);
2,256,183✔
1746
    }
2,256,183✔
1747
    return written_cluster.write(out);
1,069,716✔
1748
}
1,069,704✔
1749

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

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