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

realm / realm-core / 1850

20 Nov 2023 10:36AM UTC coverage: 91.692% (-0.2%) from 91.907%
1850

push

Evergreen

web-flow
Stop failing when sync is disabled and MacOs debug tests are set to run (#7146)

92316 of 169214 branches covered (0.0%)

231566 of 252547 relevant lines covered (91.69%)

6538009.25 hits per line

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

90.76
/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,491✔
269
    if (size() != other.size())
16,491✔
270
        return false;
×
271
    size_t sz = size();
16,491✔
272
    for (size_t i = 0; i < sz; i++) {
26,304✔
273
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
16,623✔
274
        if (this->at(i).second != other.at(i).second)
16,623✔
275
            return false;
6,810✔
276
    }
16,623✔
277
    return true;
13,086✔
278
}
16,491✔
279

280
namespace realm {
281
const char* get_data_type_name(DataType type) noexcept
282
{
649,050✔
283
    switch (type) {
649,050✔
284
        case type_Int:
1,284✔
285
            return "int";
1,284✔
286
        case type_Bool:
396✔
287
            return "bool";
396✔
288
        case type_Float:
624✔
289
            return "float";
624✔
290
        case type_Double:
786✔
291
            return "double";
786✔
292
        case type_String:
786✔
293
            return "string";
786✔
294
        case type_Binary:
324✔
295
            return "binary";
324✔
296
        case type_Timestamp:
594✔
297
            return "timestamp";
594✔
298
        case type_ObjectId:
642,852✔
299
            return "objectId";
642,852✔
300
        case type_Decimal:
666✔
301
            return "decimal128";
666✔
302
        case type_UUID:
594✔
303
            return "uuid";
594✔
304
        case type_Mixed:
✔
305
            return "mixed";
×
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
{
19,614✔
327
    switch (table_type) {
19,614✔
328
        case Table::Type::TopLevel:
19,572✔
329
            return o << "TopLevel";
19,572✔
330
        case Table::Type::Embedded:
6✔
331
            return o << "Embedded";
6✔
332
        case Table::Type::TopLevelAsymmetric:
36✔
333
            return o << "TopLevelAsymmetric";
36✔
334
    }
×
335
    return o << "Invalid table type: " << uint8_t(table_type);
×
336
}
×
337
} // namespace realm
338

339
void LinkChain::add(ColKey ck)
340
{
87,975✔
341
    // Link column can be a single Link, LinkList, or BackLink.
43,989✔
342
    REALM_ASSERT(m_current_table->valid_column(ck));
87,975✔
343
    ColumnType type = ck.get_type();
87,975✔
344
    if (type == col_type_LinkList || type == col_type_Link || type == col_type_BackLink) {
87,975✔
345
        m_current_table = m_current_table->get_opposite_table(ck);
85,389✔
346
    }
85,389✔
347
    else {
2,586✔
348
        // Only last column in link chain is allowed to be non-link
1,296✔
349
        throw LogicError(ErrorCodes::TypeMismatch,
2,586✔
350
                         util::format("Property '%1.%2' is not an object reference",
2,586✔
351
                                      m_current_table->get_class_name(), m_current_table->get_column_name(ck)));
2,586✔
352
    }
2,586✔
353
    m_link_cols.push_back(ck);
85,389✔
354
}
85,389✔
355

356
// -- Table ---------------------------------------------------------------------------------
357

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

1,779✔
374
    ref_type ref = create_empty_table(m_alloc); // Throws
3,558✔
375
    ArrayParent* parent = nullptr;
3,558✔
376
    size_t ndx_in_parent = 0;
3,558✔
377
    init(ref, parent, ndx_in_parent, true, false);
3,558✔
378
}
3,558✔
379

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

398
ColKey Table::add_column(DataType type, StringData name, bool nullable)
399
{
507,483✔
400
    REALM_ASSERT(!is_link_type(ColumnType(type)));
507,483✔
401

249,279✔
402
    Table* invalid_link = nullptr;
507,483✔
403
    ColumnAttrMask attr;
507,483✔
404
    if (nullable || type == type_Mixed)
507,483✔
405
        attr.set(col_attr_Nullable);
111,450✔
406
    ColKey col_key = generate_col_key(ColumnType(type), attr);
507,483✔
407

249,279✔
408
    return do_insert_column(col_key, type, name, invalid_link); // Throws
507,483✔
409
}
507,483✔
410

411
ColKey Table::add_column(Table& target, StringData name)
412
{
29,637✔
413
    // Both origin and target must be group-level tables, and in the same group.
14,709✔
414
    Group* origin_group = get_parent_group();
29,637✔
415
    Group* target_group = target.get_parent_group();
29,637✔
416
    REALM_ASSERT_RELEASE(origin_group && target_group);
29,637✔
417
    REALM_ASSERT_RELEASE(origin_group == target_group);
29,637✔
418
    // Incoming links from an asymmetric table are not allowed.
14,709✔
419
    if (target.is_asymmetric()) {
29,637✔
420
        throw IllegalOperation("Ephemeral objects not supported");
6✔
421
    }
6✔
422

14,706✔
423
    m_has_any_embedded_objects.reset();
29,631✔
424

14,706✔
425
    ColumnAttrMask attr;
29,631✔
426
    attr.set(col_attr_Nullable);
29,631✔
427
    ColKey col_key = generate_col_key(col_type_Link, attr);
29,631✔
428

14,706✔
429
    auto retval = do_insert_column(col_key, type_Link, name, &target); // Throws
29,631✔
430
    return retval;
29,631✔
431
}
29,631✔
432

433
ColKey Table::add_column_list(DataType type, StringData name, bool nullable)
434
{
80,325✔
435
    Table* invalid_link = nullptr;
80,325✔
436
    ColumnAttrMask attr;
80,325✔
437
    attr.set(col_attr_List);
80,325✔
438
    if (nullable || type == type_Mixed)
80,325✔
439
        attr.set(col_attr_Nullable);
28,938✔
440
    ColKey col_key = generate_col_key(ColumnType(type), attr);
80,325✔
441
    return do_insert_column(col_key, type, name, invalid_link); // Throws
80,325✔
442
}
80,325✔
443

444
ColKey Table::add_column_set(DataType type, StringData name, bool nullable)
445
{
52,611✔
446
    Table* invalid_link = nullptr;
52,611✔
447
    ColumnAttrMask attr;
52,611✔
448
    attr.set(col_attr_Set);
52,611✔
449
    if (nullable || type == type_Mixed)
52,611✔
450
        attr.set(col_attr_Nullable);
21,786✔
451
    ColKey col_key = generate_col_key(ColumnType(type), attr);
52,611✔
452
    return do_insert_column(col_key, type, name, invalid_link); // Throws
52,611✔
453
}
52,611✔
454

455
ColKey Table::add_column_list(Table& target, StringData name)
456
{
30,486✔
457
    // Both origin and target must be group-level tables, and in the same group.
15,027✔
458
    Group* origin_group = get_parent_group();
30,486✔
459
    Group* target_group = target.get_parent_group();
30,486✔
460
    REALM_ASSERT_RELEASE(origin_group && target_group);
30,486✔
461
    REALM_ASSERT_RELEASE(origin_group == target_group);
30,486✔
462
    // Incoming links from an asymmetric table are not allowed.
15,027✔
463
    if (target.is_asymmetric()) {
30,486✔
464
        throw IllegalOperation("List of ephemeral objects not supported");
×
465
    }
×
466

15,027✔
467
    m_has_any_embedded_objects.reset();
30,486✔
468

15,027✔
469
    ColumnAttrMask attr;
30,486✔
470
    attr.set(col_attr_List);
30,486✔
471
    ColKey col_key = generate_col_key(col_type_LinkList, attr);
30,486✔
472

15,027✔
473
    return do_insert_column(col_key, type_LinkList, name, &target); // Throws
30,486✔
474
}
30,486✔
475

476
ColKey Table::add_column_set(Table& target, StringData name)
477
{
10,476✔
478
    // Both origin and target must be group-level tables, and in the same group.
5,184✔
479
    Group* origin_group = get_parent_group();
10,476✔
480
    Group* target_group = target.get_parent_group();
10,476✔
481
    REALM_ASSERT_RELEASE(origin_group && target_group);
10,476✔
482
    REALM_ASSERT_RELEASE(origin_group == target_group);
10,476✔
483
    if (target.is_embedded())
10,476✔
484
        throw IllegalOperation("Set of embedded objects not supported");
×
485
    // Incoming links from an asymmetric table are not allowed.
5,184✔
486
    if (target.is_asymmetric()) {
10,476✔
487
        throw IllegalOperation("Set of ephemeral objects not supported");
×
488
    }
×
489

5,184✔
490
    ColumnAttrMask attr;
10,476✔
491
    attr.set(col_attr_Set);
10,476✔
492
    ColKey col_key = generate_col_key(col_type_Link, attr);
10,476✔
493
    return do_insert_column(col_key, type_Link, name, &target); // Throws
10,476✔
494
}
10,476✔
495

496
ColKey Table::add_column_link(DataType type, StringData name, Table& target)
497
{
×
498
    REALM_ASSERT(is_link_type(ColumnType(type)));
×
499

500
    if (type == type_LinkList) {
×
501
        return add_column_list(target, name);
×
502
    }
×
503
    else {
×
504
        REALM_ASSERT(type == type_Link);
×
505
        return add_column(target, name);
×
506
    }
×
507
}
×
508

509
ColKey Table::add_column_dictionary(DataType type, StringData name, bool nullable, DataType key_type)
510
{
42,486✔
511
    Table* invalid_link = nullptr;
42,486✔
512
    ColumnAttrMask attr;
42,486✔
513
    REALM_ASSERT(key_type != type_Mixed);
42,486✔
514
    attr.set(col_attr_Dictionary);
42,486✔
515
    if (nullable || type == type_Mixed)
42,486✔
516
        attr.set(col_attr_Nullable);
22,992✔
517
    ColKey col_key = generate_col_key(ColumnType(type), attr);
42,486✔
518
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
42,486✔
519
}
42,486✔
520

521
ColKey Table::add_column_dictionary(Table& target, StringData name, DataType key_type)
522
{
11,076✔
523
    // Both origin and target must be group-level tables, and in the same group.
5,430✔
524
    Group* origin_group = get_parent_group();
11,076✔
525
    Group* target_group = target.get_parent_group();
11,076✔
526
    REALM_ASSERT_RELEASE(origin_group && target_group);
11,076✔
527
    REALM_ASSERT_RELEASE(origin_group == target_group);
11,076✔
528
    // Incoming links from an asymmetric table are not allowed.
5,430✔
529
    if (target.is_asymmetric()) {
11,076✔
530
        throw IllegalOperation("Dictionary of ephemeral objects not supported");
×
531
    }
×
532

5,430✔
533
    ColumnAttrMask attr;
11,076✔
534
    attr.set(col_attr_Dictionary);
11,076✔
535
    attr.set(col_attr_Nullable);
11,076✔
536

5,430✔
537
    ColKey col_key = generate_col_key(ColumnType(col_type_Link), attr);
11,076✔
538
    return do_insert_column(col_key, type_Link, name, &target, key_type); // Throws
11,076✔
539
}
11,076✔
540

541
void Table::remove_recursive(CascadeState& cascade_state)
542
{
14,634✔
543
    Group* group = get_parent_group();
14,634✔
544
    REALM_ASSERT(group);
14,634✔
545
    cascade_state.m_group = group;
14,634✔
546

6,678✔
547
    do {
18,759✔
548
        cascade_state.send_notifications();
18,759✔
549

8,748✔
550
        for (auto& l : cascade_state.m_to_be_nullified) {
8,772✔
551
            Obj obj = group->get_table_unchecked(l.origin_table)->try_get_object(l.origin_key);
48✔
552
            REALM_ASSERT_DEBUG(obj);
48✔
553
            if (obj) {
48✔
554
                std::move(obj).nullify_link(l.origin_col_key, l.old_target_link);
48✔
555
            }
48✔
556
        }
48✔
557
        cascade_state.m_to_be_nullified.clear();
18,759✔
558

8,748✔
559
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
18,759✔
560
        for (auto obj : to_delete) {
16,290✔
561
            auto table = obj.first == m_key ? this : group->get_table_unchecked(obj.first);
12,705✔
562
            // This might add to the list of objects that should be deleted
7,569✔
563
            REALM_ASSERT(!obj.second.is_unresolved());
15,111✔
564
            table->m_clusters.erase(obj.second, cascade_state);
15,111✔
565
        }
15,111✔
566
        nullify_links(cascade_state);
18,759✔
567
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
18,759✔
568
}
14,634✔
569

570
void Table::nullify_links(CascadeState& cascade_state)
571
{
23,598✔
572
    Group* group = get_parent_group();
23,598✔
573
    REALM_ASSERT(group);
23,598✔
574
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
15,186✔
575
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
7,107✔
576
        if (!table->is_asymmetric())
8,040✔
577
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
8,040✔
578
    }
8,040✔
579
}
23,598✔
580

581

582
void Table::remove_column(ColKey col_key)
583
{
17,985✔
584
    check_column(col_key);
17,985✔
585

9,231✔
586
    if (Replication* repl = get_repl())
17,985✔
587
        repl->erase_column(this, col_key); // Throws
507✔
588

9,231✔
589
    if (col_key == m_primary_key_col) {
17,985✔
590
        do_set_primary_key_column(ColKey());
7,929✔
591
    }
7,929✔
592
    else {
10,056✔
593
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
10,056✔
594
    }
10,056✔
595

9,231✔
596
    erase_root_column(col_key); // Throws
17,985✔
597
    m_has_any_embedded_objects.reset();
17,985✔
598
}
17,985✔
599

600

601
void Table::rename_column(ColKey col_key, StringData name)
602
{
102✔
603
    check_column(col_key);
102✔
604

57✔
605
    auto col_ndx = colkey2spec_ndx(col_key);
102✔
606
    m_spec.rename_column(col_ndx, name); // Throws
102✔
607

57✔
608
    bump_content_version();
102✔
609
    bump_storage_version();
102✔
610

57✔
611
    if (Replication* repl = get_repl())
102✔
612
        repl->rename_column(this, col_key, name); // Throws
102✔
613
}
102✔
614

615

616
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
617
{
9,728,889✔
618
    // well, not quite "direct", more like "almost direct":
5,093,979✔
619
    Array table_top(alloc);
9,728,889✔
620
    table_top.init_from_ref(top_ref);
9,728,889✔
621
    if (table_top.size() > 3) {
9,734,577✔
622
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
9,734,577✔
623
        return TableKey(int32_t(rot.get_as_int()));
9,734,577✔
624
    }
9,734,577✔
625
    else {
4,294,967,294✔
626
        return TableKey();
4,294,967,294✔
627
    }
4,294,967,294✔
628
}
9,728,889✔
629

630

631
void Table::init(ref_type top_ref, ArrayParent* parent, size_t ndx_in_parent, bool is_writable, bool is_frzn)
632
{
5,024,940✔
633
    REALM_ASSERT(!(is_writable && is_frzn));
5,024,940✔
634
    m_is_frozen = is_frzn;
5,024,940✔
635
    m_alloc.set_read_only(!is_writable);
5,024,940✔
636
    // Load from allocated memory
2,741,130✔
637
    m_top.set_parent(parent, ndx_in_parent);
5,024,940✔
638
    m_top.init_from_ref(top_ref);
5,024,940✔
639

2,741,130✔
640
    m_spec.init_from_parent();
5,024,940✔
641

2,741,130✔
642
    while (m_top.size() <= top_position_for_pk_col) {
5,025,900✔
643
        m_top.add(0);
960✔
644
    }
960✔
645

2,741,130✔
646
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
5,024,940✔
647
        // This is an upgrade - create cluster
48✔
648
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
96✔
649
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
96✔
650
    }
96✔
651
    m_clusters.init_from_parent();
5,024,940✔
652

2,741,130✔
653
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
5,024,940✔
654
    if (!rot.is_tagged()) {
5,024,940✔
655
        // Create table key
48✔
656
        rot = RefOrTagged::make_tagged(ndx_in_parent);
96✔
657
        m_top.set(top_position_for_key, rot);
96✔
658
    }
96✔
659
    m_key = TableKey(int32_t(rot.get_as_int()));
5,024,940✔
660

2,741,130✔
661
    // index setup relies on column mapping being up to date:
2,741,130✔
662
    build_column_mapping();
5,024,940✔
663
    if (m_top.get_as_ref(top_position_for_search_indexes) == 0) {
5,024,940✔
664
        // This is an upgrade - create the necessary arrays
48✔
665
        bool context_flag = false;
96✔
666
        size_t nb_columns = m_spec.get_column_count();
96✔
667
        MemRef mem = Array::create_array(Array::type_HasRefs, context_flag, nb_columns, 0, m_top.get_alloc());
96✔
668
        m_index_refs.init_from_mem(mem);
96✔
669
        m_index_refs.update_parent();
96✔
670
        mem = Array::create_array(Array::type_Normal, context_flag, nb_columns, TableKey().value, m_top.get_alloc());
96✔
671
        m_opposite_table.init_from_mem(mem);
96✔
672
        m_opposite_table.update_parent();
96✔
673
        mem = Array::create_array(Array::type_Normal, context_flag, nb_columns, ColKey().value, m_top.get_alloc());
96✔
674
        m_opposite_column.init_from_mem(mem);
96✔
675
        m_opposite_column.update_parent();
96✔
676
    }
96✔
677
    else {
5,024,844✔
678
        m_opposite_table.init_from_parent();
5,024,844✔
679
        m_opposite_column.init_from_parent();
5,024,844✔
680
        m_index_refs.init_from_parent();
5,024,844✔
681
        m_index_accessors.resize(m_index_refs.size());
5,024,844✔
682
    }
5,024,844✔
683
    if (!m_top.get_as_ref_or_tagged(top_position_for_column_key).is_tagged()) {
5,024,940✔
684
        m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(0));
96✔
685
    }
96✔
686
    auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
5,024,940✔
687
    if (!rot_version.is_tagged()) {
5,024,940✔
688
        m_top.set(top_position_for_version, RefOrTagged::make_tagged(0));
96✔
689
        m_in_file_version_at_transaction_boundary = 0;
96✔
690
    }
96✔
691
    else
5,024,844✔
692
        m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
5,024,844✔
693

2,741,130✔
694
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
5,024,940✔
695
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
4,488,810✔
696

2,741,130✔
697
    if (m_top.size() <= top_position_for_flags) {
5,024,940✔
698
        m_table_type = Type::TopLevel;
702✔
699
    }
702✔
700
    else {
5,024,238✔
701
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
5,024,238✔
702
        m_table_type = Type(flags & table_type_mask);
5,024,238✔
703
    }
5,024,238✔
704
    m_has_any_embedded_objects.reset();
5,024,940✔
705

2,741,130✔
706
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
5,024,940✔
707
        // Tombstones exists
399,396✔
708
        if (!m_tombstones) {
821,460✔
709
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
521,352✔
710
        }
521,352✔
711
        m_tombstones->init_from_parent();
821,460✔
712
    }
821,460✔
713
    else {
4,203,480✔
714
        m_tombstones = nullptr;
4,203,480✔
715
    }
4,203,480✔
716
    m_cookie = cookie_initialized;
5,024,940✔
717
}
5,024,940✔
718

719

