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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

90.5
/src/realm/table.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/table.hpp>
20

21
#include <realm/alloc_slab.hpp>
22
#include <realm/array_binary.hpp>
23
#include <realm/array_bool.hpp>
24
#include <realm/array_decimal128.hpp>
25
#include <realm/array_fixed_bytes.hpp>
26
#include <realm/array_string.hpp>
27
#include <realm/array_timestamp.hpp>
28
#include <realm/db.hpp>
29
#include <realm/dictionary.hpp>
30
#include <realm/exceptions.hpp>
31
#include <realm/impl/destroy_guard.hpp>
32
#include <realm/index_string.hpp>
33
#include <realm/query_conditions_tpl.hpp>
34
#include <realm/replication.hpp>
35
#include <realm/table_view.hpp>
36
#include <realm/util/features.h>
37
#include <realm/util/miscellaneous.hpp>
38
#include <realm/util/serializer.hpp>
39

40
#include <stdexcept>
41

42
#ifdef REALM_DEBUG
43
#include <iostream>
44
#include <iomanip>
45
#endif
46

47
/// \page AccessorConsistencyLevels
48
///
49
/// These are the three important levels of consistency of a hierarchy of
50
/// Realm accessors rooted in a common group accessor (tables, columns, rows,
51
/// descriptors, arrays):
52
///
53
/// ### Fully Consistent Accessor Hierarchy (or just "Full Consistency")
54
///
55
/// All attached accessors are in a fully valid state and can be freely used by
56
/// the application. From the point of view of the application, the accessor
57
/// hierarchy remains in this state as long as no library function throws.
58
///
59
/// If a library function throws, and the exception is one that is considered
60
/// part of the API, such as util::File::NotFound, then the accessor hierarchy
61
/// remains fully consistent. In all other cases, such as when a library
62
/// function fails because of memory exhaustion, and it throws std::bad_alloc,
63
/// the application may no longer assume anything beyond minimal consistency.
64
///
65
/// ### Minimally Consistent Accessor Hierarchy (or just "Minimal Consistency")
66
///
67
/// No correspondence between the accessor states and the underlying node
68
/// structure can be assumed, but all parent and child accessor references are
69
/// valid (i.e., not dangling). There are specific additional guarantees, but
70
/// only on some parts of the internal accessors states, and only on some parts
71
/// of the structural state.
72
///
73
/// This level of consistency is guaranteed at all times, and it is also the
74
/// **maximum** that may be assumed by the application after a library function
75
/// fails by throwing an unexpected exception (such as std::bad_alloc). It is
76
/// also the **minimum** level of consistency that is required to be able to
77
/// properly destroy the accessor objects (manually, or as a result of stack
78
/// unwinding).
79
///
80
/// It is supposed to be a library-wide invariant that an accessor hierarchy is
81
/// at least minimally consistent, but so far, only some parts of the library
82
/// conform to it.
83
///
84
/// Note: With proper use, and maintenance of Minimal Consistency, it is
85
/// possible to ensure that no memory is leaked after a group accessor is
86
/// destroyed, even after a library function has failed because of memory
87
/// exhaustion. This is possible because the underlying nodes are allocated in
88
/// the context of the group, and they can all be freed by the group when it is
89
/// destroyed. On the other hand, when working with free-standing tables, each
90
/// underlying node is allocated individually on the heap, so in this case we
91
/// cannot prevent memory leaks, because there is no way of knowing what to free
92
/// when the table accessor is destroyed.
93
///
94
/// ### Structurally Correspondent Accessor Hierarchy (or simply "Structural Correspondence")
95
///
96
/// The structure of the accessor hierarchy is in agreement with the underlying
97
/// node structure, but the refs (references to underlying nodes) are generally
98
/// not valid, and certain other parts of the accessor states are also generally
99
/// not valid. This state of consistency is important mainly during the
100
/// advancing of read transactions (implicit transactions), and is not exposed
101
/// to the application.
102
///
103
///
104
/// Below is a detailed specification of the requirements for Minimal
105
/// Consistency and for Structural Correspondence.
106
///
107
///
108
/// Minimally Consistent Accessor Hierarchy (accessor destruction)
109
/// --------------------------------------------------------------
110
///
111
/// This section defines a level of accessor consistency known as "Minimally
112
/// Consistent Accessor Hierarchy". It applies to a set of accessors rooted in a
113
/// common group. It does not imply any level of correspondance between the
114
/// state of the accessors and the underlying node structure. It enables safe
115
/// destruction of the accessor objects by requiring that the following items
116
/// are valid (the list may not yet be complete):
117
///
118
///  - Every allocated accessor is either a group accessor, or occurs as a
119
///    direct, or an indirect child of a group accessor.
120
///
121
///  - No allocated accessor occurs as a child more than once (for example, no
122
///    doublets are allowed in Group::m_table_accessors).
123
///
124
///  - The 'is_attached' property of array accessors (Array::m_data == 0). For
125
///    example, `Table::m_top` is attached if and only if that table accessor
126
///    was attached to a table with independent dynamic type.
127
///
128
///  - The 'parent' property of array accessors (Array::m_parent), but
129
///    crucially, **not** the `index_in_parent` property.
130
///
131
///  - The list of table accessors in a group accessor
132
///    (Group::m_table_accessors). All non-null pointers refer to existing table
133
///    accessors.
134
///
135
///  - The list of column accessors in a table acccessor (Table::m_cols). All
136
///    non-null pointers refer to existing column accessors.
137
///
138
///  - The 'root_array' property of a column accessor (ColumnBase::m_array). It
139
///    always refers to an existing array accessor. The exact type of that array
140
///    accessor must be determinable from the following properties of itself:
141
///    `is_inner_bptree_node` (Array::m_is_inner_bptree_node), `has_refs`
142
///    (Array::m_has_refs), and `context_flag` (Array::m_context_flag). This
143
///    allows for a column accessor to be properly destroyed.
144
///
145
///  - The map of subtable accessors in a column acccessor
146
///    (SubtableColumnBase:m_subtable_map). All pointers refer to existing
147
///    subtable accessors, but it is not required that the set of subtable
148
///    accessors referenced from a particular parent P conincide with the set of
149
///    subtables accessors specifying P as parent.
150
///
151
///  - The `descriptor` property of a table accesor (Table::m_descriptor). If it
152
///    is not null, then it refers to an existing descriptor accessor.
153
///
154
///  - The map of subdescriptor accessors in a descriptor accessor
155
///    (Descriptor::m_subdesc_map). All non-null pointers refer to existing
156
///    subdescriptor accessors.
157
///
158
///  - The `search_index` property of a column accesor (StringColumn::m_index,
159
///    StringEnumColumn::m_index). When it is non-null, it refers to an existing
160
///    search index accessor.
161
///
162
///
163
/// Structurally Correspondent Accessor Hierarchy (accessor reattachment)
164
/// ---------------------------------------------------------------------
165
///
166
/// This section defines what it means for an accessor hierarchy to be
167
/// "Structurally Correspondent". It applies to a set of accessors rooted in a
168
/// common group. The general idea is that the structure of the accessors must
169
/// match the underlying structure to such an extent that there is never any
170
/// doubt about which underlying node that corresponds with a particular
171
/// accessor. It is assumed that the accessor tree, and the underlying node
172
/// structure are structurally sound individually.
173
///
174
/// With this level of correspondence, it is possible to reattach the accessor
175
/// tree to the underlying node structure (Table::refresh_accessor_tree()).
176
///
177
/// While all the accessors in the tree must be in the attached state (before
178
/// reattachement), they are not required to refer to existing underlying nodes;
179
/// that is, their references **are** allowed to be dangling. Roughly speaking,
180
/// this means that the accessor tree must have been attached to a node
181
/// structure at some earlier point in time.
182
///
183
//
184
/// Requirements at group level:
185
///
186
///  - The number of tables in the underlying group must be equal to the number
187
///    of entries in `Group::m_table_accessors` in the group accessor.
188
///
189
///  - For each table in the underlying group, the corresponding entry in
190
///    `Table::m_table_accessors` (at same index) is either null, or points to a
191
///    table accessor that satisfies all the "requirements for a table".
192
///
193
/// Requirements for a table:
194
///
195
///  - The corresponding underlying table has independent descriptor if, and
196
///    only if `Table::m_top` is attached.
197
///
198
///  - The row index of every row accessor is strictly less than the number of
199
///    rows in the underlying table.
200
///
201
///  - If `Table::m_columns` is unattached (degenerate table), then
202
///    `Table::m_cols` is empty, otherwise the number of columns in the
203
///    underlying table is equal to the number of entries in `Table::m_cols`.
204
///
205
///  - Each entry in `Table::m_cols` is either null, or points to a column
206
///    accessor whose type agrees with the data type (realm::DataType) of the
207
///    corresponding underlying column (at same index).
208
///
209
///  - If a column accessor is of type `StringEnumColumn`, then the
210
///    corresponding underlying column must be an enumerated strings column (the
211
///    reverse is not required).
212
///
213
///  - If a column accessor is equipped with a search index accessor, then the
214
///    corresponding underlying column must be equipped with a search index (the
215
///    reverse is not required).
216
///
217
///  - For each entry in the subtable map of a column accessor there must be an
218
///    underlying subtable at column `i` and row `j`, where `i` is the index of
219
///    the column accessor in `Table::m_cols`, and `j` is the value of
220
///    `SubtableColumnBase::SubtableMap::entry::m_subtable_ndx`. The
221
///    corresponding subtable accessor must satisfy all the "requirements for a
222
///    table" with respect to that underlying subtable.
223
///
224
///  - It the table refers to a descriptor accessor (only possible for tables
225
///    with independent descriptor), then that descriptor accessor must satisfy
226
///    all the "requirements for a descriptor" with respect to the underlying
227
///    spec structure (of this table).
228
///
229
/// Requirements for a descriptor:
230
///
231
///  - For each entry in the subdescriptor map there must be an underlying
232
///    subspec at column `i`, where `i` is the value of
233
///    `Descriptor::subdesc_entry::m_column_ndx`. The corresponding
234
///    subdescriptor accessor must satisfy all the "requirements for a
235
///    descriptor" with respect to that underlying subspec.
236
///
237
/// The 'ndx_in_parent' property of most array accessors is required to be
238
/// valid. The exceptions are:
239
///
240
///  - The top array accessor of root tables (Table::m_top). Root tables are
241
///    tables with independent descriptor.
242
///
243
///  - The columns array accessor of subtables with shared descriptor
244
///    (Table::m_columns).
245
///
246
///  - The top array accessor of spec objects of subtables with shared
247
///    descriptor (Table::m_spec.m_top).
248
///
249
///  - The root array accessor of table level columns
250
///    (*Table::m_cols[]->m_array).
251
///
252
///  - The root array accessor of the subcolumn of unique strings in an
253
///    enumerated string column (*StringEnumColumn::m_keys.m_array).
254
///
255
///  - The root array accessor of search indexes
256
///    (*Table::m_cols[]->m_index->m_array).
257
///
258
/// Note that Structural Correspondence trivially includes Minimal Consistency,
259
/// since the latter it an invariant.
260

261

262
using namespace realm;
263
using namespace realm::util;
264

265
Replication* Table::g_dummy_replication = nullptr;
266

267
bool TableVersions::operator==(const TableVersions& other) const
268
{
16,191✔
269
    if (size() != other.size())
16,191✔
270
        return false;
×
271
    size_t sz = size();
16,191✔
272
    for (size_t i = 0; i < sz; i++) {
25,722✔
273
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
16,323✔
274
        if (this->at(i).second != other.at(i).second)
16,323✔
275
            return false;
6,792✔
276
    }
16,323✔
277
    return true;
12,795✔
278
}
16,191✔
279

280
namespace realm {
281
const char* get_data_type_name(DataType type) noexcept
282
{
650,241✔
283
    switch (type) {
650,241✔
284
        case type_Int:
1,428✔
285
            return "int";
1,428✔
286
        case type_Bool:
540✔
287
            return "bool";
540✔
288
        case type_Float:
768✔
289
            return "float";
768✔
290
        case type_Double:
930✔
291
            return "double";
930✔
292
        case type_String:
804✔
293
            return "string";
804✔
294
        case type_Binary:
342✔
295
            return "binary";
342✔
296
        case type_Timestamp:
738✔
297
            return "timestamp";
738✔
298
        case type_ObjectId:
642,996✔
299
            return "objectId";
642,996✔
300
        case type_Decimal:
810✔
301
            return "decimal128";
810✔
302
        case type_UUID:
738✔
303
            return "uuid";
738✔
304
        case type_Mixed:
6✔
305
            return "mixed";
6✔
306
        case type_Link:
48✔
307
            return "link";
48✔
308
        case type_LinkList:
✔
309
            return "linklist";
×
310
        case type_TypedLink:
60✔
311
            return "typedLink";
60✔
312
        default:
36✔
313
            if (type == type_TypeOfValue)
36✔
314
                return "@type";
24✔
315
#if REALM_ENABLE_GEOSPATIAL
12✔
316
            else if (type == type_Geospatial)
12✔
317
                return "geospatial";
6✔
318
#endif
6✔
319
            else if (type == ColumnTypeTraits<null>::id)
6✔
320
                return "null";
6✔
321
    }
×
322
    return "unknown";
×
323
}
×
324

325
std::ostream& operator<<(std::ostream& o, Table::Type table_type)
326
{
119,718✔
327
    switch (table_type) {
119,718✔
328
        case Table::Type::TopLevel:
100,503✔
329
            return o << "TopLevel";
100,503✔
330
        case Table::Type::Embedded:
19,131✔
331
            return o << "Embedded";
19,131✔
332
        case Table::Type::TopLevelAsymmetric:
84✔
333
            return o << "TopLevelAsymmetric";
84✔
334
    }
×
335
    return o << "Invalid table type: " << uint8_t(table_type);
×
336
}
×
337
} // namespace realm
338

339
bool LinkChain::add(ColKey ck)
340
{
799,053✔
341
    // Link column can be a single Link, LinkList, or BackLink.
399,495✔
342
    REALM_ASSERT(m_current_table->valid_column(ck));
799,053✔
343
    ColumnType type = ck.get_type();
799,053✔
344
    if (type == col_type_LinkList || type == col_type_Link || type == col_type_BackLink) {
799,053✔
345
        m_current_table = m_current_table->get_opposite_table(ck);
87,990✔
346
        m_link_cols.push_back(ck);
87,990✔
347
        return true;
87,990✔
348
    }
87,990✔
349
    return false;
711,063✔
350
}
711,063✔
351

352
// -- Table ---------------------------------------------------------------------------------
353

354
Table::Table(Allocator& alloc)
355
    : m_alloc(alloc)
356
    , m_top(m_alloc)
357
    , m_spec(m_alloc)
358
    , m_clusters(this, m_alloc, top_position_for_cluster_tree)
359
    , m_index_refs(m_alloc)
360
    , m_opposite_table(m_alloc)
361
    , m_opposite_column(m_alloc)
362
    , m_repl(&g_dummy_replication)
363
    , m_own_ref(this, alloc.get_instance_version())
364
{
3,546✔
365
    m_spec.set_parent(&m_top, top_position_for_spec);
3,546✔
366
    m_index_refs.set_parent(&m_top, top_position_for_search_indexes);
3,546✔
367
    m_opposite_table.set_parent(&m_top, top_position_for_opposite_table);
3,546✔
368
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
3,546✔
369

1,773✔
370
    ref_type ref = create_empty_table(m_alloc); // Throws
3,546✔
371
    ArrayParent* parent = nullptr;
3,546✔
372
    size_t ndx_in_parent = 0;
3,546✔
373
    init(ref, parent, ndx_in_parent, true, false);
3,546✔
374
}
3,546✔
375

376
Table::Table(Replication* const* repl, Allocator& alloc)
377
    : m_alloc(alloc)
378
    , m_top(m_alloc)
379
    , m_spec(m_alloc)
380
    , m_clusters(this, m_alloc, top_position_for_cluster_tree)
381
    , m_index_refs(m_alloc)
382
    , m_opposite_table(m_alloc)
383
    , m_opposite_column(m_alloc)
384
    , m_repl(repl)
385
    , m_own_ref(this, alloc.get_instance_version())
386
{
21,261✔
387
    m_spec.set_parent(&m_top, top_position_for_spec);
21,261✔
388
    m_index_refs.set_parent(&m_top, top_position_for_search_indexes);
21,261✔
389
    m_opposite_table.set_parent(&m_top, top_position_for_opposite_table);
21,261✔
390
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
21,261✔
391
    m_cookie = cookie_created;
21,261✔
392
}
21,261✔
393

