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

realm / realm-core / james.stone_385

26 Sep 2023 08:24PM UTC coverage: 90.917% (+0.03%) from 90.892%
james.stone_385

Pull #6670

Evergreen

ironage
fix lint
Pull Request #6670: Sorting stage 3

97066 of 177964 branches covered (0.0%)

901 of 927 new or added lines in 13 files covered. (97.2%)

122 existing lines in 17 files now uncovered.

236115 of 259704 relevant lines covered (90.92%)

6670991.87 hits per line

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

90.15
/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,065✔
269
    if (size() != other.size())
16,065✔
270
        return false;
×
271
    size_t sz = size();
16,065✔
272
    for (size_t i = 0; i < sz; i++) {
25,470✔
273
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
16,197✔
274
        if (this->at(i).second != other.at(i).second)
16,197✔
275
            return false;
6,792✔
276
    }
16,197✔
277
    return true;
12,669✔
278
}
16,065✔
279

280
namespace realm {
281
const char* get_data_type_name(DataType type) noexcept
282
{
650,244✔
283
    switch (type) {
650,244✔
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
{
105,195✔
327
    switch (table_type) {
105,195✔
328
        case Table::Type::TopLevel:
98,679✔
329
            return o << "TopLevel";
98,679✔
330
        case Table::Type::Embedded:
6,432✔
331
            return o << "Embedded";
6,432✔
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
{
798,984✔
341
    // Link column can be a single Link, LinkList, or BackLink.
399,438✔
342
    REALM_ASSERT(m_current_table->valid_column(ck));
798,984✔
343
    ColumnType type = ck.get_type();
798,984✔
344
    if (type == col_type_LinkList || type == col_type_Link || type == col_type_BackLink) {
798,984✔
345
        m_current_table = m_current_table->get_opposite_table(ck);
88,005✔
346
        m_link_cols.push_back(ck);
88,005✔
347
        return true;
88,005✔
348
    }
88,005✔
349
    return false;
710,979✔
350
}
710,979✔
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,582✔
387
    m_spec.set_parent(&m_top, top_position_for_spec);
21,582✔
388
    m_index_refs.set_parent(&m_top, top_position_for_search_indexes);
21,582✔
389
    m_opposite_table.set_parent(&m_top, top_position_for_opposite_table);
21,582✔
390
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
21,582✔
391
    m_cookie = cookie_created;
21,582✔
392
}
21,582✔
393

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

346,035✔
402
    ColumnAttrMask attr;
702,387✔
403
    if (collection_type) {
702,387✔
404
        switch (*collection_type) {
162,942✔
405
            case CollectionType::List:
68,001✔
406
                attr.set(col_attr_List);
68,001✔
407
                break;
68,001✔
408
            case CollectionType::Set:
52,455✔
409
                attr.set(col_attr_Set);
52,455✔
410
                break;
52,455✔
411
            case CollectionType::Dictionary:
42,486✔
412
                attr.set(col_attr_Dictionary);
42,486✔
413
                break;
42,486✔
414
        }
702,387✔
415
    }
702,387✔
416
    if (nullable || type == type_Mixed)
702,387✔
417
        attr.set(col_attr_Nullable);
183,654✔
418
    ColKey col_key = generate_col_key(ColumnType(type), attr);
702,387✔
419

346,035✔
420
    Table* invalid_link = nullptr;
702,387✔
421
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
702,387✔
422
}
702,387✔
423

424
ColKey Table::add_column(Table& target, StringData name, std::optional<CollectionType> collection_type,
425
                         DataType key_type)
426
{
80,859✔
427
    // Both origin and target must be group-level tables, and in the same group.
39,924✔
428
    Group* origin_group = get_parent_group();
80,859✔
429
    Group* target_group = target.get_parent_group();
80,859✔
430
    REALM_ASSERT_RELEASE(origin_group && target_group);
80,859✔
431
    REALM_ASSERT_RELEASE(origin_group == target_group);
80,859✔
432
    // Only links to embedded objects are allowed.
39,924✔
433
    if (is_asymmetric() && !target.is_embedded()) {
80,859✔
434
        throw IllegalOperation("Object property not supported in asymmetric table");
6✔
435
    }
6✔
436
    // Incoming links from an asymmetric table are not allowed.
39,921✔
437
    if (target.is_asymmetric()) {
80,853✔
438
        throw IllegalOperation("Ephemeral objects not supported");
6✔
439
    }
6✔
440

39,918✔
441
    m_has_any_embedded_objects.reset();
80,847✔
442

39,918✔
443
    DataType data_type = type_Link;
80,847✔
444
    ColumnAttrMask attr;
80,847✔
445
    if (collection_type) {
80,847✔
446
        switch (*collection_type) {
51,426✔
447
            case CollectionType::List:
30,048✔
448
                attr.set(col_attr_List);
30,048✔
449
                data_type = type_LinkList;
30,048✔
450
                break;
30,048✔
451
            case CollectionType::Set:
10,440✔
452
                attr.set(col_attr_Set);
10,440✔
453
                break;
10,440✔
454
            case CollectionType::Dictionary:
10,938✔
455
                attr.set(col_attr_Dictionary);
10,938✔
456
                attr.set(col_attr_Nullable);
10,938✔
457
                break;
10,938✔
458
        }
29,421✔
459
    }
29,421✔
460
    else {
29,421✔
461
        attr.set(col_attr_Nullable);
29,421✔
462
    }
29,421✔
463
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
80,847✔
464

39,918✔
465
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
80,847✔
466
}
80,847✔
467

468
void Table::remove_recursive(CascadeState& cascade_state)
469
{
14,139✔
470
    Group* group = get_parent_group();
14,139✔
471
    REALM_ASSERT(group);
14,139✔
472
    cascade_state.m_group = group;
14,139✔
473

6,399✔
474
    do {
18,003✔
475
        cascade_state.send_notifications();
18,003✔
476

8,331✔
477
        for (auto& l : cascade_state.m_to_be_nullified) {
8,355✔
478
            Obj obj = group->get_table(l.origin_table)->try_get_object(l.origin_key);
48✔
479
            REALM_ASSERT_DEBUG(obj);
48✔
480
            if (obj) {
48✔
481
                std::move(obj).nullify_link(l.origin_col_key, l.old_target_link);
48✔
482
            }
48✔
483
        }
48✔
484
        cascade_state.m_to_be_nullified.clear();
18,003✔
485

8,331✔
486
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
18,003✔
487
        for (auto obj : to_delete) {
16,023✔
488
            auto table = group->get_table(obj.first);
15,360✔
489
            // This might add to the list of objects that should be deleted
7,668✔
490
            REALM_ASSERT(!obj.second.is_unresolved());
15,360✔
491
            table->m_clusters.erase(obj.second, cascade_state);
15,360✔
492
        }
15,360✔
493
        nullify_links(cascade_state);
18,003✔
494
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
18,003✔
495
}
14,139✔
496

497
void Table::nullify_links(CascadeState& cascade_state)
498
{
22,446✔
499
    Group* group = get_parent_group();
22,446✔
500
    REALM_ASSERT(group);
22,446✔
501
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
14,403✔
502
        auto table = group->get_table(to_delete.first);
7,683✔
503
        table->m_clusters.nullify_links(to_delete.second, cascade_state);
7,683✔
504
    }
7,683✔
505
}
22,446✔
506

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

519
void Table::remove_column(ColKey col_key)
520
{
17,133✔
521
    check_column(col_key);
17,133✔
522

8,895✔
523
    if (Replication* repl = get_repl())
17,133✔
524
        repl->erase_column(this, col_key); // Throws
462✔
525

8,895✔
526
    if (col_key == m_primary_key_col) {
17,133✔
527
        do_set_primary_key_column(ColKey());
7,377✔
528
    }
7,377✔
529
    else {
9,756✔
530
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
9,756✔
531
    }
9,756✔
532

8,895✔
533
    erase_root_column(col_key); // Throws
17,133✔
534
    m_has_any_embedded_objects.reset();
17,133✔
535
}
17,133✔
536

537

538
void Table::rename_column(ColKey col_key, StringData name)
539
{
105✔
540
    check_column(col_key);
105✔
541

54✔
542
    auto col_ndx = colkey2spec_ndx(col_key);
105✔
543
    m_spec.rename_column(col_ndx, name); // Throws
105✔
544

54✔
545
    bump_content_version();
105✔
546
    bump_storage_version();
105✔
547

54✔
548
    if (Replication* repl = get_repl())
105✔
549
        repl->rename_column(this, col_key, name); // Throws
105✔
550
}
105✔
551

552

553
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
554
{
10,168,350✔
555
    // well, not quite "direct", more like "almost direct":
5,475,267✔
556
    Array table_top(alloc);
10,168,350✔
557
    table_top.init_from_ref(top_ref);
10,168,350✔
558
    if (table_top.size() > 3) {
10,168,350✔
559
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
10,167,768✔
560
        return TableKey(int32_t(rot.get_as_int()));
10,167,768✔
561
    }
10,167,768✔
562
    else {
582✔
563
        return TableKey();
582✔
564
    }
582✔
565
}
10,168,350✔
566

567

568
void Table::init(ref_type top_ref, ArrayParent* parent, size_t ndx_in_parent, bool is_writable, bool is_frzn)
569
{
5,311,962✔
570
    REALM_ASSERT(!(is_writable && is_frzn));
5,311,962✔
571
    m_is_frozen = is_frzn;
5,311,962✔
572
    m_alloc.set_read_only(!is_writable);
5,311,962✔
573
    // Load from allocated memory
3,007,674✔
574
    m_top.set_parent(parent, ndx_in_parent);
5,311,962✔
575
    m_top.init_from_ref(top_ref);
5,311,962✔
576

3,007,674✔
577
    m_spec.init_from_parent();
5,311,962✔
578

3,007,674✔
579
    while (m_top.size() <= top_position_for_pk_col) {
5,311,962✔
580
        m_top.add(0);
×
581
    }
×
582

3,007,674✔
583
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
5,311,962✔
584
        // This is an upgrade - create cluster
585
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
×
586
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
×
587
    }
×
588
    m_clusters.init_from_parent();
5,311,962✔
589

3,007,674✔
590
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
5,311,962✔
591
    if (!rot.is_tagged()) {
5,311,962✔
592
        // Create table key
593
        rot = RefOrTagged::make_tagged(ndx_in_parent);
×
594
        m_top.set(top_position_for_key, rot);
×
595
    }
×
596
    m_key = TableKey(int32_t(rot.get_as_int()));
5,311,962✔
597

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

3,007,674✔
631
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
5,311,962✔
632
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
4,768,170✔
633

3,007,674✔
634
    if (m_top.size() <= top_position_for_flags) {
5,311,962✔
635
        m_table_type = Type::TopLevel;
60✔
636
    }
60✔
637
    else {
5,311,902✔
638
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
5,311,902✔
639
        m_table_type = Type(flags & table_type_mask);
5,311,902✔
640
    }
5,311,902✔
641
    m_has_any_embedded_objects.reset();
5,311,962✔
642

3,007,674✔
643
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
5,311,962✔
644
        // Tombstones exists
408,444✔
645
        if (!m_tombstones) {
844,404✔
646
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
535,230✔
647
        }
535,230✔
648
        m_tombstones->init_from_parent();
844,404✔
649
    }
844,404✔
650
    else {
4,467,558✔
651
        m_tombstones = nullptr;
4,467,558✔
652
    }
4,467,558✔
653
    m_cookie = cookie_initialized;
5,311,962✔
654
}
5,311,962✔
655

656

657
ColKey Table::do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table, DataType key_type)
658
{
783,231✔
659
    col_key = do_insert_root_column(col_key, ColumnType(type), name, key_type); // Throws
783,231✔
660

385,953✔
661
    // When the inserted column is a link-type column, we must also add a
385,953✔
662
    // backlink column to the target table.
385,953✔
663

385,953✔
664
    if (target_table) {
783,231✔
665
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
80,841✔
666
        target_table->check_column(backlink_col_key);
80,841✔
667

39,915✔
668
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
80,841✔
669
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
80,841✔
670
    }
80,841✔
671

385,953✔
672
    if (Replication* repl = get_repl())
783,231✔
673
        repl->insert_column(this, col_key, type, name, target_table); // Throws
765,714✔
674

385,953✔
675
    return col_key;
783,231✔
676
}
783,231✔
677