720
ColKey Table::do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table, DataType key_type)
721
{
764,577✔
722
    col_key = do_insert_root_column(col_key, ColumnType(type), name, key_type); // Throws
764,577✔
723

377,196✔
724
    // When the inserted column is a link-type column, we must also add a
377,196✔
725
    // backlink column to the target table.
377,196✔
726

377,196✔
727
    if (target_table) {
764,577✔
728
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
81,663✔
729
        target_table->check_column(backlink_col_key);
81,663✔
730

40,344✔
731
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
81,663✔
732
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
81,663✔
733
    }
81,663✔
734

377,196✔
735
    if (Replication* repl = get_repl())
764,577✔
736
        repl->insert_column(this, col_key, type, name, target_table); // Throws
746,853✔
737

377,196✔
738
    return col_key;
764,577✔
739
}
764,577✔
740

741

742
void Table::populate_search_index(ColKey col_key)
743
{
133,734✔
744
    auto col_ndx = col_key.get_index().val;
133,734✔
745
    StringIndex* index = m_index_accessors[col_ndx].get();
133,734✔
746

66,195✔
747
    // Insert ref to index
66,195✔
748
    for (auto o : *this) {
1,385,508✔
749
        ObjKey key = o.get_key();
1,385,508✔
750
        DataType type = get_column_type(col_key);
1,385,508✔
751

693,003✔
752
        if (type == type_Int) {
1,385,508✔
753
            if (is_nullable(col_key)) {
1,214,544✔
754
                Optional<int64_t> value = o.get<Optional<int64_t>>(col_key);
534✔
755
                index->insert(key, value); // Throws
534✔
756
            }
534✔
757
            else {
1,214,010✔
758
                int64_t value = o.get<int64_t>(col_key);
1,214,010✔
759
                index->insert(key, value); // Throws
1,214,010✔
760
            }
1,214,010✔
761
        }
1,214,544✔
762
        else if (type == type_Bool) {
170,964✔
763
            if (is_nullable(col_key)) {
156✔
764
                Optional<bool> value = o.get<Optional<bool>>(col_key);
54✔
765
                index->insert(key, value); // Throws
54✔
766
            }
54✔
767
            else {
102✔
768
                bool value = o.get<bool>(col_key);
102✔
769
                index->insert(key, value); // Throws
102✔
770
            }
102✔
771
        }
156✔
772
        else if (type == type_String) {
170,808✔
773
            if (col_key.is_list()) {
170,667✔
774
                auto list = o.get_list<String>(col_key);
60,000✔
775
                for (auto& s : list) {
180,000✔
776
                    index->insert(key, s); // Throws
180,000✔
777
                }
180,000✔
778
            }
60,000✔
779
            else {
110,667✔
780
                StringData value = o.get<StringData>(col_key);
110,667✔
781
                index->insert(key, value); // Throws
110,667✔
782
            }
110,667✔
783
        }
170,667✔
784
        else if (type == type_Timestamp) {
171✔
785
            Timestamp value = o.get<Timestamp>(col_key);
132✔
786
            index->insert(key, value); // Throws
132✔
787
        }
132✔
788
        else if (type == type_ObjectId) {
2,147,483,686✔
789
            if (is_nullable(col_key)) {
36✔
790
                Optional<ObjectId> value = o.get<Optional<ObjectId>>(col_key);
18✔
791
                index->insert(key, value); // Throws
18✔
792
            }
18✔
793
            else {
18✔
794
                ObjectId value = o.get<ObjectId>(col_key);
18✔
795
                index->insert(key, value); // Throws
18✔
796
            }
18✔
797
        }
36✔
798
        else if (type == type_UUID) {
2,147,483,668✔
799
            if (is_nullable(col_key)) {
36✔
800
                Optional<UUID> value = o.get<Optional<UUID>>(col_key);
18✔
801
                index->insert(key, value); // Throws
18✔
802
            }
18✔
803
            else {
18✔
804
                UUID value = o.get<UUID>(col_key);
18✔
805
                index->insert(key, value); // Throws
18✔
806
            }
18✔
807
        }
36✔
808
        else if (type == type_Mixed) {
2,147,483,650✔
809
            index->insert(key, o.get<Mixed>(col_key));
6✔
810
        }
6✔
811
        else {
2,147,483,647✔
812
            REALM_ASSERT_RELEASE(false && "Data type does not support search index");
2,147,483,647✔
813
        }
2,147,483,647✔
814
    }
1,385,508✔
815
}
133,734✔
816

817
void Table::erase_from_search_indexes(ObjKey key)
818
{
5,028,024✔
819
    // Tombstones do not use index - will crash if we try to erase values
2,512,809✔
820
    if (!key.is_unresolved()) {
5,028,024✔
821
        for (auto&& index : m_index_accessors) {
6,704,652✔
822
            if (index) {
6,704,652✔
823
                index->erase(key);
281,850✔
824
            }
281,850✔
825
        }
6,704,652✔
826
    }
5,014,722✔
827
}
5,028,024✔
828

829
void Table::update_indexes(ObjKey key, const FieldValues& values)
830
{
23,072,154✔
831
    // Tombstones do not use index - will crash if we try to insert values
11,414,142✔
832
    if (key.is_unresolved()) {
23,072,154✔
833
        return;
28,596✔
834
    }
28,596✔
835

11,400,237✔
836
    auto sz = m_index_accessors.size();
23,043,558✔
837
    // values are sorted by column index - there may be values missing
11,400,237✔
838
    auto value = values.begin();
23,043,558✔
839
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
57,094,467✔
840
        // Check if initial value is provided
16,823,886✔
841
        Mixed init_value;
34,051,089✔
842
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
34,051,089✔
843
            // Value for this column is provided
311,166✔
844
            init_value = value->value;
647,535✔
845
            ++value;
647,535✔
846
        }
647,535✔
847

16,823,886✔
848
        if (auto&& index = m_index_accessors[column_ndx]) {
34,051,089✔
849
            // There is an index for this column
583,350✔
850
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,192,095✔
851
            if (col_key.is_collection())
1,192,095✔
852
                continue;
102✔
853
            auto type = col_key.get_type();
1,191,993✔
854
            auto attr = col_key.get_attrs();
1,191,993✔
855
            bool nullable = attr.test(col_attr_Nullable);
1,191,993✔
856
            switch (type) {
1,191,993✔
857
                case col_type_Int:
592,179✔
858
                    if (init_value.is_null()) {
592,179✔
859
                        index->insert(key, ArrayIntNull::default_value(nullable));
165,699✔
860
                    }
165,699✔
861
                    else {
426,480✔
862
                        index->insert(key, init_value.get<int64_t>());
426,480✔
863
                    }
426,480✔
864
                    break;
592,179✔
865
                case col_type_Bool:
6,078✔
866
                    if (init_value.is_null()) {
6,078✔
867
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,042✔
868
                    }
6,042✔
869
                    else {
36✔
870
                        index->insert(key, init_value.get<bool>());
36✔
871
                    }
36✔
872
                    break;
6,078✔
873
                case col_type_String:
482,598✔
874
                    if (init_value.is_null()) {
482,598✔
875
                        index->insert(key, ArrayString::default_value(nullable));
432,996✔
876
                    }
432,996✔
877
                    else {
49,602✔
878
                        index->insert(key, init_value.get<String>());
49,602✔
879
                    }
49,602✔
880
                    break;
482,598✔
881
                case col_type_Timestamp:
6,258✔
882
                    if (init_value.is_null()) {
6,258✔
883
                        index->insert(key, ArrayTimestamp::default_value(nullable));
6,222✔
884
                    }
6,222✔
885
                    else {
36✔
886
                        index->insert(key, init_value.get<Timestamp>());
36✔
887
                    }
36✔
888
                    break;
6,258✔
889
                case col_type_ObjectId:
85,785✔
890
                    if (init_value.is_null()) {
85,785✔
891
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
892
                    }
6,120✔
893
                    else {
79,665✔
894
                        index->insert(key, init_value.get<ObjectId>());
79,665✔
895
                    }
79,665✔
896
                    break;
85,785✔
897
                case col_type_Mixed:
834✔
898
                    index->insert(key, init_value);
834✔
899
                    break;
834✔
900
                case col_type_UUID:
18,342✔
901
                    if (init_value.is_null()) {
18,342✔
902
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
6,138✔
903
                    }
6,138✔
904
                    else {
12,204✔
905
                        index->insert(key, init_value.get<UUID>());
12,204✔
906
                    }
12,204✔
907
                    break;
18,342✔
908
                default:
✔
909
                    REALM_UNREACHABLE();
910
            }
1,191,993✔
911
        }
1,191,993✔
912
    }
34,051,089✔
913
}
23,043,558✔
914

915
void Table::clear_indexes()
916
{
4,278✔
917
    for (auto&& index : m_index_accessors) {
48,528✔
918
        if (index) {
48,528✔
919
            index->clear();
3,024✔
920
        }
3,024✔
921
    }
48,528✔
922
}
4,278✔
923

924
void Table::do_add_search_index(ColKey col_key, IndexType type)
925
{
134,160✔
926
    size_t column_ndx = col_key.get_index().val;
134,160✔
927

66,408✔
928
    // Early-out if already indexed
66,408✔
929
    if (m_index_accessors[column_ndx] != nullptr)
134,160✔
930
        return;
384✔
931

66,216✔
932
    if (!StringIndex::type_supported(DataType(col_key.get_type())) ||
133,776✔
933
        (col_key.is_collection() && !(col_key.is_list() && col_key.get_type() == col_type_String)) ||
133,761✔
934
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
133,755✔
935
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
21✔
936
        // it should probably be a type mismatch exception instead.
21✔
937
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
42✔
938
    }
42✔
939

66,195✔
940
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
66,195✔
941
    // index have 0-entries.
66,195✔
942
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
133,734✔
943
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
133,734✔
944

66,195✔
945
    // Create the index
66,195✔
946
    m_index_accessors[column_ndx] =
133,734✔
947
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
133,734✔
948
    StringIndex* index = m_index_accessors[column_ndx].get();
133,734✔
949

66,195✔
950
    // Insert ref to index
66,195✔
951
    index->set_parent(&m_index_refs, column_ndx);
133,734✔
952
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
133,734✔
953

66,195✔
954
    populate_search_index(col_key);
133,734✔
955
}
133,734✔
956

957
void Table::add_search_index(ColKey col_key, IndexType type)
958
{
3,906✔
959
    check_column(col_key);
3,906✔
960

1,956✔
961
    // Check spec
1,956✔
962
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,906✔
963
    auto attr = m_spec.get_column_attr(spec_ndx);
3,906✔
964

1,956✔
965
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,906✔
966
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
967

1,953✔
968
    switch (type) {
3,900✔
969
        case IndexType::None:
✔
970
            remove_search_index(col_key);
×
971
            return;
×
972
        case IndexType::Fulltext:
54✔
973
            // Early-out if already indexed
27✔
974
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
975
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
976
                return;
×
977
            }
×
978
            if (attr.test(col_attr_Indexed)) {
54✔
979
                this->remove_search_index(col_key);
×
980
            }
×
981
            break;
54✔
982
        case IndexType::General:
3,846✔
983
            if (attr.test(col_attr_Indexed)) {
3,846✔
984
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
30✔
985
                return;
30✔
986
            }
30✔
987
            if (attr.test(col_attr_FullText_Indexed)) {
3,816✔
988
                this->remove_search_index(col_key);
×
989
            }
×
990
            break;
3,816✔
991
    }
3,870✔
992

1,938✔
993
    do_add_search_index(col_key, type);
3,870✔
994

1,938✔
995
    // Update spec
1,938✔
996
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,843✔
997
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,870✔
998
}
3,870✔
999

1000
void Table::remove_search_index(ColKey col_key)
1001
{
8,376✔
1002
    check_column(col_key);
8,376✔
1003
    auto column_ndx = col_key.get_index();
8,376✔
1004

4,281✔
1005
    // Early-out if non-indexed
4,281✔
1006
    if (m_index_accessors[column_ndx.val] == nullptr)
8,376✔
1007
        return;
66✔
1008

4,239✔
1009
    // Destroy and remove the index column
4,239✔
1010
    auto& index = m_index_accessors[column_ndx.val];
8,310✔
1011
    REALM_ASSERT(index != nullptr);
8,310✔
1012
    index->destroy();
8,310✔
1013
    index.reset();
8,310✔
1014

4,239✔
1015
    m_index_refs.set(column_ndx.val, 0);
8,310✔
1016

4,239✔
1017
    // update spec
4,239✔
1018
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
8,310✔
1019
    auto attr = m_spec.get_column_attr(spec_ndx);
8,310✔
1020
    attr.reset(col_attr_Indexed);
8,310✔
1021
    attr.reset(col_attr_FullText_Indexed);
8,310✔
1022
    m_spec.set_column_attr(spec_ndx, attr); // Throws
8,310✔
1023
}
8,310✔
1024

1025
void Table::enumerate_string_column(ColKey col_key)
1026
{
1,296✔
1027
    check_column(col_key);
1,296✔
1028
    size_t column_ndx = colkey2spec_ndx(col_key);
1,296✔
1029
    ColumnType type = col_key.get_type();
1,296✔
1030
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,296✔
1031
        m_clusters.enumerate_string_column(col_key);
690✔
1032
    }
690✔
1033
}
1,296✔
1034

1035
bool Table::is_enumerated(ColKey col_key) const noexcept
1036
{
58,443✔
1037
    size_t col_ndx = colkey2spec_ndx(col_key);
58,443✔
1038
    return m_spec.is_string_enum_type(col_ndx);
58,443✔
1039
}
58,443✔
1040

1041
size_t Table::get_num_unique_values(ColKey col_key) const
1042
{
138✔
1043
    if (!is_enumerated(col_key))
138✔
1044
        return 0;
84✔
1045

27✔
1046
    ArrayParent* parent;
54✔
1047
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
1048
    BPlusTree<StringData> col(get_alloc());
54✔
1049
    col.init_from_ref(ref);
54✔
1050

27✔
1051
    return col.size();
54✔
1052
}
54✔
1053

1054

1055
void Table::erase_root_column(ColKey col_key)
1056
{
18,261✔
1057
    check_column(col_key);
18,261✔
1058
    ColumnType col_type = col_key.get_type();
18,261✔
1059
    if (is_link_type(col_type)) {
18,261✔
1060
        auto target_table = get_opposite_table(col_key);
219✔
1061
        auto target_column = get_opposite_column(col_key);
219✔
1062
        target_table->do_erase_root_column(target_column);
219✔
1063
    }
219✔
1064
    do_erase_root_column(col_key); // Throws
18,261✔
1065
}
18,261✔
1066

1067

1068
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
1069
{
983,124✔
1070
    // if col_key specifies a key, it must be unused
485,304✔
1071
    REALM_ASSERT(!col_key || !valid_column(col_key));
983,124✔
1072

485,304✔
1073
    // locate insertion point: ordinary columns must come before backlink columns
485,304✔
1074
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
938,337✔
1075

485,304✔
1076
    if (!col_key) {
983,124✔
1077
        col_key = generate_col_key(type, {});
88,599✔
1078
    }
88,599✔
1079

485,304✔
1080
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
983,124✔
1081
    if (col_key.is_dictionary()) {
983,124✔
1082
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
53,562✔
1083
    }
53,562✔
1084
    auto col_ndx = col_key.get_index().val;
983,124✔
1085
    build_column_mapping();
983,124✔
1086
    REALM_ASSERT(col_ndx <= m_index_refs.size());
983,124✔
1087
    if (col_ndx == m_index_refs.size()) {
983,124✔
1088
        m_index_refs.insert(col_ndx, 0);
982,878✔
1089
    }
982,878✔
1090
    else {
246✔
1091
        m_index_refs.set(col_ndx, 0);
246✔
1092
    }
246✔
1093
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
983,124✔
1094
    if (col_ndx == m_opposite_table.size()) {
983,124✔
1095
        // m_opposite_table and m_opposite_column are always resized together!
485,172✔
1096
        m_opposite_table.insert(col_ndx, TableKey().value);
982,866✔
1097
        m_opposite_column.insert(col_ndx, ColKey().value);
982,866✔
1098
    }
982,866✔
1099
    else {
258✔
1100
        m_opposite_table.set(col_ndx, TableKey().value);
258✔
1101
        m_opposite_column.set(col_ndx, ColKey().value);
258✔
1102
    }
258✔
1103
    refresh_index_accessors();
983,124✔
1104
    m_clusters.insert_column(col_key);
983,124✔
1105
    if (m_tombstones) {
983,124✔
1106
        m_tombstones->insert_column(col_key);
6,828✔
1107
    }
6,828✔
1108

485,304✔
1109
    bump_storage_version();
983,124✔
1110

485,304✔
1111
    return col_key;
983,124✔
1112
}
983,124✔
1113

1114

1115
void Table::do_erase_root_column(ColKey col_key)
1116
{
18,480✔
1117
    size_t col_ndx = col_key.get_index().val;
18,480✔
1118
    // If the column had a source index we have to remove and destroy that as well
9,480✔
1119
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
18,480✔
1120
    if (index_ref) {
18,480✔
1121
        Array::destroy_deep(index_ref, m_index_refs.get_alloc());
207✔
1122
        m_index_refs.set(col_ndx, 0);
207✔
1123
        m_index_accessors[col_ndx].reset();
207✔
1124
    }
207✔
1125
    m_opposite_table.set(col_ndx, TableKey().value);
18,480✔
1126
    m_opposite_column.set(col_ndx, ColKey().value);
18,480✔
1127
    m_index_accessors[col_ndx] = nullptr;
18,480✔
1128
    m_clusters.remove_column(col_key);
18,480✔
1129
    if (m_tombstones)
18,480✔
1130
        m_tombstones->remove_column(col_key);
6,138✔
1131
    size_t spec_ndx = colkey2spec_ndx(col_key);
18,480✔
1132
    m_spec.erase_column(spec_ndx);
18,480✔
1133
    m_top.adjust(top_position_for_column_key, 2);
18,480✔
1134

9,480✔
1135
    build_column_mapping();
18,480✔
1136
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
36,384✔
1137
        REALM_ASSERT(m_index_accessors.back() == nullptr);
17,904✔
1138
        m_index_accessors.pop_back();
17,904✔
1139
    }
17,904✔
1140
    bump_content_version();
18,480✔
1141
    bump_storage_version();
18,480✔
1142
}
18,480✔
1143

1144
Query Table::where(const DictionaryLinkValues& dictionary_of_links) const
1145
{
1,524✔
1146
    return Query(m_own_ref, dictionary_of_links);
1,524✔
1147
}
1,524✔
1148

1149
void Table::set_table_type(Type table_type, bool handle_backlinks)
1150
{
312✔
1151
    if (table_type == m_table_type) {
312✔
1152
        return;
×
1153
    }
×
1154

156✔
1155
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
1156
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1157
                                                                   get_class_name(), m_table_type, table_type));
×
1158
    }
×
1159

156✔
1160
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1161
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1162
}
312✔
1163