394
ColKey Table::add_column(DataType type, StringData name, bool nullable, std::optional<CollectionType> collection_type,
395
                         DataType key_type)
396
{
686,979✔
397
    REALM_ASSERT(!is_link_type(ColumnType(type)));
686,979✔
398
    if (type == type_TypedLink) {
686,979✔
UNCOV
399
        throw IllegalOperation("TypedLink properties not yet supported");
×
UNCOV
400
    }
×
401

338,775✔
402
    ColumnAttrMask attr;
686,979✔
403
    if (collection_type) {
686,979✔
404
        switch (*collection_type) {
175,938✔
405
            case CollectionType::List:
80,733✔
406
                attr.set(col_attr_List);
80,733✔
407
                break;
80,733✔
408
            case CollectionType::Set:
52,719✔
409
                attr.set(col_attr_Set);
52,719✔
410
                break;
52,719✔
411
            case CollectionType::Dictionary:
42,486✔
412
                attr.set(col_attr_Dictionary);
42,486✔
413
                break;
42,486✔
414
        }
686,979✔
415
    }
686,979✔
416
    if (nullable || type == type_Mixed)
686,979✔
417
        attr.set(col_attr_Nullable);
186,414✔
418
    ColKey col_key = generate_col_key(ColumnType(type), attr);
686,979✔
419

338,775✔
420
    Table* invalid_link = nullptr;
686,979✔
421
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
686,979✔
422
}
686,979✔
423

424
ColKey Table::add_column(Table& target, StringData name, std::optional<CollectionType> collection_type,
425
                         DataType key_type)
426
{
81,093✔
427
    // Both origin and target must be group-level tables, and in the same group.
40,047✔
428
    Group* origin_group = get_parent_group();
81,093✔
429
    Group* target_group = target.get_parent_group();
81,093✔
430
    REALM_ASSERT_RELEASE(origin_group && target_group);
81,093✔
431
    REALM_ASSERT_RELEASE(origin_group == target_group);
81,093✔
432
    // Incoming links from an asymmetric table are not allowed.
40,047✔
433
    if (target.is_asymmetric()) {
81,093✔
434
        throw IllegalOperation("Ephemeral objects not supported");
6✔
435
    }
6✔
436

40,044✔
437
    m_has_any_embedded_objects.reset();
81,087✔
438

40,044✔
439
    DataType data_type = type_Link;
81,087✔
440
    ColumnAttrMask attr;
81,087✔
441
    if (collection_type) {
81,087✔
442
        switch (*collection_type) {
51,690✔
443
            case CollectionType::List:
30,312✔
444
                attr.set(col_attr_List);
30,312✔
445
                data_type = type_LinkList;
30,312✔
446
                break;
30,312✔
447
            case CollectionType::Set:
10,440✔
448
                attr.set(col_attr_Set);
10,440✔
449
                break;
10,440✔
450
            case CollectionType::Dictionary:
10,938✔
451
                attr.set(col_attr_Dictionary);
10,938✔
452
                attr.set(col_attr_Nullable);
10,938✔
453
                break;
10,938✔
454
        }
29,397✔
455
    }
29,397✔
456
    else {
29,397✔
457
        attr.set(col_attr_Nullable);
29,397✔
458
    }
29,397✔
459
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
81,087✔
460

40,044✔
461
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
81,087✔
462
}
81,087✔
463

464
void Table::remove_recursive(CascadeState& cascade_state)
465
{
14,586✔
466
    Group* group = get_parent_group();
14,586✔
467
    REALM_ASSERT(group);
14,586✔
468
    cascade_state.m_group = group;
14,586✔
469

6,642✔
470
    do {
18,618✔
471
        cascade_state.send_notifications();
18,618✔
472

8,658✔
473
        for (auto& l : cascade_state.m_to_be_nullified) {
8,682✔
474
            Obj obj = group->get_table(l.origin_table)->try_get_object(l.origin_key);
48✔
475
            REALM_ASSERT_DEBUG(obj);
48✔
476
            if (obj) {
48✔
477
                std::move(obj).nullify_link(l.origin_col_key, l.old_target_link);
48✔
478
            }
48✔
479
        }
48✔
480
        cascade_state.m_to_be_nullified.clear();
18,618✔
481

8,658✔
482
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
18,618✔
483
        for (auto obj : to_delete) {
16,614✔
484
            auto table = group->get_table(obj.first);
15,906✔
485
            // This might add to the list of objects that should be deleted
7,950✔
486
            REALM_ASSERT(!obj.second.is_unresolved());
15,906✔
487
            table->m_clusters.erase(obj.second, cascade_state);
15,906✔
488
        }
15,906✔
489
        nullify_links(cascade_state);
18,618✔
490
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
18,618✔
491
}
14,586✔
492

493
void Table::nullify_links(CascadeState& cascade_state)
494
{
23,193✔
495
    Group* group = get_parent_group();
23,193✔
496
    REALM_ASSERT(group);
23,193✔
497
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
14,904✔
498
        auto table = group->get_table(to_delete.first);
7,899✔
499
        table->m_clusters.nullify_links(to_delete.second, cascade_state);
7,899✔
500
    }
7,899✔
501
}
23,193✔
502

503
CollectionType Table::get_collection_type(ColKey col_key) const
504
{
7,932✔
505
    if (col_key.is_list()) {
7,932✔
506
        return CollectionType::List;
648✔
507
    }
648✔
508
    if (col_key.is_set()) {
7,284✔
509
        return CollectionType::Set;
1,056✔
510
    }
1,056✔
511
    REALM_ASSERT(col_key.is_dictionary());
6,228✔
512
    return CollectionType::Dictionary;
6,228✔
513
}
6,228✔
514

515
void Table::remove_column(ColKey col_key)
516
{
17,220✔
517
    check_column(col_key);
17,220✔
518

8,673✔
519
    if (Replication* repl = get_repl())
17,220✔
520
        repl->erase_column(this, col_key); // Throws
519✔
521

8,673✔
522
    if (col_key == m_primary_key_col) {
17,220✔
523
        do_set_primary_key_column(ColKey());
7,557✔
524
    }
7,557✔
525
    else {
9,663✔
526
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
9,663✔
527
    }
9,663✔
528

8,673✔
529
    erase_root_column(col_key); // Throws
17,220✔
530
    m_has_any_embedded_objects.reset();
17,220✔
531
}
17,220✔
532

533

534
void Table::rename_column(ColKey col_key, StringData name)
535
{
90✔
536
    check_column(col_key);
90✔
537

48✔
538
    auto col_ndx = colkey2spec_ndx(col_key);
90✔
539
    m_spec.rename_column(col_ndx, name); // Throws
90✔
540

48✔
541
    bump_content_version();
90✔
542
    bump_storage_version();
90✔
543

48✔
544
    if (Replication* repl = get_repl())
90✔
545
        repl->rename_column(this, col_key, name); // Throws
90✔
546
}
90✔
547

548

549
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
550
{
9,841,371✔
551
    // well, not quite "direct", more like "almost direct":
5,172,108✔
552
    Array table_top(alloc);
9,841,371✔
553
    table_top.init_from_ref(top_ref);
9,841,371✔
554
    if (table_top.size() > 3) {
9,841,371✔
555
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
9,840,435✔
556
        return TableKey(int32_t(rot.get_as_int()));
9,840,435✔
557
    }
9,840,435✔
558
    else {
936✔
559
        return TableKey();
936✔
560
    }
936✔
561
}
9,841,371✔
562

563

564
void Table::init(ref_type top_ref, ArrayParent* parent, size_t ndx_in_parent, bool is_writable, bool is_frzn)
565
{
5,032,674✔
566
    REALM_ASSERT(!(is_writable && is_frzn));
5,032,674✔
567
    m_is_frozen = is_frzn;
5,032,674✔
568
    m_alloc.set_read_only(!is_writable);
5,032,674✔
569
    // Load from allocated memory
2,739,915✔
570
    m_top.set_parent(parent, ndx_in_parent);
5,032,674✔
571
    m_top.init_from_ref(top_ref);
5,032,674✔
572

2,739,915✔
573
    m_spec.init_from_parent();
5,032,674✔
574

2,739,915✔
575
    while (m_top.size() <= top_position_for_pk_col) {
5,032,674✔
UNCOV
576
        m_top.add(0);
×
UNCOV
577
    }
×
578

2,739,915✔
579
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
5,032,674✔
580
        // This is an upgrade - create cluster
UNCOV
581
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
×
UNCOV
582
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
×
UNCOV
583
    }
×
584
    m_clusters.init_from_parent();
5,032,674✔
585

2,739,915✔
586
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
5,032,674✔
587
    if (!rot.is_tagged()) {
5,032,674✔
588
        // Create table key
UNCOV
589
        rot = RefOrTagged::make_tagged(ndx_in_parent);
×
UNCOV
590
        m_top.set(top_position_for_key, rot);
×
UNCOV
591
    }
×
592
    m_key = TableKey(int32_t(rot.get_as_int()));
5,032,674✔
593

2,739,915✔
594
    // index setup relies on column mapping being up to date:
2,739,915✔
595
    build_column_mapping();
5,032,674✔
596
    if (m_top.get_as_ref(top_position_for_search_indexes) == 0) {
5,032,674✔
597
        // This is an upgrade - create the necessary arrays
UNCOV
598
        bool context_flag = false;
×
UNCOV
599
        size_t nb_columns = m_spec.get_column_count();
×
UNCOV
600
        MemRef mem = Array::create_array(Array::type_HasRefs, context_flag, nb_columns, 0, m_top.get_alloc());
×
UNCOV
601
        m_index_refs.init_from_mem(mem);
×
UNCOV
602
        m_index_refs.update_parent();
×
UNCOV
603
        mem = Array::create_array(Array::type_Normal, context_flag, nb_columns, TableKey().value, m_top.get_alloc());
×
UNCOV
604
        m_opposite_table.init_from_mem(mem);
×
UNCOV
605
        m_opposite_table.update_parent();
×
UNCOV
606
        mem = Array::create_array(Array::type_Normal, context_flag, nb_columns, ColKey().value, m_top.get_alloc());
×
UNCOV
607
        m_opposite_column.init_from_mem(mem);
×
UNCOV
608
        m_opposite_column.update_parent();
×
UNCOV
609
    }
×
610
    else {
5,032,674✔
611
        m_opposite_table.init_from_parent();
5,032,674✔
612
        m_opposite_column.init_from_parent();
5,032,674✔
613
        m_index_refs.init_from_parent();
5,032,674✔
614
        m_index_accessors.resize(m_index_refs.size());
5,032,674✔
615
    }
5,032,674✔
616
    if (!m_top.get_as_ref_or_tagged(top_position_for_column_key).is_tagged()) {
5,032,674✔
UNCOV
617
        m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(0));
×
UNCOV
618
    }
×
619
    auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
5,032,674✔
620
    if (!rot_version.is_tagged()) {
5,032,674✔
UNCOV
621
        m_top.set(top_position_for_version, RefOrTagged::make_tagged(0));
×
UNCOV
622
        m_in_file_version_at_transaction_boundary = 0;
×
UNCOV
623
    }
×
624
    else
5,032,674✔
625
        m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
5,032,674✔
626

2,739,915✔
627
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
5,032,674✔
628
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
4,499,640✔
629

2,739,915✔
630
    if (m_top.size() <= top_position_for_flags) {
5,032,674✔
631
        m_table_type = Type::TopLevel;
60✔
632
    }
60✔
633
    else {
5,032,614✔
634
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
5,032,614✔
635
        m_table_type = Type(flags & table_type_mask);
5,032,614✔
636
    }
5,032,614✔
637
    m_has_any_embedded_objects.reset();
5,032,674✔
638

2,739,915✔
639
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
5,032,674✔
640
        // Tombstones exists
412,950✔
641
        if (!m_tombstones) {
830,097✔
642
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
518,613✔
643
        }
518,613✔
644
        m_tombstones->init_from_parent();
830,097✔
645
    }
830,097✔
646
    else {
4,202,577✔
647
        m_tombstones = nullptr;
4,202,577✔
648
    }
4,202,577✔
649
    m_cookie = cookie_initialized;
5,032,674✔
650
}
5,032,674✔
651

652

653
ColKey Table::do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table, DataType key_type)
654
{
768,063✔
655
    col_key = do_insert_root_column(col_key, ColumnType(type), name, key_type); // Throws
768,063✔
656

378,816✔
657
    // When the inserted column is a link-type column, we must also add a
378,816✔
658
    // backlink column to the target table.
378,816✔
659

378,816✔
660
    if (target_table) {
768,063✔
661
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
81,081✔
662
        target_table->check_column(backlink_col_key);
81,081✔
663

40,041✔
664
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
81,081✔
665
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
81,081✔
666
    }
81,081✔
667

378,816✔
668
    if (Replication* repl = get_repl())
768,063✔
669
        repl->insert_column(this, col_key, type, name, target_table); // Throws
750,546✔
670

378,816✔
671
    return col_key;
768,063✔
672
}
768,063✔
673

674
template <typename Type>
675
void do_bulk_insert_index(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
676
{
134,307✔
677
    using LeafType = typename ColumnTypeTraits<Type>::cluster_leaf_type;
134,307✔
678
    LeafType leaf(alloc);
134,307✔
679

66,405✔
680
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
139,395✔
681
        cluster->init_leaf(col_key, &leaf);
139,395✔
682
        index->insert_bulk(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
139,395✔
683
        return IteratorControl::AdvanceToNext;
139,395✔
684
    };
139,395✔
685

66,405✔
686
    table->traverse_clusters(f);
134,307✔
687
}
134,307✔
688

689
void Table::populate_search_index(ColKey col_key)
690
{
134,310✔
691
    auto col_ndx = col_key.get_index().val;
134,310✔
692
    SearchIndex* index = m_index_accessors[col_ndx].get();
134,310✔
693
    DataType type = get_column_type(col_key);
134,310✔
694

66,408✔
695
    if (type == type_Int) {
134,310✔
696
        if (is_nullable(col_key)) {
73,803✔
697
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
10,797✔
698
        }
10,797✔
699
        else {
63,006✔
700
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
63,006✔
701
        }
63,006✔
702
    }
73,803✔
703
    else if (type == type_Bool) {
60,507✔
704
        if (is_nullable(col_key)) {
48✔
705
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
706
        }
24✔
707
        else {
24✔
708
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
24✔
709
        }
24✔
710
    }
48✔
711
    else if (type == type_String) {
60,459✔
712
        do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
21,147✔
713
    }
21,147✔
714
    else if (type == type_Timestamp) {
39,312✔
715
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
90✔
716
    }
90✔
717
    else if (type == type_ObjectId) {
39,222✔
718
        if (is_nullable(col_key)) {
37,647✔
719
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
948✔
720
        }
948✔
721
        else {
36,699✔
722
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
36,699✔
723
        }
36,699✔
724
    }
37,647✔
725
    else if (type == type_UUID) {
1,575✔
726
        if (is_nullable(col_key)) {
678✔
727
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
728
        }
516✔
729
        else {
162✔
730
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
162✔
731
        }
162✔
732
    }
678✔
733
    else if (type == type_Mixed) {
897✔
734
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
894✔
735
    }
894✔
736
    else {
3✔
737
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
3✔
738
    }
3✔
739
}
134,310✔
740

741
void Table::erase_from_search_indexes(ObjKey key)
742
{
5,088,981✔
743
    // Tombstones do not use index - will crash if we try to erase values
2,544,378✔
744
    if (!key.is_unresolved()) {
5,088,981✔
745
        for (auto&& index : m_index_accessors) {
6,828,543✔
746
            if (index) {
6,828,543✔
747
                index->erase(key);
343,173✔
748
            }
343,173✔
749
        }
6,828,543✔
750
    }
5,075,820✔
751
}
5,088,981✔
752

753
void Table::update_indexes(ObjKey key, const FieldValues& values)
754
{
23,018,271✔
755
    // Tombstones do not use index - will crash if we try to insert values
11,560,032✔
756
    if (key.is_unresolved()) {
23,018,271✔
757
        return;
28,695✔
758
    }
28,695✔
759

11,545,731✔
760
    auto sz = m_index_accessors.size();
22,989,576✔
761
    // values are sorted by column index - there may be values missing
11,545,731✔
762
    auto value = values.begin();
22,989,576✔
763
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
56,776,845✔
764
        // Check if initial value is provided
16,969,776✔
765
        Mixed init_value;
33,787,350✔
766
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
33,787,350✔
767
            // Value for this column is provided
267,204✔
768
            init_value = value->value;
557,502✔
769
            ++value;
557,502✔
770
        }
557,502✔
771

16,969,776✔
772
        if (auto&& index = m_index_accessors[column_ndx]) {
33,787,350✔
773
            // There is an index for this column
552,891✔
774
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,128,906✔
775
            auto type = col_key.get_type();
1,128,906✔
776
            auto attr = col_key.get_attrs();
1,128,906✔
777
            bool nullable = attr.test(col_attr_Nullable);
1,128,906✔
778
            switch (type) {
1,128,906✔
779
                case col_type_Int:
531,951✔
780
                    if (init_value.is_null()) {
531,951✔
781
                        index->insert(key, ArrayIntNull::default_value(nullable));
165,510✔
782
                    }
165,510✔
783
                    else {
366,441✔
784
                        index->insert(key, init_value.get<int64_t>());
366,441✔
785
                    }
366,441✔
786
                    break;
531,951✔
787
                case col_type_Bool:
6,000✔
788
                    if (init_value.is_null()) {
6,000✔
789
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,000✔
790
                    }
6,000✔
UNCOV
791
                    else {
×
UNCOV
792
                        index->insert(key, init_value.get<bool>());
×
UNCOV
793
                    }
×
794
                    break;
6,000✔
795
                case col_type_String:
478,917✔
796
                    if (init_value.is_null()) {
478,917✔
797
                        index->insert(key, ArrayString::default_value(nullable));
431,169✔
798
                    }
431,169✔
799
                    else {
47,748✔
800
                        index->insert(key, init_value.get<String>());
47,748✔
801
                    }
47,748✔
802
                    break;
478,917✔
803
                case col_type_Timestamp:
6,207✔
804
                    if (init_value.is_null()) {
6,207✔
805
                        index->insert(key, ArrayTimestamp::default_value(nullable));
6,207✔
806
                    }
6,207✔
UNCOV
807
                    else {
×
UNCOV
808
                        index->insert(key, init_value.get<Timestamp>());
×
UNCOV
809
                    }
×
810
                    break;
6,207✔
811
                case col_type_ObjectId:
86,739✔
812
                    if (init_value.is_null()) {
86,739✔
813
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
814
                    }
6,120✔
815
                    else {
80,619✔
816
                        index->insert(key, init_value.get<ObjectId>());
80,619✔
817
                    }
80,619✔
818
                    break;
86,739✔
819
                case col_type_Mixed:
834✔
820
                    index->insert(key, init_value);
834✔
821
                    break;
834✔
822
                case col_type_UUID:
18,342✔
823
                    if (init_value.is_null()) {
18,342✔
824
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
6,138✔
825
                    }
6,138✔
826
                    else {
12,204✔
827
                        index->insert(key, init_value.get<UUID>());
12,204✔
828
                    }
12,204✔
829
                    break;
18,342✔
UNCOV
830
                default:
✔
UNCOV
831
                    REALM_UNREACHABLE();
×
832
            }
1,128,906✔
833
        }
1,128,906✔
834
    }