678
template <typename Type>
679
void do_bulk_insert_index(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
680
{
132,387✔
681
    using LeafType = typename ColumnTypeTraits<Type>::cluster_leaf_type;
132,387✔
682
    LeafType leaf(alloc);
132,387✔
683

65,673✔
684
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
137,475✔
685
        cluster->init_leaf(col_key, &leaf);
137,475✔
686
        index->insert_bulk(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
137,475✔
687
        return IteratorControl::AdvanceToNext;
137,475✔
688
    };
137,475✔
689

65,673✔
690
    table->traverse_clusters(f);
132,387✔
691
}
132,387✔
692

693
void Table::populate_search_index(ColKey col_key)
694
{
132,390✔
695
    auto col_ndx = col_key.get_index().val;
132,390✔
696
    SearchIndex* index = m_index_accessors[col_ndx].get();
132,390✔
697
    DataType type = get_column_type(col_key);
132,390✔
698

65,673✔
699
    if (type == type_Int) {
132,390✔
700
        if (is_nullable(col_key)) {
73,164✔
701
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
10,794✔
702
        }
10,794✔
703
        else {
62,370✔
704
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
62,370✔
705
        }
62,370✔
706
    }
73,164✔
707
    else if (type == type_Bool) {
59,226✔
708
        if (is_nullable(col_key)) {
48✔
709
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
710
        }
24✔
711
        else {
24✔
712
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
24✔
713
        }
24✔
714
    }
48✔
715
    else if (type == type_String) {
59,178✔
716
        do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
20,988✔
717
    }
20,988✔
718
    else if (type == type_Timestamp) {
38,190✔
719
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
93✔
720
    }
93✔
721
    else if (type == type_ObjectId) {
38,097✔
722
        if (is_nullable(col_key)) {
36,525✔
723
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
924✔
724
        }
924✔
725
        else {
35,601✔
726
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
35,601✔
727
        }
35,601✔
728
    }
36,525✔
729
    else if (type == type_UUID) {
1,572✔
730
        if (is_nullable(col_key)) {
678✔
731
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
732
        }
516✔
733
        else {
162✔
734
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
162✔
735
        }
162✔
736
    }
678✔
737
    else if (type == type_Mixed) {
894✔
738
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
894✔
739
    }
894✔
UNCOV
740
    else {
×
UNCOV
741
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
UNCOV
742
    }
×
743
}
132,390✔
744

745
void Table::erase_from_search_indexes(ObjKey key)
746
{
5,087,178✔
747
    // Tombstones do not use index - will crash if we try to erase values
2,542,749✔
748
    if (!key.is_unresolved()) {
5,087,178✔
749
        for (auto&& index : m_index_accessors) {
6,820,731✔
750
            if (index) {
6,820,731✔
751
                index->erase(key);
341,748✔
752
            }
341,748✔
753
        }
6,820,731✔
754
    }
5,073,795✔
755
}
5,087,178✔
756

757
void Table::update_indexes(ObjKey key, const FieldValues& values)
758
{
23,173,668✔
759
    // Tombstones do not use index - will crash if we try to insert values
11,586,594✔
760
    if (key.is_unresolved()) {
23,173,668✔
761
        return;
28,695✔
762
    }
28,695✔
763

11,572,890✔
764
    auto sz = m_index_accessors.size();
23,144,973✔
765
    // values are sorted by column index - there may be values missing
11,572,890✔
766
    auto value = values.begin();
23,144,973✔
767
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
57,192,852✔
768
        // Check if initial value is provided
16,994,613✔
769
        Mixed init_value;
34,047,915✔
770
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
34,047,915✔
771
            // Value for this column is provided
264,519✔
772
            init_value = value->value;
553,524✔
773
            ++value;
553,524✔
774
        }
553,524✔
775

16,994,613✔
776
        if (auto&& index = m_index_accessors[column_ndx]) {
34,047,915✔
777
            // There is an index for this column
550,755✔
778
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,125,774✔
779
            auto type = col_key.get_type();
1,125,774✔
780
            auto attr = col_key.get_attrs();
1,125,774✔
781
            bool nullable = attr.test(col_attr_Nullable);
1,125,774✔
782
            switch (type) {
1,125,774✔
783
                case col_type_Int:
531,072✔
784
                    if (init_value.is_null()) {
531,072✔
785
                        index->insert(key, ArrayIntNull::default_value(nullable));
165,600✔
786
                    }
165,600✔
787
                    else {
365,472✔
788
                        index->insert(key, init_value.get<int64_t>());
365,472✔
789
                    }
365,472✔
790
                    break;
531,072✔
791
                case col_type_Bool:
6,048✔
792
                    if (init_value.is_null()) {
6,048✔
793
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,048✔
794
                    }
6,048✔
795
                    else {
×
796
                        index->insert(key, init_value.get<bool>());
×
797
                    }
×
798
                    break;
6,048✔
799
                case col_type_String:
477,768✔
800
                    if (init_value.is_null()) {
477,768✔
801
                        index->insert(key, ArrayString::default_value(nullable));
431,298✔
802
                    }
431,298✔
803
                    else {
46,470✔
804
                        index->insert(key, init_value.get<String>());
46,470✔
805
                    }
46,470✔
806
                    break;
477,768✔
807
                case col_type_Timestamp:
6,114✔
808
                    if (init_value.is_null()) {
6,114✔
809
                        index->insert(key, ArrayTimestamp::default_value(nullable));
6,114✔
810
                    }
6,114✔
811
                    else {
×
812
                        index->insert(key, init_value.get<Timestamp>());
×
813
                    }
×
814
                    break;
6,114✔
815
                case col_type_ObjectId:
85,635✔
816
                    if (init_value.is_null()) {
85,635✔
817
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
818
                    }
6,120✔
819
                    else {
79,515✔
820
                        index->insert(key, init_value.get<ObjectId>());
79,515✔
821
                    }
79,515✔
822
                    break;
85,635✔
823
                case col_type_Mixed:
834✔
824
                    index->insert(key, init_value);
834✔
825
                    break;
834✔
826
                case col_type_UUID:
18,342✔
827
                    if (init_value.is_null()) {
18,342✔
828
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
6,138✔
829
                    }
6,138✔
830
                    else {
12,204✔
831
                        index->insert(key, init_value.get<UUID>());
12,204✔
832
                    }
12,204✔
833
                    break;
18,342✔
834
                default:
✔
835
                    REALM_UNREACHABLE();
×
836
            }
1,125,774✔
837
        }
1,125,774✔
838
    }
34,047,915✔
839
}
23,144,973✔
840

841
void Table::clear_indexes()
842
{
3,525✔
843
    for (auto&& index : m_index_accessors) {
42,942✔
844
        if (index) {
42,942✔
845
            index->clear();
2,262✔
846
        }
2,262✔
847
    }
42,942✔
848
}
3,525✔
849

850
void Table::do_add_search_index(ColKey col_key, IndexType type)
851
{
132,588✔
852
    size_t column_ndx = col_key.get_index().val;
132,588✔
853

65,772✔
854
    // Early-out if already indexed
65,772✔
855
    if (m_index_accessors[column_ndx] != nullptr)
132,588✔
856
        return;
150✔
857

65,697✔
858
    if (!StringIndex::type_supported(DataType(col_key.get_type())) || col_key.is_collection() ||
132,438✔
859
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
132,414✔
860
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
24✔
861
        // it should probably be a type mismatch exception instead.
24✔
862
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
48✔
863
    }
48✔
864

65,673✔
865
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
65,673✔
866
    // index have 0-entries.
65,673✔
867
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
132,390✔
868
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
132,390✔
869

65,673✔
870
    // Create the index
65,673✔
871
    m_index_accessors[column_ndx] =
132,390✔
872
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
132,390✔
873
    SearchIndex* index = m_index_accessors[column_ndx].get();
132,390✔
874
    // Insert ref to index
65,673✔
875
    index->set_parent(&m_index_refs, column_ndx);
132,390✔
876

65,673✔
877
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
132,390✔
878

65,673✔
879
    populate_search_index(col_key);
132,390✔
880
}
132,390✔
881

882
void Table::add_search_index(ColKey col_key, IndexType type)
883
{
3,963✔
884
    check_column(col_key);
3,963✔
885

1,977✔
886
    // Check spec
1,977✔
887
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,963✔
888
    auto attr = m_spec.get_column_attr(spec_ndx);
3,963✔
889

1,977✔
890
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,963✔
891
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
892

1,974✔
893
    switch (type) {
3,957✔
894
        case IndexType::None:
✔
895
            remove_search_index(col_key);
×
896
            return;
×
897
        case IndexType::Fulltext:
54✔
898
            // Early-out if already indexed
27✔
899
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
900
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
901
                return;
×
902
            }
×
903
            if (attr.test(col_attr_Indexed)) {
54✔
904
                this->remove_search_index(col_key);
×
905
            }
×
906
            break;
54✔
907
        case IndexType::General:
3,903✔
908
            if (attr.test(col_attr_Indexed)) {
3,903✔
909
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
24✔
910
                return;
24✔
911
            }
24✔
912
            if (attr.test(col_attr_FullText_Indexed)) {
3,879✔
913
                this->remove_search_index(col_key);
×
914
            }
×
915
            break;
3,879✔
916
    }
3,933✔
917

1,962✔
918
    do_add_search_index(col_key, type);
3,933✔
919

1,962✔
920
    // Update spec
1,962✔
921
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,906✔
922
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,933✔
923
}
3,933✔
924

925
void Table::remove_search_index(ColKey col_key)
926
{
7,929✔
927
    check_column(col_key);
7,929✔
928
    auto column_ndx = col_key.get_index();
7,929✔
929

4,080✔
930
    // Early-out if non-indexed
4,080✔
931
    if (m_index_accessors[column_ndx.val] == nullptr)
7,929✔
932
        return;
93✔
933

4,026✔
934
    // Destroy and remove the index column
4,026✔
935
    auto& index = m_index_accessors[column_ndx.val];
7,836✔
936
    REALM_ASSERT(index != nullptr);
7,836✔
937
    index->destroy();
7,836✔
938
    index.reset();
7,836✔
939

4,026✔
940
    m_index_refs.set(column_ndx.val, 0);
7,836✔
941

4,026✔
942
    // update spec
4,026✔
943
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
7,836✔
944
    auto attr = m_spec.get_column_attr(spec_ndx);
7,836✔
945
    attr.reset(col_attr_Indexed);
7,836✔
946
    attr.reset(col_attr_FullText_Indexed);
7,836✔
947
    m_spec.set_column_attr(spec_ndx, attr); // Throws
7,836✔
948
}
7,836✔
949

950
void Table::enumerate_string_column(ColKey col_key)
951
{
1,293✔
952
    check_column(col_key);
1,293✔
953
    size_t column_ndx = colkey2spec_ndx(col_key);
1,293✔
954
    ColumnType type = col_key.get_type();
1,293✔
955
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,293✔
956
        m_clusters.enumerate_string_column(col_key);
690✔
957
    }
690✔
958
}
1,293✔
959

960
bool Table::is_enumerated(ColKey col_key) const noexcept
961
{
103,392✔
962
    size_t col_ndx = colkey2spec_ndx(col_key);
103,392✔
963
    return m_spec.is_string_enum_type(col_ndx);
103,392✔
964
}
103,392✔
965

966
size_t Table::get_num_unique_values(ColKey col_key) const
967
{
138✔
968
    if (!is_enumerated(col_key))
138✔
969
        return 0;
84✔
970

27✔
971
    ArrayParent* parent;
54✔
972
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
973
    BPlusTree<StringData> col(get_alloc());
54✔
974
    col.init_from_ref(ref);
54✔
975

27✔
976
    return col.size();
54✔
977
}
54✔
978

979

980
void Table::erase_root_column(ColKey col_key)
981
{
17,409✔
982
    ColumnType col_type = col_key.get_type();
17,409✔
983
    if (is_link_type(col_type)) {
17,409✔
984
        auto target_table = get_opposite_table(col_key);
240✔
985
        auto target_column = get_opposite_column(col_key);
240✔
986
        target_table->do_erase_root_column(target_column);
240✔
987
    }
240✔
988
    do_erase_root_column(col_key); // Throws
17,409✔
989
}
17,409✔
990

991

992
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
993
{
999,516✔
994
    // if col_key specifies a key, it must be unused
493,071✔
995
    REALM_ASSERT(!col_key || !valid_column(col_key));
999,516✔
996

493,071✔
997
    // locate insertion point: ordinary columns must come before backlink columns
493,071✔
998
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
955,185✔
999

493,071✔
1000
    if (!col_key) {
999,516✔
1001
        col_key = generate_col_key(type, {});
87,651✔
1002
    }
87,651✔
1003

493,071✔
1004
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
999,516✔
1005
    if (col_key.is_dictionary()) {
999,516✔
1006
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
53,424✔
1007
    }
53,424✔
1008
    auto col_ndx = col_key.get_index().val;
999,516✔
1009
    build_column_mapping();
999,516✔
1010
    REALM_ASSERT(col_ndx <= m_index_refs.size());
999,516✔
1011
    if (col_ndx == m_index_refs.size()) {
999,516✔
1012
        m_index_refs.insert(col_ndx, 0);
999,243✔
1013
    }
999,243✔
1014
    else {
273✔
1015
        m_index_refs.set(col_ndx, 0);
273✔
1016
    }
273✔
1017
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
999,516✔
1018
    if (col_ndx == m_opposite_table.size()) {
999,516✔
1019
        // m_opposite_table and m_opposite_column are always resized together!
492,942✔
1020
        m_opposite_table.insert(col_ndx, TableKey().value);
999,240✔
1021
        m_opposite_column.insert(col_ndx, ColKey().value);
999,240✔
1022
    }
999,240✔
1023
    else {
276✔
1024
        m_opposite_table.set(col_ndx, TableKey().value);
276✔
1025
        m_opposite_column.set(col_ndx, ColKey().value);
276✔
1026
    }
276✔
1027
    refresh_index_accessors();
999,516✔
1028
    m_clusters.insert_column(col_key);
999,516✔
1029
    if (m_tombstones) {
999,516✔
1030
        m_tombstones->insert_column(col_key);
7,125✔
1031
    }
7,125✔
1032

493,071✔
1033
    bump_storage_version();
999,516✔
1034

493,071✔
1035
    return col_key;
999,516✔
1036
}
999,516✔
1037

1038

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

9,147✔
1059
    build_column_mapping();
17,649✔
1060
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
34,773✔
1061
        REALM_ASSERT(m_index_accessors.back() == nullptr);
17,124✔
1062
        m_index_accessors.pop_back();
17,124✔
1063
    }
17,124✔
1064
    bump_content_version();
17,649✔
1065
    bump_storage_version();