1164
void Table::set_embedded(bool embedded, bool handle_backlinks)
1165
{
312✔
1166
    if (embedded == false) {
312✔
1167
        do_set_table_type(Type::TopLevel);
24✔
1168
        return;
24✔
1169
    }
24✔
1170

144✔
1171
    // Embedded objects cannot have a primary key.
144✔
1172
    if (get_primary_key_column()) {
288✔
1173
        throw IllegalOperation(
6✔
1174
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1175
    }
6✔
1176

141✔
1177
    if (size() == 0) {
282✔
1178
        do_set_table_type(Type::Embedded);
42✔
1179
        return;
42✔
1180
    }
42✔
1181

120✔
1182
    // Check all of the objects for invalid incoming links. Each embedded object
120✔
1183
    // must have exactly one incoming link, and it must be from a non-Mixed property.
120✔
1184
    // Objects with no incoming links are either deleted or an error (depending
120✔
1185
    // on `handle_backlinks`), and objects with multiple incoming links are either
120✔
1186
    // cloned for each of the incoming links or an error (again depending on `handle_backlinks`).
120✔
1187
    // Incoming links from a Mixed property are always an error, as those can't
120✔
1188
    // link to embedded objects
120✔
1189
    ArrayInteger leaf(get_alloc());
240✔
1190
    enum class LinkCount : int8_t { None, One, Multiple };
240✔
1191
    std::vector<LinkCount> incoming_link_count;
240✔
1192
    std::vector<ObjKey> orphans;
240✔
1193
    std::vector<ObjKey> multiple_incoming_links;
240✔
1194
    traverse_clusters([&](const Cluster* cluster) {
474✔
1195
        size_t size = cluster->node_size();
474✔
1196
        incoming_link_count.assign(size, LinkCount::None);
474✔
1197

237✔
1198
        for_each_backlink_column([&](ColKey col) {
606✔
1199
            cluster->init_leaf(col, &leaf);
606✔
1200
            // Width zero means all the values are zero and there can't be any backlinks
303✔
1201
            if (leaf.get_width() == 0) {
606✔
1202
                return IteratorControl::AdvanceToNext;
36✔
1203
            }
36✔
1204

285✔
1205
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1206
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1207
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1208
                    // ref of zero means there's no backlinks
29,670✔
1209
                    continue;
59,340✔
1210
                }
59,340✔
1211

480✔
1212
                if (value.is_ref()) {
960✔
1213
                    // Any other ref indicates an array of backlinks, which will
39✔
1214
                    // always have more than one entry
39✔
1215
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1216
                }
78✔
1217
                else {
882✔
1218
                    // Otherwise it's a tagged ref to the single linking object
441✔
1219
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1220
                        incoming_link_count[i] = LinkCount::One;
792✔
1221
                    }
792✔
1222
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1223
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1224
                    }
42✔
1225
                }
882✔
1226

480✔
1227
                auto source_col = get_opposite_column(col);
960✔
1228
                if (source_col.get_type() == col_type_Mixed) {
960✔
1229
                    auto source_table = get_opposite_table(col);
54✔
1230
                    throw IllegalOperation(util::format(
54✔
1231
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1232
                        "which does not support linking to embedded objects.",
54✔
1233
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1234
                }
54✔
1235
            }
960✔
1236
            return IteratorControl::AdvanceToNext;
543✔
1237
        });
570✔
1238

237✔
1239
        for (size_t i = 0; i < size; ++i) {
60,660✔
1240
            if (incoming_link_count[i] == LinkCount::None) {
60,240✔
1241
                if (!handle_backlinks) {
59,424✔
1242
                    throw IllegalOperation(util::format("Cannot convert '%1' to embedded: at least one object has no "
18✔
1243
                                                        "incoming links and would be deleted.",
18✔
1244
                                                        get_class_name()));
18✔
1245
                }
18✔
1246
                orphans.push_back(cluster->get_real_key(i));
59,406✔
1247
            }
59,406✔
1248
            else if (incoming_link_count[i] == LinkCount::Multiple) {
816✔
1249
                if (!handle_backlinks) {
90✔
1250
                    throw IllegalOperation(util::format(
36✔
1251
                        "Cannot convert '%1' to embedded: at least one object has more than one incoming link.",
36✔
1252
                        get_class_name()));
36✔
1253
                }
36✔
1254
                multiple_incoming_links.push_back(cluster->get_real_key(i));
54✔
1255
            }
54✔
1256
        }
60,240✔
1257

237✔
1258
        return IteratorControl::AdvanceToNext;
447✔
1259
    });
474✔
1260

120✔
1261
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
120✔
1262
    for (auto key : orphans) {
59,406✔
1263
        remove_object(key);
59,406✔
1264
    }
59,406✔
1265
    for (auto key : multiple_incoming_links) {
147✔
1266
        auto obj = get_object(key);
54✔
1267
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1268
        obj.remove();
54✔
1269
    }
54✔
1270

120✔
1271
    do_set_table_type(Type::Embedded);
240✔
1272
}
240✔
1273

1274
void Table::do_set_table_type(Type table_type)
1275
{
337,134✔
1276
    while (m_top.size() <= top_position_for_flags)
337,134✔
1277
        m_top.add(0);
×
1278

167,166✔
1279
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
337,134✔
1280
    // reset bits 0-1
167,166✔
1281
    flags &= ~table_type_mask;
337,134✔
1282
    // set table type
167,166✔
1283
    flags |= static_cast<uint8_t>(table_type);
337,134✔
1284
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
337,134✔
1285
    m_table_type = table_type;
337,134✔
1286
}
337,134✔
1287

1288

1289
void Table::detach(LifeCycleCookie cookie) noexcept
1290
{
5,015,565✔
1291
    m_cookie = cookie;
5,015,565✔
1292
    m_alloc.bump_instance_version();
5,015,565✔
1293
}
5,015,565✔
1294

1295
void Table::fully_detach() noexcept
1296
{
5,003,976✔
1297
    m_spec.detach();
5,003,976✔
1298
    m_top.detach();
5,003,976✔
1299
    m_index_refs.detach();
5,003,976✔
1300
    m_opposite_table.detach();
5,003,976✔
1301
    m_opposite_column.detach();
5,003,976✔
1302
    m_index_accessors.clear();
5,003,976✔
1303
}
5,003,976✔
1304

1305

1306
Table::~Table() noexcept
1307
{
3,558✔
1308
    if (m_top.is_attached()) {
3,558✔
1309
        // If destroyed as a standalone table, destroy all memory allocated
1,779✔
1310
        if (m_top.get_parent() == nullptr) {
3,558✔
1311
            m_top.destroy_deep();
3,558✔
1312
        }
3,558✔
1313
        fully_detach();
3,558✔
1314
    }
3,558✔
1315
    else {
×
1316
        REALM_ASSERT(m_index_accessors.size() == 0);
×
1317
    }
×
1318
    m_cookie = cookie_deleted;
3,558✔
1319
}
3,558✔
1320

1321

1322
IndexType Table::search_index_type(ColKey col_key) const noexcept
1323
{
7,842,858✔
1324
    if (auto index = m_index_accessors[col_key.get_index().val].get()) {
7,842,858✔
1325
        return index->is_fulltext_index() ? IndexType::Fulltext : IndexType::General;
1,161,810✔
1326
    }
1,162,020✔
1327
    return IndexType::None;
6,680,838✔
1328
}
6,680,838✔
1329

1330
void Table::migrate_column_info()
1331
{
336✔
1332
    bool changes = false;
336✔
1333
    TableKey tk = (get_name() == "pk") ? TableKey(0) : get_key();
309✔
1334
    changes |= m_spec.convert_column_attributes();
336✔
1335
    changes |= m_spec.convert_column_keys(tk);
336✔
1336

168✔
1337
    if (changes) {
336✔
1338
        build_column_mapping();
60✔
1339
    }
60✔
1340
}
336✔
1341

1342
bool Table::verify_column_keys()
1343
{
282✔
1344
    size_t nb_public_columns = m_spec.get_public_column_count();
282✔
1345
    size_t nb_columns = m_spec.get_column_count();
282✔
1346
    bool modified = false;
282✔
1347

141✔
1348
    auto check = [&]() {
288✔
1349
        for (size_t spec_ndx = nb_public_columns; spec_ndx < nb_columns; spec_ndx++) {
426✔
1350
            if (m_spec.get_column_type(spec_ndx) == col_type_BackLink) {
144✔
1351
                auto col_key = m_spec.get_key(spec_ndx);
144✔
1352
                // This function checks for a specific error in the m_keys array where the
72✔
1353
                // backlink column keys are wrong. It can be detected by trying to find the
72✔
1354
                // corresponding origin table. If the error exists some of the results will
72✔
1355
                // give null TableKeys back.
72✔
1356
                if (!get_opposite_table_key(col_key))
144✔
1357
                    return false;
6✔
1358
                auto t = get_opposite_table(col_key);
138✔
1359
                auto c = get_opposite_column(col_key);
138✔
1360
                if (!t->valid_column(c))
138✔
1361
                    return false;
×
1362
                if (t->get_opposite_column(c) != col_key) {
138✔
1363
                    t->set_opposite_column(c, get_key(), col_key);
12✔
1364
                }
12✔
1365
            }
138✔
1366
        }
144✔
1367
        return true;
285✔
1368
    };
288✔
1369

141✔
1370
    if (!check()) {
282✔
1371
        m_spec.fix_column_keys(get_key());
6✔
1372
        build_column_mapping();
6✔
1373
        refresh_index_accessors();
6✔
1374
        REALM_ASSERT_RELEASE(check());
6✔
1375
        modified = true;
6✔
1376
    }
6✔
1377
    return modified;
282✔
1378
}
282✔
1379

1380
// Delete the indexes stored in the columns array and create corresponding
1381
// entries in m_index_accessors array. This also has the effect that the columns
1382
// array after this step does not have extra entries for certain columns
1383
void Table::migrate_indexes(ColKey pk_col_key)
1384
{
336✔
1385
    if (ref_type top_ref = m_top.get_as_ref(top_position_for_columns)) {
336✔
1386
        Array col_refs(m_alloc);
246✔
1387
        col_refs.set_parent(&m_top, top_position_for_columns);
246✔
1388
        col_refs.init_from_ref(top_ref);
246✔
1389
        auto col_count = m_spec.get_column_count();
246✔
1390
        size_t col_ndx = 0;
246✔
1391

123✔
1392
        // If col_refs.size() equals col_count, there are no indexes to migrate
123✔
1393
        while (col_ndx < col_count && col_refs.size() > col_count) {
468✔
1394
            if (m_spec.get_column_attr(col_ndx).test(col_attr_Indexed) && !m_index_refs.get(col_ndx)) {
222✔
1395
                // Simply delete entry. This will have the effect that we will not have to take
72✔
1396
                // extra entries into account
72✔
1397
                auto old_index_ref = to_ref(col_refs.get(col_ndx + 1));
144✔
1398
                col_refs.erase(col_ndx + 1);
144✔
1399
                if (old_index_ref) {
144✔
1400
                    // It should not be possible for old_index_ref to be 0, but we have seen some error
72✔
1401
                    // reports on freeing a null ref, so just to be sure ...
72✔
1402
                    Array::destroy_deep(old_index_ref, m_alloc);
144✔
1403
                }
144✔
1404

72✔
1405
                // Primary key columns does not need an index
72✔
1406
                if (m_leaf_ndx2colkey[col_ndx] != pk_col_key) {
144✔
1407
                    // Otherwise create new index. Will be updated when objects are created
45✔
1408
                    m_index_accessors[col_ndx] = std::make_unique<StringIndex>(
90✔
1409
                        ClusterColumn(&m_clusters, m_spec.get_key(col_ndx), IndexType::General),
90✔
1410
                        get_alloc()); // Throws
90✔
1411
                    auto index = m_index_accessors[col_ndx].get();
90✔
1412
                    index->set_parent(&m_index_refs, col_ndx);
90✔
1413
                    m_index_refs.set(col_ndx, index->get_ref());
90✔
1414
                }
90✔
1415
            }
144✔
1416
            col_ndx++;
222✔
1417
        };
222✔
1418
    }
246✔
1419
}
336✔
1420

1421
// Move information held in the subspec area into the structures managed by Table
1422
// This is information about origin/target tables in relation to links
1423
// This information is now held in "opposite" arrays directly in Table structure
1424
// At the same time the backlink columns are destroyed
1425
// If there is no subspec, this stage is done
1426
void Table::migrate_subspec()
1427
{
282✔
1428
    if (!m_spec.has_subspec())
282✔
1429
        return;
210✔
1430

36✔
1431
    ref_type top_ref = m_top.get_as_ref(top_position_for_columns);
72✔
1432
    Array col_refs(m_alloc);
72✔
1433
    col_refs.set_parent(&m_top, top_position_for_columns);
72✔
1434
    col_refs.init_from_ref(top_ref);
72✔
1435
    Group* group = get_parent_group();
72✔
1436

36✔
1437
    for (size_t col_ndx = 0; col_ndx < m_spec.get_column_count(); col_ndx++) {
516✔
1438
        ColumnType col_type = m_spec.get_column_type(col_ndx);
444✔
1439

222✔
1440
        if (is_link_type(col_type)) {
444✔
1441
            auto target_key = m_spec.get_opposite_link_table_key(col_ndx);
72✔
1442
            auto target_table = group->get_table(target_key);
72✔
1443
            Spec& target_spec = _impl::TableFriend::get_spec(*target_table);
72✔
1444
            // The target table spec may already be migrated.
36✔
1445
            // If it has, the new functions should be used.
36✔
1446
            ColKey backlink_col_key = target_spec.has_subspec()
72✔
1447
                                          ? target_spec.find_backlink_column(m_key, col_ndx)
72✔
1448
                                          : target_table->find_opposite_column(m_spec.get_key(col_ndx));
36✔
1449
            REALM_ASSERT(backlink_col_key.get_type() == col_type_BackLink);
72✔
1450
            if (m_opposite_table.get(col_ndx) != target_key.value) {
72✔
1451
                m_opposite_table.set(col_ndx, target_key.value);
72✔
1452
            }
72✔
1453
            if (m_opposite_column.get(col_ndx) != backlink_col_key.value) {
72✔
1454
                m_opposite_column.set(col_ndx, backlink_col_key.value);
72✔
1455
            }
72✔
1456
        }
72✔
1457
        else if (col_type == col_type_BackLink) {
372✔
1458
            auto origin_key = m_spec.get_opposite_link_table_key(col_ndx);
72✔
1459
            size_t origin_col_ndx = m_spec.get_origin_column_ndx(col_ndx);
72✔
1460
            auto origin_table = group->get_table(origin_key);
72✔
1461
            Spec& origin_spec = _impl::TableFriend::get_spec(*origin_table);
72✔
1462
            ColKey origin_col_key = origin_spec.get_key(origin_col_ndx);
72✔
1463
            REALM_ASSERT(is_link_type(origin_col_key.get_type()));
72✔
1464
            if (m_opposite_table.get(col_ndx) != origin_key.value) {
72✔
1465
                m_opposite_table.set(col_ndx, origin_key.value);
72✔
1466
            }
72✔
1467
            if (m_opposite_column.get(col_ndx) != origin_col_key.value) {
72✔
1468
                m_opposite_column.set(col_ndx, origin_col_key.value);
72✔
1469
            }
72✔
1470
        }
72✔
1471
    };
444✔
1472
    m_spec.destroy_subspec();
72✔
1473
}
72✔
1474