33,787,350✔
835
}
22,989,576✔
836

837
void Table::clear_indexes()
838
{
3,522✔
839
    for (auto&& index : m_index_accessors) {
42,885✔
840
        if (index) {
42,885✔
841
            index->clear();
2,262✔
842
        }
2,262✔
843
    }
42,885✔
844
}
3,522✔
845

846
void Table::do_add_search_index(ColKey col_key, IndexType type)
847
{
134,511✔
848
    size_t column_ndx = col_key.get_index().val;
134,511✔
849

66,510✔
850
    // Early-out if already indexed
66,510✔
851
    if (m_index_accessors[column_ndx] != nullptr)
134,511✔
852
        return;
150✔
853

66,435✔
854
    if (!StringIndex::type_supported(DataType(col_key.get_type())) || col_key.is_collection() ||
134,361✔
855
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
134,337✔
856
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
24✔
857
        // it should probably be a type mismatch exception instead.
24✔
858
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
48✔
859
    }
48✔
860

66,411✔
861
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
66,411✔
862
    // index have 0-entries.
66,411✔
863
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
134,313✔
864
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
134,313✔
865

66,411✔
866
    // Create the index
66,411✔
867
    m_index_accessors[column_ndx] =
134,313✔
868
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
134,313✔
869
    SearchIndex* index = m_index_accessors[column_ndx].get();
134,313✔
870
    // Insert ref to index
66,411✔
871
    index->set_parent(&m_index_refs, column_ndx);
134,313✔
872

66,411✔
873
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
134,313✔
874

66,411✔
875
    populate_search_index(col_key);
134,313✔
876
}
134,313✔
877

878
void Table::add_search_index(ColKey col_key, IndexType type)
879
{
3,876✔
880
    check_column(col_key);
3,876✔
881

1,935✔
882
    // Check spec
1,935✔
883
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,876✔
884
    auto attr = m_spec.get_column_attr(spec_ndx);
3,876✔
885

1,935✔
886
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,876✔
887
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
888

1,932✔
889
    switch (type) {
3,870✔
UNCOV
890
        case IndexType::None:
✔
UNCOV
891
            remove_search_index(col_key);
×
UNCOV
892
            return;
×
893
        case IndexType::Fulltext:
54✔
894
            // Early-out if already indexed
27✔
895
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
UNCOV
896
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
UNCOV
897
                return;
×
UNCOV
898
            }
×
899
            if (attr.test(col_attr_Indexed)) {
54✔
UNCOV
900
                this->remove_search_index(col_key);
×
UNCOV
901
            }
×
902
            break;
54✔
903
        case IndexType::General:
3,816✔
904
            if (attr.test(col_attr_Indexed)) {
3,816✔
905
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
24✔
906
                return;
24✔
907
            }
24✔
908
            if (attr.test(col_attr_FullText_Indexed)) {
3,792✔
909
                this->remove_search_index(col_key);
×
910
            }
×
911
            break;
3,792✔
912
    }
3,846✔
913

1,920✔
914
    do_add_search_index(col_key, type);
3,846✔
915

1,920✔
916
    // Update spec
1,920✔
917
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,819✔
918
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,846✔
919
}
3,846✔
920

921
void Table::remove_search_index(ColKey col_key)
922
{
8,001✔
923
    check_column(col_key);
8,001✔
924
    auto column_ndx = col_key.get_index();
8,001✔
925

3,951✔
926
    // Early-out if non-indexed
3,951✔
927
    if (m_index_accessors[column_ndx.val] == nullptr)
8,001✔
928
        return;
66✔
929

3,912✔
930
    // Destroy and remove the index column
3,912✔
931
    auto& index = m_index_accessors[column_ndx.val];
7,935✔
932
    REALM_ASSERT(index != nullptr);
7,935✔
933
    index->destroy();
7,935✔
934
    index.reset();
7,935✔
935

3,912✔
936
    m_index_refs.set(column_ndx.val, 0);
7,935✔
937

3,912✔
938
    // update spec
3,912✔
939
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
7,935✔
940
    auto attr = m_spec.get_column_attr(spec_ndx);
7,935✔
941
    attr.reset(col_attr_Indexed);
7,935✔
942
    attr.reset(col_attr_FullText_Indexed);
7,935✔
943
    m_spec.set_column_attr(spec_ndx, attr); // Throws
7,935✔
944
}
7,935✔
945

946
void Table::enumerate_string_column(ColKey col_key)
947
{
1,290✔
948
    check_column(col_key);
1,290✔
949
    size_t column_ndx = colkey2spec_ndx(col_key);
1,290✔
950
    ColumnType type = col_key.get_type();
1,290✔
951
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,290✔
952
        m_clusters.enumerate_string_column(col_key);
690✔
953
    }
690✔
954
}
1,290✔
955

956
bool Table::is_enumerated(ColKey col_key) const noexcept
957
{
58,707✔
958
    size_t col_ndx = colkey2spec_ndx(col_key);
58,707✔
959
    return m_spec.is_string_enum_type(col_ndx);
58,707✔
960
}
58,707✔
961

962
size_t Table::get_num_unique_values(ColKey col_key) const
963
{
138✔
964
    if (!is_enumerated(col_key))
138✔
965
        return 0;
84✔
966

27✔
967
    ArrayParent* parent;
54✔
968
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
969
    BPlusTree<StringData> col(get_alloc());
54✔
970
    col.init_from_ref(ref);
54✔
971

27✔
972
    return col.size();
54✔
973
}
54✔
974

975

976
void Table::erase_root_column(ColKey col_key)
977
{
17,496✔
978
    ColumnType col_type = col_key.get_type();
17,496✔
979
    if (is_link_type(col_type)) {
17,496✔
980
        auto target_table = get_opposite_table(col_key);
234✔
981
        auto target_column = get_opposite_column(col_key);
234✔
982
        target_table->do_erase_root_column(target_column);
234✔
983
    }
234✔
984
    do_erase_root_column(col_key); // Throws
17,496✔
985
}
17,496✔
986

987

988
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
989
{
986,592✔
990
    // if col_key specifies a key, it must be unused
486,837✔
991
    REALM_ASSERT(!col_key || !valid_column(col_key));
986,592✔
992

486,837✔
993
    // locate insertion point: ordinary columns must come before backlink columns
486,837✔
994
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
942,147✔
995

486,837✔
996
    if (!col_key) {
986,592✔
997
        col_key = generate_col_key(type, {});
87,891✔
998
    }
87,891✔
999

486,837✔
1000
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
986,592✔
1001
    if (col_key.is_dictionary()) {
986,592✔
1002
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
53,424✔
1003
    }
53,424✔
1004
    auto col_ndx = col_key.get_index().val;
986,592✔
1005
    build_column_mapping();
986,592✔
1006
    REALM_ASSERT(col_ndx <= m_index_refs.size());
986,592✔
1007
    if (col_ndx == m_index_refs.size()) {
986,592✔
1008
        m_index_refs.insert(col_ndx, 0);
986,346✔
1009
    }
986,346✔
1010
    else {
246✔
1011
        m_index_refs.set(col_ndx, 0);
246✔
1012
    }
246✔
1013
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
986,592✔
1014
    if (col_ndx == m_opposite_table.size()) {
986,592✔
1015
        // m_opposite_table and m_opposite_column are always resized together!
486,708✔
1016
        m_opposite_table.insert(col_ndx, TableKey().value);
986,337✔
1017
        m_opposite_column.insert(col_ndx, ColKey().value);
986,337✔
1018
    }
986,337✔
1019
    else {
255✔
1020
        m_opposite_table.set(col_ndx, TableKey().value);
255✔
1021
        m_opposite_column.set(col_ndx, ColKey().value);
255✔
1022
    }
255✔
1023
    refresh_index_accessors();
986,592✔
1024
    m_clusters.insert_column(col_key);
986,592✔
1025
    if (m_tombstones) {
986,592✔
1026
        m_tombstones->insert_column(col_key);
6,705✔
1027
    }
6,705✔
1028

486,837✔
1029
    bump_storage_version();
986,592✔
1030

486,837✔
1031
    return col_key;
986,592✔
1032
}
986,592✔
1033

1034

1035
void Table::do_erase_root_column(ColKey col_key)
1036
{
17,730✔
1037
    size_t col_ndx = col_key.get_index().val;
17,730✔
1038
    // If the column had a source index we have to remove and destroy that as well
8,931✔
1039
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
17,730✔
1040
    if (index_ref) {
17,730✔
1041
        Array::destroy_deep(index_ref, m_index_refs.get_alloc());
132✔
1042
        m_index_refs.set(col_ndx, 0);
132✔
1043
        m_index_accessors[col_ndx].reset();
132✔
1044
    }
132✔
1045
    m_opposite_table.set(col_ndx, TableKey().value);
17,730✔
1046
    m_opposite_column.set(col_ndx, ColKey().value);
17,730✔
1047
    m_index_accessors[col_ndx] = nullptr;
17,730✔
1048
    m_clusters.remove_column(col_key);
17,730✔
1049
    if (m_tombstones)
17,730✔
1050
        m_tombstones->remove_column(col_key);
6,234✔
1051
    size_t spec_ndx = colkey2spec_ndx(col_key);
17,730✔
1052
    m_spec.erase_column(spec_ndx);
17,730✔
1053
    m_top.adjust(top_position_for_column_key, 2);
17,730✔
1054

8,931✔
1055
    build_column_mapping();
17,730✔
1056
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
34,878✔
1057
        REALM_ASSERT(m_index_accessors.back() == nullptr);
17,148✔
1058
        m_index_accessors.pop_back();
17,148✔
1059
    }
17,148✔
1060
    bump_content_version();
17,730✔
1061
    bump_storage_version();
17,730✔
1062
}
17,730✔
1063

1064
Query Table::where(const DictionaryLinkValues& dictionary_of_links) const
1065
{
1,524✔
1066
    return Query(m_own_ref, dictionary_of_links);
1,524✔
1067
}
1,524✔
1068

1069
void Table::set_table_type(Type table_type, bool handle_backlinks)
1070
{
312✔
1071
    if (table_type == m_table_type) {
312✔
UNCOV
1072
        return;
×
UNCOV
1073
    }
×
1074

156✔
1075
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
UNCOV
1076
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
UNCOV
1077
                                                                   get_class_name(), m_table_type, table_type));
×
UNCOV
1078
    }
×
1079

156✔
1080
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1081
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1082
}
312✔
1083

1084
void Table::set_embedded(bool embedded, bool handle_backlinks)
1085
{
312✔
1086
    if (embedded == false) {
312✔
1087
        do_set_table_type(Type::TopLevel);
24✔
1088
        return;
24✔
1089
    }
24✔
1090

144✔
1091
    // Embedded objects cannot have a primary key.
144✔
1092
    if (get_primary_key_column()) {
288✔
1093
        throw IllegalOperation(
6✔
1094
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1095
    }
6✔
1096

141✔
1097
    if (size() == 0) {
282✔
1098
        do_set_table_type(Type::Embedded);
42✔
1099
        return;
42✔
1100
    }
42✔
1101

120✔
1102
    // Check all of the objects for invalid incoming links. Each embedded object
120✔
1103
    // must have exactly one incoming link, and it must be from a non-Mixed property.
120✔
1104
    // Objects with no incoming links are either deleted or an error (depending
120✔
1105
    // on `handle_backlinks`), and objects with multiple incoming links are either
120✔
1106
    // cloned for each of the incoming links or an error (again depending on `handle_backlinks`).
120✔
1107
    // Incoming links from a Mixed property are always an error, as those can't
120✔
1108
    // link to embedded objects
120✔
1109
    ArrayInteger leaf(get_alloc());
240✔
1110
    enum class LinkCount : int8_t { None, One, Multiple };
240✔
1111
    std::vector<LinkCount> incoming_link_count;
240✔
1112
    std::vector<ObjKey> orphans;
240✔
1113
    std::vector<ObjKey> multiple_incoming_links;
240✔
1114
    traverse_clusters([&](const Cluster* cluster) {
474✔
1115
        size_t size = cluster->node_size();
474✔
1116
        incoming_link_count.assign(size, LinkCount::None);
474✔
1117

237✔
1118
        for_each_backlink_column([&](ColKey col) {
606✔
1119
            cluster->init_leaf(col, &leaf);
606✔
1120
            // Width zero means all the values are zero and there can't be any backlinks
303✔
1121
            if (leaf.get_width() == 0) {
606✔
1122
                return IteratorControl::AdvanceToNext;
36✔
1123
            }
36✔
1124

285✔
1125
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1126
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1127
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1128
                    // ref of zero means there's no backlinks
29,670✔
1129
                    continue;
59,340✔
1130
                }
59,340✔
1131

480✔
1132
                if (value.is_ref()) {
960✔
1133
                    // Any other ref indicates an array of backlinks, which will
39✔
1134
                    // always have more than one entry
39✔
1135
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1136
                }
78✔
1137
                else {
882✔
1138
                    // Otherwise it's a tagged ref to the single linking object
441✔
1139
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1140
                        incoming_link_count[i] = LinkCount::One;
792✔
1141
                    }
792✔
1142
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1143
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1144
                    }
42✔
1145
                }
882✔
1146

480✔
1147
                auto source_col = get_opposite_column(col);
960✔
1148
                if (source_col.get_type() == col_type_Mixed) {
960✔
1149
                    auto source_table = get_opposite_table(col);
54✔
1150
                    throw IllegalOperation(util::format(
54✔
1151
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1152
                        "which does not support linking to embedded objects.",
54✔
1153
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1154
                }
54✔
1155
            }
960✔
1156
            return IteratorControl::AdvanceToNext;
543✔
1157
        });
570✔
1158

237✔
1159
        for (size_t i = 0; i < size; ++i) {
60,660✔
1160
            if (incoming_link_count[i] == LinkCount::None) {
60,240✔
1161
                if (!handle_backlinks) {
59,424✔
1162
                    throw IllegalOperation(util::format("Cannot convert '%1' to embedded: at least one object has no "
18✔
1163
                                                        "incoming links and would be deleted.",
18✔
1164
                                                        get_class_name()));
18✔
1165
                }
18✔
1166
                orphans.push_back(cluster->get_real_key(i));
59,406✔
1167
            }
59,406✔
1168
            else if (incoming_link_count[i] == LinkCount::Multiple) {
816✔
1169
                if (!handle_backlinks) {
90✔
1170
                    throw IllegalOperation(util::format(
36✔
1171
                        "Cannot convert '%1' to embedded: at least one object has more than one incoming link.",
36✔
1172
                        get_class_name()));
36✔
1173
                }
36✔
1174
                multiple_incoming_links.push_back(cluster->get_real_key(i));
54✔
1175
            }
54✔
1176
        }
60,240✔
1177

237✔
1178
        return IteratorControl::AdvanceToNext;
447✔
1179
    });