17,649✔
1066
}
17,649✔
1067

1068
Query Table::where(const DictionaryLinkValues& dictionary_of_links) const
1069
{
1,524✔
1070
    return Query(m_own_ref, dictionary_of_links);
1,524✔
1071
}
1,524✔
1072

1073
void Table::set_table_type(Type table_type, bool handle_backlinks)
1074
{
294✔
1075
    if (table_type == m_table_type) {
294✔
1076
        return;
×
1077
    }
×
1078

147✔
1079
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
294✔
1080
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1081
                                                                   get_class_name(), m_table_type, table_type));
×
1082
    }
×
1083

147✔
1084
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
294✔
1085
    set_embedded(table_type == Type::Embedded, handle_backlinks);
294✔
1086
}
294✔
1087

1088
void Table::set_embedded(bool embedded, bool handle_backlinks)
1089
{
294✔
1090
    if (embedded == false) {
294✔
1091
        do_set_table_type(Type::TopLevel);
24✔
1092
        return;
24✔
1093
    }
24✔
1094

135✔
1095
    // Embedded objects cannot have a primary key.
135✔
1096
    if (get_primary_key_column()) {
270✔
1097
        throw IllegalOperation(
6✔
1098
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1099
    }
6✔
1100

132✔
1101
    if (size() == 0) {
264✔
1102
        do_set_table_type(Type::Embedded);
30✔
1103
        return;
30✔
1104
    }
30✔
1105

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

234✔
1122
        for_each_backlink_column([&](ColKey col) {
600✔
1123
            cluster->init_leaf(col, &leaf);
600✔
1124
            // Width zero means all the values are zero and there can't be any backlinks
300✔
1125
            if (leaf.get_width() == 0) {
600✔
1126
                return IteratorControl::AdvanceToNext;
36✔
1127
            }
36✔
1128

282✔
1129
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,792✔
1130
                auto value = leaf.get_as_ref_or_tagged(i);
60,282✔
1131
                if (value.is_ref() && value.get_as_ref() == 0) {
60,282✔
1132
                    // ref of zero means there's no backlinks
29,670✔
1133
                    continue;
59,340✔
1134
                }
59,340✔
1135

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

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

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

234✔
1182
        return IteratorControl::AdvanceToNext;
441✔
1183
    });
468✔
1184

117✔
1185
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
117✔
1186
    for (auto key : orphans) {
59,406✔
1187
        remove_object(key);
59,406✔
1188
    }
59,406✔
1189
    for (auto key : multiple_incoming_links) {
135✔
1190
        auto obj = get_object(key);
36✔
1191
        obj.handle_multiple_backlinks_during_schema_migration();
36✔
1192
        obj.remove();
36✔
1193
    }
36✔
1194

117✔
1195
    do_set_table_type(Type::Embedded);
234✔
1196
}
234✔
1197

1198
void Table::do_set_table_type(Type table_type)
1199
{
335,136✔
1200
    while (m_top.size() <= top_position_for_flags)
335,136✔
1201
        m_top.add(0);
×
1202

166,329✔
1203
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
335,136✔
1204
    // reset bits 0-1
166,329✔
1205
    flags &= ~table_type_mask;
335,136✔
1206
    // set table type
166,329✔
1207
    flags |= static_cast<uint8_t>(table_type);
335,136✔
1208
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
335,136✔
1209
    m_table_type = table_type;
335,136✔
1210
}
335,136✔
1211

1212

1213
void Table::detach(LifeCycleCookie cookie) noexcept
1214
{
5,292,825✔
1215
    m_cookie = cookie;
5,292,825✔
1216
    m_alloc.bump_instance_version();
5,292,825✔
1217
}
5,292,825✔
1218

1219
void Table::fully_detach() noexcept
1220
{
5,291,298✔
1221
    m_spec.detach();
5,291,298✔
1222
    m_top.detach();
5,291,298✔
1223
    m_index_refs.detach();
5,291,298✔
1224
    m_opposite_table.detach();
5,291,298✔
1225
    m_opposite_column.detach();
5,291,298✔
1226
    m_index_accessors.clear();
5,291,298✔
1227
}
5,291,298✔
1228

1229

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

1245

1246
IndexType Table::search_index_type(ColKey col_key) const noexcept
1247
{
9,229,539✔
1248
    if (m_index_accessors[col_key.get_index().val].get()) {
9,229,539✔
1249
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
1,237,851✔
1250
        bool fulltext = attr.test(col_attr_FullText_Indexed);
1,237,851✔
1251
        return fulltext ? IndexType::Fulltext : IndexType::General;
1,237,641✔
1252
    }
1,237,851✔
1253
    return IndexType::None;
7,991,688✔
1254
}
7,991,688✔
1255

1256

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

1281
void Table::migrate_set_orderings()
1282
{
294✔
1283
    std::vector<ColKey> to_migrate;
294✔
1284
    for (auto col : get_column_keys()) {
714✔
1285
        if (col.is_set() && (col.get_type() == col_type_Mixed || col.get_type() == col_type_String ||
714!
1286
                             col.get_type() == col_type_Binary)) {
6!
1287
            to_migrate.push_back(col);
6✔
1288
        }
6✔
1289
    }
714✔
1290
    if (to_migrate.size()) {
294✔
1291
        for (auto obj : *this) {
6✔
1292
            for (auto col : to_migrate) {
6✔
1293
                if (col.get_type() == col_type_Mixed) {
6✔
1294
                    auto set = obj.get_set<Mixed>(col);
6✔
1295
                    set.migration_resort();
6✔
1296
                }
6✔
NEW
1297
                else if (col.get_type() == col_type_Binary) {
×
NEW
1298
                    auto set = obj.get_set<BinaryData>(col);
×
NEW
1299
                    set.migration_resort();
×
NEW
1300
                }
×
NEW
1301
                else {
×
NEW
1302
                    REALM_ASSERT_3(col.get_type(), ==, col_type_String);
×
NEW
1303
                    auto set = obj.get_set<String>(col);
×
NEW
1304
                    set.migration_resort();
×
NEW
1305
                }
×
1306
            }
6✔
1307
        }
6✔
1308
    }
6✔
1309
}
294✔
1310

1311
StringData Table::get_name() const noexcept
1312
{
11,786,067✔
1313
    const Array& real_top = m_top;
11,786,067✔
1314
    ArrayParent* parent = real_top.get_parent();
11,786,067✔
1315
    if (!parent)
11,786,067✔
1316
        return StringData("");
54✔
1317
    REALM_ASSERT(dynamic_cast<Group*>(parent));
11,786,013✔
1318
    return static_cast<Group*>(parent)->get_table_name(get_key());
11,786,013✔
1319
}
11,786,013✔
1320

1321
StringData Table::get_class_name() const noexcept
1322
{
9,072,030✔
1323
    return Group::table_name_to_class_name(get_name());
9,072,030✔
1324
}
9,072,030✔
1325

1326
const char* Table::get_state() const noexcept
1327
{
42✔
1328
    switch (m_cookie) {
42✔
1329
        case cookie_created:
✔
1330
            return "created";
×
1331
        case cookie_transaction_ended:
6✔
1332
            return "transaction_ended";
6✔
1333
        case cookie_initialized:
✔
1334
            return "initialised";
×
1335
        case cookie_removed:
36✔
1336
            return "removed";
36✔
1337
        case cookie_void:
✔
1338
            return "void";
×
1339
        case cookie_deleted:
✔
1340
            return "deleted";
×
1341
    }
×
1342
    return "";
×
1343
}
×
1344

1345

1346
bool Table::is_nullable(ColKey col_key) const
1347
{
913,188✔
1348
    REALM_ASSERT_DEBUG(valid_column(col_key));
913,188✔
1349
    return col_key.get_attrs().test(col_attr_Nullable);
913,188✔
1350
}
913,188✔
1351

1352
bool Table::is_list(ColKey col_key) const
1353
{
169,326✔
1354
    REALM_ASSERT_DEBUG(valid_column(col_key));
169,326✔
1355
    return col_key.get_attrs().test(col_attr_List);
169,326✔
1356
}
169,326✔
1357

1358

1359
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
1360
{
338,502✔
1361
    Array top(alloc);
338,502✔
1362
    _impl::DeepArrayDestroyGuard dg(&top);
338,502✔
1363
    top.create(Array::type_HasRefs); // Throws
338,502✔
1364
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
338,502✔
1365

168,012✔
1366
    {
338,502✔
1367
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
338,502✔
1368
        dg_2.reset(mem.get_ref());
338,502✔
1369
        int_fast64_t v(from_ref(mem.get_ref()));
338,502✔
1370
        top.add(v); // Throws
338,502✔
1371
        dg_2.release();
338,502✔
1372
    }
338,502✔
1373
    top.add(0); // Old position for columns
338,502✔
1374
    {
338,502✔
1375
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
338,502✔
1376
        dg_2.reset(mem.get_ref());
338,502✔
1377
        int_fast64_t v(from_ref(mem.get_ref()));
338,502✔
1378
        top.add(v); // Throws
338,502✔
1379
        dg_2.release();
338,502✔
1380
    }
338,502✔
1381

168,012✔
1382
    // Table key value
168,012✔
1383
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
338,502✔
1384
    top.add(rot);
338,502✔
1385

168,012✔
1386
    // Search indexes
168,012✔
1387
    {
338,502✔
1388
        bool context_flag = false;
338,502✔
1389
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
338,502✔
1390
        dg_2.reset(mem.get_ref());
338,502✔
1391
        int_fast64_t v(from_ref(mem.get_ref()));
338,502✔
1392
        top.add(v); // Throws
338,502✔
1393
        dg_2.release();
338,502✔
1394
    }
338,502✔
1395
    rot = RefOrTagged::make_tagged(0);
338,502✔
1396
    top.add(rot); // Column key
338,502✔
1397
    top.add(rot); // Version
338,502✔
1398
    dg.release();
338,502✔
1399
    // Opposite keys (table and column)
168,012✔
1400
    {
338,502✔
1401
        bool context_flag = false;
338,502✔
1402
        {
338,502✔
1403
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
338,502✔
1404
            dg_2.reset(mem.get_ref());
338,502✔
1405
            int_fast64_t v(from_ref(mem.get_ref()));
338,502✔
1406
            top.add(v); // Throws
338,502✔
1407
            dg_2.release();
338,502✔
1408
        }
338,502✔
1409
        {
338,502✔
1410
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
338,502✔
1411
            dg_2.reset(mem.get_ref());
338,502✔
1412
            int_fast64_t v(from_ref(mem.get_ref()));
338,502✔
1413
            top.add(v); // Throws
338,502✔
1414
            dg_2.release();
338,502✔
1415
        }
338,502✔
1416
    }
338,502✔
1417
    top.add(0); // Sequence number
338,502✔
1418
    top.add(0); // Collision_map
338,502✔
1419
    top.add(0); // pk col key
338,502✔
1420
    top.add(0); // flags
338,502✔
1421
    top.add(0); // tombstones
338,502✔
1422

168,012✔
1423
    REALM_ASSERT(top.size() == top_array_size);
338,502✔
1424

168,012✔
1425
    return top.get_ref();
338,502✔
1426
}
338,502✔
1427

1428
void Table::ensure_graveyard()
1429
{
31,737✔
1430
    if (!m_tombstones) {
31,737✔
1431
        while (m_top.size() < top_position_for_tombstones)
10,107✔
1432
            m_top.add(0);
×
1433
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
10,107✔
1434
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
10,107✔
1435
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
10,107✔
1436
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
10,107✔
1437
        m_tombstones->init_from_parent();
10,107✔
1438
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
24,339✔
1439
            ts->insert_column(col);
24,339✔
1440
            return IteratorControl::AdvanceToNext;
24,339✔
1441
        });
24,339✔
1442
    }
10,107✔
1443
}
31,737✔
1444

1445
void Table::batch_erase_rows(const KeyColumn& keys)
1446
{
5,175✔
1447
    Group* g = get_parent_group();
5,175✔
1448

2,589✔
1449
    size_t num_objs = keys.size();
5,175✔
1450
    std::vector<ObjKey> vec;
5,175✔
1451
    vec.reserve(num_objs);
5,175✔
1452
    for (size_t i = 0; i < num_objs; ++i) {
2,519,205✔
1453
        ObjKey key = keys.get(i);
2,514,030✔
1454
        if (key != null_key && is_valid(key)) {
2,514,030✔
1455
            vec.push_back(key);
2,514,024✔
1456
        }
2,514,024✔
1457
    }
2,514,030✔
1458
    sort(vec.begin(), vec.end());
5,175✔
1459
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
5,175✔
1460

2,589✔
1461
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
5,175✔
1462
        CascadeState state(CascadeState::Mode::Strong, g);
4,407✔
1463
        std::for_each(vec.begin(), vec.end(), [this, &state](ObjKey k) {
3,063✔
1464
            state.m_to_be_deleted.emplace_back(m_key, k);
1,716✔
1465
        });
1,716✔
1466
        nullify_links(state);
4,407✔
1467
        remove_recursive(state);
4,407✔
1468
    }
4,407✔
1469
    else {
768✔
1470
        CascadeState state(CascadeState::Mode::None, g);
768✔
1471
        for (auto k : vec) {
2,512,302✔
1472
            if (g) {
2,512,302✔
1473
                m_clusters.nullify_links(k, state);
2,512,224✔
1474
            }
2,512,224✔
1475
            m_clusters.erase(k, state);
2,512,302✔
1476
        }
2,512,302✔
1477
    }
768✔
1478
}
5,175✔
1479

1480