1475
namespace {
1476

1477
class LegacyStringColumn : public BPlusTree<StringData> {
1478
public:
1479
    LegacyStringColumn(Allocator& alloc, Spec* spec, size_t col_ndx, bool nullable)
1480
        : BPlusTree(alloc)
1481
        , m_spec(spec)
1482
        , m_col_ndx(col_ndx)
1483
        , m_nullable(nullable)
1484
    {
300✔
1485
    }
300✔
1486

1487
    std::unique_ptr<BPlusTreeLeaf> init_leaf_node(ref_type ref) override
1488
    {
300✔
1489
        auto leaf = std::make_unique<LeafNode>(this);
300✔
1490
        leaf->ArrayString::set_spec(m_spec, m_col_ndx);
300✔
1491
        leaf->set_nullability(m_nullable);
300✔
1492
        leaf->init_from_ref(ref);
300✔
1493
        return leaf;
300✔
1494
    }
300✔
1495

1496
    StringData get_legacy(size_t n) const
1497
    {
6,648✔
1498
        if (m_cached_leaf_begin <= n && n < m_cached_leaf_end) {
6,648!
1499
            return m_leaf_cache.get_legacy(n - m_cached_leaf_begin);
×
1500
        }
×
1501
        else {
6,648✔
1502
            StringData value;
6,648✔
1503

3,324✔
1504
            auto func = [&value](BPlusTreeNode* node, size_t ndx) {
6,648✔
1505
                auto leaf = static_cast<LeafNode*>(node);
6,648✔
1506
                value = leaf->get_legacy(ndx);
6,648✔
1507
            };
6,648✔
1508

3,324✔
1509
            m_root->bptree_access(n, func);
6,648✔
1510

3,324✔
1511
            return value;
6,648✔
1512
        }
6,648✔
1513
    }
6,648✔
1514

1515
private:
1516
    Spec* m_spec;
1517
    size_t m_col_ndx;
1518
    bool m_nullable;
1519
};
1520

1521
// We need an accessor that can read old Timestamp columns.
1522
// The new BPlusTree<Timestamp> uses a different layout
1523
class LegacyTS : private Array {
1524
public:
1525
    explicit LegacyTS(Allocator& allocator)
1526
        : Array(allocator)
1527
        , m_seconds(allocator)
1528
        , m_nanoseconds(allocator)
1529
    {
18✔
1530
        m_seconds.set_parent(this, 0);
18✔
1531
        m_nanoseconds.set_parent(this, 1);
18✔
1532
    }
18✔
1533

1534
    using Array::set_parent;
1535

1536
    void init_from_parent()
1537
    {
18✔
1538
        Array::init_from_parent();
18✔
1539
        m_seconds.init_from_parent();
18✔
1540
        m_nanoseconds.init_from_parent();
18✔
1541
    }
18✔
1542

1543
    size_t size() const
1544
    {
18✔
1545
        return m_seconds.size();
18✔
1546
    }
18✔
1547

1548
    Timestamp get(size_t ndx) const
1549
    {
3,078✔
1550
        util::Optional<int64_t> seconds = m_seconds.get(ndx);
3,078✔
1551
        return seconds ? Timestamp(*seconds, int32_t(m_nanoseconds.get(ndx))) : Timestamp{};
3,060✔
1552
    }
3,078✔
1553

1554
private:
1555
    BPlusTree<util::Optional<Int>> m_seconds;
1556
    BPlusTree<Int> m_nanoseconds;
1557
};
1558

1559
// Function that can retrieve a single value from the old columns
1560
Mixed get_val_from_column(size_t ndx, ColumnType col_type, bool nullable, BPlusTreeBase* accessor)
1561
{
28,200✔
1562
    switch (col_type) {
28,200✔
1563
        case col_type_Int:
6,612✔
1564
            if (nullable) {
6,612✔
1565
                auto val = static_cast<BPlusTree<util::Optional<Int>>*>(accessor)->get(ndx);
3,150✔
1566
                return Mixed{val};
3,150✔
1567
            }
3,150✔
1568
            else {
3,462✔
1569
                return Mixed{static_cast<BPlusTree<Int>*>(accessor)->get(ndx)};
3,462✔
1570
            }
3,462✔
1571
        case col_type_Bool:
6,084✔
1572
            if (nullable) {
6,084✔
1573
                auto val = static_cast<BPlusTree<util::Optional<Int>>*>(accessor)->get(ndx);
3,042✔
1574
                return val ? Mixed{bool(*val)} : Mixed{};
2,292✔
1575
            }
3,042✔
1576
            else {
3,042✔
1577
                return Mixed{bool(static_cast<BPlusTree<Int>*>(accessor)->get(ndx))};
3,042✔
1578
            }
3,042✔
1579
        case col_type_Float:
3,042✔
1580
            return Mixed{static_cast<BPlusTree<float>*>(accessor)->get(ndx)};
3,042✔
1581
        case col_type_Double:
3,042✔
1582
            return Mixed{static_cast<BPlusTree<double>*>(accessor)->get(ndx)};
3,042✔
1583
        case col_type_String: {
6,378✔
1584
            auto str = static_cast<LegacyStringColumn*>(accessor)->get_legacy(ndx);
6,378✔
1585
            // This is a workaround for a bug where the length could be -1
3,189✔
1586
            // Seen when upgrading very old file.
3,189✔
1587
            if (str.size() == size_t(-1)) {
6,378✔
1588
                return Mixed("");
×
1589
            }
×
1590
            return Mixed{str};
6,378✔
1591
        }
6,378✔
1592
        case col_type_Binary:
4,710✔
1593
            return Mixed{static_cast<BPlusTree<Binary>*>(accessor)->get(ndx)};
3,042✔
1594
        default:
3,189✔
1595
            REALM_UNREACHABLE();
1596
    }
28,200✔
1597
}
28,200✔
1598

1599
template <class T>
1600
void copy_list(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1601
{
6,012✔
1602
    if (sub_table_ref) {
6,012!
1603
        // Actual list is in the columns array position 0
36✔
1604
        Array cols(alloc);
72✔
1605
        cols.init_from_ref(sub_table_ref);
72✔
1606
        ref_type list_ref = cols.get_as_ref(0);
72✔
1607
        BPlusTree<T> from_list(alloc);
72✔
1608
        from_list.init_from_ref(list_ref);
72✔
1609
        size_t list_size = from_list.size();
72✔
1610
        auto l = obj.get_list<T>(col);
72✔
1611
        for (size_t j = 0; j < list_size; j++) {
1,740!
1612
            l.add(from_list.get(j));
1,668✔
1613
        }
1,668✔
1614
    }
72✔
1615
}
6,012✔
1616

1617
template <>
1618
void copy_list<util::Optional<Bool>>(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1619
{
×
1620
    if (sub_table_ref) {
×
1621
        // Actual list is in the columns array position 0
1622
        Array cols(alloc);
×
1623
        cols.init_from_ref(sub_table_ref);
×
1624
        BPlusTree<util::Optional<Int>> from_list(alloc);
×
1625
        from_list.set_parent(&cols, 0);
×
1626
        from_list.init_from_parent();
×
1627
        size_t list_size = from_list.size();
×
1628
        auto l = obj.get_list<util::Optional<Bool>>(col);
×
1629
        for (size_t j = 0; j < list_size; j++) {
×
1630
            util::Optional<Bool> val;
×
1631
            auto int_val = from_list.get(j);
×
1632
            if (int_val) {
×
1633
                val = (*int_val != 0);
×
1634
            }
×
1635
            l.add(val);
×
1636
        }
×
1637
    }
×
1638
}
×
1639

1640
template <>
1641
void copy_list<String>(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1642
{
276✔
1643
    if (sub_table_ref) {
276✔
1644
        // Actual list is in the columns array position 0
63✔
1645
        bool nullable = col.get_attrs().test(col_attr_Nullable);
126✔
1646
        Array cols(alloc);
126✔
1647
        cols.init_from_ref(sub_table_ref);
126✔
1648
        LegacyStringColumn from_list(alloc, nullptr, 0, nullable); // List of strings cannot be enumerated
126✔
1649
        from_list.set_parent(&cols, 0);
126✔
1650
        from_list.init_from_parent();
126✔
1651
        size_t list_size = from_list.size();
126✔
1652
        auto l = obj.get_list<String>(col);
126✔
1653
        for (size_t j = 0; j < list_size; j++) {
396✔
1654
            l.add(from_list.get_legacy(j));
270✔
1655
        }
270✔
1656
    }
126✔
1657
}
276✔
1658

1659
template <>
1660
void copy_list<Timestamp>(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1661
{
×
1662
    if (sub_table_ref) {
×
1663
        // Actual list is in the columns array position 0
1664
        Array cols(alloc);
×
1665
        cols.init_from_ref(sub_table_ref);
×
1666
        LegacyTS from_list(alloc);
×
1667
        from_list.set_parent(&cols, 0);
×
1668
        from_list.init_from_parent();
×
1669
        size_t list_size = from_list.size();
×
1670
        auto l = obj.get_list<Timestamp>(col);
×
1671
        for (size_t j = 0; j < list_size; j++) {
×
1672
            l.add(from_list.get(j));
×
1673
        }
×
1674
    }
×
1675
}
×
1676

1677
} // namespace
1678

1679
void Table::create_columns()
1680
{
336✔
1681
    size_t cnt;
336✔
1682
    auto get_column_cnt = [&cnt](const Cluster* cluster) {
336✔
1683
        cnt = cluster->nb_columns();
336✔
1684
        return IteratorControl::Stop;
336✔
1685
    };
336✔
1686
    traverse_clusters(get_column_cnt);
336✔
1687

168✔
1688
    size_t column_count = m_spec.get_column_count();
336✔
1689
    if (cnt != column_count) {
336✔
1690
        for (size_t col_ndx = 0; col_ndx < column_count; col_ndx++) {
846✔
1691
            m_clusters.insert_column(m_spec.get_key(col_ndx));
672✔
1692
        }
672✔
1693
    }
174✔
1694
}
336✔
1695

1696
bool Table::migrate_objects()
1697
{
336✔
1698
    size_t nb_public_columns = m_spec.get_public_column_count();
336✔
1699
    size_t nb_columns = m_spec.get_column_count();
336✔
1700
    if (!nb_columns) {
336✔
1701
        // No columns - this means no objects
12✔
1702
        return true;
24✔
1703
    }
24✔
1704

156✔
1705
    ref_type top_ref = m_top.get_as_ref(top_position_for_columns);
312✔
1706
    if (!top_ref) {
312✔
1707
        // Has already been done
45✔
1708
        return true;
90✔
1709
    }
90✔
1710
    Array col_refs(m_alloc);
222✔
1711
    col_refs.set_parent(&m_top, top_position_for_columns);
222✔
1712
    col_refs.init_from_ref(top_ref);
222✔
1713

111✔
1714
    /************************ Create column accessors ************************/
111✔
1715

111✔
1716
    std::map<ColKey, std::unique_ptr<BPlusTreeBase>> column_accessors;
222✔
1717
    std::map<ColKey, std::unique_ptr<LegacyTS>> ts_accessors;
222✔
1718
    std::map<ColKey, std::unique_ptr<BPlusTree<int64_t>>> list_accessors;
222✔
1719
    std::vector<size_t> cols_to_destroy;
222✔
1720
    bool has_link_columns = false;
222✔
1721

111✔
1722
    // helper function to determine the number of objects in the table
111✔
1723
    size_t number_of_objects = (nb_columns == 0) ? 0 : size_t(-1);
222✔
1724
    auto update_size = [&number_of_objects](size_t s) {
792✔
1725
        if (number_of_objects == size_t(-1)) {
792✔
1726
            number_of_objects = s;
222✔
1727
        }
222✔
1728
        else {
570✔
1729
            REALM_ASSERT(s == number_of_objects);
570✔
1730
        }
570✔
1731
    };
792✔
1732

111✔
1733
    for (size_t col_ndx = 0; col_ndx < nb_columns; col_ndx++) {
1,062✔
1734
        if (col_ndx < nb_public_columns && m_spec.get_column_name(col_ndx) == "!ROW_INDEX") {
840✔
1735
            // If this column has been added, we can break here
1736
            break;
×
1737
        }
×
1738

420✔
1739
        ColKey col_key = m_spec.get_key(col_ndx);
840✔
1740
        ColumnAttrMask attr = m_spec.get_column_attr(col_ndx);
840✔
1741
        ColumnType col_type = m_spec.get_column_type(col_ndx);
840✔
1742
        bool nullable = attr.test(col_attr_Nullable);
840✔
1743
        std::unique_ptr<BPlusTreeBase> acc;
840✔
1744
        std::unique_ptr<LegacyTS> ts_acc;
840✔
1745
        std::unique_ptr<BPlusTree<int64_t>> list_acc;
840✔
1746

420✔
1747
        if (!(col_ndx < col_refs.size())) {
840✔
1748
            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
1749
                               util::format("Objects in '%1' corrupted by previous upgrade attempt", get_name()));
×
1750
        }
×
1751

420✔
1752
        if (!col_refs.get(col_ndx)) {
840✔
1753
            // This column has been migrated
24✔
1754
            continue;
48✔
1755
        }
48✔
1756

396✔
1757
        if (attr.test(col_attr_List) && col_type != col_type_LinkList) {
792✔
1758
            list_acc = std::make_unique<BPlusTree<int64_t>>(m_alloc);
60✔
1759
        }
60✔
1760
        else {
732✔
1761
            switch (col_type) {
732✔
1762
                case col_type_Int:
300✔
1763
                case col_type_Bool:
312✔
1764
                    if (nullable) {
312✔
1765
                        acc = std::make_unique<BPlusTree<util::Optional<Int>>>(m_alloc);
60✔
1766
                    }
60✔
1767
                    else {
252✔
1768
                        acc = std::make_unique<BPlusTree<Int>>(m_alloc);
252✔
1769
                    }
252✔
1770
                    break;
312✔
1771
                case col_type_Float:
162✔
1772
                    acc = std::make_unique<BPlusTree<float>>(m_alloc);
12✔
1773
                    break;
12✔
1774
                case col_type_Double:
162✔
1775
                    acc = std::make_unique<BPlusTree<double>>(m_alloc);
12✔
1776
                    break;
12✔
1777
                case col_type_String:
243✔
1778
                    acc = std::make_unique<LegacyStringColumn>(m_alloc, &m_spec, col_ndx, nullable);
174✔
1779
                    break;
174✔
1780
                case col_type_Binary:
162✔
1781
                    acc = std::make_unique<BPlusTree<Binary>>(m_alloc);
12✔
1782
                    break;
12✔
1783
                case col_type_Timestamp: {
165✔
1784
                    ts_acc = std::make_unique<LegacyTS>(m_alloc);
18✔
1785
                    break;
18✔
1786
                }
300✔
1787
                case col_type_Link:
186✔
1788
                case col_type_LinkList: {
120✔
1789
                    BPlusTree<int64_t> arr(m_alloc);
120✔
1790
                    arr.set_parent(&col_refs, col_ndx);
120✔
1791
                    arr.init_from_parent();
120✔
1792
                    update_size(arr.size());
120✔
1793
                    has_link_columns = true;
120✔
1794
                    break;
120✔
1795
                }
90✔
1796
                case col_type_BackLink: {
96✔
1797
                    BPlusTree<int64_t> arr(m_alloc);
72✔
1798
                    arr.set_parent(&col_refs, col_ndx);
72✔
1799
                    arr.init_from_parent();
72✔
1800
                    update_size(arr.size());
72✔
1801
                    cols_to_destroy.push_back(col_ndx);
72✔
1802
                    break;
72✔
1803
                }
90✔
1804
                default:
60✔
1805
                    break;
×
1806
            }
792✔
1807
        }
792✔
1808

396✔
1809
        if (acc) {
792✔
1810
            acc->set_parent(&col_refs, col_ndx);
522✔
1811
            acc->init_from_parent();
522✔
1812
            update_size(acc->size());
522✔
1813
            column_accessors.emplace(col_key, std::move(acc));
522✔
1814
            cols_to_destroy.push_back(col_ndx);
522✔
1815
        }
522✔
1816
        if (ts_acc) {
792✔
1817
            ts_acc->set_parent(&col_refs, col_ndx);
18✔
1818
            ts_acc->init_from_parent();
18✔
1819
            update_size(ts_acc->size());
18✔
1820
            ts_accessors.emplace(col_key, std::move(ts_acc));
18✔
1821
            cols_to_destroy.push_back(col_ndx);
18✔
1822
        }
18✔
1823
        if (list_acc) {
792✔
1824
            list_acc->set_parent(&col_refs, col_ndx);
60✔
1825
            list_acc->init_from_parent();
60✔
1826
            update_size(list_acc->size());
60✔
1827
            list_accessors.emplace(col_key, std::move(list_acc));
60✔
1828
            cols_to_destroy.push_back(col_ndx);
60✔
1829
        }
60✔
1830
    }
792✔
1831

111✔
1832
    REALM_ASSERT(number_of_objects != size_t(-1));
222✔
1833

111✔
1834
    if (m_clusters.size() == number_of_objects) {
222✔
1835
        // We have migrated all objects
33✔
1836
        return !has_link_columns;
66✔
1837
    }
66✔
1838

78✔
1839
    // !OID column must not be present. Such columns are only present in syncked
78✔
1840
    // realms, which we cannot upgrade.
78✔
1841
    REALM_ASSERT(nb_public_columns == 0 || m_spec.get_column_name(0) != "!OID");
156✔
1842

78✔
1843
    /*************************** Create objects ******************************/
78✔
1844

78✔
1845
    for (size_t row_ndx = 0; row_ndx < number_of_objects; row_ndx++) {
3,654✔
1846
        // Build a vector of values obtained from the old columns
1,749✔
1847
        FieldValues init_values;
3,498✔
1848
        for (auto& it : column_accessors) {
28,200✔
1849
            auto col_key = it.first;
28,200✔
1850
            auto col_type = col_key.get_type();
28,200✔
1851
            auto nullable = col_key.get_attrs().test(col_attr_Nullable);
28,200✔
1852
            auto val = get_val_from_column(row_ndx, col_type, nullable, it.second.get());
28,200✔
1853
            init_values.insert(col_key, val);
28,200✔
1854
        }
28,200✔
1855
        for (auto& it : ts_accessors) {
3,288✔
1856
            init_values.insert(it.first, Mixed(it.second->get(row_ndx)));
3,078✔
1857
        }
3,078✔
1858

1,749✔
1859
        // Create object with the initial values
1,749✔
1860
        Obj obj = m_clusters.insert(ObjKey(row_ndx), init_values);
3,498✔
1861

1,749✔
1862
        // Then update possible list types
1,749✔
1863
        for (auto& it : list_accessors) {
6,288✔
1864
            switch (it.first.get_type()) {
6,288✔
1865
                case col_type_Int: {
6,012✔
1866
                    if (it.first.get_attrs().test(col_attr_Nullable)) {
6,012✔
1867
                        copy_list<util::Optional<int64_t>>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
3,006✔
1868
                    }
3,006✔
1869
                    else {
3,006✔
1870
                        copy_list<int64_t>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
3,006✔
1871
                    }
3,006✔
1872
                    break;
6,012✔
1873
                }
×
1874
                case col_type_Bool:
✔
1875
                    if (it.first.get_attrs().test(col_attr_Nullable)) {
×
1876
                        copy_list<util::Optional<Bool>>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
×
1877
                    }
×
1878
                    else {
×
1879
                        copy_list<Bool>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
×
1880
                    }
×
1881
                    break;
×
1882
                case col_type_Float:
✔
1883
                    copy_list<float>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
×
1884
                    break;
×
1885
                case col_type_Double:
✔
1886
                    copy_list<double>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
×
1887
                    break;
×
1888
                case col_type_String:
276✔
1889
                    copy_list<String>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
276✔
1890
                    break;
276✔
1891
                case col_type_Binary:
✔
1892
                    copy_list<Binary>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
×
1893
                    break;
×
1894
                case col_type_Timestamp: {
✔
1895
                    copy_list<Timestamp>(to_ref(it.second->get(row_ndx)), obj, it.first, m_alloc);
×
1896
                    break;
×
1897
                }
×
1898
                default:
✔
1899
                    break;
×
1900
            }
6,288✔
1901
        }
6,288✔
1902
    }
3,498✔
1903

78✔
1904
    // Destroy values in the old columns that has been copied.
78✔
1905
    // This frees up space in the file
78✔
1906
    for (auto ndx : cols_to_destroy) {
516✔
1907
        Array::destroy_deep(to_ref(col_refs.get(ndx)), m_alloc);
516✔
1908
        col_refs.set(ndx, 0);
516✔
1909
    }
516✔
1910

78✔
1911
    // We need to be sure that the stored 'next sequence number' is bigger than
78✔
1912
    // the biggest ObjKey currently used.
78✔
1913
    this->set_sequence_number(uint64_t(number_of_objects));
156✔
1914

78✔
1915
#if 0
1916
    if (fastrand(100) < 20) {
1917
        throw std::runtime_error("Upgrade interrupted"); // Can be used for testing
1918
    }
1919
#endif
1920
    return !has_link_columns;
156✔
1921
}
156✔
1922

1923
void Table::migrate_links()
1924
{
60✔
1925
    ref_type top_ref = m_top.get_as_ref(top_position_for_columns);
60✔
1926
    if (!top_ref) {
60✔
1927
        // All objects migrated
1928
        return;
×
1929
    }
×
1930

30✔
1931
    Array col_refs(m_alloc);
60✔
1932
    col_refs.set_parent(&m_top, top_position_for_columns);
60✔
1933
    col_refs.init_from_ref(top_ref);
60✔
1934

30✔
1935
    // Cache column accessors and other information
30✔
1936
    size_t nb_columns = m_spec.get_public_column_count();
60✔
1937
    std::vector<std::unique_ptr<BPlusTree<Int>>> link_column_accessors(nb_columns);
60✔
1938
    std::vector<ColKey> col_keys(nb_columns);
60✔
1939
    std::vector<ColumnType> col_types(nb_columns);
60✔
1940
    std::vector<Table*> target_tables(nb_columns);
60✔
1941
    std::vector<ColKey> opposite_orig_row_ndx_col(nb_columns);
60✔
1942
    for (size_t col_ndx = 0; col_ndx < nb_columns; col_ndx++) {
414✔
1943
        ColumnType col_type = m_spec.get_column_type(col_ndx);
354✔
1944

177✔
1945
        if (is_link_type(col_type)) {
354✔
1946
            link_column_accessors[col_ndx] = std::make_unique<BPlusTree<int64_t>>(m_alloc);
120✔
1947
            link_column_accessors[col_ndx]->set_parent(&col_refs, col_ndx);
120✔
1948
            link_column_accessors[col_ndx]->init_from_parent();
120✔
1949
            col_keys[col_ndx] = m_spec.get_key(col_ndx);
120✔
1950
            col_types[col_ndx] = col_type;
120✔
1951
            target_tables[col_ndx] = get_opposite_table(col_keys[col_ndx]).unchecked_ptr();
120✔
1952
            opposite_orig_row_ndx_col[col_ndx] = target_tables[col_ndx]->get_column_key("!ROW_INDEX");
120✔
1953
        }
120✔
1954
    }
354✔
1955

30✔
1956
    auto orig_row_ndx_col_key = get_column_key("!ROW_INDEX");
60✔
1957
    for (auto obj : *this) {
3,186✔
1958
        for (size_t col_ndx = 0; col_ndx < nb_columns; col_ndx++) {
46,314✔
1959
            if (col_keys[col_ndx]) {
43,128✔
1960
                // If no !ROW_INDEX column is found, the original row index number is
3,186✔
1961
                // equal to the ObjKey value
3,186✔
1962
                size_t orig_row_ndx =
6,372✔
1963
                    size_t(orig_row_ndx_col_key ? obj.get<Int>(orig_row_ndx_col_key) : obj.get_key().value);
6,372✔
1964
                // Get original link value
3,186✔
1965
                int64_t link_val = link_column_accessors[col_ndx]->get(orig_row_ndx);
6,372✔
1966

3,186✔
1967
                Table* target_table = target_tables[col_ndx];
6,372✔
1968
                ColKey search_col = opposite_orig_row_ndx_col[col_ndx];
6,372✔
1969
                auto get_target_key = [target_table, search_col](int64_t orig_link_val) -> ObjKey {
3,444✔
1970
                    if (search_col)
516✔
1971
                        return target_table->find_first_int(search_col, orig_link_val);
36✔
1972
                    else
480✔
1973
                        return ObjKey(orig_link_val);
480✔
1974
                };
516✔
1975

3,186✔
1976
                if (link_val) {
6,372✔
1977
                    if (col_types[col_ndx] == col_type_Link) {
240✔
1978
                        obj.set(col_keys[col_ndx], get_target_key(link_val - 1));
138✔
1979
                    }
138✔
1980
                    else {
102✔
1981
                        auto ll = obj.get_linklist(col_keys[col_ndx]);
102✔
1982
                        BPlusTree<Int> links(m_alloc);
102✔
1983
                        links.init_from_ref(ref_type(link_val));
102✔
1984
                        size_t nb_links = links.size();
102✔
1985
                        for (size_t j = 0; j < nb_links; j++) {
480✔
1986
                            ll.add(get_target_key(links.get(j)));
378✔
1987
                        }
378✔
1988
                    }
102✔
1989
                }
240✔
1990
            }
6,372✔
1991
        }
43,128✔
1992
    }
3,186✔
1993
}
60✔
1994

1995
void Table::finalize_migration(ColKey pk_col_key)
1996
{
282✔
1997
    if (ref_type ref = m_top.get_as_ref(top_position_for_columns)) {
282✔
1998
        Array::destroy_deep(ref, m_alloc);
234✔
1999
        m_top.set(top_position_for_columns, 0);
234✔
2000
    }
234✔
2001

141✔
2002
    if (auto orig_row_ndx_col = get_column_key("!ROW_INDEX")) {
282✔
2003
        remove_column(orig_row_ndx_col);
18✔
2004
    }
18✔
2005

141✔
2006
    if (auto oid_col = get_column_key("!OID")) {
282✔
2007
        remove_column(oid_col);
×
2008
    }
×
2009

141✔
2010
    REALM_ASSERT_RELEASE(!pk_col_key || valid_column(pk_col_key));
282✔
2011
    do_set_primary_key_column(pk_col_key);
282✔
2012
}
282✔
2013

2014
void Table::migrate_sets_and_dictionaries()
2015
{
180✔
2016
    std::vector<ColKey> to_migrate;
180✔
2017
    for (auto col : get_column_keys()) {
570✔
2018
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
2019
            to_migrate.push_back(col);
12✔
2020
        }
12✔
2021
    }
570✔
2022
    if (to_migrate.size()) {
180✔
2023
        for (auto obj : *this) {
6✔
2024
            for (auto col : to_migrate) {
12✔
2025
                if (col.is_set()) {
12✔
2026
                    auto set = obj.get_set<Mixed>(col);
6✔
2027
                    set.migrate();
6✔
2028
                }
6✔
2029
                else if (col.is_dictionary()) {
6✔
2030
                    auto dict = obj.get_dictionary(col);
6✔
2031
                    dict.migrate();
6✔
2032
                }
6✔
2033
            }
12✔
2034
        }
6✔
2035
    }
6✔
2036
}
180✔
2037

2038
StringData Table::get_name() const noexcept
2039
{
2,913,582✔
2040
    const Array& real_top = m_top;
2,913,582✔
2041
    ArrayParent* parent = real_top.get_parent();
2,913,582✔
2042
    if (!parent)
2,913,582✔
2043
        return StringData("");
51✔
2044
    REALM_ASSERT(dynamic_cast<Group*>(parent));
2,913,531✔
2045
    return static_cast<Group*>(parent)->get_table_name(get_key());
2,913,531✔
2046
}
2,913,531✔
2047

2048
StringData Table::get_class_name() const noexcept
2049
{
14,976✔
2050
    return Group::table_name_to_class_name(get_name());
14,976✔
2051
}
14,976✔
2052

2053
const char* Table::get_state() const noexcept
2054
{
42✔
2055
    switch (m_cookie) {
42✔
2056
        case cookie_created:
✔
2057
            return "created";
×
2058
        case cookie_transaction_ended:
6✔
2059
            return "transaction_ended";
6✔
2060
        case cookie_initialized:
✔
2061
            return "initialised";
×
2062
        case cookie_removed:
36✔
2063
            return "removed";
36✔
2064
        case cookie_void:
✔
2065
            return "void";
×
2066
        case cookie_deleted:
✔
2067
            return "deleted";
×
2068
    }
×
2069
    return "";
×
2070
}
×
2071

2072

2073
bool Table::is_nullable(ColKey col_key) const
2074
{
2,020,404✔
2075
    REALM_ASSERT_DEBUG(valid_column(col_key));
2,020,404✔
2076
    return col_key.get_attrs().test(col_attr_Nullable);
2,020,404✔
2077
}
2,020,404✔
2078

2079
bool Table::is_list(ColKey col_key) const
2080
{
172,662✔
2081
    REALM_ASSERT_DEBUG(valid_column(col_key));
172,662✔
2082
    return col_key.get_attrs().test(col_attr_List);
172,662✔
2083
}
172,662✔
2084

2085

2086
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
2087
{
340,488✔
2088
    Array top(alloc);
340,488✔
2089
    _impl::DeepArrayDestroyGuard dg(&top);
340,488✔
2090
    top.create(Array::type_HasRefs); // Throws
340,488✔
2091
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
340,488✔
2092

168,840✔
2093
    {
340,488✔
2094
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
340,488✔
2095
        dg_2.reset(mem.get_ref());
340,488✔
2096
        int_fast64_t v(from_ref(mem.get_ref()));
340,488✔
2097
        top.add(v); // Throws
340,488✔
2098
        dg_2.release();
340,488✔
2099
    }
340,488✔
2100
    top.add(0); // Old position for columns
340,488✔
2101
    {
340,488✔
2102
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
340,488✔
2103
        dg_2.reset(mem.get_ref());
340,488✔
2104
        int_fast64_t v(from_ref(mem.get_ref()));
340,488✔
2105
        top.add(v); // Throws
340,488✔
2106
        dg_2.release();
340,488✔
2107
    }
340,488✔
2108

168,840✔
2109
    // Table key value
168,840✔
2110
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
340,488✔
2111
    top.add(rot);
340,488✔
2112

168,840✔
2113
    // Search indexes
168,840✔
2114
    {
340,488✔
2115
        bool context_flag = false;
340,488✔
2116
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
340,488✔
2117
        dg_2.reset(mem.get_ref());
340,488✔
2118
        int_fast64_t v(from_ref(mem.get_ref()));
340,488✔
2119
        top.add(v); // Throws
340,488✔
2120
        dg_2.release();
340,488✔
2121
    }
340,488✔
2122
    rot = RefOrTagged::make_tagged(0);
340,488✔
2123
    top.add(rot); // Column key
340,488✔
2124
    top.add(rot); // Version
340,488✔
2125
    dg.release();
340,488✔
2126
    // Opposite keys (table and column)
168,840✔
2127
    {
340,488✔
2128
        bool context_flag = false;
340,488✔
2129
        {
340,488✔
2130
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
340,488✔
2131
            dg_2.reset(mem.get_ref());
340,488✔
2132
            int_fast64_t v(from_ref(mem.get_ref()));
340,488✔
2133
            top.add(v); // Throws
340,488✔
2134
            dg_2.release();
340,488✔
2135
        }
340,488✔
2136
        {
340,488✔
2137
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
340,488✔
2138
            dg_2.reset(mem.get_ref());
340,488✔
2139
            int_fast64_t v(from_ref(mem.get_ref()));
340,488✔
2140
            top.add(v); // Throws
340,488✔
2141
            dg_2.release();
340,488✔
2142
        }
340,488✔
2143
    }
340,488✔
2144
    top.add(0); // Sequence number
340,488✔
2145
    top.add(0); // Collision_map
340,488✔
2146
    top.add(0); // pk col key
340,488✔
2147
    top.add(0); // flags
340,488✔
2148
    top.add(0); // tombstones
340,488✔
2149

168,840✔
2150
    REALM_ASSERT(top.size() == top_array_size);
340,488✔
2151

168,840✔
2152
    return top.get_ref();
340,488✔
2153
}
340,488✔
2154

2155
void Table::ensure_graveyard()
2156
{
31,815✔
2157
    if (!m_tombstones) {
31,815✔
2158
        while (m_top.size() < top_position_for_tombstones)
10,038✔
2159
            m_top.add(0);
×
2160
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
10,038✔
2161
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
10,038✔
2162
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
10,038✔
2163
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
10,038✔
2164
        m_tombstones->init_from_parent();
10,038✔
2165
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
24,843✔
2166
            ts->insert_column(col);
24,843✔
2167
            return IteratorControl::AdvanceToNext;
24,843✔
2168
        });
24,843✔
2169
    }
10,038✔
2170
}
31,815✔
2171