474✔
1180

120✔
1181
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
120✔
1182
    for (auto key : orphans) {
59,406✔
1183
        remove_object(key);
59,406✔
1184
    }
59,406✔
1185
    for (auto key : multiple_incoming_links) {
147✔
1186
        auto obj = get_object(key);
54✔
1187
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1188
        obj.remove();
54✔
1189
    }
54✔
1190

120✔
1191
    do_set_table_type(Type::Embedded);
240✔
1192
}
240✔
1193

1194
void Table::do_set_table_type(Type table_type)
1195
{
338,616✔
1196
    while (m_top.size() <= top_position_for_flags)
338,616✔
UNCOV
1197
        m_top.add(0);
×
1198

167,805✔
1199
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
338,616✔
1200
    // reset bits 0-1
167,805✔
1201
    flags &= ~table_type_mask;
338,616✔
1202
    // set table type
167,805✔
1203
    flags |= static_cast<uint8_t>(table_type);
338,616✔
1204
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
338,616✔
1205
    m_table_type = table_type;
338,616✔
1206
}
338,616✔
1207

1208

1209
void Table::detach(LifeCycleCookie cookie) noexcept
1210
{
5,021,508✔
1211
    m_cookie = cookie;
5,021,508✔
1212
    m_alloc.bump_instance_version();
5,021,508✔
1213
}
5,021,508✔
1214

1215
void Table::fully_detach() noexcept
1216
{
5,011,797✔
1217
    m_spec.detach();
5,011,797✔
1218
    m_top.detach();
5,011,797✔
1219
    m_index_refs.detach();
5,011,797✔
1220
    m_opposite_table.detach();
5,011,797✔
1221
    m_opposite_column.detach();
5,011,797✔
1222
    m_index_accessors.clear();
5,011,797✔
1223
}
5,011,797✔
1224

1225

1226
Table::~Table() noexcept
1227
{
3,546✔
1228
    if (m_top.is_attached()) {
3,546✔
1229
        // If destroyed as a standalone table, destroy all memory allocated
1,773✔
1230
        if (m_top.get_parent() == nullptr) {
3,546✔
1231
            m_top.destroy_deep();
3,546✔
1232
        }
3,546✔
1233
        fully_detach();
3,546✔
1234
    }
3,546✔
UNCOV
1235
    else {
×
UNCOV
1236
        REALM_ASSERT(m_index_accessors.size() == 0);
×
UNCOV
1237
    }
×
1238
    m_cookie = cookie_deleted;
3,546✔
1239
}
3,546✔
1240

1241

1242
IndexType Table::search_index_type(ColKey col_key) const noexcept
1243
{
8,057,157✔
1244
    if (m_index_accessors[col_key.get_index().val].get()) {
8,057,157✔
1245
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
1,173,375✔
1246
        bool fulltext = attr.test(col_attr_FullText_Indexed);
1,173,375✔
1247
        return fulltext ? IndexType::Fulltext : IndexType::General;
1,173,165✔
1248
    }
1,173,375✔
1249
    return IndexType::None;
6,883,782✔
1250
}
6,883,782✔
1251

1252

1253
void Table::migrate_sets_and_dictionaries()
1254
{
180✔
1255
    std::vector<ColKey> to_migrate;
180✔
1256
    for (auto col : get_column_keys()) {
570✔
1257
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
1258
            to_migrate.push_back(col);
12✔
1259
        }
12✔
1260
    }
570✔
1261
    if (to_migrate.size()) {
180✔
1262
        for (auto obj : *this) {
6✔
1263
            for (auto col : to_migrate) {
12✔
1264
                if (col.is_set()) {
12✔
1265
                    auto set = obj.get_set<Mixed>(col);
6✔
1266
                    set.migrate();
6✔
1267
                }
6✔
1268
                else if (col.is_dictionary()) {
6✔
1269
                    auto dict = obj.get_dictionary(col);
6✔
1270
                    dict.migrate();
6✔
1271
                }
6✔
1272
            }
12✔
1273
        }
6✔
1274
    }
6✔
1275
}
180✔
1276

1277
StringData Table::get_name() const noexcept
1278
{
11,755,830✔
1279
    const Array& real_top = m_top;
11,755,830✔
1280
    ArrayParent* parent = real_top.get_parent();
11,755,830✔
1281
    if (!parent)
11,755,830✔
1282
        return StringData("");
51✔
1283
    REALM_ASSERT(dynamic_cast<Group*>(parent));
11,755,779✔
1284
    return static_cast<Group*>(parent)->get_table_name(get_key());
11,755,779✔
1285
}
11,755,779✔
1286

1287
StringData Table::get_class_name() const noexcept
1288
{
9,063,903✔
1289
    return Group::table_name_to_class_name(get_name());
9,063,903✔
1290
}
9,063,903✔
1291

1292
const char* Table::get_state() const noexcept
1293
{
42✔
1294
    switch (m_cookie) {
42✔
UNCOV
1295
        case cookie_created:
✔
UNCOV
1296
            return "created";
×
1297
        case cookie_transaction_ended:
6✔
1298
            return "transaction_ended";
6✔
UNCOV
1299
        case cookie_initialized:
✔
UNCOV
1300
            return "initialised";
×
1301
        case cookie_removed:
36✔
1302
            return "removed";
36✔
UNCOV
1303
        case cookie_void:
✔
UNCOV
1304
            return "void";
×
UNCOV
1305
        case cookie_deleted:
✔
UNCOV
1306
            return "deleted";
×
UNCOV
1307
    }
×
UNCOV
1308
    return "";
×
UNCOV
1309
}
×
1310

1311

1312
bool Table::is_nullable(ColKey col_key) const
1313
{
921,027✔
1314
    REALM_ASSERT_DEBUG(valid_column(col_key));
921,027✔
1315
    return col_key.get_attrs().test(col_attr_Nullable);
921,027✔
1316
}
921,027✔
1317

1318
bool Table::is_list(ColKey col_key) const
1319
{
172,215✔
1320
    REALM_ASSERT_DEBUG(valid_column(col_key));
172,215✔
1321
    return col_key.get_attrs().test(col_attr_List);
172,215✔
1322
}
172,215✔
1323

1324

1325
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
1326
{
341,961✔
1327
    Array top(alloc);
341,961✔
1328
    _impl::DeepArrayDestroyGuard dg(&top);
341,961✔
1329
    top.create(Array::type_HasRefs); // Throws
341,961✔
1330
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
341,961✔
1331

169,476✔
1332
    {
341,961✔
1333
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
341,961✔
1334
        dg_2.reset(mem.get_ref());
341,961✔
1335
        int_fast64_t v(from_ref(mem.get_ref()));
341,961✔
1336
        top.add(v); // Throws
341,961✔
1337
        dg_2.release();
341,961✔
1338
    }
341,961✔
1339
    top.add(0); // Old position for columns
341,961✔
1340
    {
341,961✔
1341
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
341,961✔
1342
        dg_2.reset(mem.get_ref());
341,961✔
1343
        int_fast64_t v(from_ref(mem.get_ref()));
341,961✔
1344
        top.add(v); // Throws
341,961✔
1345
        dg_2.release();
341,961✔
1346
    }
341,961✔
1347

169,476✔
1348
    // Table key value
169,476✔
1349
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
341,961✔
1350
    top.add(rot);
341,961✔
1351

169,476✔
1352
    // Search indexes
169,476✔
1353
    {
341,961✔
1354
        bool context_flag = false;
341,961✔
1355
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
341,961✔
1356
        dg_2.reset(mem.get_ref());
341,961✔
1357
        int_fast64_t v(from_ref(mem.get_ref()));
341,961✔
1358
        top.add(v); // Throws
341,961✔
1359
        dg_2.release();
341,961✔
1360
    }
341,961✔
1361
    rot = RefOrTagged::make_tagged(0);
341,961✔
1362
    top.add(rot); // Column key
341,961✔
1363
    top.add(rot); // Version
341,961✔
1364
    dg.release();
341,961✔
1365
    // Opposite keys (table and column)
169,476✔
1366
    {
341,961✔
1367
        bool context_flag = false;
341,961✔
1368
        {
341,961✔
1369
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
341,961✔
1370
            dg_2.reset(mem.get_ref());
341,961✔
1371
            int_fast64_t v(from_ref(mem.get_ref()));
341,961✔
1372
            top.add(v); // Throws
341,961✔
1373
            dg_2.release();
341,961✔
1374
        }
341,961✔
1375
        {
341,961✔
1376
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
341,961✔
1377
            dg_2.reset(mem.get_ref());
341,961✔
1378
            int_fast64_t v(from_ref(mem.get_ref()));
341,961✔
1379
            top.add(v); // Throws
341,961✔
1380
            dg_2.release();
341,961✔
1381
        }
341,961✔
1382
    }
341,961✔
1383
    top.add(0); // Sequence number
341,961✔
1384
    top.add(0); // Collision_map
341,961✔
1385
    top.add(0); // pk col key
341,961✔
1386
    top.add(0); // flags
341,961✔
1387
    top.add(0); // tombstones
341,961✔
1388

169,476✔
1389
    REALM_ASSERT(top.size() == top_array_size);
341,961✔
1390

169,476✔
1391
    return top.get_ref();
341,961✔
1392
}
341,961✔
1393

1394
void Table::ensure_graveyard()
1395
{
31,647✔
1396
    if (!m_tombstones) {
31,647✔
1397
        while (m_top.size() < top_position_for_tombstones)
10,167✔
UNCOV
1398
            m_top.add(0);
×
1399
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
10,167✔
1400
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
10,167✔
1401
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
10,167✔
1402
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
10,167✔
1403
        m_tombstones->init_from_parent();
10,167✔
1404
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
25,155✔
1405
            ts->insert_column(col);
25,155✔
1406
            return IteratorControl::AdvanceToNext;
25,155✔
1407
        });
25,155✔
1408
    }
10,167✔
1409
}
31,647✔
1410

1411
void Table::batch_erase_rows(const KeyColumn& keys)
1412
{
5,301✔
1413
    Group* g = get_parent_group();
5,301✔
1414

2,652✔
1415
    size_t num_objs = keys.size();
5,301✔
1416
    std::vector<ObjKey> vec;
5,301✔
1417
    vec.reserve(num_objs);
5,301✔
1418
    for (size_t i = 0; i < num_objs; ++i) {
2,519,373✔
1419
        ObjKey key = keys.get(i);
2,514,072✔
1420
        if (key != null_key && is_valid(key)) {
2,514,072✔
1421
            vec.push_back(key);
2,514,066✔
1422
        }
2,514,066✔
1423
    }
2,514,072✔
1424
    sort(vec.begin(), vec.end());
5,301✔
1425
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
5,301✔
1426

2,652✔
1427
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
5,301✔
1428
        CascadeState state(CascadeState::Mode::Strong, g);
4,533✔
1429
        std::for_each(vec.begin(), vec.end(), [this, &state](ObjKey k) {
3,147✔
1430
            state.m_to_be_deleted.emplace_back(m_key, k);
1,758✔
1431
        });
1,758✔
1432
        nullify_links(state);
4,533✔
1433
        remove_recursive(state);
4,533✔
1434
    }
4,533✔
1435
    else {
768✔
1436
        CascadeState state(CascadeState::Mode::None, g);
768✔
1437
        for (auto k : vec) {
2,512,302✔
1438
            if (g) {
2,512,302✔
1439
                m_clusters.nullify_links(k, state);
2,512,224✔
1440
            }
2,512,224✔
1441
            m_clusters.erase(k, state);
2,512,302✔
1442
        }
2,512,302✔
1443
    }
768✔
1444
}
5,301✔
1445

1446

1447
void Table::clear()
1448
{
3,522✔
1449
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
3,522✔
1450
    m_clusters.clear(state);
3,522✔
1451
    free_collision_table();
3,522✔
1452
}
3,522✔
1453

1454

1455
Group* Table::get_parent_group() const noexcept
1456
{
60,080,379✔
1457
    if (!m_top.is_attached())
60,080,379✔
UNCOV
1458
        return 0;                             // Subtable with shared descriptor
×
1459
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
60,080,379✔
1460
    if (!parent)
60,080,379✔
1461
        return 0; // Free-standing table
25,571,772✔
1462

17,120,751✔
1463
    return static_cast<Group*>(parent);
34,508,607✔
1464
}
34,508,607✔
1465

1466
inline uint64_t Table::get_sync_file_id() const noexcept
1467
{
38,626,827✔
1468
    Group* g = get_parent_group();
38,626,827✔
1469
    return g ? g->get_sync_file_id() : 0;
32,014,536✔
1470
}
38,626,827✔
1471

1472
size_t Table::get_index_in_group() const noexcept
1473
{
42✔
1474
    if (!m_top.is_attached())
42✔
UNCOV
1475
        return realm::npos;                   // Subtable with shared descriptor
×
1476
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
42✔
1477
    if (!parent)
42✔
UNCOV
1478
        return realm::npos; // Free-standing table
×
1479
    return m_top.get_ndx_in_parent();
42✔
1480
}
42✔
1481

1482
uint64_t Table::allocate_sequence_number()
1483
{
19,839,000✔
1484
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
19,839,000✔
1485
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,717,302✔
1486
    rot = RefOrTagged::make_tagged(sn + 1);
19,839,000✔
1487
    m_top.set(top_position_for_sequence_number, rot);
19,839,000✔
1488

9,982,941✔
1489
    return sn;
19,839,000✔
1490
}
19,839,000✔
1491

1492
void Table::set_sequence_number(uint64_t seq)
UNCOV
1493
{
×
UNCOV
1494
    m_top.set(top_position_for_sequence_number, RefOrTagged::make_tagged(seq));
×
UNCOV
1495
}
×
1496

1497
void Table::set_collision_map(ref_type ref)
UNCOV
1498
{
×
1499
    m_top.set(top_position_for_collision_map, RefOrTagged::make_ref(ref));
×
1500
}
×
1501

1502
TableRef Table::get_link_target(ColKey col_key) noexcept
1503
{
309,741✔
1504
    return get_opposite_table(col_key);
309,741✔
1505
}
309,741✔
1506

1507
// count ----------------------------------------------
1508

1509
size_t Table::count_int(ColKey col_key, int64_t value) const
1510
{
12,006✔
1511
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1512
        return index->count(value);
12,000✔
1513
    }
12,000✔
1514

3✔
1515
    return where().equal(col_key, value).count();
6✔
1516
}
6✔
1517
size_t Table::count_float(ColKey col_key, float value) const
1518
{
6✔
1519
    return where().equal(col_key, value).count();
6✔
1520
}
6✔
1521
size_t Table::count_double(ColKey col_key, double value) const
1522
{
6✔
1523
    return where().equal(col_key, value).count();
6✔
1524
}
6✔
1525
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
1526
{
18✔
1527
    ArrayDecimal128 leaf(get_alloc());
18✔
1528
    size_t cnt = 0;
18✔
1529
    bool null_value = value.is_null();
18✔
1530
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
1531
        // direct aggregate on the leaf
9✔
1532
        cluster->init_leaf(col_key, &leaf);
18✔
1533
        auto sz = leaf.size();
18✔
1534
        for (size_t i = 0; i < sz; i++) {
1,296✔
1535
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
1536
                cnt++;
24✔
1537
            }
24✔
1538
        }
1,278✔
1539
        return IteratorControl::AdvanceToNext;
18✔
1540
    };
18✔
1541

9✔
1542
    traverse_clusters(f);
18✔
1543

9✔
1544
    return cnt;