1481
void Table::clear()
1482
{
3,525✔
1483
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
3,525✔
1484
    m_clusters.clear(state);
3,525✔
1485
    free_collision_table();
3,525✔
1486
}
3,525✔
1487

1488

1489
Group* Table::get_parent_group() const noexcept
1490
{
62,134,701✔
1491
    if (!m_top.is_attached())
62,134,701✔
1492
        return 0;                             // Subtable with shared descriptor
×
1493
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
62,134,701✔
1494
    if (!parent)
62,134,701✔
1495
        return 0; // Free-standing table
25,907,466✔
1496

17,699,499✔
1497
    return static_cast<Group*>(parent);
36,227,235✔
1498
}
36,227,235✔
1499

1500
inline uint64_t Table::get_sync_file_id() const noexcept
1501
{
38,873,076✔
1502
    Group* g = get_parent_group();
38,873,076✔
1503
    return g ? g->get_sync_file_id() : 0;
32,254,671✔
1504
}
38,873,076✔
1505

1506
size_t Table::get_index_in_group() const noexcept
1507
{
36✔
1508
    if (!m_top.is_attached())
36✔
1509
        return realm::npos;                   // Subtable with shared descriptor
×
1510
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
36✔
1511
    if (!parent)
36✔
1512
        return realm::npos; // Free-standing table
×
1513
    return m_top.get_ndx_in_parent();
36✔
1514
}
36✔
1515

1516
uint64_t Table::allocate_sequence_number()
1517
{
19,966,749✔
1518
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
19,966,749✔
1519
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,843,383✔
1520
    rot = RefOrTagged::make_tagged(sn + 1);
19,966,749✔
1521
    m_top.set(top_position_for_sequence_number, rot);
19,966,749✔
1522

9,995,970✔
1523
    return sn;
19,966,749✔
1524
}
19,966,749✔
1525

1526
void Table::set_sequence_number(uint64_t seq)
1527
{
×
1528
    m_top.set(top_position_for_sequence_number, RefOrTagged::make_tagged(seq));
×
1529
}
×
1530

1531
void Table::set_collision_map(ref_type ref)
1532
{
×
1533
    m_top.set(top_position_for_collision_map, RefOrTagged::make_ref(ref));
×
1534
}
×
1535

1536
TableRef Table::get_link_target(ColKey col_key) noexcept
1537
{
326,421✔
1538
    return get_opposite_table(col_key);
326,421✔
1539
}
326,421✔
1540

1541
// count ----------------------------------------------
1542

1543
size_t Table::count_int(ColKey col_key, int64_t value) const
1544
{
12,006✔
1545
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1546
        return index->count(value);
12,000✔
1547
    }
12,000✔
1548

3✔
1549
    return where().equal(col_key, value).count();
6✔
1550
}
6✔
1551
size_t Table::count_float(ColKey col_key, float value) const
1552
{
6✔
1553
    return where().equal(col_key, value).count();
6✔
1554
}
6✔
1555
size_t Table::count_double(ColKey col_key, double value) const
1556
{
6✔
1557
    return where().equal(col_key, value).count();
6✔
1558
}
6✔
1559
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
1560
{
18✔
1561
    ArrayDecimal128 leaf(get_alloc());
18✔
1562
    size_t cnt = 0;
18✔
1563
    bool null_value = value.is_null();
18✔
1564
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
1565
        // direct aggregate on the leaf
9✔
1566
        cluster->init_leaf(col_key, &leaf);
18✔
1567
        auto sz = leaf.size();
18✔
1568
        for (size_t i = 0; i < sz; i++) {
1,296✔
1569
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
1570
                cnt++;
24✔
1571
            }
24✔
1572
        }
1,278✔
1573
        return IteratorControl::AdvanceToNext;
18✔
1574
    };
18✔
1575

9✔
1576
    traverse_clusters(f);
18✔
1577

9✔
1578
    return cnt;
18✔
1579
}
18✔
1580
size_t Table::count_string(ColKey col_key, StringData value) const
1581
{
1,476✔
1582
    if (auto index = this->get_search_index(col_key)) {
1,476✔
1583
        return index->count(value);
732✔
1584
    }
732✔
1585
    return where().equal(col_key, value).count();
744✔
1586
}
744✔
1587

1588
template <typename T>
1589
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1590
{
26,898✔
1591
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
26,898✔
1592
    LeafType leaf(get_alloc());
26,898✔
1593

13,443✔
1594
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
26,916✔
1595
        // direct aggregate on the leaf
13,452✔
1596
        cluster->init_leaf(column_key, &leaf);
26,916✔
1597
        st.m_key_offset = cluster->get_offset();
26,916✔
1598
        st.m_key_values = cluster->get_key_array();
26,916✔
1599

13,452✔
1600
        bool cont = true;
26,916✔
1601
        size_t sz = leaf.size();
26,916✔
1602
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
123,807✔
1603
            auto v = leaf.get(local_index);
96,891✔
1604
            cont = st.match(local_index, v);
96,891✔
1605
        }
96,891✔
1606
        return IteratorControl::AdvanceToNext;
26,916✔
1607
    };
26,916✔
1608

13,443✔
1609
    traverse_clusters(f);
26,898✔
1610
}
26,898✔
1611

1612
// This template is also used by the query engine
1613
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1614
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1615
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1616
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1617
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1618
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1619
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1620

1621
std::optional<Mixed> Table::sum(ColKey col_key) const
1622
{
756✔
1623
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1624
}
756✔
1625

1626
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1627
{
822✔
1628
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1629
}
822✔
1630

1631
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1632
{
1,170✔
1633
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
1634
}
1,170✔
1635

1636
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1637
{
20,622✔
1638
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
20,622✔
1639
}
20,622✔
1640

1641

1642
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1643
{
30,049,761✔
1644
    check_column(col);
30,049,761✔
1645
    return m_index_accessors[col.get_index().val].get();
30,049,761✔
1646
}
30,049,761✔
1647

1648
StringIndex* Table::get_string_index(ColKey col) const noexcept
1649
{
696✔
1650
    check_column(col);
696✔
1651
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
696✔
1652
}
696✔
1653

1654
template <class T>
1655
ObjKey Table::find_first(ColKey col_key, T value) const
1656
{
35,013✔
1657
    check_column(col_key);
35,013✔
1658

17,526✔
1659
    if (!col_key.is_nullable() && value_is_null(value)) {
35,013!
1660
        return {}; // this is a precaution/optimization
6✔
1661
    }
6✔
1662
    // You cannot call GetIndexData on ObjKey
17,523✔
1663
    if constexpr (!std::is_same_v<T, ObjKey>) {
35,007✔
1664
        if (SearchIndex* index = get_search_index(col_key)) {
34,989!
1665
            return index->find_first(value);
27,141✔
1666
        }
27,141✔
1667
        if (col_key == m_primary_key_col) {
7,848!
1668
            return find_primary_key(value);
×
1669
        }
×
1670
    }
7,848✔
1671

3,924✔
1672
    ObjKey key;
7,848✔
1673
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
7,848✔
1674
    LeafType leaf(get_alloc());
7,848✔
1675

3,924✔
1676
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
9,330✔
1677
        cluster->init_leaf(col_key, &leaf);
9,330✔
1678
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
1679
        if (row != realm::npos) {
9,330!
1680
            key = cluster->get_real_key(row);
7,704✔
1681
            return IteratorControl::Stop;
7,704✔
1682
        }
7,704✔
1683
        return IteratorControl::AdvanceToNext;
1,626✔
1684
    };
1,626✔
1685

3,924✔
1686
    traverse_clusters(f);
7,848✔
1687

3,924✔
1688
    return key;
7,848✔
1689
}
7,848✔
1690

1691
namespace realm {
1692

1693
template <>
1694
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1695
{
×
1696
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1697
}
×
1698

1699
template <>
1700
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
1701
{
×
1702
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1703
}
×
1704

1705
template <>
1706
ObjKey Table::find_first(ColKey col_key, null) const
1707
{
×
1708
    return find_first_null(col_key);
×
1709
}
×
1710
} // namespace realm
1711

1712
// Explicitly instantiate the generic case of the template for the types we care about.
1713
template ObjKey Table::find_first(ColKey col_key, bool) const;
1714
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
1715
template ObjKey Table::find_first(ColKey col_key, float) const;
1716
template ObjKey Table::find_first(ColKey col_key, double) const;
1717
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
1718
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
1719
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
1720
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
1721
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
1722
template ObjKey Table::find_first(ColKey col_key, StringData) const;
1723
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
1724
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
1725
template ObjKey Table::find_first(ColKey col_key, UUID) const;
1726
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
1727
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
1728

1729
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1730
{
7,698✔
1731
    if (is_nullable(col_key))
7,698✔
1732
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1733
    else
7,662✔
1734
        return find_first<int64_t>(col_key, value);
7,662✔
1735
}
7,698✔
1736

1737
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1738
{
78✔
1739
    if (is_nullable(col_key))
78✔
1740
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1741
    else
42✔
1742
        return find_first<bool>(col_key, value);
42✔
1743
}
78✔
1744

1745
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1746
{
108✔
1747
    return find_first(col_key, value);
108✔
1748
}
108✔
1749

1750
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1751
{
6✔
1752
    return find_first(col_key, value);
6✔
1753
}
6✔
1754

1755
ObjKey Table::find_first_float(ColKey col_key, float value) const
1756
{
12✔
1757
    return find_first<Float>(col_key, value);
12✔
1758
}
12✔
1759

1760
ObjKey Table::find_first_double(ColKey col_key, double value) const
1761
{
12✔
1762
    return find_first<Double>(col_key, value);
12✔
1763
}
12✔
1764

1765
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
1766
{
×
1767
    return find_first<Decimal128>(col_key, value);
×
1768
}
×
1769

1770
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1771
{
11,427✔
1772
    return find_first<StringData>(col_key, value);
11,427✔
1773
}
11,427✔
1774

1775
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
1776
{
×
1777
    return find_first<BinaryData>(col_key, value);
×
1778
}
×
1779

1780
ObjKey Table::find_first_null(ColKey col_key) const
1781
{
114✔
1782
    return where().equal(col_key, null{}).find();
114✔
1783
}
114✔
1784

1785
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1786
{
18✔
1787
    return find_first(col_key, value);
18✔
1788
}
18✔
1789

1790
template <class T>
1791
TableView Table::find_all(ColKey col_key, T value)
1792
{
108✔
1793
    return where().equal(col_key, value).find_all();
108✔
1794
}
108✔
1795

1796
TableView Table::find_all_int(ColKey col_key, int64_t value)
1797
{
102✔
1798
    return find_all<int64_t>(col_key, value);
102✔
1799
}
102✔
1800

1801
TableView Table::find_all_int(ColKey col_key, int64_t value) const
1802
{
×
1803
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1804
}
×
1805

1806
TableView Table::find_all_bool(ColKey col_key, bool value)
1807
{
×
1808
    return find_all<bool>(col_key, value);
×
1809
}
×
1810

1811
TableView Table::find_all_bool(ColKey col_key, bool value) const
1812
{
×
1813
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1814
}
×
1815

1816

1817
TableView Table::find_all_float(ColKey col_key, float value)
1818
{
×
1819
    return find_all<float>(col_key, value);
×
1820
}
×
1821

1822
TableView Table::find_all_float(ColKey col_key, float value) const
1823
{
×
1824
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
1825
}
×
1826

1827
TableView Table::find_all_double(ColKey col_key, double value)
1828
{
6✔
1829
    return find_all<double>(col_key, value);
6✔
1830
}
6✔
1831

1832
TableView Table::find_all_double(ColKey col_key, double value) const
1833
{
×
1834
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
1835
}
×
1836

1837
TableView Table::find_all_string(ColKey col_key, StringData value)
1838
{
282✔
1839
    return where().equal(col_key, value).find_all();
282✔
1840
}
282✔
1841

1842
TableView Table::find_all_string(ColKey col_key, StringData value) const
1843
{
×
1844
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
1845
}
×
1846

1847
TableView Table::find_all_binary(ColKey, BinaryData)
1848
{
×
1849
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
1850
}
×
1851

1852
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
1853
{
×
1854
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
1855
}
×
1856

1857
TableView Table::find_all_null(ColKey col_key)
1858
{
×
1859
    return where().equal(col_key, null{}).find_all();
×
1860
}
×
1861

1862
TableView Table::find_all_null(ColKey col_key) const
1863
{
×
1864
    return const_cast<Table*>(this)->find_all_null(col_key);
×
1865
}
×
1866

1867
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1868
{
6✔
1869
    return where().fulltext(col_key, terms).find_all();
6✔
1870
}
6✔
1871

1872
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1873
{
72✔
1874
    TableView tv = where().find_all();
72✔
1875
    tv.sort(col_key, ascending);
72✔
1876
    return tv;
72✔
1877
}
72✔
1878

1879
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1880
{
6✔
1881
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1882
}
6✔
1883

1884
TableView Table::get_sorted_view(SortDescriptor order)
1885
{
126✔
1886
    TableView tv = where().find_all();
126✔
1887
    tv.sort(std::move(order));
126✔
1888
    return tv;
126✔
1889
}
126✔
1890

1891
TableView Table::get_sorted_view(SortDescriptor order) const
1892
{
×
1893
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
1894
}
×
1895

1896
util::Logger* Table::get_logger() const noexcept
1897
{
1,793,751✔
1898
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
2,148,383,710✔
1899
}
1,793,751✔
1900