2172
void Table::batch_erase_rows(const KeyColumn& keys)
2173
{
558✔
2174
    size_t num_objs = keys.size();
558✔
2175
    std::vector<ObjKey> vec;
558✔
2176
    vec.reserve(num_objs);
558✔
2177
    for (size_t i = 0; i < num_objs; ++i) {
2,898✔
2178
        ObjKey key = keys.get(i);
2,340✔
2179
        if (key != null_key && is_valid(key)) {
2,340✔
2180
            vec.push_back(key);
2,340✔
2181
        }
2,340✔
2182
    }
2,340✔
2183

279✔
2184
    sort(vec.begin(), vec.end());
558✔
2185
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
558✔
2186

279✔
2187
    batch_erase_objects(vec);
558✔
2188
}
558✔
2189

2190
void Table::batch_erase_objects(std::vector<ObjKey>& keys)
2191
{
5,559✔
2192
    Group* g = get_parent_group();
5,559✔
2193
    bool maybe_has_incoming_links = g && !is_asymmetric();
5,559✔
2194

2,778✔
2195
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
5,559✔
2196
        CascadeState state(CascadeState::Mode::Strong, g);
4,791✔
2197
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
3,279✔
2198
            state.m_to_be_deleted.emplace_back(m_key, k);
1,770✔
2199
        });
1,770✔
2200
        if (maybe_has_incoming_links)
4,791✔
2201
            nullify_links(state);
4,791✔
2202
        remove_recursive(state);
4,791✔
2203
    }
4,791✔
2204
    else {
768✔
2205
        CascadeState state(CascadeState::Mode::None, g);
768✔
2206
        for (auto k : keys) {
2,512,302✔
2207
            if (maybe_has_incoming_links) {
2,512,302✔
2208
                m_clusters.nullify_incoming_links(k, state);
2,512,224✔
2209
            }
2,512,224✔
2210
            m_clusters.erase(k, state);
2,512,302✔
2211
        }
2,512,302✔
2212
    }
768✔
2213
    keys.clear();
5,559✔
2214
}
5,559✔
2215

2216
void Table::clear()
2217
{
4,278✔
2218
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
4,278✔
2219
    m_clusters.clear(state);
4,278✔
2220
    free_collision_table();
4,278✔
2221
}
4,278✔
2222

2223

2224
Group* Table::get_parent_group() const noexcept
2225
{
59,656,131✔
2226
    if (!m_top.is_attached())
59,656,131✔
2227
        return 0;                             // Subtable with shared descriptor
×
2228
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
59,656,131✔
2229
    if (!parent)
59,656,131✔
2230
        return 0; // Free-standing table
25,633,650✔
2231

16,793,478✔
2232
    return static_cast<Group*>(parent);
34,022,481✔
2233
}
34,022,481✔
2234

2235
inline uint64_t Table::get_sync_file_id() const noexcept
2236
{
38,525,616✔
2237
    Group* g = get_parent_group();
38,525,616✔
2238
    return g ? g->get_sync_file_id() : 0;
31,898,883✔
2239
}
38,525,616✔
2240

2241
size_t Table::get_index_in_group() const noexcept
2242
{
48✔
2243
    if (!m_top.is_attached())
48✔
2244
        return realm::npos;                   // Subtable with shared descriptor
×
2245
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
48✔
2246
    if (!parent)
48✔
2247
        return realm::npos; // Free-standing table
×
2248
    return m_top.get_ndx_in_parent();
48✔
2249
}
48✔
2250

2251
uint64_t Table::allocate_sequence_number()
2252
{
19,914,459✔
2253
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
19,914,459✔
2254
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,781,481✔
2255
    rot = RefOrTagged::make_tagged(sn + 1);
19,914,459✔
2256
    m_top.set(top_position_for_sequence_number, rot);
19,914,459✔
2257

9,853,773✔
2258
    return sn;
19,914,459✔
2259
}
19,914,459✔
2260

2261
void Table::set_sequence_number(uint64_t seq)
2262
{
156✔
2263
    m_top.set(top_position_for_sequence_number, RefOrTagged::make_tagged(seq));
156✔
2264
}
156✔
2265

2266
void Table::set_collision_map(ref_type ref)
2267
{
×
2268
    m_top.set(top_position_for_collision_map, RefOrTagged::make_ref(ref));
×
2269
}
×
2270

2271
void Table::set_col_key_sequence_number(uint64_t seq)
2272
{
24✔
2273
    m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(seq));
24✔
2274
}
24✔
2275

2276
TableRef Table::get_link_target(ColKey col_key) noexcept
2277
{
310,587✔
2278
    return get_opposite_table(col_key);
310,587✔
2279
}
310,587✔
2280

2281
// count ----------------------------------------------
2282

2283
size_t Table::count_int(ColKey col_key, int64_t value) const
2284
{
12,006✔
2285
    if (auto index = this->get_search_index(col_key)) {
12,006✔
2286
        return index->count(value);
12,000✔
2287
    }
12,000✔
2288

3✔
2289
    return where().equal(col_key, value).count();
6✔
2290
}
6✔
2291
size_t Table::count_float(ColKey col_key, float value) const
2292
{
6✔
2293
    return where().equal(col_key, value).count();
6✔
2294
}
6✔
2295
size_t Table::count_double(ColKey col_key, double value) const
2296
{
6✔
2297
    return where().equal(col_key, value).count();
6✔
2298
}
6✔
2299
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
2300
{
18✔
2301
    ArrayDecimal128 leaf(get_alloc());
18✔
2302
    size_t cnt = 0;
18✔
2303
    bool null_value = value.is_null();
18✔
2304
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
2305
        // direct aggregate on the leaf
9✔
2306
        cluster->init_leaf(col_key, &leaf);
18✔
2307
        auto sz = leaf.size();
18✔
2308
        for (size_t i = 0; i < sz; i++) {
1,296✔
2309
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
2310
                cnt++;
24✔
2311
            }
24✔
2312
        }
1,278✔
2313
        return IteratorControl::AdvanceToNext;
18✔
2314
    };
18✔
2315

9✔
2316
    traverse_clusters(f);
18✔
2317

9✔
2318
    return cnt;
18✔
2319
}
18✔
2320
size_t Table::count_string(ColKey col_key, StringData value) const
2321
{
1,476✔
2322
    if (auto index = this->get_search_index(col_key)) {
1,476✔
2323
        return index->count(value);
732✔
2324
    }
732✔
2325
    return where().equal(col_key, value).count();
744✔
2326
}
744✔
2327

2328
template <typename T>
2329
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
2330
{
28,542✔
2331
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
28,542✔
2332
    LeafType leaf(get_alloc());
28,542✔
2333

14,265✔
2334
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
28,560✔
2335
        // direct aggregate on the leaf
14,274✔
2336
        cluster->init_leaf(column_key, &leaf);
28,560✔
2337
        st.m_key_offset = cluster->get_offset();
28,560✔
2338
        st.m_key_values = cluster->get_key_array();
28,560✔
2339
        st.set_payload_column(&leaf);
28,560✔
2340
        bool cont = true;
28,560✔
2341
        size_t sz = leaf.size();
28,560✔
2342
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
123,036✔
2343
            cont = st.match(local_index);
94,476✔
2344
        }
94,476✔
2345
        return IteratorControl::AdvanceToNext;
28,560✔
2346
    };
28,560✔
2347

14,265✔
2348
    traverse_clusters(f);
28,542✔
2349
}
28,542✔
2350

2351
// This template is also used by the query engine
2352
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
2353
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
2354
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
2355
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
2356
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
2357
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
2358
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
2359

2360
std::optional<Mixed> Table::sum(ColKey col_key) const
2361
{
756✔
2362
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
2363
}
756✔
2364

2365
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
2366
{
822✔
2367
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
2368
}
822✔
2369

2370
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
2371
{
1,170✔
2372
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
2373
}
1,170✔
2374

2375
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
2376
{
22,266✔
2377
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
22,266✔
2378
}
22,266✔
2379

2380
template <class T>
2381
ObjKey Table::find_first(ColKey col_key, T value) const
2382
{
35,067✔
2383
    check_column(col_key);
35,067✔
2384

17,445✔
2385
    if (!col_key.is_nullable() && value_is_null(value)) {
35,067!
2386
        return {}; // this is a precaution/optimization
6✔
2387
    }
6✔
2388
    // You cannot call GetIndexData on ObjKey
17,442✔
2389
    if constexpr (!std::is_same_v<T, ObjKey>) {
35,061✔
2390
        if (StringIndex* index = get_search_index(col_key)) {
35,043!
2391
            return index->find_first(value);
27,195✔
2392
        }
27,195✔
2393
        if (col_key == m_primary_key_col) {
7,848!
2394
            return find_primary_key(value);
×
2395
        }
×
2396
    }
7,848✔
2397

3,924✔
2398
    ObjKey key;
7,848✔
2399
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
7,848✔
2400
    LeafType leaf(get_alloc());
7,848✔
2401

3,924✔
2402
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
9,330✔
2403
        cluster->init_leaf(col_key, &leaf);
9,330✔
2404
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
2405
        if (row != realm::npos) {
9,330!
2406
            key = cluster->get_real_key(row);
7,692✔
2407
            return IteratorControl::Stop;
7,692✔
2408
        }
7,692✔
2409
        return IteratorControl::AdvanceToNext;
1,638✔
2410
    };
1,638✔
2411

3,924✔
2412
    traverse_clusters(f);
7,848✔
2413

3,924✔
2414
    return key;
7,848✔
2415
}
7,848✔
2416

2417
namespace realm {
2418

2419
template <>
2420
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
2421
{
×
2422
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
2423
}
×
2424

2425
template <>
2426
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
2427
{
×
2428
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
2429
}
×
2430

2431
template <>
2432
ObjKey Table::find_first(ColKey col_key, null) const
2433
{
×
2434
    return find_first_null(col_key);
×
2435
}
×
2436
} // namespace realm
2437

2438
// Explicitly instantiate the generic case of the template for the types we care about.
2439
template ObjKey Table::find_first(ColKey col_key, bool) const;
2440
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
2441
template ObjKey Table::find_first(ColKey col_key, float) const;
2442
template ObjKey Table::find_first(ColKey col_key, double) const;
2443
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
2444
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
2445
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
2446
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
2447
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
2448
template ObjKey Table::find_first(ColKey col_key, StringData) const;
2449
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
2450
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
2451
template ObjKey Table::find_first(ColKey col_key, UUID) const;
2452
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
2453
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
2454

2455
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
2456
{
7,740✔
2457
    if (is_nullable(col_key))
7,740✔
2458
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
2459
    else
7,704✔
2460
        return find_first<int64_t>(col_key, value);
7,704✔
2461
}
7,740✔
2462

2463
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
2464
{
78✔
2465
    if (is_nullable(col_key))
78✔
2466
        return find_first<util::Optional<bool>>(col_key, value);
36✔
2467
    else
42✔
2468
        return find_first<bool>(col_key, value);
42✔
2469
}
78✔
2470

2471
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
2472
{
108✔
2473
    return find_first(col_key, value);
108✔
2474
}
108✔
2475

2476
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
2477
{
6✔
2478
    return find_first(col_key, value);
6✔
2479
}
6✔
2480

2481
ObjKey Table::find_first_float(ColKey col_key, float value) const
2482
{
12✔
2483
    return find_first<Float>(col_key, value);
12✔
2484
}
12✔
2485

2486
ObjKey Table::find_first_double(ColKey col_key, double value) const
2487
{
12✔
2488
    return find_first<Double>(col_key, value);
12✔
2489
}
12✔
2490

2491
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
2492
{
×
2493
    return find_first<Decimal128>(col_key, value);
×
2494
}
×
2495

2496
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
2497
{
11,667✔
2498
    return find_first<StringData>(col_key, value);
11,667✔
2499
}
11,667✔
2500