18✔
1545
}
18✔
1546
size_t Table::count_string(ColKey col_key, StringData value) const
1547
{
1,476✔
1548
    if (auto index = this->get_search_index(col_key)) {
1,476✔
1549
        return index->count(value);
732✔
1550
    }
732✔
1551
    return where().equal(col_key, value).count();
744✔
1552
}
744✔
1553

1554
template <typename T>
1555
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1556
{
27,579✔
1557
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
27,579✔
1558
    LeafType leaf(get_alloc());
27,579✔
1559

13,812✔
1560
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
27,597✔
1561
        // direct aggregate on the leaf
13,821✔
1562
        cluster->init_leaf(column_key, &leaf);
27,597✔
1563
        st.m_key_offset = cluster->get_offset();
27,597✔
1564
        st.m_key_values = cluster->get_key_array();
27,597✔
1565

13,821✔
1566
        bool cont = true;
27,597✔
1567
        size_t sz = leaf.size();
27,597✔
1568
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
123,879✔
1569
            auto v = leaf.get(local_index);
96,282✔
1570
            cont = st.match(local_index, v);
96,282✔
1571
        }
96,282✔
1572
        return IteratorControl::AdvanceToNext;
27,597✔
1573
    };
27,597✔
1574

13,812✔
1575
    traverse_clusters(f);
27,579✔
1576
}
27,579✔
1577

1578
// This template is also used by the query engine
1579
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1580
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1581
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1582
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1583
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1584
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1585
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1586

1587
std::optional<Mixed> Table::sum(ColKey col_key) const
1588
{
756✔
1589
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1590
}
756✔
1591

1592
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1593
{
822✔
1594
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1595
}
822✔
1596

1597
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1598
{
1,170✔
1599
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
1600
}
1,170✔
1601

1602
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1603
{
21,303✔
1604
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
21,303✔
1605
}
21,303✔
1606

1607

1608
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1609
{
29,736,801✔
1610
    check_column(col);
29,736,801✔
1611
    return m_index_accessors[col.get_index().val].get();
29,736,801✔
1612
}
29,736,801✔
1613

1614
StringIndex* Table::get_string_index(ColKey col) const noexcept
1615
{
696✔
1616
    check_column(col);
696✔
1617
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
696✔
1618
}
696✔
1619

1620
template <class T>
1621
ObjKey Table::find_first(ColKey col_key, T value) const
1622
{
34,899✔
1623
    check_column(col_key);
34,899✔
1624

17,391✔
1625
    if (!col_key.is_nullable() && value_is_null(value)) {
34,899!
1626
        return {}; // this is a precaution/optimization
6✔
1627
    }
6✔
1628
    // You cannot call GetIndexData on ObjKey
17,388✔
1629
    if constexpr (!std::is_same_v<T, ObjKey>) {
34,893✔
1630
        if (SearchIndex* index = get_search_index(col_key)) {
34,875!
1631
            return index->find_first(value);
27,027✔
1632
        }
27,027✔
1633
        if (col_key == m_primary_key_col) {
7,848!
1634
            return find_primary_key(value);
×
1635
        }
×
1636
    }
7,848✔
1637

3,924✔
1638
    ObjKey key;
7,848✔
1639
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
7,848✔
1640
    LeafType leaf(get_alloc());
7,848✔
1641

3,924✔
1642
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
9,330✔
1643
        cluster->init_leaf(col_key, &leaf);
9,330✔
1644
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
1645
        if (row != realm::npos) {
9,330!
1646
            key = cluster->get_real_key(row);
7,704✔
1647
            return IteratorControl::Stop;
7,704✔
1648
        }
7,704✔
1649
        return IteratorControl::AdvanceToNext;
1,626✔
1650
    };
1,626✔
1651

3,924✔
1652
    traverse_clusters(f);
7,848✔
1653

3,924✔
1654
    return key;
7,848✔
1655
}
7,848✔
1656

1657
namespace realm {
1658

1659
template <>
1660
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1661
{
×
1662
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1663
}
×
1664

1665
template <>
1666
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
1667
{
×
1668
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1669
}
×
1670

1671
template <>
1672
ObjKey Table::find_first(ColKey col_key, null) const
1673
{
×
1674
    return find_first_null(col_key);
×
1675
}
×
1676
} // namespace realm
1677

1678
// Explicitly instantiate the generic case of the template for the types we care about.
1679
template ObjKey Table::find_first(ColKey col_key, bool) const;
1680
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
1681
template ObjKey Table::find_first(ColKey col_key, float) const;
1682
template ObjKey Table::find_first(ColKey col_key, double) const;
1683
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
1684
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
1685
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
1686
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
1687
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
1688
template ObjKey Table::find_first(ColKey col_key, StringData) const;
1689
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
1690
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
1691
template ObjKey Table::find_first(ColKey col_key, UUID) const;
1692
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
1693
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
1694

1695
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1696
{
7,698✔
1697
    if (is_nullable(col_key))
7,698✔
1698
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1699
    else
7,662✔
1700
        return find_first<int64_t>(col_key, value);
7,662✔
1701
}
7,698✔
1702

1703
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1704
{
78✔
1705
    if (is_nullable(col_key))
78✔
1706
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1707
    else
42✔
1708
        return find_first<bool>(col_key, value);
42✔
1709
}
78✔
1710

1711
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1712
{
108✔
1713
    return find_first(col_key, value);
108✔
1714
}
108✔
1715

1716
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1717
{
6✔
1718
    return find_first(col_key, value);
6✔
1719
}
6✔
1720

1721
ObjKey Table::find_first_float(ColKey col_key, float value) const
1722
{
12✔
1723
    return find_first<Float>(col_key, value);
12✔
1724
}
12✔
1725

1726
ObjKey Table::find_first_double(ColKey col_key, double value) const
1727
{
12✔
1728
    return find_first<Double>(col_key, value);
12✔
1729
}
12✔
1730

1731
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
UNCOV
1732
{
×
UNCOV
1733
    return find_first<Decimal128>(col_key, value);
×
UNCOV
1734
}
×
1735

1736
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1737
{
11,553✔
1738
    return find_first<StringData>(col_key, value);
11,553✔
1739
}
11,553✔
1740

1741
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
UNCOV
1742
{
×
UNCOV
1743
    return find_first<BinaryData>(col_key, value);
×
UNCOV
1744
}
×
1745

1746
ObjKey Table::find_first_null(ColKey col_key) const
1747
{
114✔
1748
    return where().equal(col_key, null{}).find();
114✔
1749
}
114✔
1750

1751
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1752
{
18✔
1753
    return find_first(col_key, value);
18✔
1754
}
18✔
1755

1756
template <class T>
1757
TableView Table::find_all(ColKey col_key, T value)
1758
{
108✔
1759
    return where().equal(col_key, value).find_all();
108✔
1760
}
108✔
1761

1762
TableView Table::find_all_int(ColKey col_key, int64_t value)
1763
{
102✔
1764
    return find_all<int64_t>(col_key, value);
102✔
1765
}
102✔
1766

1767
TableView Table::find_all_int(ColKey col_key, int64_t value) const
UNCOV
1768
{
×
UNCOV
1769
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
UNCOV
1770
}
×
1771

1772
TableView Table::find_all_bool(ColKey col_key, bool value)
UNCOV
1773
{
×
UNCOV
1774
    return find_all<bool>(col_key, value);
×
UNCOV
1775
}
×
1776

1777
TableView Table::find_all_bool(ColKey col_key, bool value) const
UNCOV
1778
{
×
UNCOV
1779
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
UNCOV
1780
}
×
1781

1782

1783
TableView Table::find_all_float(ColKey col_key, float value)
UNCOV
1784
{
×
UNCOV
1785
    return find_all<float>(col_key, value);
×
UNCOV
1786
}
×
1787

1788
TableView Table::find_all_float(ColKey col_key, float value) const
UNCOV
1789
{
×
UNCOV
1790
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
UNCOV
1791
}
×
1792

1793
TableView Table::find_all_double(ColKey col_key, double value)
1794
{
6✔
1795
    return find_all<double>(col_key, value);
6✔
1796
}
6✔
1797

1798
TableView Table::find_all_double(ColKey col_key, double value) const
UNCOV
1799
{
×
UNCOV
1800
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
UNCOV
1801
}
×
1802

1803
TableView Table::find_all_string(ColKey col_key, StringData value)
1804
{
282✔
1805
    return where().equal(col_key, value).find_all();
282✔
1806
}
282✔
1807

1808
TableView Table::find_all_string(ColKey col_key, StringData value) const
UNCOV
1809
{
×
UNCOV
1810
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
UNCOV
1811
}
×
1812

1813
TableView Table::find_all_binary(ColKey, BinaryData)
UNCOV
1814
{
×
UNCOV
1815
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
UNCOV
1816
}
×
1817

1818
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
UNCOV
1819
{
×
UNCOV
1820
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
UNCOV
1821
}
×
1822

1823
TableView Table::find_all_null(ColKey col_key)
UNCOV
1824
{
×
UNCOV
1825
    return where().equal(col_key, null{}).find_all();
×
UNCOV
1826
}
×
1827

1828
TableView Table::find_all_null(ColKey col_key) const
UNCOV
1829
{
×
UNCOV
1830
    return const_cast<Table*>(this)->find_all_null(col_key);
×
UNCOV
1831
}
×
1832

1833
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1834
{
6✔
1835
    return where().fulltext(col_key, terms).find_all();
6✔
1836
}
6✔
1837

1838
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1839
{
72✔
1840
    TableView tv = where().find_all();
72✔
1841
    tv.sort(col_key, ascending);
72✔
1842
    return tv;
72✔
1843
}
72✔
1844

1845
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1846
{
6✔
1847
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1848
}
6✔
1849

1850
TableView Table::get_sorted_view(SortDescriptor order)
1851
{
126✔
1852
    TableView tv = where().find_all();
126✔
1853
    tv.sort(std::move(order));
126✔
1854
    return tv;
126✔
1855
}
126✔
1856

1857
TableView Table::get_sorted_view(SortDescriptor order) const
UNCOV
1858
{
×
UNCOV
1859
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
UNCOV
1860
}
×
1861

1862
util::Logger* Table::get_logger() const noexcept
1863
{
1,807,188✔
1864
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,713,339✔
1865
}
1,807,188✔
1866

1867
// Called after a commit. Table will effectively contain the same as before,
1868
// but now with new refs from the file
1869
void Table::update_from_parent() noexcept
1870
{
813,453✔
1871
    // There is no top for sub-tables sharing spec
404,118✔
1872
    if (m_top.is_attached()) {
813,453✔
1873
        m_top.update_from_parent();
813,447✔
1874
        m_spec.update_from_parent();
813,447✔
1875
        m_clusters.update_from_parent();
813,447✔
1876
        m_index_refs.update_from_parent();
813,447✔
1877
        for (auto&& index : m_index_accessors) {
2,653,761✔
1878
            if (index != nullptr) {
2,653,761✔
1879
                index->update_from_parent();
272,880✔
1880
            }
272,880✔
1881
        }
2,653,761✔
1882

404,118✔
1883
        m_opposite_table.update_from_parent();
813,447✔
1884
        m_opposite_column.update_from_parent();
813,447✔
1885
        if (m_top.size() > top_position_for_flags) {
813,453✔
1886
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
813,450✔
1887
            m_table_type = Type(flags & table_type_mask);
813,450✔
1888
        }
813,450✔
1889
        else {
2,147,483,650✔
1890
            m_table_type = Type::TopLevel;
2,147,483,650✔
1891
        }
2,147,483,650✔
1892
        if (m_tombstones)
813,447✔
1893
            m_tombstones->update_from_parent();
1,278✔
1894

404,118✔
1895
        refresh_content_version();
813,447✔
1896
        m_has_any_embedded_objects.reset();
813,447✔
1897
    }
813,447✔
1898
    m_alloc.bump_storage_version();
813,453✔
1899
}
813,453✔
1900

1901
void Table::schema_to_json(std::ostream& out, const std::map<std::string, std::string>& renames) const
1902
{
12✔
1903
    out << "{";
12✔
1904
    auto name = get_name();
12✔
1905
    if (renames.count(name))
12✔
UNCOV
1906
        name = renames.at(name);
×
1907
    out << "\"name\":\"" << name << "\"";
12✔
1908
    if (this->m_primary_key_col) {
12✔
UNCOV
1909
        out << ",";
×
UNCOV
1910
        out << "\"primaryKey\":\"" << this->get_column_name(m_primary_key_col) << "\"";
×
UNCOV
1911
    }
×
1912
    out << ",\"tableType\":\"" << this->get_table_type() << "\"";
12✔
1913
    out << ",\"properties\":[";
12✔
1914
    auto col_keys = get_column_keys();
12✔
1915
    int sz = int(col_keys.size());
12✔
1916
    for (int i = 0; i < sz; ++i) {
54✔
1917
        auto col_key = col_keys[i];
42✔
1918
        name = get_column_name(col_key);
42✔
1919
        auto type = col_key.get_type();
42✔
1920
        if (renames.count(name))
42✔
UNCOV
1921
            name = renames.at(name);
×
1922
        out << "{";
42✔
1923
        out << "\"name\":\"" << name << "\"";
42✔
1924
        if (this->is_link_type(type)) {
42✔
1925
            out << ",\"type\":\"object\"";
6✔
1926
            name = this->get_opposite_table(col_key)->get_name();
6✔
1927
            if (renames.count(name))
6✔
1928
                name = renames.at(name);
×
1929
            out << ",\"objectType\":\"" << name << "\"";
6✔
1930
        }
6✔
1931
        else {
36✔
1932
            out << ",\"type\":\"" << get_data_type_name(DataType(type)) << "\"";
36✔
1933
        }
36✔
1934
        if (col_key.is_list()) {
42✔
1935
            out << ",\"isArray\":true";
12✔
1936
        }
12✔
1937
        else if (col_key.is_set()) {
30✔
UNCOV
1938
            out << ",\"isSet\":true";
×
UNCOV
1939
        }
×
1940
        else if (col_key.is_dictionary()) {
30✔
1941
            out << ",\"isMap\":true";
6✔
1942
            auto key_type = get_dictionary_key_type(col_key);
6✔
1943
            out << ",\"keyType\":\"" << get_data_type_name(key_type) << "\"";
6✔
1944
        }
6✔
1945
        if (col_key.is_nullable()) {
42✔
1946
            out << ",\"isOptional\":true";
12✔
1947
        }
12✔
1948
        auto index_type = search_index_type(col_key);
42✔
1949
        if (index_type == IndexType::General) {
42✔
UNCOV
1950
            out << ",\"isIndexed\":true";
×
UNCOV
1951
        }
×
1952
        if (index_type == IndexType::Fulltext) {
42✔
UNCOV
1953
            out << ",\"isFulltextIndexed\":true";
×
UNCOV
1954
        }
×
1955
        out << "}";
42✔
1956
        if (i < sz - 1) {
42✔
1957
            out << ",";
30✔
1958
        }
30✔
1959
    }
42✔
1960
    out << "]}";
12✔
1961
}
12✔
1962

1963
bool Table::operator==(const Table& t) const
1964
{
138✔
1965
    if (size() != t.size()) {
138✔
1966
        return false;
12✔
1967
    }
12✔
1968
    // Check columns
63✔
1969
    for (auto ck : this->get_column_keys()) {
534✔
1970
        auto name = get_column_name(ck);
534✔
1971
        auto other_ck = t.get_column_key(name);
534✔
1972
        auto attrs = ck.get_attrs();
534✔
1973
        if (search_index_type(ck) != t.search_index_type(other_ck))
534✔
UNCOV
1974
            return false;
×
1975

267✔
1976
        if (!other_ck || other_ck.get_attrs() != attrs) {
534✔
UNCOV
1977
            return false;
×
UNCOV
1978
        }
×
1979
    }
534✔
1980
    auto pk_col = get_primary_key_column();
126✔
1981
    for (auto o : *this) {
2,898✔
1982
        Obj other_o;
2,898✔
1983
        if (pk_col) {
2,898✔
1984
            auto pk = o.get_any(pk_col);
90✔
1985
            other_o = t.get_object_with_primary_key(pk);
90✔
1986
        }
90✔
1987
        else {
2,808✔
1988
            other_o = t.get_object(o.get_key());
2,808✔
1989
        }
2,808✔
1990
        if (!(other_o && o == other_o))
2,898✔
1991
            return false;
18✔
1992
    }
2,898✔
1993

63✔
1994
    return true;
117✔
1995
}
126✔
1996

1997