1901
// Called after a commit. Table will effectively contain the same as before,
1902
// but now with new refs from the file
1903
void Table::update_from_parent() noexcept
1904
{
807,624✔
1905
    // There is no top for sub-tables sharing spec
401,556✔
1906
    if (m_top.is_attached()) {
807,624✔
1907
        m_top.update_from_parent();
807,621✔
1908
        m_spec.update_from_parent();
807,621✔
1909
        m_clusters.update_from_parent();
807,621✔
1910
        m_index_refs.update_from_parent();
807,621✔
1911
        for (auto&& index : m_index_accessors) {
2,787,198✔
1912
            if (index != nullptr) {
2,787,198✔
1913
                index->update_from_parent();
270,456✔
1914
            }
270,456✔
1915
        }
2,787,198✔
1916

401,553✔
1917
        m_opposite_table.update_from_parent();
807,621✔
1918
        m_opposite_column.update_from_parent();
807,621✔
1919
        if (m_top.size() > top_position_for_flags) {
807,621✔
1920
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
807,597✔
1921
            m_table_type = Type(flags & table_type_mask);
807,597✔
1922
        }
807,597✔
1923
        else {
24✔
1924
            m_table_type = Type::TopLevel;
24✔
1925
        }
24✔
1926
        if (m_tombstones)
807,621✔
1927
            m_tombstones->update_from_parent();
1,266✔
1928

401,553✔
1929
        refresh_content_version();
807,621✔
1930
        m_has_any_embedded_objects.reset();
807,621✔
1931
    }
807,621✔
1932
    m_alloc.bump_storage_version();
807,624✔
1933
}
807,624✔
1934

1935
void Table::schema_to_json(std::ostream& out, const std::map<std::string, std::string>& renames) const
1936
{
12✔
1937
    out << "{";
12✔
1938
    auto name = get_name();
12✔
1939
    if (renames.count(name))
12✔
1940
        name = renames.at(name);
×
1941
    out << "\"name\":\"" << name << "\"";
12✔
1942
    if (this->m_primary_key_col) {
12✔
1943
        out << ",";
×
1944
        out << "\"primaryKey\":\"" << this->get_column_name(m_primary_key_col) << "\"";
×
1945
    }
×
1946
    out << ",\"tableType\":\"" << this->get_table_type() << "\"";
12✔
1947
    out << ",\"properties\":[";
12✔
1948
    auto col_keys = get_column_keys();
12✔
1949
    int sz = int(col_keys.size());
12✔
1950
    for (int i = 0; i < sz; ++i) {
54✔
1951
        auto col_key = col_keys[i];
42✔
1952
        name = get_column_name(col_key);
42✔
1953
        auto type = col_key.get_type();
42✔
1954
        if (renames.count(name))
42✔
1955
            name = renames.at(name);
×
1956
        out << "{";
42✔
1957
        out << "\"name\":\"" << name << "\"";
42✔
1958
        if (this->is_link_type(type)) {
42✔
1959
            out << ",\"type\":\"object\"";
6✔
1960
            name = this->get_opposite_table(col_key)->get_name();
6✔
1961
            if (renames.count(name))
6✔
1962
                name = renames.at(name);
×
1963
            out << ",\"objectType\":\"" << name << "\"";
6✔
1964
        }
6✔
1965
        else {
36✔
1966
            out << ",\"type\":\"" << get_data_type_name(DataType(type)) << "\"";
36✔
1967
        }
36✔
1968
        if (col_key.is_list()) {
42✔
1969
            out << ",\"isArray\":true";
12✔
1970
        }
12✔
1971
        else if (col_key.is_set()) {
30✔
1972
            out << ",\"isSet\":true";
×
1973
        }
×
1974
        else if (col_key.is_dictionary()) {
30✔
1975
            out << ",\"isMap\":true";
6✔
1976
            auto key_type = get_dictionary_key_type(col_key);
6✔
1977
            out << ",\"keyType\":\"" << get_data_type_name(key_type) << "\"";
6✔
1978
        }
6✔
1979
        if (col_key.is_nullable()) {
42✔
1980
            out << ",\"isOptional\":true";
12✔
1981
        }
12✔
1982
        auto index_type = search_index_type(col_key);
42✔
1983
        if (index_type == IndexType::General) {
42✔
1984
            out << ",\"isIndexed\":true";
×
1985
        }
×
1986
        if (index_type == IndexType::Fulltext) {
42✔
1987
            out << ",\"isFulltextIndexed\":true";
×
1988
        }
×
1989
        out << "}";
42✔
1990
        if (i < sz - 1) {
42✔
1991
            out << ",";
30✔
1992
        }
30✔
1993
    }
42✔
1994
    out << "]}";
12✔
1995
}
12✔
1996

1997
bool Table::operator==(const Table& t) const
1998
{
138✔
1999
    if (size() != t.size()) {
138✔
2000
        return false;
12✔
2001
    }
12✔
2002
    // Check columns
63✔
2003
    for (auto ck : this->get_column_keys()) {
534✔
2004
        auto name = get_column_name(ck);
534✔
2005
        auto other_ck = t.get_column_key(name);
534✔
2006
        auto attrs = ck.get_attrs();
534✔
2007
        if (search_index_type(ck) != t.search_index_type(other_ck))
534✔
2008
            return false;
×
2009

267✔
2010
        if (!other_ck || other_ck.get_attrs() != attrs) {
534✔
2011
            return false;
×
2012
        }
×
2013
    }
534✔
2014
    auto pk_col = get_primary_key_column();
126✔
2015
    for (auto o : *this) {
2,898✔
2016
        Obj other_o;
2,898✔
2017
        if (pk_col) {
2,898✔
2018
            auto pk = o.get_any(pk_col);
90✔
2019
            other_o = t.get_object_with_primary_key(pk);
90✔
2020
        }
90✔
2021
        else {
2,808✔
2022
            other_o = t.get_object(o.get_key());
2,808✔
2023
        }
2,808✔
2024
        if (!(other_o && o == other_o))
2,898✔
2025
            return false;
18✔
2026
    }
2,898✔
2027

63✔
2028
    return true;
117✔
2029
}
126✔
2030

2031

2032
void Table::flush_for_commit()
2033
{
3,985,464✔
2034
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
3,985,464✔
2035
        if (!m_top.is_read_only()) {
3,985,431✔
2036
            ++m_in_file_version_at_transaction_boundary;
1,237,413✔
2037
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
1,237,413✔
2038
            m_top.set(top_position_for_version, rot_version);
1,237,413✔
2039
        }
1,237,413✔
2040
    }
3,985,431✔
2041
}
3,985,464✔
2042

2043
void Table::refresh_content_version()
2044
{
1,167,816✔
2045
    REALM_ASSERT(m_top.is_attached());
1,167,816✔
2046
    if (m_top.size() >= top_position_for_version) {
1,167,816✔
2047
        // we have versioning info in the file. Use this to conditionally
612,441✔
2048
        // bump the version counter:
612,441✔
2049
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,167,495✔
2050
        REALM_ASSERT(rot_version.is_tagged());
1,167,495✔
2051
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,167,495✔
2052
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
226,290✔
2053
            bump_content_version();
226,290✔
2054
        }
226,290✔
2055
    }
1,167,495✔
2056
    else {
321✔
2057
        // assume the worst:
297✔
2058
        bump_content_version();
321✔
2059
    }
321✔
2060
}
1,167,816✔
2061

2062

2063
// Called when Group is moved to another version - either a rollback or an advance.
2064
// The content of the table is potentially different, so make no assumptions.
2065
void Table::refresh_accessor_tree()
2066
{
360,171✔
2067
    REALM_ASSERT(m_cookie == cookie_initialized);
360,171✔
2068
    REALM_ASSERT(m_top.is_attached());
360,171✔
2069
    m_top.init_from_parent();
360,171✔
2070
    m_spec.init_from_parent();
360,171✔
2071
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
360,171✔
2072
    m_clusters.init_from_parent();
360,171✔
2073
    m_index_refs.init_from_parent();
360,171✔
2074
    m_opposite_table.init_from_parent();
360,171✔
2075
    m_opposite_column.init_from_parent();
360,171✔
2076
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
360,171✔
2077
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
305,880✔
2078
    if (m_top.size() > top_position_for_flags) {
360,171✔
2079
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
359,985✔
2080
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
359,985✔
2081
    }
359,985✔
2082
    else {
186✔
2083
        m_table_type = Type::TopLevel;
186✔
2084
    }
186✔
2085
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
360,171✔
2086
        // Tombstones exists
348✔
2087
        if (!m_tombstones) {
696✔
2088
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
228✔
2089
        }
228✔
2090
        m_tombstones->init_from_parent();
696✔
2091
    }
696✔
2092
    else {
359,475✔
2093
        m_tombstones = nullptr;
359,475✔
2094
    }
359,475✔
2095
    refresh_content_version();
360,171✔
2096
    bump_storage_version();
360,171✔
2097
    build_column_mapping();
360,171✔
2098
    refresh_index_accessors();
360,171✔
2099
}
360,171✔
2100

2101
void Table::refresh_index_accessors()
2102
{
6,659,340✔
2103
    // Refresh search index accessors
3,701,382✔
2104

3,701,382✔
2105
    // First eliminate any index accessors for eliminated last columns
3,701,382✔
2106
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
6,659,340✔
2107
    m_index_accessors.resize(col_ndx_end);
6,659,340✔
2108

3,701,382✔
2109
    // Then eliminate/refresh/create accessors within column range
3,701,382✔
2110
    // we can not use for_each_column() here, since the columns may have changed
3,701,382✔
2111
    // and the index accessor vector is not updated correspondingly.
3,701,382✔
2112
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
29,201,472✔
2113
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
22,542,132✔
2114

11,901,183✔
2115
        if (ref == 0) {
22,542,132✔
2116
            // accessor drop
9,873,444✔
2117
            m_index_accessors[col_ndx].reset();
18,512,358✔
2118
        }
18,512,358✔
2119
        else {
4,029,774✔
2120
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
4,029,774✔
2121
            bool fulltext = attr.test(col_attr_FullText_Indexed);
4,029,774✔
2122
            auto col_key = m_leaf_ndx2colkey[col_ndx];
4,029,774✔
2123
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
4,029,759✔
2124

2,027,739✔
2125
            if (m_index_accessors[col_ndx]) { // still there, refresh:
4,029,774✔
2126
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
471,702✔
2127
            }
471,702✔
2128
            else { // new index!
3,558,072✔
2129
                m_index_accessors[col_ndx] =
3,558,072✔
2130
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
3,558,072✔
2131
            }
3,558,072✔
2132
        }
4,029,774✔
2133
    }
22,542,132✔
2134
}
6,659,340✔
2135

2136
bool Table::is_cross_table_link_target() const noexcept
2137
{
7,626✔
2138
    auto is_cross_link = [this](ColKey col_key) {
3,963✔
2139
        auto t = col_key.get_type();
63✔
2140
        // look for a backlink with a different target than ourselves
30✔
2141
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
63✔
2142
                   ? IteratorControl::Stop
42✔
2143
                   : IteratorControl::AdvanceToNext;
51✔
2144
    };
63✔
2145
    return for_each_backlink_column(is_cross_link);
7,626✔
2146
}
7,626✔
2147

2148
// LCOV_EXCL_START ignore debug functions
2149

2150
void Table::verify() const
2151
{
2,973,450✔
2152
#ifdef REALM_DEBUG
2,973,450✔
2153
    if (m_top.is_attached())
2,973,450✔
2154
        m_top.verify();
2,973,450✔
2155
    m_spec.verify();
2,973,450✔
2156
    m_clusters.verify();
2,973,450✔
2157
    if (nb_unresolved())
2,973,450✔
2158
        m_tombstones->verify();
761,607✔
2159
#endif
2,973,450✔
2160
}
2,973,450✔
2161

2162
#ifdef REALM_DEBUG
2163
MemStats Table::stats() const
2164
{
×
2165
    MemStats mem_stats;
×
2166
    m_top.stats(mem_stats);
×
2167
    return mem_stats;
×
2168
}
×
2169
#endif // LCOV_EXCL_STOP ignore debug functions
2170

2171
Obj Table::create_object(ObjKey key, const FieldValues& values)
2172
{
22,674,291✔
2173
    if (is_embedded())
22,674,291✔
2174
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2175
    if (m_primary_key_col)
22,674,243✔
2176
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2177
    if (key == null_key) {
22,674,243✔
2178
        GlobalKey object_id = allocate_object_id_squeezed();
19,506,213✔
2179
        key = object_id.get_local_key(get_sync_file_id());
19,506,213✔
2180
        // Check if this key collides with an already existing object
9,784,437✔
2181
        // This could happen if objects were at some point created with primary keys,
9,784,437✔
2182
        // but later primary key property was removed from the schema.
9,784,437✔
2183
        while (m_clusters.is_valid(key)) {
19,506,213✔
2184
            object_id = allocate_object_id_squeezed();
×
2185
            key = object_id.get_local_key(get_sync_file_id());
×
2186
        }
×
2187
        if (auto repl = get_repl())
19,506,213✔
2188
            repl->create_object(this, object_id);
4,302,618✔
2189
    }
19,506,213✔
2190

11,366,610✔
2191
    REALM_ASSERT(key.value >= 0);
22,674,243✔
2192

11,366,610✔
2193
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,674,243✔
2194

11,366,610✔
2195
    return obj;
22,674,243✔
2196
}
22,674,243✔
2197