2501
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
2502
{
×
2503
    return find_first<BinaryData>(col_key, value);
×
2504
}
×
2505

2506
ObjKey Table::find_first_null(ColKey col_key) const
2507
{
114✔
2508
    return where().equal(col_key, null{}).find();
114✔
2509
}
114✔
2510

2511
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
2512
{
18✔
2513
    return find_first(col_key, value);
18✔
2514
}
18✔
2515

2516
template <class T>
2517
TableView Table::find_all(ColKey col_key, T value)
2518
{
102✔
2519
    return where().equal(col_key, value).find_all();
102✔
2520
}
102✔
2521

2522
TableView Table::find_all_int(ColKey col_key, int64_t value)
2523
{
96✔
2524
    return find_all<int64_t>(col_key, value);
96✔
2525
}
96✔
2526

2527
TableView Table::find_all_int(ColKey col_key, int64_t value) const
2528
{
×
2529
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
2530
}
×
2531

2532
TableView Table::find_all_bool(ColKey col_key, bool value)
2533
{
×
2534
    return find_all<bool>(col_key, value);
×
2535
}
×
2536

2537
TableView Table::find_all_bool(ColKey col_key, bool value) const
2538
{
×
2539
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
2540
}
×
2541

2542

2543
TableView Table::find_all_float(ColKey col_key, float value)
2544
{
×
2545
    return find_all<float>(col_key, value);
×
2546
}
×
2547

2548
TableView Table::find_all_float(ColKey col_key, float value) const
2549
{
×
2550
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
2551
}
×
2552

2553
TableView Table::find_all_double(ColKey col_key, double value)
2554
{
6✔
2555
    return find_all<double>(col_key, value);
6✔
2556
}
6✔
2557

2558
TableView Table::find_all_double(ColKey col_key, double value) const
2559
{
×
2560
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
2561
}
×
2562

2563
TableView Table::find_all_string(ColKey col_key, StringData value)
2564
{
282✔
2565
    return where().equal(col_key, value).find_all();
282✔
2566
}
282✔
2567

2568
TableView Table::find_all_string(ColKey col_key, StringData value) const
2569
{
×
2570
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
2571
}
×
2572

2573
TableView Table::find_all_binary(ColKey, BinaryData)
2574
{
×
2575
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
2576
}
×
2577

2578
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
2579
{
×
2580
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
2581
}
×
2582

2583
TableView Table::find_all_null(ColKey col_key)
2584
{
×
2585
    return where().equal(col_key, null{}).find_all();
×
2586
}
×
2587

2588
TableView Table::find_all_null(ColKey col_key) const
2589
{
×
2590
    return const_cast<Table*>(this)->find_all_null(col_key);
×
2591
}
×
2592

2593
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
2594
{
6✔
2595
    return where().fulltext(col_key, terms).find_all();
6✔
2596
}
6✔
2597

2598
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
2599
{
72✔
2600
    TableView tv = where().find_all();
72✔
2601
    tv.sort(col_key, ascending);
72✔
2602
    return tv;
72✔
2603
}
72✔
2604

2605
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
2606
{
6✔
2607
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
2608
}
6✔
2609

2610
TableView Table::get_sorted_view(SortDescriptor order)
2611
{
126✔
2612
    TableView tv = where().find_all();
126✔
2613
    tv.sort(std::move(order));
126✔
2614
    return tv;
126✔
2615
}
126✔
2616

2617
TableView Table::get_sorted_view(SortDescriptor order) const
2618
{
×
2619
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
2620
}
×
2621

2622
util::Logger* Table::get_logger() const noexcept
2623
{
1,825,572✔
2624
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,735,674✔
2625
}
1,825,572✔
2626

2627
// Called after a commit. Table will effectively contain the same as before,
2628
// but now with new refs from the file
2629
void Table::update_from_parent() noexcept
2630
{
814,422✔
2631
    // There is no top for sub-tables sharing spec
405,342✔
2632
    if (m_top.is_attached()) {
814,422✔
2633
        m_top.update_from_parent();
814,419✔
2634
        m_spec.update_from_parent();
814,419✔
2635
        m_clusters.update_from_parent();
814,419✔
2636
        m_index_refs.update_from_parent();
814,419✔
2637
        for (auto&& index : m_index_accessors) {
2,665,653✔
2638
            if (index != nullptr) {
2,665,653✔
2639
                index->update_from_parent();
274,524✔
2640
            }
274,524✔
2641
        }
2,665,653✔
2642

405,339✔
2643
        m_opposite_table.update_from_parent();
814,419✔
2644
        m_opposite_column.update_from_parent();
814,419✔
2645
        if (m_top.size() > top_position_for_flags) {
814,419✔
2646
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
812,580✔
2647
            m_table_type = Type(flags & table_type_mask);
812,580✔
2648
        }
812,580✔
2649
        else {
1,839✔
2650
            m_table_type = Type::TopLevel;
1,839✔
2651
        }
1,839✔
2652
        if (m_tombstones)
814,419✔
2653
            m_tombstones->update_from_parent();
1,314✔
2654

405,339✔
2655
        refresh_content_version();
814,419✔
2656
        m_has_any_embedded_objects.reset();
814,419✔
2657
    }
814,419✔
2658
    m_alloc.bump_storage_version();
814,422✔
2659
}
814,422✔
2660

2661
void Table::schema_to_json(std::ostream& out, const std::map<std::string, std::string>& renames) const
2662
{
12✔
2663
    out << "{";
12✔
2664
    auto name = get_name();
12✔
2665
    if (renames.count(name))
12✔
2666
        name = renames.at(name);
×
2667
    out << "\"name\":\"" << name << "\"";
12✔
2668
    if (this->m_primary_key_col) {
12✔
2669
        out << ",";
×
2670
        out << "\"primaryKey\":\"" << this->get_column_name(m_primary_key_col) << "\"";
×
2671
    }
×
2672
    out << ",\"tableType\":\"" << this->get_table_type() << "\"";
12✔
2673
    out << ",\"properties\":[";
12✔
2674
    auto col_keys = get_column_keys();
12✔
2675
    int sz = int(col_keys.size());
12✔
2676
    for (int i = 0; i < sz; ++i) {
48✔
2677
        auto col_key = col_keys[i];
36✔
2678
        name = get_column_name(col_key);
36✔
2679
        auto type = col_key.get_type();
36✔
2680
        if (renames.count(name))
36✔
2681
            name = renames.at(name);
×
2682
        out << "{";
36✔
2683
        out << "\"name\":\"" << name << "\"";
36✔
2684
        if (this->is_link_type(type)) {
36✔
2685
            out << ",\"type\":\"object\"";
6✔
2686
            name = this->get_opposite_table(col_key)->get_name();
6✔
2687
            if (renames.count(name))
6✔
2688
                name = renames.at(name);
×
2689
            out << ",\"objectType\":\"" << name << "\"";
6✔
2690
        }
6✔
2691
        else {
30✔
2692
            out << ",\"type\":\"" << get_data_type_name(DataType(type)) << "\"";
30✔
2693
        }
30✔
2694
        if (col_key.is_list()) {
36✔
2695
            out << ",\"isArray\":true";
12✔
2696
        }
12✔
2697
        else if (col_key.is_set()) {
24✔
2698
            out << ",\"isSet\":true";
×
2699
        }
×
2700
        else if (col_key.is_dictionary()) {
24✔
2701
            out << ",\"isMap\":true";
×
2702
            auto key_type = get_dictionary_key_type(col_key);
×
2703
            out << ",\"keyType\":\"" << get_data_type_name(key_type) << "\"";
×
2704
        }
×
2705
        if (col_key.is_nullable()) {
36✔
2706
            out << ",\"isOptional\":true";
6✔
2707
        }
6✔
2708
        auto index_type = search_index_type(col_key);
36✔
2709
        if (index_type == IndexType::General) {
36✔
2710
            out << ",\"isIndexed\":true";
×
2711
        }
×
2712
        if (index_type == IndexType::Fulltext) {
36✔
2713
            out << ",\"isFulltextIndexed\":true";
×
2714
        }
×
2715
        out << "}";
36✔
2716
        if (i < sz - 1) {
36✔
2717
            out << ",";
24✔
2718
        }
24✔
2719
    }
36✔
2720
    out << "]}";
12✔
2721
}
12✔
2722

2723
void Table::to_json(std::ostream& out, size_t link_depth, const std::map<std::string, std::string>& renames,
2724
                    JSONOutputMode output_mode) const
2725
{
300✔
2726
    // Represent table as list of objects
150✔
2727
    out << "[";
300✔
2728
    bool first = true;
300✔
2729

150✔
2730
    for (auto& obj : *this) {
4,050✔
2731
        if (first) {
4,050✔
2732
            first = false;
294✔
2733
        }
294✔
2734
        else {
3,756✔
2735
            out << ",";
3,756✔
2736
        }
3,756✔
2737
        obj.to_json(out, link_depth, renames, output_mode);
4,050✔
2738
    }
4,050✔
2739

150✔
2740
    out << "]";
300✔
2741
}
300✔
2742

2743

2744
bool Table::operator==(const Table& t) const
2745
{
138✔
2746
    if (size() != t.size()) {
138✔
2747
        return false;
12✔
2748
    }
12✔
2749
    // Check columns
63✔
2750
    for (auto ck : this->get_column_keys()) {
522✔
2751
        auto name = get_column_name(ck);
522✔
2752
        auto other_ck = t.get_column_key(name);
522✔
2753
        auto attrs = ck.get_attrs();
522✔
2754
        if (search_index_type(ck) != t.search_index_type(other_ck))
522✔
2755
            return false;
×
2756

261✔
2757
        if (!other_ck || other_ck.get_attrs() != attrs) {
522✔
2758
            return false;
×
2759
        }
×
2760
    }
522✔
2761
    auto pk_col = get_primary_key_column();
126✔
2762
    for (auto o : *this) {
2,898✔
2763
        Obj other_o;
2,898✔
2764
        if (pk_col) {
2,898✔
2765
            auto pk = o.get_any(pk_col);
90✔
2766
            other_o = t.get_object_with_primary_key(pk);
90✔
2767
        }
90✔
2768
        else {
2,808✔
2769
            other_o = t.get_object(o.get_key());
2,808✔
2770
        }
2,808✔
2771
        if (!(other_o && o == other_o))
2,898✔
2772
            return false;
18✔
2773
    }
2,898✔
2774

63✔
2775
    return true;
117✔
2776
}
126✔
2777

2778

2779
void Table::flush_for_commit()
2780
{
3,961,167✔
2781
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
3,961,170✔
2782
        if (!m_top.is_read_only()) {
3,961,167✔
2783
            ++m_in_file_version_at_transaction_boundary;
1,239,297✔
2784
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
1,239,297✔
2785
            m_top.set(top_position_for_version, rot_version);
1,239,297✔
2786
        }
1,239,297✔
2787
    }
3,961,167✔
2788
}
3,961,167✔
2789

2790
void Table::refresh_content_version()
2791
{
1,088,469✔
2792
    REALM_ASSERT(m_top.is_attached());
1,088,469✔
2793
    if (m_top.size() >= top_position_for_version) {
1,088,469✔
2794
        // we have versioning info in the file. Use this to conditionally
545,277✔
2795
        // bump the version counter:
545,277✔
2796
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,088,403✔
2797
        REALM_ASSERT(rot_version.is_tagged());
1,088,403✔
2798
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,088,403✔
2799
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
160,128✔
2800
            bump_content_version();
160,128✔
2801
        }
160,128✔
2802
    }
1,088,403✔
2803
    else {
66✔
2804
        // assume the worst:
24✔
2805
        bump_content_version();
66✔
2806
    }
66✔
2807
}
1,088,469✔
2808

2809

2810
// Called when Group is moved to another version - either a rollback or an advance.
2811
// The content of the table is potentially different, so make no assumptions.
2812
void Table::refresh_accessor_tree()
2813
{
274,032✔
2814
    REALM_ASSERT(m_cookie == cookie_initialized);
274,032✔
2815
    REALM_ASSERT(m_top.is_attached());
274,032✔
2816
    m_top.init_from_parent();
274,032✔
2817
    m_spec.init_from_parent();
274,032✔
2818
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
274,032✔
2819
    m_clusters.init_from_parent();
274,032✔
2820
    m_index_refs.init_from_parent();
274,032✔
2821
    m_opposite_table.init_from_parent();
274,032✔
2822
    m_opposite_column.init_from_parent();
274,032✔
2823
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
274,032✔
2824
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
225,069✔
2825
    if (m_top.size() > top_position_for_flags) {
274,035✔
2826
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
274,005✔
2827
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
274,005✔
2828
    }
274,005✔
2829
    else {
2,147,483,677✔
2830
        m_table_type = Type::TopLevel;
2,147,483,677✔
2831
    }
2,147,483,677✔
2832
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
274,035✔
2833
        // Tombstones exists
408✔
2834
        if (!m_tombstones) {
816✔
2835
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
282✔
2836
        }
282✔
2837
        m_tombstones->init_from_parent();
816✔
2838
    }
816✔
2839
    else {
273,216✔
2840
        m_tombstones = nullptr;
273,216✔
2841
    }
273,216✔
2842
    refresh_content_version();
274,032✔
2843
    bump_storage_version();
274,032✔
2844
    build_column_mapping();
274,032✔
2845
    refresh_index_accessors();
274,032✔
2846
}
274,032✔
2847

2848
void Table::refresh_index_accessors()
2849
{
6,275,682✔
2850
    // Refresh search index accessors
3,361,785✔
2851

3,361,785✔
2852
    // First eliminate any index accessors for eliminated last columns
3,361,785✔
2853
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
6,275,682✔
2854
    m_index_accessors.resize(col_ndx_end);
6,275,682✔
2855

3,361,785✔
2856
    // Then eliminate/refresh/create accessors within column range
3,361,785✔
2857
    // we can not use for_each_column() here, since the columns may have changed
3,361,785✔
2858
    // and the index accessor vector is not updated correspondingly.
3,361,785✔
2859
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
27,622,926✔
2860
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
21,347,244✔
2861

11,060,472✔
2862
        if (ref == 0) {
21,347,244✔
2863
            // accessor drop
9,089,958✔
2864
            m_index_accessors[col_ndx].reset();
17,391,342✔
2865
        }
17,391,342✔
2866
        else {
3,955,902✔
2867
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
3,955,902✔
2868
            bool fulltext = attr.test(col_attr_FullText_Indexed);
3,955,902✔
2869
            auto col_key = m_leaf_ndx2colkey[col_ndx];
3,955,902✔
2870
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
3,955,887✔
2871

1,970,514✔
2872
            if (m_index_accessors[col_ndx]) { // still there, refresh:
3,955,902✔
2873
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
464,469✔
2874
            }
464,469✔
2875
            else { // new index!
3,491,433✔
2876
                m_index_accessors[col_ndx] =
3,491,433✔
2877
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
3,491,433✔
2878
            }
3,491,433✔
2879
        }
3,955,902✔
2880
    }
21,347,244✔
2881
}
6,275,682✔
2882

2883
bool Table::is_cross_table_link_target() const noexcept
2884
{
8,217✔
2885
    auto is_cross_link = [this](ColKey col_key) {
4,218✔
2886
        auto t = col_key.get_type();
54✔
2887
        // look for a backlink with a different target than ourselves
27✔
2888
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
54✔
2889
                   ? IteratorControl::Stop
36✔
2890
                   : IteratorControl::AdvanceToNext;
45✔
2891
    };
54✔
2892
    return for_each_backlink_column(is_cross_link);
8,217✔
2893
}
8,217✔
2894

2895
// LCOV_EXCL_START ignore debug functions
2896

2897
void Table::verify() const
2898
{
2,941,740✔
2899
#ifdef REALM_DEBUG
2,941,740✔
2900
    if (m_top.is_attached())
2,941,740✔
2901
        m_top.verify();
2,941,740✔
2902
    m_spec.verify();
2,941,740✔
2903
    m_clusters.verify();
2,941,740✔
2904
    if (nb_unresolved())
2,941,740✔
2905
        m_tombstones->verify();
746,613✔
2906
#endif
2,941,740✔
2907
}
2,941,740✔
2908

2909
#ifdef REALM_DEBUG
2910
MemStats Table::stats() const
2911
{
×
2912
    MemStats mem_stats;
×
2913
    m_top.stats(mem_stats);
×
2914
    return mem_stats;
×
2915
}
×
2916
#endif // LCOV_EXCL_STOP ignore debug functions
2917

2918
Obj Table::create_object(ObjKey key, const FieldValues& values)
2919
{
22,575,828✔
2920
    if (is_embedded())
22,575,828✔
2921
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2922
    if (m_primary_key_col)
22,575,780✔
2923
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2924
    if (key == null_key) {
22,575,780✔
2925
        GlobalKey object_id = allocate_object_id_squeezed();
19,399,461✔
2926
        key = object_id.get_local_key(get_sync_file_id());
19,399,461✔
2927
        // Check if this key collides with an already existing object
9,634,737✔
2928
        // This could happen if objects were at some point created with primary keys,
9,634,737✔
2929
        // but later primary key property was removed from the schema.
9,634,737✔
2930
        while (m_clusters.is_valid(key)) {
19,399,461✔
2931
            object_id = allocate_object_id_squeezed();
×
2932
            key = object_id.get_local_key(get_sync_file_id());
×
2933
        }
×
2934
        if (auto repl = get_repl())
19,399,461✔
2935
            repl->create_object(this, object_id);
4,239,198✔
2936
    }
19,399,461✔
2937

11,223,660✔
2938
    REALM_ASSERT(key.value >= 0);
22,575,780✔
2939

11,223,660✔
2940
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,575,780✔
2941

11,223,660✔
2942
    return obj;
22,575,780✔
2943
}
22,575,780✔
2944

2945
Obj Table::create_linked_object()
2946
{
40,182✔
2947
    REALM_ASSERT(is_embedded());
40,182✔
2948

20,004✔
2949
    GlobalKey object_id = allocate_object_id_squeezed();
40,182✔
2950
    ObjKey key = object_id.get_local_key(get_sync_file_id());
40,182✔
2951

20,004✔
2952
    REALM_ASSERT(key.value >= 0);
40,182✔
2953

20,004✔
2954
    Obj obj = m_clusters.insert(key, {});
40,182✔
2955

20,004✔
2956
    return obj;
40,182✔
2957
}
40,182✔
2958

2959
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2960
{
30✔
2961
    if (is_embedded())
30✔
2962
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2963
    if (m_primary_key_col)
30✔
2964
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2965
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2966

15✔
2967
    if (auto repl = get_repl())
30✔
2968
        repl->create_object(this, object_id);
30✔
2969

15✔
2970
    try {
30✔
2971
        Obj obj = m_clusters.insert(key, values);
30✔
2972
        // Check if tombstone exists
15✔
2973
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2974
            auto unres_key = key.get_unresolved();
6✔
2975
            // Copy links over
3✔
2976
            auto tombstone = m_tombstones->get(unres_key);
6✔
2977
            obj.assign_pk_and_backlinks(tombstone);
6✔
2978
            // If tombstones had no links to it, it may still be alive
3✔
2979
            if (m_tombstones->is_valid(unres_key)) {
6✔
2980
                CascadeState state(CascadeState::Mode::None);
6✔
2981
                m_tombstones->erase(unres_key, state);
6✔
2982
            }
6✔
2983
        }
6✔
2984

15✔
2985
        return obj;
30✔
2986
    }
30✔
2987
    catch (const KeyAlreadyUsed&) {
6✔
2988
        return m_clusters.get(key);
6✔
2989
    }
6✔
2990
}
30✔
2991

2992
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2993
                                          bool* did_create)