1998
void Table::flush_for_commit()
1999
{
3,978,366✔
2000
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
3,978,366✔
2001
        if (!m_top.is_read_only()) {
3,978,330✔
2002
            ++m_in_file_version_at_transaction_boundary;
1,240,005✔
2003
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
1,240,005✔
2004
            m_top.set(top_position_for_version, rot_version);
1,240,005✔
2005
        }
1,240,005✔
2006
    }
3,978,330✔
2007
}
3,978,366✔
2008

2009
void Table::refresh_content_version()
2010
{
1,187,520✔
2011
    REALM_ASSERT(m_top.is_attached());
1,187,520✔
2012
    if (m_top.size() >= top_position_for_version) {
1,187,520✔
2013
        // we have versioning info in the file. Use this to conditionally
628,434✔
2014
        // bump the version counter:
628,434✔
2015
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,187,217✔
2016
        REALM_ASSERT(rot_version.is_tagged());
1,187,217✔
2017
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,187,217✔
2018
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
238,866✔
2019
            bump_content_version();
238,866✔
2020
        }
238,866✔
2021
    }
1,187,217✔
2022
    else {
303✔
2023
        // assume the worst:
288✔
2024
        bump_content_version();
303✔
2025
    }
303✔
2026
}
1,187,520✔
2027

2028

2029
// Called when Group is moved to another version - either a rollback or an advance.
2030
// The content of the table is potentially different, so make no assumptions.
2031
void Table::refresh_accessor_tree()
2032
{
374,031✔
2033
    REALM_ASSERT(m_cookie == cookie_initialized);
374,031✔
2034
    REALM_ASSERT(m_top.is_attached());
374,031✔
2035
    m_top.init_from_parent();
374,031✔
2036
    m_spec.init_from_parent();
374,031✔
2037
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
374,031✔
2038
    m_clusters.init_from_parent();
374,031✔
2039
    m_index_refs.init_from_parent();
374,031✔
2040
    m_opposite_table.init_from_parent();
374,031✔
2041
    m_opposite_column.init_from_parent();
374,031✔
2042
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
374,031✔
2043
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
319,245✔
2044
    if (m_top.size() > top_position_for_flags) {
374,031✔
2045
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
373,878✔
2046
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
373,878✔
2047
    }
373,878✔
2048
    else {
153✔
2049
        m_table_type = Type::TopLevel;
153✔
2050
    }
153✔
2051
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
374,031✔
2052
        // Tombstones exists
354✔
2053
        if (!m_tombstones) {
708✔
2054
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
240✔
2055
        }
240✔
2056
        m_tombstones->init_from_parent();
708✔
2057
    }
708✔
2058
    else {
373,323✔
2059
        m_tombstones = nullptr;
373,323✔
2060
    }
373,323✔
2061
    refresh_content_version();
374,031✔
2062
    bump_storage_version();
374,031✔
2063
    build_column_mapping();
374,031✔
2064
    refresh_index_accessors();
374,031✔
2065
}
374,031✔
2066

2067
void Table::refresh_index_accessors()
2068
{
6,384,444✔
2069
    // Refresh search index accessors
3,444,249✔
2070

3,444,249✔
2071
    // First eliminate any index accessors for eliminated last columns
3,444,249✔
2072
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
6,384,444✔
2073
    m_index_accessors.resize(col_ndx_end);
6,384,444✔
2074

3,444,249✔
2075
    // Then eliminate/refresh/create accessors within column range
3,444,249✔
2076
    // we can not use for_each_column() here, since the columns may have changed
3,444,249✔
2077
    // and the index accessor vector is not updated correspondingly.
3,444,249✔
2078
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
27,932,910✔
2079
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
21,548,466✔
2080

11,209,440✔
2081
        if (ref == 0) {
21,548,466✔
2082
            // accessor drop
9,209,067✔
2083
            m_index_accessors[col_ndx].reset();
17,543,757✔
2084
        }
17,543,757✔
2085
        else {
4,004,709✔
2086
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
4,004,709✔
2087
            bool fulltext = attr.test(col_attr_FullText_Indexed);
4,004,709✔
2088
            auto col_key = m_leaf_ndx2colkey[col_ndx];
4,004,709✔
2089
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
4,004,694✔
2090

2,000,373✔
2091
            if (m_index_accessors[col_ndx]) { // still there, refresh:
4,004,709✔
2092
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
478,350✔
2093
            }
478,350✔
2094
            else { // new index!
3,526,359✔
2095
                m_index_accessors[col_ndx] =
3,526,359✔
2096
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
3,526,359✔
2097
            }
3,526,359✔
2098
        }
4,004,709✔
2099
    }
21,548,466✔
2100
}
6,384,444✔
2101

2102
bool Table::is_cross_table_link_target() const noexcept
2103
{
7,800✔
2104
    auto is_cross_link = [this](ColKey col_key) {
3,876✔
2105
        auto t = col_key.get_type();
63✔
2106
        // look for a backlink with a different target than ourselves
33✔
2107
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
63✔
2108
                   ? IteratorControl::Stop
42✔
2109
                   : IteratorControl::AdvanceToNext;
54✔
2110
    };
63✔
2111
    return for_each_backlink_column(is_cross_link);
7,800✔
2112
}
7,800✔
2113

2114
// LCOV_EXCL_START ignore debug functions
2115

2116
void Table::verify() const
2117
{
2,960,484✔
2118
#ifdef REALM_DEBUG
2,960,484✔
2119
    if (m_top.is_attached())
2,960,484✔
2120
        m_top.verify();
2,960,481✔
2121
    m_spec.verify();
2,960,484✔
2122
    m_clusters.verify();
2,960,484✔
2123
    if (nb_unresolved())
2,960,484✔
2124
        m_tombstones->verify();
749,472✔
2125
#endif
2,960,484✔
2126
}
2,960,484✔
2127

2128
#ifdef REALM_DEBUG
2129
MemStats Table::stats() const
UNCOV
2130
{
×
UNCOV
2131
    MemStats mem_stats;
×
UNCOV
2132
    m_top.stats(mem_stats);
×
UNCOV
2133
    return mem_stats;
×
UNCOV
2134
}
×
2135
#endif // LCOV_EXCL_STOP ignore debug functions
2136

2137
Obj Table::create_object(ObjKey key, const FieldValues& values)
2138
{
22,499,493✔
2139
    if (is_embedded())
22,499,493✔
2140
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2141
    if (m_primary_key_col)
22,499,445✔
UNCOV
2142
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2143
    if (key == null_key) {
22,499,445✔
2144
        GlobalKey object_id = allocate_object_id_squeezed();
19,341,519✔
2145
        key = object_id.get_local_key(get_sync_file_id());
19,341,519✔
2146
        // Check if this key collides with an already existing object
9,749,421✔
2147
        // This could happen if objects were at some point created with primary keys,
9,749,421✔
2148
        // but later primary key property was removed from the schema.
9,749,421✔
2149
        while (m_clusters.is_valid(key)) {
19,341,519✔
UNCOV
2150
            object_id = allocate_object_id_squeezed();
×
UNCOV
2151
            key = object_id.get_local_key(get_sync_file_id());
×
UNCOV
2152
        }
×
2153
        if (auto repl = get_repl())
19,341,519✔
2154
            repl->create_object(this, object_id);
4,304,976✔
2155
    }
19,341,519✔
2156

11,325,921✔
2157
    REALM_ASSERT(key.value >= 0);
22,499,445✔
2158

11,325,921✔
2159
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,499,445✔
2160

11,325,921✔
2161
    return obj;
22,499,445✔
2162
}
22,499,445✔
2163

2164
Obj Table::create_linked_object()
2165
{
39,654✔
2166
    REALM_ASSERT(is_embedded());
39,654✔
2167

19,734✔
2168
    GlobalKey object_id = allocate_object_id_squeezed();
39,654✔
2169
    ObjKey key = object_id.get_local_key(get_sync_file_id());
39,654✔
2170

19,734✔
2171
    REALM_ASSERT(key.value >= 0);
39,654✔
2172

19,734✔
2173
    Obj obj = m_clusters.insert(key, {});
39,654✔
2174

19,734✔
2175
    return obj;
39,654✔
2176
}
39,654✔
2177

2178
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2179
{
30✔
2180
    if (is_embedded())
30✔
UNCOV
2181
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2182
    if (m_primary_key_col)
30✔
UNCOV
2183
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2184
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2185

15✔
2186
    if (auto repl = get_repl())
30✔
2187
        repl->create_object(this, object_id);
30✔
2188

15✔
2189
    try {
30✔
2190
        Obj obj = m_clusters.insert(key, values);
30✔
2191
        // Check if tombstone exists
15✔
2192
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2193
            auto unres_key = key.get_unresolved();
6✔
2194
            // Copy links over
3✔
2195
            auto tombstone = m_tombstones->get(unres_key);
6✔
2196
            obj.assign_pk_and_backlinks(tombstone);
6✔
2197
            // If tombstones had no links to it, it may still be alive
3✔
2198
            if (m_tombstones->is_valid(unres_key)) {
6✔
2199
                CascadeState state(CascadeState::Mode::None);
6✔
2200
                m_tombstones->erase(unres_key, state);
6✔
2201
            }
6✔
2202
        }
6✔
2203

15✔
2204
        return obj;
30✔
2205
    }
30✔
2206
    catch (const KeyAlreadyUsed&) {
6✔
2207
        return m_clusters.get(key);
6✔
2208
    }
6✔
2209
}
30✔
2210

2211
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2212
                                          bool* did_create)
2213
{
589,740✔
2214
    auto primary_key_col = get_primary_key_column();
589,740✔
2215
    if (is_embedded() || !primary_key_col)
589,740✔
2216
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2217
                              util::format("Table has no primary key: %1", get_name()));
6✔
2218

282,678✔
2219
    DataType type = DataType(primary_key_col.get_type());
589,734✔
2220

282,678✔
2221
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
589,734✔
2222
        throw InvalidArgument(
6✔
2223
            ErrorCodes::PropertyNotNullable,
6✔
2224
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2225
    }
6✔
2226

282,675✔
2227
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
589,728✔
2228
        primary_key.get_type() != type) {
589,503✔
2229
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2230
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2231
    }
6✔
2232

282,672✔
2233
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
589,722✔
2234

282,672✔
2235
    if (did_create)
589,722✔
2236
        *did_create = false;
74,793✔
2237

282,672✔
2238
    // Check for existing object
282,672✔
2239
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
589,722✔
2240
        if (mode == UpdateMode::never) {
82,596✔
2241
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2242
        }
6✔
2243
        auto obj = m_clusters.get(key);
82,590✔
2244
        for (auto& val : field_values) {
40,659✔
2245
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2246
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2247
            }
12✔
2248
        }
12✔
2249
        return obj;
82,590✔
2250
    }
82,590✔
2251

242,016✔
2252
    ObjKey unres_key;
507,126✔
2253
    if (m_tombstones) {
507,126✔
2254
        // Check for potential tombstone
19,500✔
2255
        GlobalKey object_id{primary_key};
39,627✔
2256
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
39,627✔
2257

19,500✔
2258
        ObjKey key = object_key.get_unresolved();
39,627✔
2259
        if (auto obj = m_tombstones->try_get_obj(key)) {
39,627✔
2260
            auto existing_pk_value = obj.get_any(primary_key_col);
13,083✔
2261

6,615✔
2262
            // If the primary key is the same, the object should be resurrected below
6,615✔
2263
            if (existing_pk_value == primary_key) {
13,083✔
2264
                unres_key = key;
13,077✔
2265
            }
13,077✔
2266
        }
13,083✔
2267
    }
39,627✔
2268

242,016✔
2269
    ObjKey key = get_next_valid_key();
507,126✔
2270

242,016✔
2271
    auto repl = get_repl();
507,126✔
2272
    if (repl) {
507,126✔
2273
        repl->create_object_with_primary_key(this, key, primary_key);
446,178✔
2274
    }
446,178✔
2275
    if (did_create) {
507,126✔
2276
        *did_create = true;
36,333✔
2277
    }
36,333✔
2278

242,016✔
2279
    field_values.insert(primary_key_col, primary_key);
507,126✔
2280
    Obj ret = m_clusters.insert(key, field_values);
507,126✔
2281

242,016✔
2282
    // Check if unresolved exists
242,016✔
2283
    if (unres_key) {
507,126✔
2284
        auto tombstone = m_tombstones->get(unres_key);
13,077✔
2285
        ret.assign_pk_and_backlinks(tombstone);
13,077✔
2286
        // If tombstones had no links to it, it may still be alive
6,612✔
2287
        if (m_tombstones->is_valid(unres_key)) {
13,077✔
2288
            CascadeState state(CascadeState::Mode::None);
3,909✔
2289
            m_tombstones->erase(unres_key, state);
3,909✔
2290
        }
3,909✔
2291
    }
13,077✔
2292
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
507,126✔
2293
        get_parent_group()->m_objects_to_delete.emplace_back(this->m_key, ret.get_key());
1,266✔
2294
    }
1,266✔
2295
    return ret;
507,126✔
2296
}
507,126✔
2297

2298
ObjKey Table::find_primary_key(Mixed primary_key) const
2299
{
916,881✔
2300
    auto primary_key_col = get_primary_key_column();
916,881✔
2301
    REALM_ASSERT(primary_key_col);
916,881✔
2302
    DataType type = DataType(primary_key_col.get_type());
916,881✔
2303
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
916,881✔
2304
                 primary_key.get_type() == type);
916,881✔
2305

460,656✔
2306
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
916,881✔
2307
        return index->find_first(primary_key);
915,939✔
2308
    }
915,939✔
2309

930✔
2310
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
930✔
2311
    // so try the old algorithm
930✔
2312
    GlobalKey object_id{primary_key};
942✔
2313
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
942✔
2314

930✔
2315
    // Check if existing
930✔
2316
    if (auto obj = m_clusters.try_get_obj(object_key)) {
942✔
UNCOV
2317
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2318

UNCOV
2319
        if (existing_pk_value == primary_key) {
×
UNCOV
2320
            return object_key;
×
UNCOV
2321
        }
×
2322
    }
942✔
2323
    return {};
942✔
2324
}
942✔
2325

2326
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2327
{
745,074✔
2328
    // Check if existing
374,799✔
2329
    if (auto key = find_primary_key(primary_key)) {
745,074✔
2330
        return key;
713,310✔
2331
    }
713,310✔
2332

16,185✔
2333
    // Object does not exist - create tombstone
16,185✔
2334
    GlobalKey object_id{primary_key};
31,764✔
2335
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
31,764✔
2336
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
31,764✔
2337
}
31,764✔
2338

2339
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2340
{
18✔
2341
    REALM_ASSERT(!m_primary_key_col);
18✔
2342
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2343

9✔
2344
    // Check if existing
9✔
2345
    if (m_clusters.is_valid(object_key)) {
18✔
2346
        return object_key;
12✔
2347
    }
12✔
2348

3✔
2349
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2350
}
6✔
2351

2352
ObjKey Table::get_objkey(GlobalKey global_key) const
2353
{
18✔
2354
    ObjKey key;
18✔
2355
    REALM_ASSERT(!m_primary_key_col);
18✔
2356
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
2357
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
2358
        key = global_key.get_local_key(get_sync_file_id());
6✔
2359
    }
6✔
2360
    if (key && !is_valid(key)) {
18✔
2361
        key = realm::null_key;
6✔
2362
    }
6✔
2363
    return key;
18✔
2364
}
18✔
2365

2366
GlobalKey Table::get_object_id(ObjKey key) const
2367
{
144✔
2368
    auto col = get_primary_key_column();
144✔
2369
    if (col) {
144✔
2370
        const Obj obj = get_object(key);
24✔
2371
        auto val = obj.get_any(col);
24✔
2372
        return {val};
24✔
2373
    }
24✔
2374
    else {
120✔
2375
        return {key, get_sync_file_id()};
120✔
2376
    }
120✔
UNCOV
2377
    return {};
×
UNCOV
2378
}
×
2379

2380
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2381
{
63,111✔
2382
    auto primary_key_col = get_primary_key_column();
63,111✔
2383
    REALM_ASSERT(primary_key_col);
63,111✔
2384
    DataType type = DataType(primary_key_col.get_type());
63,111✔
2385
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
63,111✔
2386
                 primary_key.get_type() == type);
63,111✔
2387
    return m_clusters.get(m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key));
63,111✔
2388
}
63,111✔
2389