2198
Obj Table::create_linked_object()
2199
{
36,336✔
2200
    REALM_ASSERT(is_embedded());
36,336✔
2201

18,108✔
2202
    GlobalKey object_id = allocate_object_id_squeezed();
36,336✔
2203
    ObjKey key = object_id.get_local_key(get_sync_file_id());
36,336✔
2204

18,108✔
2205
    REALM_ASSERT(key.value >= 0);
36,336✔
2206

18,108✔
2207
    Obj obj = m_clusters.insert(key, {});
36,336✔
2208

18,108✔
2209
    return obj;
36,336✔
2210
}
36,336✔
2211

2212
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2213
{
30✔
2214
    if (is_embedded())
30✔
2215
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2216
    if (m_primary_key_col)
30✔
2217
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2218
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2219

15✔
2220
    if (auto repl = get_repl())
30✔
2221
        repl->create_object(this, object_id);
30✔
2222

15✔
2223
    try {
30✔
2224
        Obj obj = m_clusters.insert(key, values);
30✔
2225
        // Check if tombstone exists
15✔
2226
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2227
            auto unres_key = key.get_unresolved();
6✔
2228
            // Copy links over
3✔
2229
            auto tombstone = m_tombstones->get(unres_key);
6✔
2230
            obj.assign_pk_and_backlinks(tombstone);
6✔
2231
            // If tombstones had no links to it, it may still be alive
3✔
2232
            if (m_tombstones->is_valid(unres_key)) {
6✔
2233
                CascadeState state(CascadeState::Mode::None);
6✔
2234
                m_tombstones->erase(unres_key, state);
6✔
2235
            }
6✔
2236
        }
6✔
2237

15✔
2238
        return obj;
30✔
2239
    }
30✔
2240
    catch (const KeyAlreadyUsed&) {
6✔
2241
        return m_clusters.get(key);
6✔
2242
    }
6✔
2243
}
30✔
2244

2245
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2246
                                          bool* did_create)
2247
{
586,062✔
2248
    auto primary_key_col = get_primary_key_column();
586,062✔
2249
    if (is_embedded() || !primary_key_col)
586,062✔
2250
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2251
                              util::format("Table has no primary key: %1", get_name()));
6✔
2252

279,903✔
2253
    DataType type = DataType(primary_key_col.get_type());
586,056✔
2254

279,903✔
2255
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
586,056✔
2256
        throw InvalidArgument(
6✔
2257
            ErrorCodes::PropertyNotNullable,
6✔
2258
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2259
    }
6✔
2260

279,900✔
2261
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
586,050✔
2262
        primary_key.get_type() != type) {
585,831✔
2263
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2264
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2265
    }
6✔
2266

279,897✔
2267
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
586,044✔
2268

279,897✔
2269
    if (did_create)
586,044✔
2270
        *did_create = false;
74,289✔
2271

279,897✔
2272
    // Check for existing object
279,897✔
2273
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
586,044✔
2274
        if (mode == UpdateMode::never) {
82,263✔
2275
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2276
        }
6✔
2277
        auto obj = m_clusters.get(key);
82,257✔
2278
        for (auto& val : field_values) {
40,248✔
2279
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2280
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2281
            }
12✔
2282
        }
12✔
2283
        return obj;
82,257✔
2284
    }
82,257✔
2285

239,652✔
2286
    ObjKey unres_key;
503,781✔
2287
    if (m_tombstones) {
503,781✔
2288
        // Check for potential tombstone
19,383✔
2289
        GlobalKey object_id{primary_key};
39,936✔
2290
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
39,936✔
2291

19,383✔
2292
        ObjKey key = object_key.get_unresolved();
39,936✔
2293
        if (auto obj = m_tombstones->try_get_obj(key)) {
39,936✔
2294
            auto existing_pk_value = obj.get_any(primary_key_col);
13,287✔
2295

6,519✔
2296
            // If the primary key is the same, the object should be resurrected below
6,519✔
2297
            if (existing_pk_value == primary_key) {
13,287✔
2298
                unres_key = key;
13,281✔
2299
            }
13,281✔
2300
        }
13,287✔
2301
    }
39,936✔
2302

239,652✔
2303
    ObjKey key = get_next_valid_key();
503,781✔
2304

239,652✔
2305
    auto repl = get_repl();
503,781✔
2306
    if (repl) {
503,781✔
2307
        repl->create_object_with_primary_key(this, key, primary_key);
442,854✔
2308
    }
442,854✔
2309
    if (did_create) {
503,781✔
2310
        *did_create = true;
36,225✔
2311
    }
36,225✔
2312

239,652✔
2313
    field_values.insert(primary_key_col, primary_key);
503,781✔
2314
    Obj ret = m_clusters.insert(key, field_values);
503,781✔
2315

239,652✔
2316
    // Check if unresolved exists
239,652✔
2317
    if (unres_key) {
503,781✔
2318
        auto tombstone = m_tombstones->get(unres_key);
13,281✔
2319
        ret.assign_pk_and_backlinks(tombstone);
13,281✔
2320
        // If tombstones had no links to it, it may still be alive
6,516✔
2321
        if (m_tombstones->is_valid(unres_key)) {
13,281✔
2322
            CascadeState state(CascadeState::Mode::None);
4,113✔
2323
            m_tombstones->erase(unres_key, state);
4,113✔
2324
        }
4,113✔
2325
    }
13,281✔
2326
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
503,781✔
2327
        get_parent_group()->m_objects_to_delete.emplace_back(this->m_key, ret.get_key());
1,266✔
2328
    }
1,266✔
2329
    return ret;
503,781✔
2330
}
503,781✔
2331

2332
ObjKey Table::find_primary_key(Mixed primary_key) const
2333
{
921,174✔
2334
    auto primary_key_col = get_primary_key_column();
921,174✔
2335
    REALM_ASSERT(primary_key_col);
921,174✔
2336
    DataType type = DataType(primary_key_col.get_type());
921,174✔
2337
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
921,174✔
2338
                 primary_key.get_type() == type);
921,174✔
2339

460,872✔
2340
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
921,174✔
2341
        return index->find_first(primary_key);
921,111✔
2342
    }
921,111✔
2343

33✔
2344
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
33✔
2345
    // so try the old algorithm
33✔
2346
    GlobalKey object_id{primary_key};
63✔
2347
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
63✔
2348

33✔
2349
    // Check if existing
33✔
2350
    if (auto obj = m_clusters.try_get_obj(object_key)) {
63✔
2351
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2352

2353
        if (existing_pk_value == primary_key) {
×
2354
            return object_key;
×
2355
        }
×
2356
    }
63✔
2357
    return {};
63✔
2358
}
63✔
2359

2360
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2361
{
750,009✔
2362
    // Check if existing
376,566✔
2363
    if (auto key = find_primary_key(primary_key)) {
750,009✔
2364
        return key;
718,815✔
2365
    }
718,815✔
2366

14,655✔
2367
    // Object does not exist - create tombstone
14,655✔
2368
    GlobalKey object_id{primary_key};
31,194✔
2369
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
31,194✔
2370
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
31,194✔
2371
}
31,194✔
2372

2373
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2374
{
18✔
2375
    REALM_ASSERT(!m_primary_key_col);
18✔
2376
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2377

9✔
2378
    // Check if existing
9✔
2379
    if (m_clusters.is_valid(object_key)) {
18✔
2380
        return object_key;
12✔
2381
    }
12✔
2382

3✔
2383
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2384
}
6✔
2385

2386
ObjKey Table::get_objkey(GlobalKey global_key) const
2387
{
18✔
2388
    ObjKey key;
18✔
2389
    REALM_ASSERT(!m_primary_key_col);
18✔
2390
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
2391
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
2392
        key = global_key.get_local_key(get_sync_file_id());
6✔
2393
    }
6✔
2394
    if (key && !is_valid(key)) {
18✔
2395
        key = realm::null_key;
6✔
2396
    }
6✔
2397
    return key;
18✔
2398
}
18✔
2399

2400
GlobalKey Table::get_object_id(ObjKey key) const
2401
{
144✔
2402
    auto col = get_primary_key_column();
144✔
2403
    if (col) {
144✔
2404
        const Obj obj = get_object(key);
24✔
2405
        auto val = obj.get_any(col);
24✔
2406
        return {val};
24✔
2407
    }
24✔
2408
    else {
120✔
2409
        return {key, get_sync_file_id()};
120✔
2410
    }
120✔
2411
    return {};
×
2412
}
×
2413

2414
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2415
{
61,626✔
2416
    auto primary_key_col = get_primary_key_column();
61,626✔
2417
    REALM_ASSERT(primary_key_col);
61,626✔
2418
    DataType type = DataType(primary_key_col.get_type());
61,626✔
2419
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
61,626✔
2420
                 primary_key.get_type() == type);
61,626✔
2421
    return m_clusters.get(m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key));
61,626✔
2422
}
61,626✔
2423

2424
Mixed Table::get_primary_key(ObjKey key) const
2425
{
853,848✔
2426
    auto primary_key_col = get_primary_key_column();
853,848✔
2427
    REALM_ASSERT(primary_key_col);
853,848✔
2428
    if (key.is_unresolved()) {
853,848✔
2429
        REALM_ASSERT(m_tombstones);
186✔
2430
        return m_tombstones->get(key).get_any(primary_key_col);
186✔
2431
    }
186✔
2432
    else {
853,662✔
2433
        return m_clusters.get(key).get_any(primary_key_col);
853,662✔
2434
    }
853,662✔
2435
}
853,848✔
2436

2437
GlobalKey Table::allocate_object_id_squeezed()
2438
{
19,537,980✔
2439
    // m_client_file_ident will be zero if we haven't been in contact with
9,799,794✔
2440
    // the server yet.
9,799,794✔
2441
    auto peer_id = get_sync_file_id();
19,537,980✔
2442
    auto sequence = allocate_sequence_number();
19,537,980✔
2443
    return GlobalKey{peer_id, sequence};
19,537,980✔
2444
}
19,537,980✔
2445

2446
namespace {
2447

2448
/// Calculate optimistic local ID that may collide with others. It is up to
2449
/// the caller to ensure that collisions are detected and that
2450
/// allocate_local_id_after_collision() is called to obtain a non-colliding
2451
/// ID.
2452
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
2453
{
71,499✔
2454
#if REALM_EXERCISE_OBJECT_ID_COLLISION
2455
    const uint64_t optimistic_mask = 0xff;
2456
#else
2457
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
71,499✔
2458
#endif
71,499✔
2459
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
71,499✔
2460
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
71,499✔
2461
}
71,499✔
2462

2463
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2464
{
12✔
2465
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2466
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2467
}
12✔
2468

2469
} // namespace
2470

2471
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2472
{
71,499✔
2473
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
71,499✔
2474

34,230✔
2475
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
71,499✔
2476
        Allocator& alloc = m_top.get_alloc();
24✔
2477
        Array collision_map{alloc};
24✔
2478
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2479

12✔
2480
        Array hi{alloc};
24✔
2481
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2482

12✔
2483
        // Entries are ordered by hi,lo
12✔
2484
        size_t found = hi.find_first(object_id.hi());
24✔
2485
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
2486
            Array lo{alloc};
24✔
2487
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
2488
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
2489
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
2490
                Array local_id{alloc};
24✔
2491
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
2492
                return ObjKey{local_id.get(candidate)};
24✔
2493
            }
24✔
2494
        }
71,475✔
2495
    }
24✔
2496

34,218✔
2497
    return optimistic;
71,475✔
2498
}
71,475✔
2499

2500
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2501
                                                     ObjKey colliding_local_id)
2502
{
12✔
2503
    // Possible optimization: Cache these accessors
6✔
2504
    Allocator& alloc = m_top.get_alloc();
12✔
2505
    Array collision_map{alloc};
12✔
2506
    Array hi{alloc};
12✔
2507
    Array lo{alloc};
12✔
2508
    Array local_id{alloc};
12✔
2509

6✔
2510
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2511
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2512
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2513
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2514

6✔
2515
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2516
    if (collision_map_ref) {
12✔
2517
        collision_map.init_from_parent(); // Throws
×
2518
    }
×
2519
    else {
12✔
2520
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2521
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2522
        collision_map.update_parent();
12✔
2523

6✔
2524
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2525
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2526
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2527
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2528
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2529
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2530
    }
12✔
2531

6✔
2532
    hi.init_from_parent();       // Throws
12✔
2533
    lo.init_from_parent();       // Throws
12✔
2534
    local_id.init_from_parent(); // Throws
12✔
2535

6✔
2536
    size_t num_entries = hi.size();
12✔
2537
    REALM_ASSERT(lo.size() == num_entries);
12✔
2538
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2539

6✔
2540
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2541
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2542
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2543
            ++i;
6✔
2544
        return i;
24✔
2545
    };
24✔
2546

6✔
2547
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
2548
        size_t i = lower_bound_object_id(object_id);
24✔
2549
        if (i != num_entries) {
24✔
2550
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
2551
            if (existing == object_id) {
6✔
2552
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
2553
                return;
×
2554
            }
×
2555
        }
24✔
2556
        hi.insert(i, int64_t(object_id.hi()));
24✔
2557
        lo.insert(i, int64_t(object_id.lo()));
24✔
2558
        local_id.insert(i, new_local_id.value);
24✔
2559
        ++num_entries;
24✔
2560
    };
24✔
2561

6✔
2562
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2563
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2564
    insert_collision(incoming_id, new_local_id);
12✔
2565
    insert_collision(colliding_id, colliding_local_id);
12✔
2566

6✔
2567
    return new_local_id;
12✔
2568
}
12✔
2569