2994
{
648,120✔
2995
    auto primary_key_col = get_primary_key_column();
648,120✔
2996
    if (is_embedded() || !primary_key_col)
648,120✔
2997
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2998
                              util::format("Table has no primary key: %1", get_name()));
6✔
2999

310,788✔
3000
    DataType type = DataType(primary_key_col.get_type());
648,114✔
3001

310,788✔
3002
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
648,114✔
3003
        throw InvalidArgument(
6✔
3004
            ErrorCodes::PropertyNotNullable,
6✔
3005
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
3006
    }
6✔
3007

310,785✔
3008
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
648,108✔
3009
        primary_key.get_type() != type) {
647,889✔
3010
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
3011
                                                                     Group::table_name_to_class_name(get_name())));
6✔
3012
    }
6✔
3013

310,782✔
3014
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
648,102✔
3015

310,782✔
3016
    if (did_create)
648,102✔
3017
        *did_create = false;
74,910✔
3018

310,782✔
3019
    // Check for existing object
310,782✔
3020
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
648,102✔
3021
        if (mode == UpdateMode::never) {
81,840✔
3022
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
3023
        }
6✔
3024
        auto obj = m_clusters.get(key);
81,834✔
3025
        for (auto& val : field_values) {
40,254✔
3026
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
3027
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
3028
            }
12✔
3029
        }
12✔
3030
        return obj;
81,834✔
3031
    }
81,834✔
3032

270,531✔
3033
    ObjKey unres_key;
566,262✔
3034
    if (m_tombstones) {
566,262✔
3035
        // Check for potential tombstone
18,783✔
3036
        GlobalKey object_id{primary_key};
39,195✔
3037
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
39,195✔
3038

18,783✔
3039
        ObjKey key = object_key.get_unresolved();
39,195✔
3040
        if (auto obj = m_tombstones->try_get_obj(key)) {
39,195✔
3041
            auto existing_pk_value = obj.get_any(primary_key_col);
13,224✔
3042

6,465✔
3043
            // If the primary key is the same, the object should be resurrected below
6,465✔
3044
            if (existing_pk_value == primary_key) {
13,224✔
3045
                unres_key = key;
13,218✔
3046
            }
13,218✔
3047
        }
13,224✔
3048
    }
39,195✔
3049

270,531✔
3050
    ObjKey key = get_next_valid_key();
566,262✔
3051

270,531✔
3052
    auto repl = get_repl();
566,262✔
3053
    if (repl) {
566,262✔
3054
        repl->create_object_with_primary_key(this, key, primary_key);
445,167✔
3055
    }
445,167✔
3056
    if (did_create) {
566,262✔
3057
        *did_create = true;
36,582✔
3058
    }
36,582✔
3059

270,531✔
3060
    field_values.insert(primary_key_col, primary_key);
566,262✔
3061
    Obj ret = m_clusters.insert(key, field_values);
566,262✔
3062

270,531✔
3063
    // Check if unresolved exists
270,531✔
3064
    if (unres_key) {
566,262✔
3065
        auto tombstone = m_tombstones->get(unres_key);
13,218✔
3066
        ret.assign_pk_and_backlinks(tombstone);
13,218✔
3067
        // If tombstones had no links to it, it may still be alive
6,462✔
3068
        if (m_tombstones->is_valid(unres_key)) {
13,218✔
3069
            CascadeState state(CascadeState::Mode::None);
4,062✔
3070
            m_tombstones->erase(unres_key, state);
4,062✔
3071
        }
4,062✔
3072
    }
13,218✔
3073
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
566,262✔
3074
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
3075
    }
1,290✔
3076
    return ret;
566,262✔
3077
}
566,262✔
3078

3079
ObjKey Table::find_primary_key(Mixed primary_key) const
3080
{
917,430✔
3081
    auto primary_key_col = get_primary_key_column();
917,430✔
3082
    REALM_ASSERT(primary_key_col);
917,430✔
3083
    DataType type = DataType(primary_key_col.get_type());
917,430✔
3084
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
917,430✔
3085
                 primary_key.get_type() == type);
917,430✔
3086

458,595✔
3087
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
917,430✔
3088
        return index->find_first(primary_key);
917,370✔
3089
    }
917,370✔
3090

6✔
3091
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
6✔
3092
    // so try the old algorithm
6✔
3093
    GlobalKey object_id{primary_key};
60✔
3094
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
60✔
3095

6✔
3096
    // Check if existing
6✔
3097
    if (auto obj = m_clusters.try_get_obj(object_key)) {
60✔
3098
        auto existing_pk_value = obj.get_any(primary_key_col);
×
3099

3100
        if (existing_pk_value == primary_key) {
×
3101
            return object_key;
×
3102
        }
×
3103
    }
60✔
3104
    return {};
60✔
3105
}
60✔
3106

3107
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
3108
{
747,213✔
3109
    // Check if existing
374,490✔
3110
    if (auto key = find_primary_key(primary_key)) {
747,213✔
3111
        return key;
715,941✔
3112
    }
715,941✔
3113

15,123✔
3114
    // Object does not exist - create tombstone
15,123✔
3115
    GlobalKey object_id{primary_key};
31,272✔
3116
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
31,272✔
3117
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
31,272✔
3118
}
31,272✔
3119

3120
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
3121
{
18✔
3122
    REALM_ASSERT(!m_primary_key_col);
18✔
3123
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
3124

9✔
3125
    // Check if existing
9✔
3126
    if (m_clusters.is_valid(object_key)) {
18✔
3127
        return object_key;
12✔
3128
    }
12✔
3129

3✔
3130
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
3131
}
6✔
3132

3133
ObjKey Table::get_objkey(GlobalKey global_key) const
3134
{
18✔
3135
    ObjKey key;
18✔
3136
    REALM_ASSERT(!m_primary_key_col);
18✔
3137
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
3138
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
3139
        key = global_key.get_local_key(get_sync_file_id());
6✔
3140
    }
6✔
3141
    if (key && !is_valid(key)) {
18✔
3142
        key = realm::null_key;
6✔
3143
    }
6✔
3144
    return key;
18✔
3145
}
18✔
3146

3147
GlobalKey Table::get_object_id(ObjKey key) const
3148
{
144✔
3149
    auto col = get_primary_key_column();
144✔
3150
    if (col) {
144✔
3151
        const Obj obj = get_object(key);
24✔
3152
        auto val = obj.get_any(col);
24✔
3153
        return {val};
24✔
3154
    }
24✔
3155
    else {
120✔
3156
        return {key, get_sync_file_id()};
120✔
3157
    }
120✔
3158
    return {};
×
3159
}
×
3160

3161
Obj Table::get_object_with_primary_key(Mixed primary_key) const
3162
{
58,998✔
3163
    auto primary_key_col = get_primary_key_column();
58,998✔
3164
    REALM_ASSERT(primary_key_col);
58,998✔
3165
    DataType type = DataType(primary_key_col.get_type());
58,998✔
3166
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
58,998✔
3167
                 primary_key.get_type() == type);
58,998✔
3168
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
58,998✔
3169
    return k ? m_clusters.get(k) : Obj{};
58,989✔
3170
}
58,998✔
3171

3172
Mixed Table::get_primary_key(ObjKey key) const
3173
{
616,386✔
3174
    auto primary_key_col = get_primary_key_column();
616,386✔
3175
    REALM_ASSERT(primary_key_col);
616,386✔
3176
    if (key.is_unresolved()) {
616,386✔
3177
        REALM_ASSERT(m_tombstones);
72✔
3178
        return m_tombstones->get(key).get_any(primary_key_col);
72✔
3179
    }
72✔
3180
    else {
616,314✔
3181
        return m_clusters.get(key).get_any(primary_key_col);
616,314✔
3182
    }
616,314✔
3183
}
616,386✔
3184

3185
GlobalKey Table::allocate_object_id_squeezed()
3186
{
19,438,992✔
3187
    // m_client_file_ident will be zero if we haven't been in contact with
9,654,558✔
3188
    // the server yet.
9,654,558✔
3189
    auto peer_id = get_sync_file_id();
19,438,992✔
3190
    auto sequence = allocate_sequence_number();
19,438,992✔
3191
    return GlobalKey{peer_id, sequence};
19,438,992✔
3192
}
19,438,992✔
3193

3194
namespace {
3195

3196
/// Calculate optimistic local ID that may collide with others. It is up to
3197
/// the caller to ensure that collisions are detected and that
3198
/// allocate_local_id_after_collision() is called to obtain a non-colliding
3199
/// ID.
3200
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
3201
{
70,836✔
3202
#if REALM_EXERCISE_OBJECT_ID_COLLISION
3203
    const uint64_t optimistic_mask = 0xff;
3204
#else
3205
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
70,836✔
3206
#endif
70,836✔
3207
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
70,836✔
3208
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
70,836✔
3209
}
70,836✔
3210

3211
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
3212
{
12✔
3213
    REALM_ASSERT(!(sequence_number >> 62));
12✔
3214
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
3215
}
12✔
3216

3217
} // namespace
3218

3219
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
3220
{
70,836✔
3221
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
70,836✔
3222

34,119✔
3223
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
70,836✔
3224
        Allocator& alloc = m_top.get_alloc();
24✔
3225
        Array collision_map{alloc};
24✔
3226
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
3227

12✔
3228
        Array hi{alloc};
24✔
3229
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
3230

12✔
3231
        // Entries are ordered by hi,lo
12✔
3232
        size_t found = hi.find_first(object_id.hi());
24✔
3233
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
3234
            Array lo{alloc};
24✔
3235
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
3236
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
3237
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
3238
                Array local_id{alloc};
24✔
3239
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
3240
                return ObjKey{local_id.get(candidate)};
24✔
3241
            }
24✔
3242
        }
70,812✔
3243
    }
24✔
3244

34,107✔
3245
    return optimistic;
70,812✔
3246
}
70,812✔
3247

3248
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
3249
                                                     ObjKey colliding_local_id)
3250
{
12✔
3251
    // Possible optimization: Cache these accessors
6✔
3252
    Allocator& alloc = m_top.get_alloc();
12✔
3253
    Array collision_map{alloc};
12✔
3254
    Array hi{alloc};
12✔
3255
    Array lo{alloc};
12✔
3256
    Array local_id{alloc};
12✔
3257

6✔
3258
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
3259
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
3260
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
3261
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
3262

6✔
3263
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
3264
    if (collision_map_ref) {
12✔
3265
        collision_map.init_from_parent(); // Throws
×
3266
    }
×
3267
    else {
12✔
3268
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
3269
        collision_map.init_from_mem(mem);                                          // Throws
12✔
3270
        collision_map.update_parent();
12✔
3271

6✔
3272
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
3273
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
3274
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
3275
        collision_map.add(lo_ref);                                                                     // Throws
12✔
3276
        collision_map.add(hi_ref);                                                                     // Throws
12✔
3277
        collision_map.add(local_id_ref);                                                               // Throws
12✔
3278
    }
12✔
3279

6✔
3280
    hi.init_from_parent();       // Throws
12✔
3281
    lo.init_from_parent();       // Throws
12✔
3282
    local_id.init_from_parent(); // Throws
12✔
3283

6✔
3284
    size_t num_entries = hi.size();
12✔
3285
    REALM_ASSERT(lo.size() == num_entries);
12✔
3286
    REALM_ASSERT(local_id.size() == num_entries);
12✔
3287

6✔
3288
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
3289
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
3290
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
3291
            ++i;
6✔
3292
        return i;
24✔
3293
    };
24✔
3294

6✔
3295
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
3296
        size_t i = lower_bound_object_id(object_id);
24✔
3297
        if (i != num_entries) {
24✔
3298
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
3299
            if (existing == object_id) {
6✔
3300
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
3301
                return;
×
3302
            }
×
3303
        }
24✔
3304
        hi.insert(i, int64_t(object_id.hi()));
24✔
3305
        lo.insert(i, int64_t(object_id.lo()));
24✔
3306
        local_id.insert(i, new_local_id.value);
24✔
3307
        ++num_entries;
24✔
3308
    };
24✔
3309

6✔
3310
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
3311
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
3312
    insert_collision(incoming_id, new_local_id);
12✔
3313
    insert_collision(colliding_id, colliding_local_id);
12✔
3314

6✔
3315
    return new_local_id;
12✔
3316
}
12✔
3317

3318
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
3319
{
31,815✔
3320
    auto unres_key = key.get_unresolved();
31,815✔
3321

15,423✔
3322
    ensure_graveyard();
31,815✔
3323
    auto tombstone = m_tombstones->try_get_obj(unres_key);
31,815✔
3324
    if (tombstone) {
31,815✔
3325
        if (pk_col) {
3,219✔
3326
            auto existing_pk_value = tombstone.get_any(pk_col);
3,219✔
3327
            // It may just be the same object
1,518✔
3328
            if (existing_pk_value != pk_val) {
3,219✔
3329
                // We have a collision - create new ObjKey
6✔
3330
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
3331
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
3332
            }
12✔
3333
        }
3,207✔
3334
        return tombstone;
3,207✔
3335
    }
3,207✔
3336
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
28,596✔
3337
}
28,596✔
3338

3339
void Table::free_local_id_after_hash_collision(ObjKey key)
3340
{
5,028,042✔
3341
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,028,042✔
3342
        if (key.is_unresolved()) {
30✔
3343
            // Keys will always be inserted as resolved
12✔
3344
            key = key.get_unresolved();
24✔
3345
        }
24✔
3346
        // Possible optimization: Cache these accessors
15✔
3347
        Array collision_map{m_alloc};
30✔
3348
        Array local_id{m_alloc};
30✔
3349

15✔
3350
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
3351
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
3352
        collision_map.init_from_ref(collision_map_ref);
30✔
3353
        local_id.init_from_parent();
30✔
3354
        auto ndx = local_id.find_first(key.value);
30✔
3355
        if (ndx != realm::npos) {
30✔
3356
            Array hi{m_alloc};
24✔
3357
            Array lo{m_alloc};
24✔
3358

12✔
3359
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
3360
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
3361
            hi.init_from_parent();
24✔
3362
            lo.init_from_parent();
24✔
3363

12✔
3364
            hi.erase(ndx);
24✔
3365
            lo.erase(ndx);
24✔
3366
            local_id.erase(ndx);
24✔
3367
            if (hi.size() == 0) {
24✔
3368
                free_collision_table();
12✔
3369
            }
12✔
3370
        }
24✔
3371
    }
30✔
3372
}
5,028,042✔
3373

3374
void Table::free_collision_table()
3375
{
4,290✔
3376
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
4,290✔
3377
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
3378
        m_top.set(top_position_for_collision_map, 0);
12✔
3379
    }
12✔
3380
}
4,290✔
3381

3382
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
3383
{
3,864✔
3384
    while (number--) {
6,323,232✔
3385
        keys.push_back(create_object().get_key());
6,319,368✔
3386
    }
6,319,368✔
3387
}
3,864✔
3388

3389
void Table::create_objects(const std::vector<ObjKey>& keys)
3390
{
630✔
3391
    for (auto k : keys) {
5,616✔
3392
        create_object(k);
5,616✔
3393
    }
5,616✔
3394
}
630✔
3395

3396
void Table::dump_objects()
3397
{
×
3398
    m_clusters.dump_objects();
×
3399
    if (nb_unresolved())
×
3400
        m_tombstones->dump_objects();
×
3401
}
×
3402

3403
void Table::remove_object(ObjKey key)
3404
{
2,489,934✔
3405
    Group* g = get_parent_group();
2,489,934✔
3406

1,243,899✔
3407
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,489,934✔
3408
        CascadeState state(CascadeState::Mode::Strong, g);
2,643✔
3409
        state.m_to_be_deleted.emplace_back(m_key, key);
2,643✔
3410
        m_clusters.nullify_incoming_links(key, state);
2,643✔
3411
        remove_recursive(state);
2,643✔
3412
    }
2,643✔
3413
    else {
2,487,291✔
3414
        CascadeState state(CascadeState::Mode::None, g);
2,487,291✔
3415
        if (g) {
2,487,291✔
3416
            m_clusters.nullify_incoming_links(key, state);
2,405,526✔
3417
        }
2,405,526✔
3418
        m_clusters.erase(key, state);
2,487,291✔
3419
    }
2,487,291✔
3420
}
2,489,934✔
3421

3422
ObjKey Table::invalidate_object(ObjKey key)
3423
{
63,705✔
3424
    if (is_embedded())
63,705✔
3425
        throw IllegalOperation("Deletion of embedded object not allowed");
×
3426
    REALM_ASSERT(!key.is_unresolved());
63,705✔
3427

31,128✔
3428
    Obj tombstone;
63,705✔
3429
    auto obj = get_object(key);
63,705✔
3430
    if (obj.has_backlinks(false)) {
63,705✔
3431
        // If the object has backlinks, we should make a tombstone
267✔
3432
        // and make inward links point to it,
267✔
3433
        if (auto primary_key_col = get_primary_key_column()) {
534✔
3434
            auto pk = obj.get_any(primary_key_col);
378✔
3435
            GlobalKey object_id{pk};
378✔
3436
            auto unres_key = global_to_local_object_id_hashed(object_id);
378✔
3437
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
378✔
3438
        }
378✔
3439
        else {
156✔
3440
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
3441
        }
156✔
3442
        tombstone.assign_pk_and_backlinks(obj);
534✔
3443
    }
534✔
3444

31,128✔
3445
    remove_object(key);
63,705✔
3446

31,128✔
3447
    return tombstone.get_key();
63,705✔
3448
}
63,705✔
3449

3450
void Table::remove_object_recursive(ObjKey key)
3451
{
48✔
3452
    size_t table_ndx = get_index_in_group();
48✔
3453
    if (table_ndx != realm::npos) {
48✔
3454
        CascadeState state(CascadeState::Mode::All, get_parent_group());
48✔
3455
        state.m_to_be_deleted.emplace_back(m_key, key);
48✔
3456
        nullify_links(state);
48✔
3457
        remove_recursive(state);
48✔
3458
    }
48✔
3459
    else {
×
3460
        // No links in freestanding table
3461
        CascadeState state(CascadeState::Mode::None);
×
3462
        m_clusters.erase(key, state);
×
3463
    }
×
3464
}
48✔
3465

3466
Table::Iterator Table::begin() const
3467
{
684,540✔
3468
    return Iterator(m_clusters, 0);
684,540✔
3469
}
684,540✔
3470

3471
Table::Iterator Table::end() const
3472
{
9,672,987✔
3473
    return Iterator(m_clusters, size());
9,672,987✔
3474
}
9,672,987✔
3475

3476
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
3477
{
7,089,570✔
3478
    TableRef ret;
7,089,570✔
3479
    if (col_key) {
7,090,104✔
3480
        return table.get_opposite_table(col_key);
7,090,104✔
3481
    }
7,090,104✔
3482
    return ret;
4,294,967,294✔
3483
}
4,294,967,294✔
3484

3485
const uint64_t Table::max_num_columns;
3486

3487
void Table::build_column_mapping()
3488
{
6,297,996✔
3489
    // build column mapping from spec
3,373,272✔
3490
    // TODO: Optimization - Don't rebuild this for every change
3,373,272✔
3491
    m_spec_ndx2leaf_ndx.clear();
6,297,996✔
3492
    m_leaf_ndx2spec_ndx.clear();
6,297,996✔
3493
    m_leaf_ndx2colkey.clear();
6,297,996✔
3494
    size_t num_spec_cols = m_spec.get_column_count();
6,297,996✔
3495
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
6,297,996✔
3496
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
27,610,707✔
3497
        ColKey col_key = m_spec.get_key(spec_ndx);
21,312,711✔
3498
        unsigned leaf_ndx = col_key.get_index().val;
21,312,711✔
3499
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
21,312,711✔
3500
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
20,846,682✔
3501
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
20,846,682✔
3502
        }