2390
Mixed Table::get_primary_key(ObjKey key) const
2391
{
856,803✔
2392
    auto primary_key_col = get_primary_key_column();
856,803✔
2393
    REALM_ASSERT(primary_key_col);
856,803✔
2394
    if (key.is_unresolved()) {
856,803✔
2395
        REALM_ASSERT(m_tombstones);
186✔
2396
        return m_tombstones->get(key).get_any(primary_key_col);
186✔
2397
    }
186✔
2398
    else {
856,617✔
2399
        return m_clusters.get(key).get_any(primary_key_col);
856,617✔
2400
    }
856,617✔
2401
}
856,803✔
2402

2403
GlobalKey Table::allocate_object_id_squeezed()
2404
{
19,384,974✔
2405
    // m_client_file_ident will be zero if we haven't been in contact with
9,769,356✔
2406
    // the server yet.
9,769,356✔
2407
    auto peer_id = get_sync_file_id();
19,384,974✔
2408
    auto sequence = allocate_sequence_number();
19,384,974✔
2409
    return GlobalKey{peer_id, sequence};
19,384,974✔
2410
}
19,384,974✔
2411

2412
namespace {
2413

2414
/// Calculate optimistic local ID that may collide with others. It is up to
2415
/// the caller to ensure that collisions are detected and that
2416
/// allocate_local_id_after_collision() is called to obtain a non-colliding
2417
/// ID.
2418
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
2419
{
71,100✔
2420
#if REALM_EXERCISE_OBJECT_ID_COLLISION
2421
    const uint64_t optimistic_mask = 0xff;
2422
#else
2423
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
71,100✔
2424
#endif
71,100✔
2425
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
71,100✔
2426
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
71,100✔
2427
}
71,100✔
2428

2429
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2430
{
12✔
2431
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2432
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2433
}
12✔
2434

2435
} // namespace
2436

2437
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2438
{
71,100✔
2439
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
71,100✔
2440

35,184✔
2441
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
71,100✔
2442
        Allocator& alloc = m_top.get_alloc();
24✔
2443
        Array collision_map{alloc};
24✔
2444
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2445

12✔
2446
        Array hi{alloc};
24✔
2447
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2448

12✔
2449
        // Entries are ordered by hi,lo
12✔
2450
        size_t found = hi.find_first(object_id.hi());
24✔
2451
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
2452
            Array lo{alloc};
24✔
2453
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
2454
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
2455
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
2456
                Array local_id{alloc};
24✔
2457
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
2458
                return ObjKey{local_id.get(candidate)};
24✔
2459
            }
24✔
2460
        }
71,076✔
2461
    }
24✔
2462

35,172✔
2463
    return optimistic;
71,076✔
2464
}
71,076✔
2465

2466
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2467
                                                     ObjKey colliding_local_id)
2468
{
12✔
2469
    // Possible optimization: Cache these accessors
6✔
2470
    Allocator& alloc = m_top.get_alloc();
12✔
2471
    Array collision_map{alloc};
12✔
2472
    Array hi{alloc};
12✔
2473
    Array lo{alloc};
12✔
2474
    Array local_id{alloc};
12✔
2475

6✔
2476
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2477
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2478
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2479
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2480

6✔
2481
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2482
    if (collision_map_ref) {
12✔
UNCOV
2483
        collision_map.init_from_parent(); // Throws
×
UNCOV
2484
    }
×
2485
    else {
12✔
2486
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2487
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2488
        collision_map.update_parent();
12✔
2489

6✔
2490
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2491
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2492
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2493
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2494
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2495
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2496
    }
12✔
2497

6✔
2498
    hi.init_from_parent();       // Throws
12✔
2499
    lo.init_from_parent();       // Throws
12✔
2500
    local_id.init_from_parent(); // Throws
12✔
2501

6✔
2502
    size_t num_entries = hi.size();
12✔
2503
    REALM_ASSERT(lo.size() == num_entries);
12✔
2504
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2505

6✔
2506
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2507
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2508
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2509
            ++i;
6✔
2510
        return i;
24✔
2511
    };
24✔
2512

6✔
2513
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
2514
        size_t i = lower_bound_object_id(object_id);
24✔
2515
        if (i != num_entries) {
24✔
2516
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
2517
            if (existing == object_id) {
6✔
2518
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
UNCOV
2519
                return;
×
UNCOV
2520
            }
×
2521
        }
24✔
2522
        hi.insert(i, int64_t(object_id.hi()));
24✔
2523
        lo.insert(i, int64_t(object_id.lo()));
24✔
2524
        local_id.insert(i, new_local_id.value);
24✔
2525
        ++num_entries;
24✔
2526
    };
24✔
2527

6✔
2528
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2529
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2530
    insert_collision(incoming_id, new_local_id);
12✔
2531
    insert_collision(colliding_id, colliding_local_id);
12✔
2532

6✔
2533
    return new_local_id;
12✔
2534
}
12✔
2535

2536
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2537
{
31,647✔
2538
    auto unres_key = key.get_unresolved();
31,647✔
2539

15,771✔
2540
    ensure_graveyard();
31,647✔
2541
    auto tombstone = m_tombstones->try_get_obj(unres_key);
31,647✔
2542
    if (tombstone) {
31,647✔
2543
        if (pk_col) {
2,952✔
2544
            auto existing_pk_value = tombstone.get_any(pk_col);
2,952✔
2545
            // It may just be the same object
1,470✔
2546
            if (existing_pk_value != pk_val) {
2,952✔
2547
                // We have a collision - create new ObjKey
6✔
2548
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
2549
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
2550
            }
12✔
2551
        }
2,940✔
2552
        return tombstone;
2,940✔
2553
    }
2,940✔
2554
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
28,695✔
2555
}
28,695✔
2556

2557
void Table::free_local_id_after_hash_collision(ObjKey key)
2558
{
5,089,032✔
2559
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,089,032✔
2560
        if (key.is_unresolved()) {
30✔
2561
            // Keys will always be inserted as resolved
12✔
2562
            key = key.get_unresolved();
24✔
2563
        }
24✔
2564
        // Possible optimization: Cache these accessors
15✔
2565
        Array collision_map{m_alloc};
30✔
2566
        Array local_id{m_alloc};
30✔
2567

15✔
2568
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2569
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2570
        collision_map.init_from_ref(collision_map_ref);
30✔
2571
        local_id.init_from_parent();
30✔
2572
        auto ndx = local_id.find_first(key.value);
30✔
2573
        if (ndx != realm::npos) {
30✔
2574
            Array hi{m_alloc};
24✔
2575
            Array lo{m_alloc};
24✔
2576

12✔
2577
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2578
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2579
            hi.init_from_parent();
24✔
2580
            lo.init_from_parent();
24✔
2581

12✔
2582
            hi.erase(ndx);
24✔
2583
            lo.erase(ndx);
24✔
2584
            local_id.erase(ndx);
24✔
2585
            if (hi.size() == 0) {
24✔
2586
                free_collision_table();
12✔
2587
            }
12✔
2588
        }
24✔
2589
    }
30✔
2590
}
5,089,032✔
2591

2592
void Table::free_collision_table()
2593
{
3,534✔
2594
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
3,534✔
2595
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2596
        m_top.set(top_position_for_collision_map, 0);
12✔
2597
    }
12✔
2598
}
3,534✔
2599

2600
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2601
{
3,843✔
2602
    while (number--) {
6,327,168✔
2603
        keys.push_back(create_object().get_key());
6,323,325✔
2604
    }
6,323,325✔
2605
}
3,843✔
2606

2607
void Table::create_objects(const std::vector<ObjKey>& keys)
2608
{
630✔
2609
    for (auto k : keys) {
5,616✔
2610
        create_object(k);
5,616✔
2611
    }
5,616✔
2612
}
630✔
2613

2614
void Table::dump_objects()
UNCOV
2615
{
×
UNCOV
2616
    m_clusters.dump_objects();
×
UNCOV
2617
    if (nb_unresolved())
×
UNCOV
2618
        m_tombstones->dump_objects();
×
UNCOV
2619
}
×
2620

2621
void Table::remove_object(ObjKey key)
2622
{
2,551,386✔
2623
    Group* g = get_parent_group();
2,551,386✔
2624

1,275,486✔
2625
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,551,386✔
2626
        CascadeState state(CascadeState::Mode::Strong, g);
3,732✔
2627
        state.m_to_be_deleted.emplace_back(m_key, key);
3,732✔
2628
        m_clusters.nullify_links(key, state);
3,732✔
2629
        remove_recursive(state);
3,732✔
2630
    }
3,732✔
2631
    else {
2,547,654✔
2632
        CascadeState state(CascadeState::Mode::None, g);
2,547,654✔
2633
        if (g) {
2,547,654✔
2634
            m_clusters.nullify_links(key, state);
2,465,871✔
2635
        }
2,465,871✔
2636
        m_clusters.erase(key, state);
2,547,654✔
2637
    }
2,547,654✔
2638
}
2,551,386✔
2639

2640
ObjKey Table::invalidate_object(ObjKey key)
2641
{
64,416✔
2642
    if (is_embedded())
64,416✔
UNCOV
2643
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2644
    REALM_ASSERT(!key.is_unresolved());
64,416✔
2645

32,025✔
2646
    Obj tombstone;
64,416✔
2647
    auto obj = get_object(key);
64,416✔
2648
    if (obj.has_backlinks(false)) {
64,416✔
2649
        // If the object has backlinks, we should make a tombstone
273✔
2650
        // and make inward links point to it,
273✔
2651
        if (auto primary_key_col = get_primary_key_column()) {
546✔
2652
            auto pk = obj.get_any(primary_key_col);
390✔
2653
            GlobalKey object_id{pk};
390✔
2654
            auto unres_key = global_to_local_object_id_hashed(object_id);
390✔
2655
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
390✔
2656
        }
390✔
2657
        else {
156✔
2658
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
2659
        }
156✔
2660
        tombstone.assign_pk_and_backlinks(obj);
546✔
2661
    }
546✔
2662

32,025✔
2663
    remove_object(key);
64,416✔
2664

32,025✔
2665
    return tombstone.get_key();
64,416✔
2666
}
64,416✔
2667

2668
void Table::remove_object_recursive(ObjKey key)
2669
{
42✔
2670
    size_t table_ndx = get_index_in_group();
42✔
2671
    if (table_ndx != realm::npos) {
42✔
2672
        CascadeState state(CascadeState::Mode::All, get_parent_group());
42✔
2673
        state.m_to_be_deleted.emplace_back(m_key, key);
42✔
2674
        nullify_links(state);
42✔
2675
        remove_recursive(state);
42✔
2676
    }
42✔
UNCOV
2677
    else {
×
2678
        // No links in freestanding table
UNCOV
2679
        CascadeState state(CascadeState::Mode::None);
×
UNCOV
2680
        m_clusters.erase(key, state);
×
UNCOV
2681
    }
×
2682
}
42✔
2683

2684
Table::Iterator Table::begin() const
2685
{
532,077✔
2686
    return Iterator(m_clusters, 0);
532,077✔
2687
}
532,077✔
2688

2689
Table::Iterator Table::end() const
2690
{
9,286,107✔
2691
    return Iterator(m_clusters, size());
9,286,107✔
2692
}
9,286,107✔
2693

2694
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2695
{
7,076,178✔
2696
    TableRef ret;
7,076,178✔
2697
    if (col_key) {
7,076,409✔
2698
        return table.get_opposite_table(col_key);
7,076,355✔
2699
    }
7,076,355✔
2700
    return ret;
2,147,483,701✔
2701
}
2,147,483,701✔
2702

2703
const uint64_t Table::max_num_columns;
2704

2705
void Table::build_column_mapping()
2706
{
6,407,928✔
2707
    // build column mapping from spec
3,457,152✔
2708
    // TODO: Optimization - Don't rebuild this for every change
3,457,152✔
2709
    m_spec_ndx2leaf_ndx.clear();
6,407,928✔
2710
    m_leaf_ndx2spec_ndx.clear();
6,407,928✔
2711
    m_leaf_ndx2colkey.clear();
6,407,928✔
2712
    size_t num_spec_cols = m_spec.get_column_count();
6,407,928✔
2713
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
6,407,928✔
2714
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
27,926,355✔
2715
        ColKey col_key = m_spec.get_key(spec_ndx);
21,518,427✔
2716
        unsigned leaf_ndx = col_key.get_index().val;
21,518,427✔
2717
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
21,518,427✔
2718
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
21,050,469✔
2719
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
21,050,469✔
2720
        }
21,050,469✔
2721
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
21,518,427✔
2722
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
21,518,427✔
2723
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
21,518,427✔
2724
    }
21,518,427✔
2725
}
6,407,928✔
2726

2727
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2728
{
986,595✔
2729
    REALM_ASSERT(!attr.test(col_attr_Indexed));
986,595✔
2730
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
986,595✔
2731

486,840✔
2732
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
986,595✔
2733
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
986,595✔
2734

486,840✔
2735
    // reuse lowest available leaf ndx:
486,840✔
2736
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
986,595✔
2737
    // look for an unused entry:
486,840✔
2738
    for (unsigned idx = 0; idx < lower; ++idx) {
6,268,161✔
2739
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
5,281,656✔
2740
            lower = idx;
90✔
2741
            break;
90✔
2742
        }
90✔
2743
    }
5,281,656✔
2744
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
986,595✔
2745
}
986,595✔
2746

2747
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2748
                                                  StringData origin_col_name) const noexcept
UNCOV
2749
{
×
UNCOV
2750
    BacklinkOrigin ret;
×
UNCOV
2751
    auto f = [&](ColKey backlink_col_key) {
×
UNCOV
2752
        auto origin_table = get_opposite_table(backlink_col_key);
×
UNCOV
2753
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
UNCOV
2754
        if (origin_table->get_name() == origin_table_name &&
×
UNCOV
2755
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
UNCOV
2756
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
UNCOV
2757
            return IteratorControl::Stop;
×
UNCOV
2758
        }
×
UNCOV
2759
        return IteratorControl::AdvanceToNext;
×
UNCOV
2760
    };
×
UNCOV
2761
    this->for_each_backlink_column(f);
×
UNCOV
2762
    return ret;
×
UNCOV
2763
}
×
2764

2765
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2766
{
360✔
2767
    try {
360✔
2768
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
360✔
2769
        ColKey linked_column_key = get_opposite_column(backlink_col);
360✔
2770
        if (linked_table_key == m_key) {
360✔
2771
            return {{m_own_ref, linked_column_key}};
24✔
2772
        }
24✔
2773
        else {
336✔
2774
            Group* current_group = get_parent_group();
336✔
2775
            if (current_group) {
336✔
2776
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2777
                return {{linked_table_ref, linked_column_key}};
336✔
2778
            }
336✔
UNCOV
2779
        }
×
2780
    }
360✔
UNCOV
2781
    catch (...) {
×
2782
        // backlink column not found, returning empty optional
UNCOV
2783
    }
×
2784
    return {};
180✔
2785
}
360✔
2786

2787
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
UNCOV
2788
{
×
UNCOV
2789
    std::vector<std::pair<TableKey, ColKey>> origins;
×
UNCOV
2790
    auto f = [&](ColKey backlink_col_key) {
×
UNCOV
2791
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
UNCOV
2792
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
UNCOV
2793
        origins.emplace_back(origin_table_key, origin_link_col);
×
UNCOV
2794
        return IteratorControl::AdvanceToNext;
×
UNCOV
2795
    };
×
UNCOV
2796
    this->for_each_backlink_column(f);
×
UNCOV
2797
    return origins;
×
UNCOV
2798
}
×
2799

2800
ColKey Table::get_primary_key_column() const
2801
{
25,474,443✔
2802
    return m_primary_key_col;
25,474,443✔
2803
}
25,474,443✔
2804

2805
void Table::set_primary_key_column(ColKey col_key)
2806
{
720✔
2807
    if (col_key == m_primary_key_col) {
720✔
2808
        return;
378✔
2809
    }
378✔
2810

171✔
2811
    if (Replication* repl = get_repl()) {
342✔
2812
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
UNCOV
2813
            throw RuntimeError(
×
UNCOV
2814
                ErrorCodes::BrokenInvariant,
×
UNCOV
2815
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
UNCOV
2816
        }
×
2817
    }
342✔
2818

171✔
2819
    REALM_ASSERT_RELEASE(col_key.value >= 0); // Just to be sure. We have an issue where value seems to be -1
342✔
2820

171✔
2821
    if (col_key) {
342✔
2822
        check_column(col_key);
222✔
2823
        validate_column_is_unique(col_key);
222✔
2824
        do_set_primary_key_column(col_key);
222✔
2825
    }
222✔
2826
    else {
120✔
2827
        do_set_primary_key_column(col_key);
120✔
2828
    }
120✔
2829
}
342✔
2830

2831