2570
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2571
{
31,737✔
2572
    auto unres_key = key.get_unresolved();
31,737✔
2573

14,934✔
2574
    ensure_graveyard();
31,737✔
2575
    auto tombstone = m_tombstones->try_get_obj(unres_key);
31,737✔
2576
    if (tombstone) {
31,737✔
2577
        if (pk_col) {
3,042✔
2578
            auto existing_pk_value = tombstone.get_any(pk_col);
3,042✔
2579
            // It may just be the same object
1,230✔
2580
            if (existing_pk_value != pk_val) {
3,042✔
2581
                // We have a collision - create new ObjKey
6✔
2582
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
2583
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
2584
            }
12✔
2585
        }
3,030✔
2586
        return tombstone;
3,030✔
2587
    }
3,030✔
2588
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
28,695✔
2589
}
28,695✔
2590

2591
void Table::free_local_id_after_hash_collision(ObjKey key)
2592
{
5,087,355✔
2593
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,087,355✔
2594
        if (key.is_unresolved()) {
30✔
2595
            // Keys will always be inserted as resolved
12✔
2596
            key = key.get_unresolved();
24✔
2597
        }
24✔
2598
        // Possible optimization: Cache these accessors
15✔
2599
        Array collision_map{m_alloc};
30✔
2600
        Array local_id{m_alloc};
30✔
2601

15✔
2602
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2603
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2604
        collision_map.init_from_ref(collision_map_ref);
30✔
2605
        local_id.init_from_parent();
30✔
2606
        auto ndx = local_id.find_first(key.value);
30✔
2607
        if (ndx != realm::npos) {
30✔
2608
            Array hi{m_alloc};
24✔
2609
            Array lo{m_alloc};
24✔
2610

12✔
2611
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2612
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2613
            hi.init_from_parent();
24✔
2614
            lo.init_from_parent();
24✔
2615

12✔
2616
            hi.erase(ndx);
24✔
2617
            lo.erase(ndx);
24✔
2618
            local_id.erase(ndx);
24✔
2619
            if (hi.size() == 0) {
24✔
2620
                free_collision_table();
12✔
2621
            }
12✔
2622
        }
24✔
2623
    }
30✔
2624
}
5,087,355✔
2625

2626
void Table::free_collision_table()
2627
{
3,537✔
2628
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
3,537✔
2629
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2630
        m_top.set(top_position_for_collision_map, 0);
12✔
2631
    }
12✔
2632
}
3,537✔
2633

2634
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2635
{
3,885✔
2636
    while (number--) {
6,322,203✔
2637
        keys.push_back(create_object().get_key());
6,318,318✔
2638
    }
6,318,318✔
2639
}
3,885✔
2640

2641
void Table::create_objects(const std::vector<ObjKey>& keys)
2642
{
630✔
2643
    for (auto k : keys) {
5,616✔
2644
        create_object(k);
5,616✔
2645
    }
5,616✔
2646
}
630✔
2647

2648
void Table::dump_objects()
2649
{
×
2650
    m_clusters.dump_objects();
×
2651
    if (nb_unresolved())
×
2652
        m_tombstones->dump_objects();
×
2653
}
×
2654

2655
void Table::remove_object(ObjKey key)
2656
{
2,549,880✔
2657
    Group* g = get_parent_group();
2,549,880✔
2658

1,274,103✔
2659
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,549,880✔
2660
        CascadeState state(CascadeState::Mode::Strong, g);
3,546✔
2661
        state.m_to_be_deleted.emplace_back(m_key, key);
3,546✔
2662
        m_clusters.nullify_links(key, state);
3,546✔
2663
        remove_recursive(state);
3,546✔
2664
    }
3,546✔
2665
    else {
2,546,334✔
2666
        CascadeState state(CascadeState::Mode::None, g);
2,546,334✔
2667
        if (g) {
2,546,334✔
2668
            m_clusters.nullify_links(key, state);
2,464,545✔
2669
        }
2,464,545✔
2670
        m_clusters.erase(key, state);
2,546,334✔
2671
    }
2,546,334✔
2672
}
2,549,880✔
2673

2674
ObjKey Table::invalidate_object(ObjKey key)
2675
{
63,546✔
2676
    if (is_embedded())
63,546✔
2677
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2678
    REALM_ASSERT(!key.is_unresolved());
63,546✔
2679

31,404✔
2680
    Obj tombstone;
63,546✔
2681
    auto obj = get_object(key);
63,546✔
2682
    if (obj.has_backlinks(false)) {
63,546✔
2683
        // If the object has backlinks, we should make a tombstone
273✔
2684
        // and make inward links point to it,
273✔
2685
        if (auto primary_key_col = get_primary_key_column()) {
546✔
2686
            auto pk = obj.get_any(primary_key_col);
390✔
2687
            GlobalKey object_id{pk};
390✔
2688
            auto unres_key = global_to_local_object_id_hashed(object_id);
390✔
2689
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
390✔
2690
        }
390✔
2691
        else {
156✔
2692
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
2693
        }
156✔
2694
        tombstone.assign_pk_and_backlinks(obj);
546✔
2695
    }
546✔
2696

31,404✔
2697
    remove_object(key);
63,546✔
2698

31,404✔
2699
    return tombstone.get_key();
63,546✔
2700
}
63,546✔
2701

2702
void Table::remove_object_recursive(ObjKey key)
2703
{
36✔
2704
    size_t table_ndx = get_index_in_group();
36✔
2705
    if (table_ndx != realm::npos) {
36✔
2706
        CascadeState state(CascadeState::Mode::All, get_parent_group());
36✔
2707
        state.m_to_be_deleted.emplace_back(m_key, key);
36✔
2708
        nullify_links(state);
36✔
2709
        remove_recursive(state);
36✔
2710
    }
36✔
2711
    else {
×
2712
        // No links in freestanding table
2713
        CascadeState state(CascadeState::Mode::None);
×
2714
        m_clusters.erase(key, state);
×
2715
    }
×
2716
}
36✔
2717

2718
Table::Iterator Table::begin() const
2719
{
497,754✔
2720
    return Iterator(m_clusters, 0);
497,754✔
2721
}
497,754✔
2722

2723
Table::Iterator Table::end() const
2724
{
9,783,768✔
2725
    return Iterator(m_clusters, size());
9,783,768✔
2726
}
9,783,768✔
2727

2728
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2729
{
7,077,534✔
2730
    TableRef ret;
7,077,534✔
2731
    if (col_key) {
7,077,768✔
2732
        return table.get_opposite_table(col_key);
7,077,768✔
2733
    }
7,077,768✔
2734
    return ret;
4,294,967,294✔
2735
}
4,294,967,294✔
2736

2737
const uint64_t Table::max_num_columns;
2738

2739
void Table::build_column_mapping()
2740
{
6,682,302✔
2741
    // build column mapping from spec
3,714,066✔
2742
    // TODO: Optimization - Don't rebuild this for every change
3,714,066✔
2743
    m_spec_ndx2leaf_ndx.clear();
6,682,302✔
2744
    m_leaf_ndx2spec_ndx.clear();
6,682,302✔
2745
    m_leaf_ndx2colkey.clear();
6,682,302✔
2746
    size_t num_spec_cols = m_spec.get_column_count();
6,682,302✔
2747
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
6,682,302✔
2748
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
29,194,704✔
2749
        ColKey col_key = m_spec.get_key(spec_ndx);
22,512,402✔
2750
        unsigned leaf_ndx = col_key.get_index().val;
22,512,402✔
2751
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
22,512,402✔
2752
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
22,042,896✔
2753
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
22,042,896✔
2754
        }
22,042,896✔
2755
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
22,512,402✔
2756
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
22,512,402✔
2757
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
22,512,402✔
2758
    }
22,512,402✔
2759
}
6,682,302✔
2760

2761
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2762
{
999,516✔
2763
    REALM_ASSERT(!attr.test(col_attr_Indexed));
999,516✔
2764
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
999,516✔
2765

493,071✔
2766
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
999,516✔
2767
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
999,516✔
2768

493,071✔
2769
    // reuse lowest available leaf ndx:
493,071✔
2770
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
999,516✔
2771
    // look for an unused entry:
493,071✔
2772
    for (unsigned idx = 0; idx < lower; ++idx) {
6,491,004✔
2773
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
5,491,581✔
2774
            lower = idx;
93✔
2775
            break;
93✔
2776
        }
93✔
2777
    }
5,491,581✔
2778
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
999,516✔
2779
}
999,516✔
2780

2781
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2782
                                                  StringData origin_col_name) const noexcept
2783
{
×
2784
    BacklinkOrigin ret;
×
2785
    auto f = [&](ColKey backlink_col_key) {
×
2786
        auto origin_table = get_opposite_table(backlink_col_key);
×
2787
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2788
        if (origin_table->get_name() == origin_table_name &&
×
2789
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
2790
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
2791
            return IteratorControl::Stop;
×
2792
        }
×
2793
        return IteratorControl::AdvanceToNext;
×
2794
    };
×
2795
    this->for_each_backlink_column(f);
×
2796
    return ret;
×
2797
}
×
2798

2799
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2800
{
360✔
2801
    try {
360✔
2802
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
360✔
2803
        ColKey linked_column_key = get_opposite_column(backlink_col);
360✔
2804
        if (linked_table_key == m_key) {
360✔
2805
            return {{m_own_ref, linked_column_key}};
24✔
2806
        }
24✔
2807
        else {
336✔
2808
            Group* current_group = get_parent_group();
336✔
2809
            if (current_group) {
336✔
2810
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2811
                return {{linked_table_ref, linked_column_key}};
336✔
2812
            }
336✔
2813
        }
×
2814
    }
360✔
2815
    catch (...) {
×
2816
        // backlink column not found, returning empty optional
2817
    }
×
2818
    return {};
180✔
2819
}
360✔
2820

2821
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
2822
{
×
2823
    std::vector<std::pair<TableKey, ColKey>> origins;
×
2824
    auto f = [&](ColKey backlink_col_key) {
×
2825
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
2826
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2827
        origins.emplace_back(origin_table_key, origin_link_col);
×
2828
        return IteratorControl::AdvanceToNext;
×
2829
    };
×
2830
    this->for_each_backlink_column(f);
×
2831
    return origins;
×
2832
}
×
2833

2834
ColKey Table::get_primary_key_column() const
2835
{
25,533,012✔
2836
    return m_primary_key_col;
25,533,012✔
2837
}
25,533,012✔
2838

2839
void Table::set_primary_key_column(ColKey col_key)
2840
{
720✔
2841
    if (col_key == m_primary_key_col) {
720✔
2842
        return;
378✔
2843
    }
378✔
2844

171✔
2845
    if (Replication* repl = get_repl()) {
342✔
2846
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
2847
            throw RuntimeError(
×
2848
                ErrorCodes::BrokenInvariant,
×
2849
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
2850
        }
×
2851
    }
342✔
2852

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

171✔
2855
    if (col_key) {
342✔
2856
        check_column(col_key);
222✔
2857
        validate_column_is_unique(col_key);
222✔
2858
        do_set_primary_key_column(col_key);
222✔
2859
    }
222✔
2860
    else {
120✔
2861
        do_set_primary_key_column(col_key);
120✔
2862
    }
120✔
2863
}
342✔
2864

2865

2866
void Table::do_set_primary_key_column(ColKey col_key)
2867
{
136,056✔
2868
    if (col_key) {
136,056✔
2869
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
128,553✔
2870
        auto attr = m_spec.get_column_attr(spec_ndx);
128,553✔
2871
        if (attr.test(col_attr_FullText_Indexed)) {
128,553✔
2872
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
2873
        }
6✔
2874
    }
136,050✔
2875

67,620✔
2876
    if (m_primary_key_col) {
136,050✔
2877
        // If the search index has not been set explicitly on current pk col, we remove it again
3,876✔
2878
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
7,527✔
2879
        auto attr = m_spec.get_column_attr(spec_ndx);
7,527✔
2880
        if (!attr.test(col_attr_Indexed)) {
7,527✔
2881
            remove_search_index(m_primary_key_col);
7,515✔
2882
        }
7,515✔
2883
    }
7,527✔
2884

67,620✔
2885
    if (col_key) {
136,050✔
2886
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
128,547✔
2887
        do_add_search_index(col_key, IndexType::General);
128,547✔
2888
    }
128,547✔
2889
    else {
7,503✔
2890
        m_top.set(top_position_for_pk_col, 0);
7,503✔
2891
    }
7,503✔
2892

67,620✔
2893
    m_primary_key_col = col_key;
136,050✔
2894
}
136,050✔
2895

2896
bool Table::contains_unique_values(ColKey col) const
2897
{
798✔
2898
    if (search_index_type(col) == IndexType::General) {
798✔
2899
        auto search_index = get_search_index(col);
708✔
2900
        return !search_index->has_duplicate_values();
708✔
2901
    }
708✔
2902
    else {
90✔
2903
        TableView tv = where().find_all();
90✔
2904
        tv.distinct(col);
90✔
2905
        return tv.size() == size();
90✔
2906
    }
90✔
2907
}
798✔
2908

2909
void Table::validate_column_is_unique(ColKey col) const
2910
{
798✔
2911
    if (!contains_unique_values(col)) {
798✔
2912
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
2913
                                           get_class_name(), get_column_name(col)));
30✔
2914
    }
30✔
2915
}
798✔
2916

2917
void Table::validate_primary_column()
2918
{
1,686✔
2919
    if (ColKey col = get_primary_key_column()) {
1,686✔
2920
        validate_column_is_unique(col);
576✔
2921
    }
576✔
2922
}
1,686✔
2923