20,846,682✔
3503
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
21,312,711✔
3504
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
21,312,711✔
3505
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
21,312,711✔
3506
    }
21,312,711✔
3507
}
6,297,996✔
3508

3509
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
3510
{
983,118✔
3511
    REALM_ASSERT(!attr.test(col_attr_Indexed));
983,118✔
3512
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
983,118✔
3513

485,298✔
3514
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
983,118✔
3515
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
983,118✔
3516

485,298✔
3517
    // reuse lowest available leaf ndx:
485,298✔
3518
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
983,118✔
3519
    // look for an unused entry:
485,298✔
3520
    for (unsigned idx = 0; idx < lower; ++idx) {
6,257,424✔
3521
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
5,274,390✔
3522
            lower = idx;
84✔
3523
            break;
84✔
3524
        }
84✔
3525
    }
5,274,390✔
3526
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
983,118✔
3527
}
983,118✔
3528

3529
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
3530
                                                  StringData origin_col_name) const noexcept
3531
{
×
3532
    BacklinkOrigin ret;
×
3533
    auto f = [&](ColKey backlink_col_key) {
×
3534
        auto origin_table = get_opposite_table(backlink_col_key);
×
3535
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
3536
        if (origin_table->get_name() == origin_table_name &&
×
3537
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
3538
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
3539
            return IteratorControl::Stop;
×
3540
        }
×
3541
        return IteratorControl::AdvanceToNext;
×
3542
    };
×
3543
    this->for_each_backlink_column(f);
×
3544
    return ret;
×
3545
}
×
3546

3547
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
3548
{
360✔
3549
    try {
360✔
3550
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
360✔
3551
        ColKey linked_column_key = get_opposite_column(backlink_col);
360✔
3552
        if (linked_table_key == m_key) {
360✔
3553
            return {{m_own_ref, linked_column_key}};
24✔
3554
        }
24✔
3555
        else {
336✔
3556
            Group* current_group = get_parent_group();
336✔
3557
            if (current_group) {
336✔
3558
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
3559
                return {{linked_table_ref, linked_column_key}};
336✔
3560
            }
336✔
3561
        }
×
3562
    }
360✔
3563
    catch (...) {
×
3564
        // backlink column not found, returning empty optional
3565
    }
×
3566
    return {};
180✔
3567
}
360✔
3568

3569
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
3570
{
×
3571
    std::vector<std::pair<TableKey, ColKey>> origins;
×
3572
    auto f = [&](ColKey backlink_col_key) {
×
3573
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
3574
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
3575
        origins.emplace_back(origin_table_key, origin_link_col);
×
3576
        return IteratorControl::AdvanceToNext;
×
3577
    };
×
3578
    this->for_each_backlink_column(f);
×
3579
    return origins;
×
3580
}
×
3581

3582
ColKey Table::get_primary_key_column() const
3583
{
19,804,968✔
3584
    return m_primary_key_col;
19,804,968✔
3585
}
19,804,968✔
3586

3587
void Table::set_primary_key_column(ColKey col_key)
3588
{
720✔
3589
    if (col_key == m_primary_key_col) {
720✔
3590
        return;
378✔
3591
    }
378✔
3592

171✔
3593
    if (Replication* repl = get_repl()) {
342✔
3594
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
3595
            throw RuntimeError(
×
3596
                ErrorCodes::BrokenInvariant,
×
3597
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
3598
        }
×
3599
    }
342✔
3600

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

171✔
3603
    if (col_key) {
342✔
3604
        check_column(col_key);
222✔
3605
        validate_column_is_unique(col_key);
222✔
3606
        do_set_primary_key_column(col_key);
222✔
3607
    }
222✔
3608
    else {
120✔
3609
        do_set_primary_key_column(col_key);
120✔
3610
    }
120✔
3611
}
342✔
3612

3613

3614
void Table::do_set_primary_key_column(ColKey col_key)
3615
{
138,210✔
3616
    if (col_key) {
138,210✔
3617
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
130,038✔
3618
        auto attr = m_spec.get_column_attr(spec_ndx);
130,038✔
3619
        if (attr.test(col_attr_FullText_Indexed)) {
130,038✔
3620
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
3621
        }
6✔
3622
    }
138,204✔
3623

68,508✔
3624
    if (m_primary_key_col) {
138,204✔
3625
        // If the search index has not been set explicitly on current pk col, we remove it again
4,122✔
3626
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
8,079✔
3627
        auto attr = m_spec.get_column_attr(spec_ndx);
8,079✔
3628
        if (!attr.test(col_attr_Indexed)) {
8,079✔
3629
            remove_search_index(m_primary_key_col);
8,067✔
3630
        }
8,067✔
3631
    }
8,079✔
3632

68,508✔
3633
    if (col_key) {
138,204✔
3634
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
130,029✔
3635
        do_add_search_index(col_key, IndexType::General);
130,029✔
3636
    }
130,029✔
3637
    else {
8,175✔
3638
        m_top.set(top_position_for_pk_col, 0);
8,175✔
3639
    }
8,175✔
3640

68,508✔
3641
    m_primary_key_col = col_key;
138,204✔
3642
}
138,204✔
3643

3644
bool Table::contains_unique_values(ColKey col) const
3645
{
834✔
3646
    if (search_index_type(col) == IndexType::General) {
834✔
3647
        auto search_index = get_search_index(col);
744✔
3648
        return !search_index->has_duplicate_values();
744✔
3649
    }
744✔
3650
    else {
90✔
3651
        TableView tv = where().find_all();
90✔
3652
        tv.distinct(col);
90✔
3653
        return tv.size() == size();
90✔
3654
    }
90✔
3655
}
834✔
3656

3657
void Table::validate_column_is_unique(ColKey col) const
3658
{
834✔
3659
    if (!contains_unique_values(col)) {
834✔
3660
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
3661
                                           get_class_name(), get_column_name(col)));
30✔
3662
    }
30✔
3663
}
834✔
3664

3665
void Table::validate_primary_column()
3666
{
1,812✔
3667
    if (ColKey col = get_primary_key_column()) {
1,812✔
3668
        validate_column_is_unique(col);
612✔
3669
    }
612✔
3670
}
1,812✔
3671

3672
ObjKey Table::get_next_valid_key()
3673
{
566,265✔
3674
    ObjKey key;
566,265✔
3675
    do {
566,271✔
3676
        key = ObjKey(allocate_sequence_number());
566,271✔
3677
    } while (m_clusters.is_valid(key));
566,271✔
3678

270,531✔
3679
    return key;
566,265✔
3680
}
566,265✔
3681

3682
namespace {
3683
template <class T>
3684
typename util::RemoveOptional<T>::type remove_optional(T val)
3685
{
87,867✔
3686
    return val;
87,867✔
3687
}
87,867✔
3688
template <>
3689
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
3690
{
5,424✔
3691
    return *val;
5,424✔
3692
}
5,424✔
3693
template <>
3694
bool remove_optional<Optional<bool>>(Optional<bool> val)
3695
{
11,493✔
3696
    return *val;
11,493✔
3697
}
11,493✔
3698
template <>
3699
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
3700
{
5,481✔
3701
    return *val;
5,481✔
3702
}
5,481✔
3703
template <>
3704
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
3705
{
6,060✔
3706
    return *val;
6,060✔
3707
}
6,060✔
3708
} // namespace
3709

3710
template <class F, class T>
3711
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
3712
{
162✔
3713
    Allocator& allocator = this->get_alloc();
162✔
3714
    bool from_nullability = is_nullable(key_from);
162✔
3715
    auto func = [&](Cluster* cluster) {
162✔
3716
        size_t sz = cluster->node_size();
162✔
3717

81✔
3718
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
3719
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
3720
        cluster->init_leaf(key_from, &from_arr);
162✔
3721
        cluster->init_leaf(key_to, &to_arr);
162✔
3722

81✔
3723
        for (size_t i = 0; i < sz; i++) {
1,512✔
3724
            if (from_nullability && from_arr.is_null(i)) {
1,356!
3725
                if (throw_on_null) {
54!
3726
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
3727
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
3728
                                                    get_column_name(key_from)));
6✔
3729
                }
6✔
3730
                else {
48✔
3731
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
48✔
3732
                }
48✔
3733
            }
54✔
3734
            else {
1,302✔
3735
                auto v = remove_optional(from_arr.get(i));
1,302✔
3736
                to_arr.set(i, v);
1,302✔
3737
            }
1,302✔
3738
        }
1,356✔
3739
    };
162✔
3740

81✔
3741
    m_clusters.update(func);
162✔
3742
}
162✔
3743

3744
template <class F, class T>
3745
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
3746
{
120✔
3747
    Allocator& allocator = this->get_alloc();
120✔
3748
    bool from_nullability = is_nullable(key_from);
120✔
3749
    auto func = [&](Cluster* cluster) {
120✔
3750
        size_t sz = cluster->node_size();
120✔
3751

60✔
3752
        ArrayInteger from_arr(allocator);
120✔
3753
        ArrayInteger to_arr(allocator);
120✔
3754
        cluster->init_leaf(key_from, &from_arr);
120✔
3755
        cluster->init_leaf(key_to, &to_arr);
120✔
3756

60✔
3757
        for (size_t i = 0; i < sz; i++) {
360✔
3758
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3759
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3760
            REALM_ASSERT(!ref_to);
240✔
3761

120✔
3762
            if (ref_from) {
240✔
3763
                BPlusTree<F> from_list(allocator);
120✔
3764
                BPlusTree<T> to_list(allocator);
120✔
3765
                from_list.init_from_ref(ref_from);
120✔
3766
                to_list.create();
120✔
3767
                size_t n = from_list.size();
120✔
3768
                for (size_t j = 0; j < n; j++) {
120,120✔
3769
                    auto v = from_list.get(j);
120,000✔
3770
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
3771
                        to_list.add(remove_optional(v));
115,023✔
3772
                    }
115,023✔
3773
                    else {
4,977✔
3774
                        if (throw_on_null) {
4,977!
3775
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3776
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3777
                                                            get_name(), get_column_name(key_from)));
×
3778
                        }
×
3779
                        else {
4,977✔
3780
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,977✔
3781
                        }
4,977✔
3782
                    }
4,977✔
3783
                }
120,000✔
3784
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3785
            }
120✔
3786
        }
240✔
3787
    };
120✔
3788

60✔
3789
    m_clusters.update(func);
120✔
3790
}
120✔
3791

3792
void Table::convert_column(ColKey from, ColKey to, bool throw_on_null)
3793
{
282✔
3794
    realm::DataType type_id = get_column_type(from);
282✔
3795
    bool _is_list = is_list(from);
282✔
3796
    if (_is_list) {
282✔
3797
        switch (type_id) {
120✔
3798
            case type_Int:
12✔
3799
                if (is_nullable(from)) {
12✔
3800
                    change_nullability_list<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3801
                }
6✔
3802
                else {
6✔
3803
                    change_nullability_list<int64_t, Optional<int64_t>>(from, to, throw_on_null);
6✔
3804
                }
6✔
3805
                break;
12✔
3806
            case type_Float:
12✔
3807
                change_nullability_list<float, float>(from, to, throw_on_null);
12✔
3808
                break;
12✔
3809
            case type_Double:
12✔
3810
                change_nullability_list<double, double>(from, to, throw_on_null);
12✔
3811
                break;
12✔
3812
            case type_Bool:
12✔
3813
                change_nullability_list<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3814
                break;
12✔
3815
            case type_String:
12✔
3816
                change_nullability_list<StringData, StringData>(from, to, throw_on_null);
12✔
3817
                break;
12✔
3818
            case type_Binary:
12✔
3819
                change_nullability_list<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3820
                break;
12✔
3821
            case type_Timestamp:
12✔
3822
                change_nullability_list<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3823
                break;
12✔
3824
            case type_ObjectId:
12✔
3825
                if (is_nullable(from)) {
12✔
3826
                    change_nullability_list<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3827
                }
6✔
3828
                else {
6✔
3829
                    change_nullability_list<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3830
                }
6✔
3831
                break;
12✔
3832
            case type_Decimal:
12✔
3833
                change_nullability_list<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3834
                break;
12✔
3835
            case type_UUID:
12✔
3836
                if (is_nullable(from)) {
12✔
3837
                    change_nullability_list<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3838
                }
6✔
3839
                else {
6✔
3840
                    change_nullability_list<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3841
                }
6✔
3842
                break;
12✔
3843
            case type_Link:
✔
3844
            case type_TypedLink:
✔
3845
            case type_LinkList:
✔
3846
                // Can't have lists of these types
3847
            case type_Mixed:
✔
3848
                // These types are no longer supported at all
3849
                REALM_UNREACHABLE();
3850
                break;
×
3851
        }
162✔
3852
    }
162✔
3853
    else {
162✔
3854
        switch (type_id) {
162✔
3855
            case type_Int:
36✔
3856
                if (is_nullable(from)) {
36✔
3857
                    change_nullability<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3858
                }
6✔
3859
                else {
30✔
3860
                    change_nullability<int64_t, Optional<int64_t>>(from, to, throw_on_null);
30✔
3861
                }
30✔
3862
                break;
36✔
3863
            case type_Float:
12✔
3864
                change_nullability<float, float>(from, to, throw_on_null);
12✔
3865
                break;
12✔
3866
            case type_Double:
12✔
3867
                change_nullability<double, double>(from, to, throw_on_null);
12✔
3868
                break;
12✔
3869
            case type_Bool:
12✔
3870
                change_nullability<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3871
                break;
12✔
3872
            case type_String:
30✔
3873
                change_nullability<StringData, StringData>(from, to, throw_on_null);
30✔
3874
                break;
30✔
3875
            case type_Binary:
12✔
3876
                change_nullability<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3877
                break;
12✔
3878
            case type_Timestamp:
12✔
3879
                change_nullability<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3880
                break;
12✔
3881
            case type_ObjectId:
12✔
3882
                if (is_nullable(from)) {
12✔
3883
                    change_nullability<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3884
                }
6✔
3885
                else {
6✔
3886
                    change_nullability<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3887
                }
6✔
3888
                break;
12✔
3889
            case type_Decimal:
12✔
3890
                change_nullability<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3891
                break;
12✔
3892
            case type_UUID:
12✔
3893
                if (is_nullable(from)) {
12✔
3894
                    change_nullability<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3895
                }
6✔
3896
                else {
6✔
3897
                    change_nullability<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3898
                }
6✔
3899
                break;
12✔
3900
            case type_TypedLink:
✔
3901
            case type_Link:
✔
3902
                // Always nullable, so can't convert
3903
            case type_LinkList:
✔
3904
                // Never nullable, so can't convert
3905
            case type_Mixed:
✔
3906
                // These types are no longer supported at all
3907
                REALM_UNREACHABLE();
3908
                break;
×
3909
        }
162✔
3910
    }
162✔
3911
}
282✔
3912

3913

3914
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3915
{
522✔
3916
    if (col_key.is_nullable() == nullable)
522✔
3917
        return col_key;
240✔
3918

141✔
3919
    check_column(col_key);
282✔
3920

141✔
3921
    auto index_type = search_index_type(col_key);
282✔
3922
    std::string column_name(get_column_name(col_key));
282✔
3923
    auto type = col_key.get_type();
282✔
3924
    auto attr = col_key.get_attrs();
282✔
3925
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3926
    if (nullable) {
282✔
3927
        attr.set(col_attr_Nullable);
150✔
3928
    }
150✔
3929
    else {
132✔
3930
        attr.reset(col_attr_Nullable);
132✔
3931
    }
132✔
3932

141✔
3933
    ColKey new_col = generate_col_key(type, attr);
282✔
3934
    do_insert_root_column(new_col, type, "__temporary");
282✔
3935

141✔
3936
    try {
282✔
3937
        convert_column(col_key, new_col, throw_on_null);
282✔
3938
    }
282✔
3939
    catch (...) {
144✔
3940
        // remove any partially filled column
3✔
3941
        remove_column(new_col);
6✔
3942
        throw;
6✔
3943
    }
6✔
3944

138✔
3945
    if (is_pk_col) {
276✔
3946
        // If we go from non nullable to nullable, no values change,
6✔
3947
        // so it is safe to preserve the pk column. Otherwise it is not
6✔
3948
        // safe as a null entry might have been converted to default value.
6✔
3949
        do_set_primary_key_column(nullable ? new_col : ColKey{});
9✔
3950
    }
12✔
3951

138✔
3952
    erase_root_column(col_key);
276✔
3953
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3954

138✔
3955
    if (index_type != IndexType::None)
276✔
3956
        do_add_search_index(new_col, index_type);
30✔
3957

138✔
3958
    return new_col;
276✔
3959
}
276✔
3960

3961
bool Table::has_any_embedded_objects()
3962
{
2,495,484✔
3963
    if (!m_has_any_embedded_objects) {
2,495,484✔
3964
        m_has_any_embedded_objects = false;
110,682✔
3965
        for_each_public_column([&](ColKey col_key) {
223,977✔
3966
            auto target_table_key = get_opposite_table_key(col_key);
223,977✔
3967
            if (target_table_key && is_link_type(col_key.get_type())) {
223,977✔
3968
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
9,501✔
3969
                if (target_table->is_embedded()) {
9,501✔
3970
                    m_has_any_embedded_objects = true;
7,377✔
3971
                    return IteratorControl::Stop; // early out
7,377✔
3972
                }
7,377✔
3973
            }
216,600✔
3974
            return IteratorControl::AdvanceToNext;
216,600✔
3975
        });
216,600✔
3976
    }
110,682✔
3977
    return *m_has_any_embedded_objects;
2,495,484✔
3978
}
2,495,484✔
3979

3980
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3981
{
170,271✔
3982
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
170,271✔
3983
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
170,271✔
3984
}
170,271✔
3985

3986
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3987
{
41,652✔
3988
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
145,365✔
3989
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
138,429✔
3990
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
34,716✔
3991
        }
34,716✔
3992
    }
138,429✔
3993

20,709✔
3994
    return {};
24,177✔
3995
}
41,652✔
3996

3997
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3998
{
41,004✔
3999
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
41,004✔
4000

20,385✔
4001
    if (!backlink_col_key) {
41,004✔
4002
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
6,936✔
4003
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
6,936✔
4004

3,468✔
4005
        if (Replication* repl = get_repl())
6,936✔
4006
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
6,606✔
4007
                                    m_key); // Throws
6,606✔
4008
    }
6,936✔
4009

20,385✔
4010
    return backlink_col_key;
41,004✔
4011
}
41,004✔
4012

4013
TableKey Table::get_opposite_table_key(ColKey col_key) const
4014
{
14,509,308✔
4015
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,509,308✔
4016
}
14,509,308✔
4017

4018
bool Table::links_to_self(ColKey col_key) const
4019
{
58,548✔
4020
    return get_opposite_table_key(col_key) == m_key;
58,548✔
4021
}
58,548✔
4022

4023
TableRef Table::get_opposite_table(ColKey col_key) const
4024
{
7,720,314✔
4025
    if (auto k = get_opposite_table_key(col_key)) {
7,720,314✔
4026
        return get_parent_group()->get_table(k);
7,666,599✔
4027
    }
7,666,599✔
4028
    return {};
53,715✔
4029
}
53,715✔
4030

4031
ColKey Table::get_opposite_column(ColKey col_key) const
4032
{
7,054,227✔
4033
    return ColKey(m_opposite_column.get(col_key.get_index().val));
7,054,227✔
4034
}
7,054,227✔
4035

4036
ColKey Table::find_opposite_column(ColKey col_key) const
4037
{
×
4038
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
4039
        if (m_opposite_column.get(i) == col_key.value) {
×
4040
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
4041
        }
×
4042
    }
×
4043
    return ColKey();
×
4044
}
×
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