2832
void Table::do_set_primary_key_column(ColKey col_key)
2833
{
138,246✔
2834
    if (col_key) {
138,246✔
2835
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
130,563✔
2836
        auto attr = m_spec.get_column_attr(spec_ndx);
130,563✔
2837
        if (attr.test(col_attr_FullText_Indexed)) {
130,563✔
2838
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
2839
        }
6✔
2840
    }
138,240✔
2841

68,325✔
2842
    if (m_primary_key_col) {
138,240✔
2843
        // If the search index has not been set explicitly on current pk col, we remove it again
3,801✔
2844
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
7,707✔
2845
        auto attr = m_spec.get_column_attr(spec_ndx);
7,707✔
2846
        if (!attr.test(col_attr_Indexed)) {
7,707✔
2847
            remove_search_index(m_primary_key_col);
7,695✔
2848
        }
7,695✔
2849
    }
7,707✔
2850

68,325✔
2851
    if (col_key) {
138,240✔
2852
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
130,557✔
2853
        do_add_search_index(col_key, IndexType::General);
130,557✔
2854
    }
130,557✔
2855
    else {
7,683✔
2856
        m_top.set(top_position_for_pk_col, 0);
7,683✔
2857
    }
7,683✔
2858

68,325✔
2859
    m_primary_key_col = col_key;
138,240✔
2860
}
138,240✔
2861

2862
bool Table::contains_unique_values(ColKey col) const
2863
{
834✔
2864
    if (search_index_type(col) == IndexType::General) {
834✔
2865
        auto search_index = get_search_index(col);
744✔
2866
        return !search_index->has_duplicate_values();
744✔
2867
    }
744✔
2868
    else {
90✔
2869
        TableView tv = where().find_all();
90✔
2870
        tv.distinct(col);
90✔
2871
        return tv.size() == size();
90✔
2872
    }
90✔
2873
}
834✔
2874

2875
void Table::validate_column_is_unique(ColKey col) const
2876
{
834✔
2877
    if (!contains_unique_values(col)) {
834✔
2878
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
2879
                                           get_class_name(), get_column_name(col)));
30✔
2880
    }
30✔
2881
}
834✔
2882

2883
void Table::validate_primary_column()
2884
{
1,812✔
2885
    if (ColKey col = get_primary_key_column()) {
1,812✔
2886
        validate_column_is_unique(col);
612✔
2887
    }
612✔
2888
}
1,812✔
2889

2890
ObjKey Table::get_next_valid_key()
2891
{
507,135✔
2892
    ObjKey key;
507,135✔
2893
    do {
507,141✔
2894
        key = ObjKey(allocate_sequence_number());
507,141✔
2895
    } while (m_clusters.is_valid(key));
507,141✔
2896

242,022✔
2897
    return key;
507,135✔
2898
}
507,135✔
2899

2900
namespace {
2901
template <class T>
2902
typename util::RemoveOptional<T>::type remove_optional(T val)
2903
{
88,083✔
2904
    return val;
88,083✔
2905
}
88,083✔
2906
template <>
2907
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
2908
{
5,490✔
2909
    return *val;
5,490✔
2910
}
5,490✔
2911
template <>
2912
bool remove_optional<Optional<bool>>(Optional<bool> val)
2913
{
11,544✔
2914
    return *val;
11,544✔
2915
}
11,544✔
2916
template <>
2917
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
2918
{
5,526✔
2919
    return *val;
5,526✔
2920
}
5,526✔
2921
template <>
2922
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
2923
{
6,060✔
2924
    return *val;
6,060✔
2925
}
6,060✔
2926
} // namespace
2927

2928
template <class F, class T>
2929
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
2930
{
162✔
2931
    Allocator& allocator = this->get_alloc();
162✔
2932
    bool from_nullability = is_nullable(key_from);
162✔
2933
    auto func = [&](Cluster* cluster) {
162✔
2934
        size_t sz = cluster->node_size();
162✔
2935

81✔
2936
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
2937
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
2938
        cluster->init_leaf(key_from, &from_arr);
162✔
2939
        cluster->init_leaf(key_to, &to_arr);
162✔
2940

81✔
2941
        for (size_t i = 0; i < sz; i++) {
1,512✔
2942
            if (from_nullability && from_arr.is_null(i)) {
1,356!
2943
                if (throw_on_null) {
54!
2944
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
2945
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
2946
                                                    get_column_name(key_from)));
6✔
2947
                }
6✔
2948
                else {
48✔
2949
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
48✔
2950
                }
48✔
2951
            }
54✔
2952
            else {
1,302✔
2953
                auto v = remove_optional(from_arr.get(i));
1,302✔
2954
                to_arr.set(i, v);
1,302✔
2955
            }
1,302✔
2956
        }
1,356✔
2957
    };
162✔
2958

81✔
2959
    m_clusters.update(func);
162✔
2960
}
162✔
2961

2962
template <class F, class T>
2963
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
2964
{
120✔
2965
    Allocator& allocator = this->get_alloc();
120✔
2966
    bool from_nullability = is_nullable(key_from);
120✔
2967
    auto func = [&](Cluster* cluster) {
120✔
2968
        size_t sz = cluster->node_size();
120✔
2969

60✔
2970
        ArrayInteger from_arr(allocator);
120✔
2971
        ArrayInteger to_arr(allocator);
120✔
2972
        cluster->init_leaf(key_from, &from_arr);
120✔
2973
        cluster->init_leaf(key_to, &to_arr);
120✔
2974

60✔
2975
        for (size_t i = 0; i < sz; i++) {
360✔
2976
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
2977
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
2978
            REALM_ASSERT(!ref_to);
240✔
2979

120✔
2980
            if (ref_from) {
240✔
2981
                BPlusTree<F> from_list(allocator);
120✔
2982
                BPlusTree<T> to_list(allocator);
120✔
2983
                from_list.init_from_ref(ref_from);
120✔
2984
                to_list.create();
120✔
2985
                size_t n = from_list.size();
120✔
2986
                for (size_t j = 0; j < n; j++) {
120,120✔
2987
                    auto v = from_list.get(j);
120,000✔
2988
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
2989
                        to_list.add(remove_optional(v));
115,401✔
2990
                    }
115,401✔
2991
                    else {
4,599✔
2992
                        if (throw_on_null) {
4,599!
UNCOV
2993
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
UNCOV
2994
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
UNCOV
2995
                                                            get_name(), get_column_name(key_from)));
×
UNCOV
2996
                        }
×
2997
                        else {
4,599✔
2998
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,599✔
2999
                        }
4,599✔
3000
                    }
4,599✔
3001
                }
120,000✔
3002
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3003
            }
120✔
3004
        }
240✔
3005
    };
120✔
3006

60✔
3007
    m_clusters.update(func);
120✔
3008
}
120✔
3009

3010
void Table::convert_column(ColKey from, ColKey to, bool throw_on_null)
3011
{
282✔
3012
    realm::DataType type_id = get_column_type(from);
282✔
3013
    bool _is_list = is_list(from);
282✔
3014
    if (_is_list) {
282✔
3015
        switch (type_id) {
120✔
3016
            case type_Int:
12✔
3017
                if (is_nullable(from)) {
12✔
3018
                    change_nullability_list<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3019
                }
6✔
3020
                else {
6✔
3021
                    change_nullability_list<int64_t, Optional<int64_t>>(from, to, throw_on_null);
6✔
3022
                }
6✔
3023
                break;
12✔
3024
            case type_Float:
12✔
3025
                change_nullability_list<float, float>(from, to, throw_on_null);
12✔
3026
                break;
12✔
3027
            case type_Double:
12✔
3028
                change_nullability_list<double, double>(from, to, throw_on_null);
12✔
3029
                break;
12✔
3030
            case type_Bool:
12✔
3031
                change_nullability_list<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3032
                break;
12✔
3033
            case type_String:
12✔
3034
                change_nullability_list<StringData, StringData>(from, to, throw_on_null);
12✔
3035
                break;
12✔
3036
            case type_Binary:
12✔
3037
                change_nullability_list<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3038
                break;
12✔
3039
            case type_Timestamp:
12✔
3040
                change_nullability_list<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3041
                break;
12✔
3042
            case type_ObjectId:
12✔
3043
                if (is_nullable(from)) {
12✔
3044
                    change_nullability_list<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3045
                }
6✔
3046
                else {
6✔
3047
                    change_nullability_list<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3048
                }
6✔
3049
                break;
12✔
3050
            case type_Decimal:
12✔
3051
                change_nullability_list<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3052
                break;
12✔
3053
            case type_UUID:
12✔
3054
                if (is_nullable(from)) {
12✔
3055
                    change_nullability_list<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3056
                }
6✔
3057
                else {
6✔
3058
                    change_nullability_list<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3059
                }
6✔
3060
                break;
12✔
UNCOV
3061
            case type_Link:
✔
UNCOV
3062
            case type_TypedLink:
✔
UNCOV
3063
            case type_LinkList:
✔
3064
                // Can't have lists of these types
UNCOV
3065
            case type_Mixed:
✔
3066
                // These types are no longer supported at all
UNCOV
3067
                REALM_UNREACHABLE();
×
UNCOV
3068
                break;
×
3069
        }
162✔
3070
    }
162✔
3071
    else {
162✔
3072
        switch (type_id) {
162✔
3073
            case type_Int:
36✔
3074
                if (is_nullable(from)) {
36✔
3075
                    change_nullability<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3076
                }
6✔
3077
                else {
30✔
3078
                    change_nullability<int64_t, Optional<int64_t>>(from, to, throw_on_null);
30✔
3079
                }
30✔
3080
                break;
36✔
3081
            case type_Float:
12✔
3082
                change_nullability<float, float>(from, to, throw_on_null);
12✔
3083
                break;
12✔
3084
            case type_Double:
12✔
3085
                change_nullability<double, double>(from, to, throw_on_null);
12✔
3086
                break;
12✔
3087
            case type_Bool:
12✔
3088
                change_nullability<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3089
                break;
12✔
3090
            case type_String:
30✔
3091
                change_nullability<StringData, StringData>(from, to, throw_on_null);
30✔
3092
                break;
30✔
3093
            case type_Binary:
12✔
3094
                change_nullability<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3095
                break;
12✔
3096
            case type_Timestamp:
12✔
3097
                change_nullability<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3098
                break;
12✔
3099
            case type_ObjectId:
12✔
3100
                if (is_nullable(from)) {
12✔
3101
                    change_nullability<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3102
                }
6✔
3103
                else {
6✔
3104
                    change_nullability<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3105
                }
6✔
3106
                break;
12✔
3107
            case type_Decimal:
12✔
3108
                change_nullability<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3109
                break;
12✔
3110
            case type_UUID:
12✔
3111
                if (is_nullable(from)) {
12✔
3112
                    change_nullability<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3113
                }
6✔
3114
                else {
6✔
3115
                    change_nullability<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3116
                }
6✔
3117
                break;
12✔
UNCOV
3118
            case type_TypedLink:
✔
UNCOV
3119
            case type_Link:
✔
3120
                // Always nullable, so can't convert
UNCOV
3121
            case type_LinkList:
✔
3122
                // Never nullable, so can't convert
UNCOV
3123
            case type_Mixed:
✔
3124
                // These types are no longer supported at all
UNCOV
3125
                REALM_UNREACHABLE();
×
UNCOV
3126
                break;
×
3127
        }
162✔
3128
    }
162✔
3129
}
282✔
3130

3131

3132
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3133
{
522✔
3134
    if (col_key.is_nullable() == nullable)
522✔
3135
        return col_key;
240✔
3136

141✔
3137
    check_column(col_key);
282✔
3138

141✔
3139
    auto index_type = search_index_type(col_key);
282✔
3140
    std::string column_name(get_column_name(col_key));
282✔
3141
    auto type = col_key.get_type();
282✔
3142
    auto attr = col_key.get_attrs();
282✔
3143
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3144
    if (nullable) {
282✔
3145
        attr.set(col_attr_Nullable);
150✔
3146
    }
150✔
3147
    else {
132✔
3148
        attr.reset(col_attr_Nullable);
132✔
3149
    }
132✔
3150

141✔
3151
    ColKey new_col = generate_col_key(type, attr);
282✔
3152
    do_insert_root_column(new_col, type, "__temporary");
282✔
3153

141✔
3154
    try {
282✔
3155
        convert_column(col_key, new_col, throw_on_null);
282✔
3156
    }
282✔
3157
    catch (...) {
144✔
3158
        // remove any partially filled column
3✔
3159
        remove_column(new_col);
6✔
3160
        throw;
6✔
3161
    }
6✔
3162

138✔
3163
    if (is_pk_col) {
276✔
3164
        // If we go from non nullable to nullable, no values change,
6✔
3165
        // so it is safe to preserve the pk column. Otherwise it is not
6✔
3166
        // safe as a null entry might have been converted to default value.
6✔
3167
        do_set_primary_key_column(nullable ? new_col : ColKey{});
9✔
3168
    }
12✔
3169

138✔
3170
    erase_root_column(col_key);
276✔
3171
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3172

138✔
3173
    if (index_type != IndexType::None)
276✔
3174
        do_add_search_index(new_col, index_type);
30✔
3175

138✔
3176
    return new_col;
276✔
3177
}
276✔
3178

3179
bool Table::has_any_embedded_objects()
3180
{
2,556,663✔
3181
    if (!m_has_any_embedded_objects) {
2,556,663✔
3182
        m_has_any_embedded_objects = false;
111,342✔
3183
        for_each_public_column([&](ColKey col_key) {
227,253✔
3184
            auto target_table_key = get_opposite_table_key(col_key);
227,253✔
3185
            if (target_table_key && is_link_type(col_key.get_type())) {
227,253✔
3186
                auto target_table = get_parent_group()->get_table(target_table_key);
9,753✔
3187
                if (target_table->is_embedded()) {
9,753✔
3188
                    m_has_any_embedded_objects = true;
7,623✔
3189
                    return IteratorControl::Stop; // early out
7,623✔
3190
                }
7,623✔
3191
            }
219,630✔
3192
            return IteratorControl::AdvanceToNext;
219,630✔
3193
        });
219,630✔
3194
    }
111,342✔
3195
    return *m_has_any_embedded_objects;
2,556,663✔
3196
}
2,556,663✔
3197

3198
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3199
{
168,966✔
3200
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
168,966✔
3201
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
168,966✔
3202
}
168,966✔
3203

3204
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3205
{
41,586✔
3206
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
144,981✔
3207
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
138,171✔
3208
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
34,776✔
3209
        }
34,776✔
3210
    }
138,171✔
3211

20,676✔
3212
    return {};
24,081✔
3213
}
41,586✔
3214

3215
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3216
{
41,514✔
3217
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
41,514✔
3218

20,640✔
3219
    if (!backlink_col_key) {
41,514✔
3220
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
6,810✔
3221
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
6,810✔
3222

3,405✔
3223
        if (Replication* repl = get_repl())
6,810✔
3224
            repl->typed_link_change(get_parent_group()->get_table(origin_table).unchecked_ptr(), origin_col_key,
6,522✔
3225
                                    m_key); // Throws
6,522✔
3226
    }
6,810✔
3227

20,640✔
3228
    return backlink_col_key;
41,514✔
3229
}
41,514✔
3230

3231
TableKey Table::get_opposite_table_key(ColKey col_key) const
3232
{
14,605,662✔
3233
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,605,662✔
3234
}
14,605,662✔
3235

3236
bool Table::links_to_self(ColKey col_key) const
3237
{
52,905✔
3238
    return get_opposite_table_key(col_key) == m_key;
52,905✔
3239
}
52,905✔
3240

3241
TableRef Table::get_opposite_table(ColKey col_key) const
3242
{
7,818,075✔
3243
    if (auto k = get_opposite_table_key(col_key)) {
7,818,075✔
3244
        return get_parent_group()->get_table(k);
7,761,933✔
3245
    }
7,761,933✔
3246
    return {};
56,142✔
3247
}
56,142✔
3248

3249
ColKey Table::get_opposite_column(ColKey col_key) const
3250
{
7,213,824✔
3251
    return ColKey(m_opposite_column.get(col_key.get_index().val));
7,213,824✔
3252
}
7,213,824✔
3253

3254
ColKey Table::find_opposite_column(ColKey col_key) const
UNCOV
3255
{
×
UNCOV
3256
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
UNCOV
3257
        if (m_opposite_column.get(i) == col_key.value) {
×
UNCOV
3258
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
UNCOV
3259
        }
×
UNCOV
3260
    }
×
UNCOV
3261
    return ColKey();
×
UNCOV
3262
}
×
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