2924
ObjKey Table::get_next_valid_key()
2925
{
503,790✔
2926
    ObjKey key;
503,790✔
2927
    do {
503,796✔
2928
        key = ObjKey(allocate_sequence_number());
503,796✔
2929
    } while (m_clusters.is_valid(key));
503,796✔
2930

239,652✔
2931
    return key;
503,790✔
2932
}
503,790✔
2933

2934
namespace {
2935
template <class T>
2936
typename util::RemoveOptional<T>::type remove_optional(T val)
2937
{
88,050✔
2938
    return val;
88,050✔
2939
}
88,050✔
2940
template <>
2941
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
2942
{
5,415✔
2943
    return *val;
5,415✔
2944
}
5,415✔
2945
template <>
2946
bool remove_optional<Optional<bool>>(Optional<bool> val)
2947
{
11,457✔
2948
    return *val;
11,457✔
2949
}
11,457✔
2950
template <>
2951
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
2952
{
5,427✔
2953
    return *val;
5,427✔
2954
}
5,427✔
2955
template <>
2956
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
2957
{
6,060✔
2958
    return *val;
6,060✔
2959
}
6,060✔
2960
} // namespace
2961

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

81✔
2970
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
2971
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
2972
        cluster->init_leaf(key_from, &from_arr);
162✔
2973
        cluster->init_leaf(key_to, &to_arr);
162✔
2974

81✔
2975
        for (size_t i = 0; i < sz; i++) {
1,512✔
2976
            if (from_nullability && from_arr.is_null(i)) {
1,356!
2977
                if (throw_on_null) {
66!
2978
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
2979
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
2980
                                                    get_column_name(key_from)));
6✔
2981
                }
6✔
2982
                else {
60✔
2983
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
60✔
2984
                }
60✔
2985
            }
66✔
2986
            else {
1,290✔
2987
                auto v = remove_optional(from_arr.get(i));
1,290✔
2988
                to_arr.set(i, v);
1,290✔
2989
            }
1,290✔
2990
        }
1,356✔
2991
    };
162✔
2992

81✔
2993
    m_clusters.update(func);
162✔
2994
}
162✔
2995

2996
template <class F, class T>
2997
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
2998
{
120✔
2999
    Allocator& allocator = this->get_alloc();
120✔
3000
    bool from_nullability = is_nullable(key_from);
120✔
3001
    auto func = [&](Cluster* cluster) {
120✔
3002
        size_t sz = cluster->node_size();
120✔
3003

60✔
3004
        ArrayInteger from_arr(allocator);
120✔
3005
        ArrayInteger to_arr(allocator);
120✔
3006
        cluster->init_leaf(key_from, &from_arr);
120✔
3007
        cluster->init_leaf(key_to, &to_arr);
120✔
3008

60✔
3009
        for (size_t i = 0; i < sz; i++) {
360✔
3010
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3011
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3012
            REALM_ASSERT(!ref_to);
240✔
3013

120✔
3014
            if (ref_from) {
240✔
3015
                BPlusTree<F> from_list(allocator);
120✔
3016
                BPlusTree<T> to_list(allocator);
120✔
3017
                from_list.init_from_ref(ref_from);
120✔
3018
                to_list.create();
120✔
3019
                size_t n = from_list.size();
120✔
3020
                for (size_t j = 0; j < n; j++) {
120,120✔
3021
                    auto v = from_list.get(j);
120,000✔
3022
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
3023
                        to_list.add(remove_optional(v));
115,119✔
3024
                    }
115,119✔
3025
                    else {
4,881✔
3026
                        if (throw_on_null) {
4,881!
3027
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3028
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3029
                                                            get_name(), get_column_name(key_from)));
×
3030
                        }
×
3031
                        else {
4,881✔
3032
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,881✔
3033
                        }
4,881✔
3034
                    }
4,881✔
3035
                }
120,000✔
3036
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3037
            }
120✔
3038
        }
240✔
3039
    };
120✔
3040

60✔
3041
    m_clusters.update(func);
120✔
3042
}
120✔
3043

3044
void Table::convert_column(ColKey from, ColKey to, bool throw_on_null)
3045
{
282✔
3046
    realm::DataType type_id = get_column_type(from);
282✔
3047
    bool _is_list = is_list(from);
282✔
3048
    if (_is_list) {
282✔
3049
        switch (type_id) {
120✔
3050
            case type_Int:
12✔
3051
                if (is_nullable(from)) {
12✔
3052
                    change_nullability_list<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3053
                }
6✔
3054
                else {
6✔
3055
                    change_nullability_list<int64_t, Optional<int64_t>>(from, to, throw_on_null);
6✔
3056
                }
6✔
3057
                break;
12✔
3058
            case type_Float:
12✔
3059
                change_nullability_list<float, float>(from, to, throw_on_null);
12✔
3060
                break;
12✔
3061
            case type_Double:
12✔
3062
                change_nullability_list<double, double>(from, to, throw_on_null);
12✔
3063
                break;
12✔
3064
            case type_Bool:
12✔
3065
                change_nullability_list<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3066
                break;
12✔
3067
            case type_String:
12✔
3068
                change_nullability_list<StringData, StringData>(from, to, throw_on_null);
12✔
3069
                break;
12✔
3070
            case type_Binary:
12✔
3071
                change_nullability_list<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3072
                break;
12✔
3073
            case type_Timestamp:
12✔
3074
                change_nullability_list<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3075
                break;
12✔
3076
            case type_ObjectId:
12✔
3077
                if (is_nullable(from)) {
12✔
3078
                    change_nullability_list<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3079
                }
6✔
3080
                else {
6✔
3081
                    change_nullability_list<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3082
                }
6✔
3083
                break;
12✔
3084
            case type_Decimal:
12✔
3085
                change_nullability_list<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3086
                break;
12✔
3087
            case type_UUID:
12✔
3088
                if (is_nullable(from)) {
12✔
3089
                    change_nullability_list<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3090
                }
6✔
3091
                else {
6✔
3092
                    change_nullability_list<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3093
                }
6✔
3094
                break;
12✔
3095
            case type_Link:
✔
3096
            case type_TypedLink:
✔
3097
            case type_LinkList:
✔
3098
                // Can't have lists of these types
3099
            case type_Mixed:
✔
3100
                // These types are no longer supported at all
3101
                REALM_UNREACHABLE();
×
3102
                break;
×
3103
        }
162✔
3104
    }
162✔
3105
    else {
162✔
3106
        switch (type_id) {
162✔
3107
            case type_Int:
36✔
3108
                if (is_nullable(from)) {
36✔
3109
                    change_nullability<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3110
                }
6✔
3111
                else {
30✔
3112
                    change_nullability<int64_t, Optional<int64_t>>(from, to, throw_on_null);
30✔
3113
                }
30✔
3114
                break;
36✔
3115
            case type_Float:
12✔
3116
                change_nullability<float, float>(from, to, throw_on_null);
12✔
3117
                break;
12✔
3118
            case type_Double:
12✔
3119
                change_nullability<double, double>(from, to, throw_on_null);
12✔
3120
                break;
12✔
3121
            case type_Bool:
12✔
3122
                change_nullability<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3123
                break;
12✔
3124
            case type_String:
30✔
3125
                change_nullability<StringData, StringData>(from, to, throw_on_null);
30✔
3126
                break;
30✔
3127
            case type_Binary:
12✔
3128
                change_nullability<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3129
                break;
12✔
3130
            case type_Timestamp:
12✔
3131
                change_nullability<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3132
                break;
12✔
3133
            case type_ObjectId:
12✔
3134
                if (is_nullable(from)) {
12✔
3135
                    change_nullability<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3136
                }
6✔
3137
                else {
6✔
3138
                    change_nullability<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3139
                }
6✔
3140
                break;
12✔
3141
            case type_Decimal:
12✔
3142
                change_nullability<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3143
                break;
12✔
3144
            case type_UUID:
12✔
3145
                if (is_nullable(from)) {
12✔
3146
                    change_nullability<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3147
                }
6✔
3148
                else {
6✔
3149
                    change_nullability<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3150
                }
6✔
3151
                break;
12✔
3152
            case type_TypedLink:
✔
3153
            case type_Link:
✔
3154
                // Always nullable, so can't convert
3155
            case type_LinkList:
✔
3156
                // Never nullable, so can't convert
3157
            case type_Mixed:
✔
3158
                // These types are no longer supported at all
3159
                REALM_UNREACHABLE();
×
3160
                break;
×
3161
        }
162✔
3162
    }
162✔
3163
}
282✔
3164

3165

3166
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3167
{
522✔
3168
    if (col_key.is_nullable() == nullable)
522✔
3169
        return col_key;
240✔
3170

141✔
3171
    check_column(col_key);
282✔
3172

141✔
3173
    auto index_type = search_index_type(col_key);
282✔
3174
    std::string column_name(get_column_name(col_key));
282✔
3175
    auto type = col_key.get_type();
282✔
3176
    auto attr = col_key.get_attrs();
282✔
3177
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3178
    if (nullable) {
282✔
3179
        attr.set(col_attr_Nullable);
150✔
3180
    }
150✔
3181
    else {
132✔
3182
        attr.reset(col_attr_Nullable);
132✔
3183
    }
132✔
3184

141✔
3185
    ColKey new_col = generate_col_key(type, attr);
282✔
3186
    do_insert_root_column(new_col, type, "__temporary");
282✔
3187

141✔
3188
    try {
282✔
3189
        convert_column(col_key, new_col, throw_on_null);
282✔
3190
    }
282✔
3191
    catch (...) {
144✔
3192
        // remove any partially filled column
3✔
3193
        remove_column(new_col);
6✔
3194
        throw;
6✔
3195
    }
6✔
3196

138✔
3197
    if (is_pk_col) {
276✔
3198
        // If we go from non nullable to nullable, no values change,
6✔
3199
        // so it is safe to preserve the pk column. Otherwise it is not
6✔
3200
        // safe as a null entry might have been converted to default value.
6✔
3201
        do_set_primary_key_column(nullable ? new_col : ColKey{});
9✔
3202
    }
12✔
3203

138✔
3204
    erase_root_column(col_key);
276✔
3205
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3206

138✔
3207
    if (index_type != IndexType::None)
276✔
3208
        do_add_search_index(new_col, index_type);
30✔
3209

138✔
3210
    return new_col;
276✔
3211
}
276✔
3212

3213
bool Table::has_any_embedded_objects()
3214
{
2,555,010✔
3215
    if (!m_has_any_embedded_objects) {
2,555,010✔
3216
        m_has_any_embedded_objects = false;
109,791✔
3217
        for_each_public_column([&](ColKey col_key) {
222,165✔
3218
            auto target_table_key = get_opposite_table_key(col_key);
222,165✔
3219
            if (target_table_key && is_link_type(col_key.get_type())) {
222,165✔
3220
                auto target_table = get_parent_group()->get_table(target_table_key);
9,573✔
3221
                if (target_table->is_embedded()) {
9,573✔
3222
                    m_has_any_embedded_objects = true;
7,311✔
3223
                    return IteratorControl::Stop; // early out
7,311✔
3224
                }
7,311✔
3225
            }
214,854✔
3226
            return IteratorControl::AdvanceToNext;
214,854✔
3227
        });
214,854✔
3228
    }
109,791✔
3229
    return *m_has_any_embedded_objects;
2,555,010✔
3230
}
2,555,010✔
3231

3232
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3233
{
168,492✔
3234
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
168,492✔
3235
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
168,492✔
3236
}
168,492✔
3237

3238
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3239
{
41,586✔
3240
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
144,981✔
3241
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
138,171✔
3242
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
34,776✔
3243
        }
34,776✔
3244
    }
138,171✔
3245

20,676✔
3246
    return {};
24,081✔
3247
}
41,586✔
3248

3249
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3250
{
41,514✔
3251
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
41,514✔
3252

20,640✔
3253
    if (!backlink_col_key) {
41,514✔
3254
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
6,810✔
3255
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
6,810✔
3256

3,405✔
3257
        if (Replication* repl = get_repl())
6,810✔
3258
            repl->typed_link_change(get_parent_group()->get_table(origin_table).unchecked_ptr(), origin_col_key,
6,522✔
3259
                                    m_key); // Throws
6,522✔
3260
    }
6,810✔
3261

20,640✔
3262
    return backlink_col_key;
41,514✔
3263
}
41,514✔
3264

3265
TableKey Table::get_opposite_table_key(ColKey col_key) const
3266
{
14,614,776✔
3267
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,614,776✔
3268
}
14,614,776✔
3269

3270
bool Table::links_to_self(ColKey col_key) const
3271
{
52,959✔
3272
    return get_opposite_table_key(col_key) == m_key;
52,959✔
3273
}
52,959✔
3274

3275
TableRef Table::get_opposite_table(ColKey col_key) const
3276
{
7,832,283✔
3277
    if (auto k = get_opposite_table_key(col_key)) {
7,832,283✔
3278
        return get_parent_group()->get_table(k);
7,777,209✔
3279
    }
7,777,209✔
3280
    return {};
55,074✔
3281
}
55,074✔
3282

3283
ColKey Table::get_opposite_column(ColKey col_key) const
3284
{
7,211,037✔
3285
    return ColKey(m_opposite_column.get(col_key.get_index().val));
7,211,037✔
3286
}
7,211,037✔
3287

3288
ColKey Table::find_opposite_column(ColKey col_key) const
3289
{
×
3290
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3291
        if (m_opposite_column.get(i) == col_key.value) {
×
3292
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3293
        }
×
3294
    }
×
3295
    return ColKey();
×
3296
}
×
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