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

realm / realm-core / 1878

01 Dec 2023 01:56AM UTC coverage: 91.684% (+0.02%) from 91.661%
1878

push

Evergreen

web-flow
Js/device testing (#6964)

* add a combined test executable

* disable a recently added test on mobile

* simplify link dependencies

* add missing namespace

* fix merge

* fix object-store benchmarks on android

92352 of 169306 branches covered (0.0%)

433 of 500 new or added lines in 5 files covered. (86.6%)

27 existing lines in 9 files now uncovered.

231812 of 252838 relevant lines covered (91.68%)

6548444.46 hits per line

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

90.63
/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/serializer.hpp>
38

39
#include <stdexcept>
40

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

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

260

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

264
Replication* Table::g_dummy_replication = nullptr;
265

266
bool TableVersions::operator==(const TableVersions& other) const
267
{
17,247✔
268
    if (size() != other.size())
17,247✔
269
        return false;
×
270
    size_t sz = size();
17,247✔
271
    for (size_t i = 0; i < sz; i++) {
27,123✔
272
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
17,379✔
273
        if (this->at(i).second != other.at(i).second)
17,379✔
274
            return false;
7,503✔
275
    }
17,379✔
276
    return true;
13,494✔
277
}
17,247✔
278

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

324
std::ostream& operator<<(std::ostream& o, Table::Type table_type)
325
{
19,419✔
326
    switch (table_type) {
19,419✔
327
        case Table::Type::TopLevel:
19,293✔
328
            return o << "TopLevel";
19,293✔
329
        case Table::Type::Embedded:
6✔
330
            return o << "Embedded";
6✔
331
        case Table::Type::TopLevelAsymmetric:
120✔
332
            return o << "TopLevelAsymmetric";
120✔
333
    }
×
334
    return o << "Invalid table type: " << uint8_t(table_type);
×
335
}
×
336
} // namespace realm
337

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

355
// -- Table ---------------------------------------------------------------------------------
356

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

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

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

397
ColKey Table::add_column(DataType type, StringData name, bool nullable)
398
{
531,222✔
399
    REALM_ASSERT(!is_link_type(ColumnType(type)));
531,222✔
400

261,207✔
401
    Table* invalid_link = nullptr;
531,222✔
402
    ColumnAttrMask attr;
531,222✔
403
    if (nullable || type == type_Mixed)
531,222✔
404
        attr.set(col_attr_Nullable);
118,296✔
405
    ColKey col_key = generate_col_key(ColumnType(type), attr);
531,222✔
406

261,207✔
407
    return do_insert_column(col_key, type, name, invalid_link); // Throws
531,222✔
408
}
531,222✔
409

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

14,769✔
422
    m_has_any_embedded_objects.reset();
29,760✔
423

14,769✔
424
    ColumnAttrMask attr;
29,760✔
425
    attr.set(col_attr_Nullable);
29,760✔
426
    ColKey col_key = generate_col_key(col_type_Link, attr);
29,760✔
427

14,769✔
428
    auto retval = do_insert_column(col_key, type_Link, name, &target); // Throws
29,760✔
429
    return retval;
29,760✔
430
}
29,760✔
431

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

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

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

15,648✔
466
    m_has_any_embedded_objects.reset();
31,725✔
467

15,648✔
468
    ColumnAttrMask attr;
31,725✔
469
    attr.set(col_attr_List);
31,725✔
470
    ColKey col_key = generate_col_key(col_type_LinkList, attr);
31,725✔
471

15,648✔
472
    return do_insert_column(col_key, type_LinkList, name, &target); // Throws
31,725✔
473
}
31,725✔
474

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

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

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

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

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

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

5,541✔
532
    ColumnAttrMask attr;
11,298✔
533
    attr.set(col_attr_Dictionary);
11,298✔
534
    attr.set(col_attr_Nullable);
11,298✔
535

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

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

6,684✔
546
    do {
18,810✔
547
        cascade_state.send_notifications();
18,810✔
548

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

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

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

580

581
void Table::remove_column(ColKey col_key)
582
{
18,303✔
583
    check_column(col_key);
18,303✔
584

9,111✔
585
    if (Replication* repl = get_repl())
18,303✔
586
        repl->erase_column(this, col_key); // Throws
519✔
587

9,111✔
588
    if (col_key == m_primary_key_col) {
18,303✔
589
        do_set_primary_key_column(ColKey());
7,899✔
590
    }
7,899✔
591
    else {
10,404✔
592
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
10,404✔
593
    }
10,404✔
594

9,111✔
595
    erase_root_column(col_key); // Throws
18,303✔
596
    m_has_any_embedded_objects.reset();
18,303✔
597
}
18,303✔
598

599

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

45✔
604
    auto col_ndx = colkey2spec_ndx(col_key);
99✔
605
    m_spec.rename_column(col_ndx, name); // Throws
99✔
606

45✔
607
    bump_content_version();
99✔
608
    bump_storage_version();
99✔
609

45✔
610
    if (Replication* repl = get_repl())
99✔
611
        repl->rename_column(this, col_key, name); // Throws
99✔
612
}
99✔
613

614

615
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
616
{
9,781,791✔
617
    // well, not quite "direct", more like "almost direct":
5,175,918✔
618
    Array table_top(alloc);
9,781,791✔
619
    table_top.init_from_ref(top_ref);
9,781,791✔
620
    if (table_top.size() > 3) {
9,781,791✔
621
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
9,781,143✔
622
        return TableKey(int32_t(rot.get_as_int()));
9,781,143✔
623
    }
9,781,143✔
624
    else {
648✔
625
        return TableKey();
648✔
626
    }
648✔
627
}
9,781,791✔
628

629

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

2,725,743✔
639
    m_spec.init_from_parent();
5,001,894✔
640

2,725,743✔
641
    while (m_top.size() <= top_position_for_pk_col) {
5,002,854✔
642
        m_top.add(0);
960✔
643
    }
960✔
644

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

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

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

2,725,743✔
693
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
5,001,894✔
694
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
4,453,443✔
695

2,725,743✔
696
    if (m_top.size() <= top_position_for_flags) {
5,001,894✔
697
        m_table_type = Type::TopLevel;
702✔
698
    }
702✔
699
    else {
5,001,192✔
700
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
5,001,192✔
701
        m_table_type = Type(flags & table_type_mask);
5,001,192✔
702
    }
5,001,192✔
703
    m_has_any_embedded_objects.reset();
5,001,894✔
704

2,725,743✔
705
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
5,001,894✔
706
        // Tombstones exists
441,657✔
707
        if (!m_tombstones) {
849,705✔
708
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
522,642✔
709
        }
522,642✔
710
        m_tombstones->init_from_parent();
849,705✔
711
    }
849,705✔
712
    else {
4,152,189✔
713
        m_tombstones = nullptr;
4,152,189✔
714
    }
4,152,189✔
715
    m_cookie = cookie_initialized;
5,001,894✔
716
}
5,001,894✔
717

718

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

391,896✔
723
    // When the inserted column is a link-type column, we must also add a
391,896✔
724
    // backlink column to the target table.
391,896✔
725

391,896✔
726
    if (target_table) {
794,589✔
727
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
83,499✔
728
        target_table->check_column(backlink_col_key);
83,499✔
729

41,262✔
730
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
83,499✔
731
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
83,499✔
732
    }
83,499✔
733

391,896✔
734
    if (Replication* repl = get_repl())
794,589✔
735
        repl->insert_column(this, col_key, type, name, target_table); // Throws
776,892✔
736

391,896✔
737
    return col_key;
794,589✔
738
}
794,589✔
739

740

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

69,414✔
746
    // Insert ref to index
69,414✔
747
    for (auto o : *this) {
1,324,938✔
748
        ObjKey key = o.get_key();
1,324,938✔
749
        DataType type = get_column_type(col_key);
1,324,938✔
750

662,523✔
751
        if (type == type_Int) {
1,324,938✔
752
            if (is_nullable(col_key)) {
1,214,070✔
753
                Optional<int64_t> value = o.get<Optional<int64_t>>(col_key);
60✔
754
                index->insert(key, value); // Throws
60✔
755
            }
60✔
756
            else {
1,214,010✔
757
                int64_t value = o.get<int64_t>(col_key);
1,214,010✔
758
                index->insert(key, value); // Throws
1,214,010✔
759
            }
1,214,010✔
760
        }
1,214,070✔
761
        else if (type == type_Bool) {
110,868✔
762
            if (is_nullable(col_key)) {
156✔
763
                Optional<bool> value = o.get<Optional<bool>>(col_key);
54✔
764
                index->insert(key, value); // Throws
54✔
765
            }
54✔
766
            else {
102✔
767
                bool value = o.get<bool>(col_key);
102✔
768
                index->insert(key, value); // Throws
102✔
769
            }
102✔
770
        }
156✔
771
        else if (type == type_String) {
110,712✔
772
            StringData value = o.get<StringData>(col_key);
110,502✔
773
            index->insert(key, value); // Throws
110,502✔
774
        }
110,502✔
775
        else if (type == type_Timestamp) {
210✔
776
            Timestamp value = o.get<Timestamp>(col_key);
132✔
777
            index->insert(key, value); // Throws
132✔
778
        }
132✔
779
        else if (type == type_ObjectId) {
78✔
780
            if (is_nullable(col_key)) {
36✔
781
                Optional<ObjectId> value = o.get<Optional<ObjectId>>(col_key);
18✔
782
                index->insert(key, value); // Throws
18✔
783
            }
18✔
784
            else {
18✔
785
                ObjectId value = o.get<ObjectId>(col_key);
18✔
786
                index->insert(key, value); // Throws
18✔
787
            }
18✔
788
        }
36✔
789
        else if (type == type_UUID) {
42✔
790
            if (is_nullable(col_key)) {
36✔
791
                Optional<UUID> value = o.get<Optional<UUID>>(col_key);
18✔
792
                index->insert(key, value); // Throws
18✔
793
            }
18✔
794
            else {
18✔
795
                UUID value = o.get<UUID>(col_key);
18✔
796
                index->insert(key, value); // Throws
18✔
797
            }
18✔
798
        }
36✔
799
        else if (type == type_Mixed) {
6✔
800
            index->insert(key, o.get<Mixed>(col_key));
6✔
801
        }
6✔
UNCOV
802
        else {
×
UNCOV
803
            REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
UNCOV
804
        }
×
805
    }
1,324,938✔
806
}
140,304✔
807

808
void Table::erase_from_search_indexes(ObjKey key)
809
{
5,031,786✔
810
    // Tombstones do not use index - will crash if we try to erase values
2,516,115✔
811
    if (!key.is_unresolved()) {
5,031,786✔
812
        for (auto&& index : m_index_accessors) {
6,714,870✔
813
            if (index) {
6,714,870✔
814
                index->erase(key);
285,327✔
815
            }
285,327✔
816
        }
6,714,870✔
817
    }
5,018,289✔
818
}
5,031,786✔
819

820
void Table::update_indexes(ObjKey key, const FieldValues& values)
821
{
23,124,654✔
822
    // Tombstones do not use index - will crash if we try to insert values
11,502,087✔
823
    if (key.is_unresolved()) {
23,124,654✔
824
        return;
29,400✔
825
    }
29,400✔
826

11,487,180✔
827
    auto sz = m_index_accessors.size();
23,095,254✔
828
    // values are sorted by column index - there may be values missing
11,487,180✔
829
    auto value = values.begin();
23,095,254✔
830
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
57,048,834✔
831
        // Check if initial value is provided
16,814,232✔
832
        Mixed init_value;
33,953,709✔
833
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
33,953,709✔
834
            // Value for this column is provided
290,208✔
835
            init_value = value->value;
602,007✔
836
            ++value;
602,007✔
837
        }
602,007✔
838

16,814,232✔
839
        if (auto&& index = m_index_accessors[column_ndx]) {
33,953,709✔
840
            // There is an index for this column
561,384✔
841
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,144,326✔
842
            auto type = col_key.get_type();
1,144,326✔
843
            auto attr = col_key.get_attrs();
1,144,326✔
844
            bool nullable = attr.test(col_attr_Nullable);
1,144,326✔
845
            switch (type) {
1,144,326✔
846
                case col_type_Int:
542,748✔
847
                    if (init_value.is_null()) {
542,748✔
848
                        index->insert(key, ArrayIntNull::default_value(nullable));
166,074✔
849
                    }
166,074✔
850
                    else {
376,674✔
851
                        index->insert(key, init_value.get<int64_t>());
376,674✔
852
                    }
376,674✔
853
                    break;
542,748✔
854
                case col_type_Bool:
6,126✔
855
                    if (init_value.is_null()) {
6,126✔
856
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,090✔
857
                    }
6,090✔
858
                    else {
36✔
859
                        index->insert(key, init_value.get<bool>());
36✔
860
                    }
36✔
861
                    break;
6,126✔
862
                case col_type_String:
481,863✔
863
                    if (init_value.is_null()) {
481,863✔
864
                        index->insert(key, ArrayString::default_value(nullable));
432,657✔
865
                    }
432,657✔
866
                    else {
49,206✔
867
                        index->insert(key, init_value.get<String>());
49,206✔
868
                    }
49,206✔
869
                    break;
481,863✔
870
                case col_type_Timestamp:
6,324✔
871
                    if (init_value.is_null()) {
6,324✔
872
                        index->insert(key, ArrayTimestamp::default_value(nullable));
6,288✔
873
                    }
6,288✔
874
                    else {
36✔
875
                        index->insert(key, init_value.get<Timestamp>());
36✔
876
                    }
36✔
877
                    break;
6,324✔
878
                case col_type_ObjectId:
88,170✔
879
                    if (init_value.is_null()) {
88,170✔
880
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
881
                    }
6,120✔
882
                    else {
82,050✔
883
                        index->insert(key, init_value.get<ObjectId>());
82,050✔
884
                    }
82,050✔
885
                    break;
88,170✔
886
                case col_type_Mixed:
834✔
887
                    index->insert(key, init_value);
834✔
888
                    break;
834✔
889
                case col_type_UUID:
18,342✔
890
                    if (init_value.is_null()) {
18,342✔
891
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
6,138✔
892
                    }
6,138✔
893
                    else {
12,204✔
894
                        index->insert(key, init_value.get<UUID>());
12,204✔
895
                    }
12,204✔
896
                    break;
18,342✔
897
                default:
✔
898
                    REALM_UNREACHABLE();
899
            }
1,144,326✔
900
        }
1,144,326✔
901
    }
33,953,709✔
902
}
23,095,254✔
903

904
void Table::clear_indexes()
905
{
4,314✔
906
    for (auto&& index : m_index_accessors) {
48,588✔
907
        if (index) {
48,588✔
908
            index->clear();
3,030✔
909
        }
3,030✔
910
    }
48,588✔
911
}
4,314✔
912

913
void Table::do_add_search_index(ColKey col_key, IndexType type)
914
{
140,736✔
915
    size_t column_ndx = col_key.get_index().val;
140,736✔
916

69,630✔
917
    // Early-out if already indexed
69,630✔
918
    if (m_index_accessors[column_ndx] != nullptr)
140,736✔
919
        return;
384✔
920

69,438✔
921
    if (!StringIndex::type_supported(DataType(col_key.get_type())) || col_key.is_collection() ||
140,352✔
922
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
140,328✔
923
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
24✔
924
        // it should probably be a type mismatch exception instead.
24✔
925
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
48✔
926
    }
48✔
927

69,414✔
928
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
69,414✔
929
    // index have 0-entries.
69,414✔
930
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
140,304✔
931
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
140,304✔
932

69,414✔
933
    // Create the index
69,414✔
934
    m_index_accessors[column_ndx] =
140,304✔
935
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
140,304✔
936
    StringIndex* index = m_index_accessors[column_ndx].get();
140,304✔
937

69,414✔
938
    // Insert ref to index
69,414✔
939
    index->set_parent(&m_index_refs, column_ndx);
140,304✔
940
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
140,304✔
941

69,414✔
942
    populate_search_index(col_key);
140,304✔
943
}
140,304✔
944

945
void Table::add_search_index(ColKey col_key, IndexType type)
946
{
3,861✔
947
    check_column(col_key);
3,861✔
948

1,935✔
949
    // Check spec
1,935✔
950
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,861✔
951
    auto attr = m_spec.get_column_attr(spec_ndx);
3,861✔
952

1,935✔
953
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,861✔
954
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
955

1,932✔
956
    switch (type) {
3,855✔
957
        case IndexType::None:
✔
958
            remove_search_index(col_key);
×
959
            return;
×
960
        case IndexType::Fulltext:
54✔
961
            // Early-out if already indexed
27✔
962
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
963
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
964
                return;
×
965
            }
×
966
            if (attr.test(col_attr_Indexed)) {
54✔
967
                this->remove_search_index(col_key);
×
968
            }
×
969
            break;
54✔
970
        case IndexType::General:
3,801✔
971
            if (attr.test(col_attr_Indexed)) {
3,801✔
972
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
24✔
973
                return;
24✔
974
            }
24✔
975
            if (attr.test(col_attr_FullText_Indexed)) {
3,777✔
976
                this->remove_search_index(col_key);
×
977
            }
×
978
            break;
3,777✔
979
    }
3,831✔
980

1,920✔
981
    do_add_search_index(col_key, type);
3,831✔
982

1,920✔
983
    // Update spec
1,920✔
984
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,804✔
985
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,831✔
986
}
3,831✔
987

988
void Table::remove_search_index(ColKey col_key)
989
{
8,310✔
990
    check_column(col_key);
8,310✔
991
    auto column_ndx = col_key.get_index();
8,310✔
992

4,113✔
993
    // Early-out if non-indexed
4,113✔
994
    if (m_index_accessors[column_ndx.val] == nullptr)
8,310✔
995
        return;
60✔
996

4,080✔
997
    // Destroy and remove the index column
4,080✔
998
    auto& index = m_index_accessors[column_ndx.val];
8,250✔
999
    REALM_ASSERT(index != nullptr);
8,250✔
1000
    index->destroy();
8,250✔
1001
    index.reset();
8,250✔
1002

4,080✔
1003
    m_index_refs.set(column_ndx.val, 0);
8,250✔
1004

4,080✔
1005
    // update spec
4,080✔
1006
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
8,250✔
1007
    auto attr = m_spec.get_column_attr(spec_ndx);
8,250✔
1008
    attr.reset(col_attr_Indexed);
8,250✔
1009
    attr.reset(col_attr_FullText_Indexed);
8,250✔
1010
    m_spec.set_column_attr(spec_ndx, attr); // Throws
8,250✔
1011
}
8,250✔
1012

1013
void Table::enumerate_string_column(ColKey col_key)
1014
{
1,299✔
1015
    check_column(col_key);
1,299✔
1016
    size_t column_ndx = colkey2spec_ndx(col_key);
1,299✔
1017
    ColumnType type = col_key.get_type();
1,299✔
1018
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,299✔
1019
        m_clusters.enumerate_string_column(col_key);
693✔
1020
    }
693✔
1021
}
1,299✔
1022

1023
bool Table::is_enumerated(ColKey col_key) const noexcept
1024
{
60,003✔
1025
    size_t col_ndx = colkey2spec_ndx(col_key);
60,003✔
1026
    return m_spec.is_string_enum_type(col_ndx);
60,003✔
1027
}
60,003✔
1028

1029
size_t Table::get_num_unique_values(ColKey col_key) const
1030
{
138✔
1031
    if (!is_enumerated(col_key))
138✔
1032
        return 0;
84✔
1033

27✔
1034
    ArrayParent* parent;
54✔
1035
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
1036
    BPlusTree<StringData> col(get_alloc());
54✔
1037
    col.init_from_ref(ref);
54✔
1038

27✔
1039
    return col.size();
54✔
1040
}
54✔
1041

1042

1043
void Table::erase_root_column(ColKey col_key)
1044
{
18,579✔
1045
    check_column(col_key);
18,579✔
1046
    ColumnType col_type = col_key.get_type();
18,579✔
1047
    if (is_link_type(col_type)) {
18,579✔
1048
        auto target_table = get_opposite_table(col_key);
225✔
1049
        auto target_column = get_opposite_column(col_key);
225✔
1050
        target_table->do_erase_root_column(target_column);
225✔
1051
    }
225✔
1052
    do_erase_root_column(col_key); // Throws
18,579✔
1053
}
18,579✔
1054

1055

1056
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
1057
{
1,022,334✔
1058
    // if col_key specifies a key, it must be unused
504,540✔
1059
    REALM_ASSERT(!col_key || !valid_column(col_key));
1,022,334✔
1060

504,540✔
1061
    // locate insertion point: ordinary columns must come before backlink columns
504,540✔
1062
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
976,257✔
1063

504,540✔
1064
    if (!col_key) {
1,022,334✔
1065
        col_key = generate_col_key(type, {});
91,179✔
1066
    }
91,179✔
1067

504,540✔
1068
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
1,022,334✔
1069
    if (col_key.is_dictionary()) {
1,022,334✔
1070
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
54,576✔
1071
    }
54,576✔
1072
    auto col_ndx = col_key.get_index().val;
1,022,334✔
1073
    build_column_mapping();
1,022,334✔
1074
    REALM_ASSERT(col_ndx <= m_index_refs.size());
1,022,334✔
1075
    if (col_ndx == m_index_refs.size()) {
1,022,334✔
1076
        m_index_refs.insert(col_ndx, 0);
1,022,088✔
1077
    }
1,022,088✔
1078
    else {
246✔
1079
        m_index_refs.set(col_ndx, 0);
246✔
1080
    }
246✔
1081
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
1,022,334✔
1082
    if (col_ndx == m_opposite_table.size()) {
1,022,334✔
1083
        // m_opposite_table and m_opposite_column are always resized together!
504,414✔
1084
        m_opposite_table.insert(col_ndx, TableKey().value);
1,022,085✔
1085
        m_opposite_column.insert(col_ndx, ColKey().value);
1,022,085✔
1086
    }
1,022,085✔
1087
    else {
249✔
1088
        m_opposite_table.set(col_ndx, TableKey().value);
249✔
1089
        m_opposite_column.set(col_ndx, ColKey().value);
249✔
1090
    }
249✔
1091
    refresh_index_accessors();
1,022,334✔
1092
    m_clusters.insert_column(col_key);
1,022,334✔
1093
    if (m_tombstones) {
1,022,334✔
1094
        m_tombstones->insert_column(col_key);
7,368✔
1095
    }
7,368✔
1096

504,540✔
1097
    bump_storage_version();
1,022,334✔
1098

504,540✔
1099
    return col_key;
1,022,334✔
1100
}
1,022,334✔
1101

1102

1103
void Table::do_erase_root_column(ColKey col_key)
1104
{
18,804✔
1105
    size_t col_ndx = col_key.get_index().val;
18,804✔
1106
    // If the column had a source index we have to remove and destroy that as well
9,363✔
1107
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
18,804✔
1108
    if (index_ref) {
18,804✔
1109
        Array::destroy_deep(index_ref, m_index_refs.get_alloc());
213✔
1110
        m_index_refs.set(col_ndx, 0);
213✔
1111
        m_index_accessors[col_ndx].reset();
213✔
1112
    }
213✔
1113
    m_opposite_table.set(col_ndx, TableKey().value);
18,804✔
1114
    m_opposite_column.set(col_ndx, ColKey().value);
18,804✔
1115
    m_index_accessors[col_ndx] = nullptr;
18,804✔
1116
    m_clusters.remove_column(col_key);
18,804✔
1117
    if (m_tombstones)
18,804✔
1118
        m_tombstones->remove_column(col_key);
6,405✔
1119
    size_t spec_ndx = colkey2spec_ndx(col_key);
18,804✔
1120
    m_spec.erase_column(spec_ndx);
18,804✔
1121
    m_top.adjust(top_position_for_column_key, 2);
18,804✔
1122

9,363✔
1123
    build_column_mapping();
18,804✔
1124
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
37,038✔
1125
        REALM_ASSERT(m_index_accessors.back() == nullptr);
18,234✔
1126
        m_index_accessors.pop_back();
18,234✔
1127
    }
18,234✔
1128
    bump_content_version();
18,804✔
1129
    bump_storage_version();
18,804✔
1130
}
18,804✔
1131

1132
Query Table::where(const DictionaryLinkValues& dictionary_of_links) const
1133
{
1,524✔
1134
    return Query(m_own_ref, dictionary_of_links);
1,524✔
1135
}
1,524✔
1136

1137
void Table::set_table_type(Type table_type, bool handle_backlinks)
1138
{
312✔
1139
    if (table_type == m_table_type) {
312✔
1140
        return;
×
1141
    }
×
1142

156✔
1143
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
1144
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1145
                                                                   get_class_name(), m_table_type, table_type));
×
1146
    }
×
1147

156✔
1148
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1149
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1150
}
312✔
1151

1152
void Table::set_embedded(bool embedded, bool handle_backlinks)
1153
{
312✔
1154
    if (embedded == false) {
312✔
1155
        do_set_table_type(Type::TopLevel);
24✔
1156
        return;
24✔
1157
    }
24✔
1158

144✔
1159
    // Embedded objects cannot have a primary key.
144✔
1160
    if (get_primary_key_column()) {
288✔
1161
        throw IllegalOperation(
6✔
1162
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1163
    }
6✔
1164

141✔
1165
    if (size() == 0) {
282✔
1166
        do_set_table_type(Type::Embedded);
42✔
1167
        return;
42✔
1168
    }
42✔
1169

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

237✔
1186
        for_each_backlink_column([&](ColKey col) {
606✔
1187
            cluster->init_leaf(col, &leaf);
606✔
1188
            // Width zero means all the values are zero and there can't be any backlinks
303✔
1189
            if (leaf.get_width() == 0) {
606✔
1190
                return IteratorControl::AdvanceToNext;
36✔
1191
            }
36✔
1192

285✔
1193
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1194
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1195
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1196
                    // ref of zero means there's no backlinks
29,670✔
1197
                    continue;
59,340✔
1198
                }
59,340✔
1199

480✔
1200
                if (value.is_ref()) {
960✔
1201
                    // Any other ref indicates an array of backlinks, which will
39✔
1202
                    // always have more than one entry
39✔
1203
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1204
                }
78✔
1205
                else {
882✔
1206
                    // Otherwise it's a tagged ref to the single linking object
441✔
1207
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1208
                        incoming_link_count[i] = LinkCount::One;
792✔
1209
                    }
792✔
1210
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1211
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1212
                    }
42✔
1213
                }
882✔
1214

480✔
1215
                auto source_col = get_opposite_column(col);
960✔
1216
                if (source_col.get_type() == col_type_Mixed) {
960✔
1217
                    auto source_table = get_opposite_table(col);
54✔
1218
                    throw IllegalOperation(util::format(
54✔
1219
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1220
                        "which does not support linking to embedded objects.",
54✔
1221
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1222
                }
54✔
1223
            }
960✔
1224
            return IteratorControl::AdvanceToNext;
543✔
1225
        });
570✔
1226

237✔
1227
        for (size_t i = 0; i < size; ++i) {
60,660✔
1228
            if (incoming_link_count[i] == LinkCount::None) {
60,240✔
1229
                if (!handle_backlinks) {
59,424✔
1230
                    throw IllegalOperation(util::format("Cannot convert '%1' to embedded: at least one object has no "
18✔
1231
                                                        "incoming links and would be deleted.",
18✔
1232
                                                        get_class_name()));
18✔
1233
                }
18✔
1234
                orphans.push_back(cluster->get_real_key(i));
59,406✔
1235
            }
59,406✔
1236
            else if (incoming_link_count[i] == LinkCount::Multiple) {
816✔
1237
                if (!handle_backlinks) {
90✔
1238
                    throw IllegalOperation(util::format(
36✔
1239
                        "Cannot convert '%1' to embedded: at least one object has more than one incoming link.",
36✔
1240
                        get_class_name()));
36✔
1241
                }
36✔
1242
                multiple_incoming_links.push_back(cluster->get_real_key(i));
54✔
1243
            }
54✔
1244
        }
60,240✔
1245

237✔
1246
        return IteratorControl::AdvanceToNext;
447✔
1247
    });
474✔
1248

120✔
1249
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
120✔
1250
    for (auto key : orphans) {
59,406✔
1251
        remove_object(key);
59,406✔
1252
    }
59,406✔
1253
    for (auto key : multiple_incoming_links) {
147✔
1254
        auto obj = get_object(key);
54✔
1255
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1256
        obj.remove();
54✔
1257
    }
54✔
1258

120✔
1259
    do_set_table_type(Type::Embedded);
240✔
1260
}
240✔
1261

1262
void Table::do_set_table_type(Type table_type)
1263
{
348,387✔
1264
    while (m_top.size() <= top_position_for_flags)
348,387✔
1265
        m_top.add(0);
×
1266

172,707✔
1267
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
348,387✔
1268
    // reset bits 0-1
172,707✔
1269
    flags &= ~table_type_mask;
348,387✔
1270
    // set table type
172,707✔
1271
    flags |= static_cast<uint8_t>(table_type);
348,387✔
1272
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
348,387✔
1273
    m_table_type = table_type;
348,387✔
1274
}
348,387✔
1275

1276

1277
void Table::detach(LifeCycleCookie cookie) noexcept
1278
{
4,990,251✔
1279
    m_cookie = cookie;
4,990,251✔
1280
    m_alloc.bump_instance_version();
4,990,251✔
1281
}
4,990,251✔
1282

1283
void Table::fully_detach() noexcept
1284
{
4,966,212✔
1285
    m_spec.detach();
4,966,212✔
1286
    m_top.detach();
4,966,212✔
1287
    m_index_refs.detach();
4,966,212✔
1288
    m_opposite_table.detach();
4,966,212✔
1289
    m_opposite_column.detach();
4,966,212✔
1290
    m_index_accessors.clear();
4,966,212✔
1291
}
4,966,212✔
1292

1293

1294
Table::~Table() noexcept
1295
{
3,558✔
1296
    if (m_top.is_attached()) {
3,558✔
1297
        // If destroyed as a standalone table, destroy all memory allocated
1,779✔
1298
        if (m_top.get_parent() == nullptr) {
3,558✔
1299
            m_top.destroy_deep();
3,558✔
1300
        }
3,558✔
1301
        fully_detach();
3,558✔
1302
    }
3,558✔
1303
    else {
×
1304
        REALM_ASSERT(m_index_accessors.size() == 0);
×
1305
    }
×
1306
    m_cookie = cookie_deleted;
3,558✔
1307
}
3,558✔
1308

1309

1310
IndexType Table::search_index_type(ColKey col_key) const noexcept
1311
{
8,203,422✔
1312
    if (auto index = m_index_accessors[col_key.get_index().val].get()) {
8,203,422✔
1313
        return index->is_fulltext_index() ? IndexType::Fulltext : IndexType::General;
1,194,846✔
1314
    }
1,195,056✔
1315
    return IndexType::None;
7,008,366✔
1316
}
7,008,366✔
1317

1318
void Table::migrate_column_info()
1319
{
336✔
1320
    bool changes = false;
336✔
1321
    TableKey tk = (get_name() == "pk") ? TableKey(0) : get_key();
309✔
1322
    changes |= m_spec.convert_column_attributes();
336✔
1323
    changes |= m_spec.convert_column_keys(tk);
336✔
1324

168✔
1325
    if (changes) {
336✔
1326
        build_column_mapping();
60✔
1327
    }
60✔
1328
}
336✔
1329

1330
bool Table::verify_column_keys()
1331
{
282✔
1332
    size_t nb_public_columns = m_spec.get_public_column_count();
282✔
1333
    size_t nb_columns = m_spec.get_column_count();
282✔
1334
    bool modified = false;
282✔
1335

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

141✔
1358
    if (!check()) {
282✔
1359
        m_spec.fix_column_keys(get_key());
6✔
1360
        build_column_mapping();
6✔
1361
        refresh_index_accessors();
6✔
1362
        REALM_ASSERT_RELEASE(check());
6✔
1363
        modified = true;
6✔
1364
    }
6✔
1365
    return modified;
282✔
1366
}
282✔
1367

1368
// Delete the indexes stored in the columns array and create corresponding
1369
// entries in m_index_accessors array. This also has the effect that the columns
1370
// array after this step does not have extra entries for certain columns
1371
void Table::migrate_indexes(ColKey pk_col_key)
1372
{
336✔
1373
    if (ref_type top_ref = m_top.get_as_ref(top_position_for_columns)) {
336✔
1374
        Array col_refs(m_alloc);
246✔
1375
        col_refs.set_parent(&m_top, top_position_for_columns);
246✔
1376
        col_refs.init_from_ref(top_ref);
246✔
1377
        auto col_count = m_spec.get_column_count();
246✔
1378
        size_t col_ndx = 0;
246✔
1379

123✔
1380
        // If col_refs.size() equals col_count, there are no indexes to migrate
123✔
1381
        while (col_ndx < col_count && col_refs.size() > col_count) {
468✔
1382
            if (m_spec.get_column_attr(col_ndx).test(col_attr_Indexed) && !m_index_refs.get(col_ndx)) {
222✔
1383
                // Simply delete entry. This will have the effect that we will not have to take
72✔
1384
                // extra entries into account
72✔
1385
                auto old_index_ref = to_ref(col_refs.get(col_ndx + 1));
144✔
1386
                col_refs.erase(col_ndx + 1);
144✔
1387
                if (old_index_ref) {
144✔
1388
                    // It should not be possible for old_index_ref to be 0, but we have seen some error
72✔
1389
                    // reports on freeing a null ref, so just to be sure ...
72✔
1390
                    Array::destroy_deep(old_index_ref, m_alloc);
144✔
1391
                }
144✔
1392

72✔
1393
                // Primary key columns does not need an index
72✔
1394
                if (m_leaf_ndx2colkey[col_ndx] != pk_col_key) {
144✔
1395
                    // Otherwise create new index. Will be updated when objects are created
45✔
1396
                    m_index_accessors[col_ndx] = std::make_unique<StringIndex>(
90✔
1397
                        ClusterColumn(&m_clusters, m_spec.get_key(col_ndx), IndexType::General),
90✔
1398
                        get_alloc()); // Throws
90✔
1399
                    auto index = m_index_accessors[col_ndx].get();
90✔
1400
                    index->set_parent(&m_index_refs, col_ndx);
90✔
1401
                    m_index_refs.set(col_ndx, index->get_ref());
90✔
1402
                }
90✔
1403
            }
144✔
1404
            col_ndx++;
222✔
1405
        };
222✔
1406
    }
246✔
1407
}
336✔
1408

1409
// Move information held in the subspec area into the structures managed by Table
1410
// This is information about origin/target tables in relation to links
1411
// This information is now held in "opposite" arrays directly in Table structure
1412
// At the same time the backlink columns are destroyed
1413
// If there is no subspec, this stage is done
1414
void Table::migrate_subspec()
1415
{
282✔
1416
    if (!m_spec.has_subspec())
282✔
1417
        return;
210✔
1418

36✔
1419
    ref_type top_ref = m_top.get_as_ref(top_position_for_columns);
72✔
1420
    Array col_refs(m_alloc);
72✔
1421
    col_refs.set_parent(&m_top, top_position_for_columns);
72✔
1422
    col_refs.init_from_ref(top_ref);
72✔
1423
    Group* group = get_parent_group();
72✔
1424

36✔
1425
    for (size_t col_ndx = 0; col_ndx < m_spec.get_column_count(); col_ndx++) {
516✔
1426
        ColumnType col_type = m_spec.get_column_type(col_ndx);
444✔
1427

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

1463
namespace {
1464

1465
class LegacyStringColumn : public BPlusTree<StringData> {
1466
public:
1467
    LegacyStringColumn(Allocator& alloc, Spec* spec, size_t col_ndx, bool nullable)
1468
        : BPlusTree(alloc)
1469
        , m_spec(spec)
1470
        , m_col_ndx(col_ndx)
1471
        , m_nullable(nullable)
1472
    {
300✔
1473
    }
300✔
1474

1475
    std::unique_ptr<BPlusTreeLeaf> init_leaf_node(ref_type ref) override
1476
    {
300✔
1477
        auto leaf = std::make_unique<LeafNode>(this);
300✔
1478
        leaf->ArrayString::set_spec(m_spec, m_col_ndx);
300✔
1479
        leaf->set_nullability(m_nullable);
300✔
1480
        leaf->init_from_ref(ref);
300✔
1481
        return leaf;
300✔
1482
    }
300✔
1483

1484
    StringData get_legacy(size_t n) const
1485
    {
6,648✔
1486
        if (m_cached_leaf_begin <= n && n < m_cached_leaf_end) {
6,648!
1487
            return m_leaf_cache.get_legacy(n - m_cached_leaf_begin);
×
1488
        }
×
1489
        else {
6,648✔
1490
            StringData value;
6,648✔
1491

3,324✔
1492
            auto func = [&value](BPlusTreeNode* node, size_t ndx) {
6,648✔
1493
                auto leaf = static_cast<LeafNode*>(node);
6,648✔
1494
                value = leaf->get_legacy(ndx);
6,648✔
1495
            };
6,648✔
1496

3,324✔
1497
            m_root->bptree_access(n, func);
6,648✔
1498

3,324✔
1499
            return value;
6,648✔
1500
        }
6,648✔
1501
    }
6,648✔
1502

1503
private:
1504
    Spec* m_spec;
1505
    size_t m_col_ndx;
1506
    bool m_nullable;
1507
};
1508

1509
// We need an accessor that can read old Timestamp columns.
1510
// The new BPlusTree<Timestamp> uses a different layout
1511
class LegacyTS : private Array {
1512
public:
1513
    explicit LegacyTS(Allocator& allocator)
1514
        : Array(allocator)
1515
        , m_seconds(allocator)
1516
        , m_nanoseconds(allocator)
1517
    {
18✔
1518
        m_seconds.set_parent(this, 0);
18✔
1519
        m_nanoseconds.set_parent(this, 1);
18✔
1520
    }
18✔
1521

1522
    using Array::set_parent;
1523

1524
    void init_from_parent()
1525
    {
18✔
1526
        Array::init_from_parent();
18✔
1527
        m_seconds.init_from_parent();
18✔
1528
        m_nanoseconds.init_from_parent();
18✔
1529
    }
18✔
1530

1531
    size_t size() const
1532
    {
18✔
1533
        return m_seconds.size();
18✔
1534
    }
18✔
1535

1536
    Timestamp get(size_t ndx) const
1537
    {
3,078✔
1538
        util::Optional<int64_t> seconds = m_seconds.get(ndx);
3,078✔
1539
        return seconds ? Timestamp(*seconds, int32_t(m_nanoseconds.get(ndx))) : Timestamp{};
3,060✔
1540
    }
3,078✔
1541

1542
private:
1543
    BPlusTree<util::Optional<Int>> m_seconds;
1544
    BPlusTree<Int> m_nanoseconds;
1545
};
1546

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

1587
template <class T>
1588
void copy_list(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1589
{
6,012✔
1590
    if (sub_table_ref) {
6,012!
1591
        // Actual list is in the columns array position 0
36✔
1592
        Array cols(alloc);
72✔
1593
        cols.init_from_ref(sub_table_ref);
72✔
1594
        ref_type list_ref = cols.get_as_ref(0);
72✔
1595
        BPlusTree<T> from_list(alloc);
72✔
1596
        from_list.init_from_ref(list_ref);
72✔
1597
        size_t list_size = from_list.size();
72✔
1598
        auto l = obj.get_list<T>(col);
72✔
1599
        for (size_t j = 0; j < list_size; j++) {
1,740!
1600
            l.add(from_list.get(j));
1,668✔
1601
        }
1,668✔
1602
    }
72✔
1603
}
6,012✔
1604

1605
template <>
1606
void copy_list<util::Optional<Bool>>(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1607
{
×
1608
    if (sub_table_ref) {
×
1609
        // Actual list is in the columns array position 0
1610
        Array cols(alloc);
×
1611
        cols.init_from_ref(sub_table_ref);
×
1612
        BPlusTree<util::Optional<Int>> from_list(alloc);
×
1613
        from_list.set_parent(&cols, 0);
×
1614
        from_list.init_from_parent();
×
1615
        size_t list_size = from_list.size();
×
1616
        auto l = obj.get_list<util::Optional<Bool>>(col);
×
1617
        for (size_t j = 0; j < list_size; j++) {
×
1618
            util::Optional<Bool> val;
×
1619
            auto int_val = from_list.get(j);
×
1620
            if (int_val) {
×
1621
                val = (*int_val != 0);
×
1622
            }
×
1623
            l.add(val);
×
1624
        }
×
1625
    }
×
1626
}
×
1627

1628
template <>
1629
void copy_list<String>(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1630
{
276✔
1631
    if (sub_table_ref) {
276✔
1632
        // Actual list is in the columns array position 0
63✔
1633
        bool nullable = col.get_attrs().test(col_attr_Nullable);
126✔
1634
        Array cols(alloc);
126✔
1635
        cols.init_from_ref(sub_table_ref);
126✔
1636
        LegacyStringColumn from_list(alloc, nullptr, 0, nullable); // List of strings cannot be enumerated
126✔
1637
        from_list.set_parent(&cols, 0);
126✔
1638
        from_list.init_from_parent();
126✔
1639
        size_t list_size = from_list.size();
126✔
1640
        auto l = obj.get_list<String>(col);
126✔
1641
        for (size_t j = 0; j < list_size; j++) {
396✔
1642
            l.add(from_list.get_legacy(j));
270✔
1643
        }
270✔
1644
    }
126✔
1645
}
276✔
1646

1647
template <>
1648
void copy_list<Timestamp>(ref_type sub_table_ref, Obj& obj, ColKey col, Allocator& alloc)
1649
{
×
1650
    if (sub_table_ref) {
×
1651
        // Actual list is in the columns array position 0
1652
        Array cols(alloc);
×
1653
        cols.init_from_ref(sub_table_ref);
×
1654
        LegacyTS from_list(alloc);
×
1655
        from_list.set_parent(&cols, 0);
×
1656
        from_list.init_from_parent();
×
1657
        size_t list_size = from_list.size();
×
1658
        auto l = obj.get_list<Timestamp>(col);
×
1659
        for (size_t j = 0; j < list_size; j++) {
×
1660
            l.add(from_list.get(j));
×
1661
        }
×
1662
    }
×
1663
}
×
1664

1665
} // namespace
1666

1667
void Table::create_columns()
1668
{
336✔
1669
    size_t cnt;
336✔
1670
    auto get_column_cnt = [&cnt](const Cluster* cluster) {
336✔
1671
        cnt = cluster->nb_columns();
336✔
1672
        return IteratorControl::Stop;
336✔
1673
    };
336✔
1674
    traverse_clusters(get_column_cnt);
336✔
1675

168✔
1676
    size_t column_count = m_spec.get_column_count();
336✔
1677
    if (cnt != column_count) {
336✔
1678
        for (size_t col_ndx = 0; col_ndx < column_count; col_ndx++) {
846✔
1679
            m_clusters.insert_column(m_spec.get_key(col_ndx));
672✔
1680
        }
672✔
1681
    }
174✔
1682
}
336✔
1683

1684
bool Table::migrate_objects()
1685
{
336✔
1686
    size_t nb_public_columns = m_spec.get_public_column_count();
336✔
1687
    size_t nb_columns = m_spec.get_column_count();
336✔
1688
    if (!nb_columns) {
336✔
1689
        // No columns - this means no objects
12✔
1690
        return true;
24✔
1691
    }
24✔
1692

156✔
1693
    ref_type top_ref = m_top.get_as_ref(top_position_for_columns);
312✔
1694
    if (!top_ref) {
312✔
1695
        // Has already been done
45✔
1696
        return true;
90✔
1697
    }
90✔
1698
    Array col_refs(m_alloc);
222✔
1699
    col_refs.set_parent(&m_top, top_position_for_columns);
222✔
1700
    col_refs.init_from_ref(top_ref);
222✔
1701

111✔
1702
    /************************ Create column accessors ************************/
111✔
1703

111✔
1704
    std::map<ColKey, std::unique_ptr<BPlusTreeBase>> column_accessors;
222✔
1705
    std::map<ColKey, std::unique_ptr<LegacyTS>> ts_accessors;
222✔
1706
    std::map<ColKey, std::unique_ptr<BPlusTree<int64_t>>> list_accessors;
222✔
1707
    std::vector<size_t> cols_to_destroy;
222✔
1708
    bool has_link_columns = false;
222✔
1709

111✔
1710
    // helper function to determine the number of objects in the table
111✔
1711
    size_t number_of_objects = (nb_columns == 0) ? 0 : size_t(-1);
222✔
1712
    auto update_size = [&number_of_objects](size_t s) {
792✔
1713
        if (number_of_objects == size_t(-1)) {
792✔
1714
            number_of_objects = s;
222✔
1715
        }
222✔
1716
        else {
570✔
1717
            REALM_ASSERT(s == number_of_objects);
570✔
1718
        }
570✔
1719
    };
792✔
1720

111✔
1721
    for (size_t col_ndx = 0; col_ndx < nb_columns; col_ndx++) {
1,062✔
1722
        if (col_ndx < nb_public_columns && m_spec.get_column_name(col_ndx) == "!ROW_INDEX") {
840✔
1723
            // If this column has been added, we can break here
1724
            break;
×
1725
        }
×
1726

420✔
1727
        ColKey col_key = m_spec.get_key(col_ndx);
840✔
1728
        ColumnAttrMask attr = m_spec.get_column_attr(col_ndx);
840✔
1729
        ColumnType col_type = m_spec.get_column_type(col_ndx);
840✔
1730
        bool nullable = attr.test(col_attr_Nullable);
840✔
1731
        std::unique_ptr<BPlusTreeBase> acc;
840✔
1732
        std::unique_ptr<LegacyTS> ts_acc;
840✔
1733
        std::unique_ptr<BPlusTree<int64_t>> list_acc;
840✔
1734

420✔
1735
        if (!(col_ndx < col_refs.size())) {
840✔
1736
            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
1737
                               util::format("Objects in '%1' corrupted by previous upgrade attempt", get_name()));
×
1738
        }
×
1739

420✔
1740
        if (!col_refs.get(col_ndx)) {
840✔
1741
            // This column has been migrated
24✔
1742
            continue;
48✔
1743
        }
48✔
1744

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

396✔
1797
        if (acc) {
792✔
1798
            acc->set_parent(&col_refs, col_ndx);
522✔
1799
            acc->init_from_parent();
522✔
1800
            update_size(acc->size());
522✔
1801
            column_accessors.emplace(col_key, std::move(acc));
522✔
1802
            cols_to_destroy.push_back(col_ndx);
522✔
1803
        }
522✔
1804
        if (ts_acc) {
792✔
1805
            ts_acc->set_parent(&col_refs, col_ndx);
18✔
1806
            ts_acc->init_from_parent();
18✔
1807
            update_size(ts_acc->size());
18✔
1808
            ts_accessors.emplace(col_key, std::move(ts_acc));
18✔
1809
            cols_to_destroy.push_back(col_ndx);
18✔
1810
        }
18✔
1811
        if (list_acc) {
792✔
1812
            list_acc->set_parent(&col_refs, col_ndx);
60✔
1813
            list_acc->init_from_parent();
60✔
1814
            update_size(list_acc->size());
60✔
1815
            list_accessors.emplace(col_key, std::move(list_acc));
60✔
1816
            cols_to_destroy.push_back(col_ndx);
60✔
1817
        }
60✔
1818
    }
792✔
1819

111✔
1820
    REALM_ASSERT(number_of_objects != size_t(-1));
222✔
1821

111✔
1822
    if (m_clusters.size() == number_of_objects) {
222✔
1823
        // We have migrated all objects
33✔
1824
        return !has_link_columns;
66✔
1825
    }
66✔
1826

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

78✔
1831
    /*************************** Create objects ******************************/
78✔
1832

78✔
1833
    for (size_t row_ndx = 0; row_ndx < number_of_objects; row_ndx++) {
3,654✔
1834
        // Build a vector of values obtained from the old columns
1,749✔
1835
        FieldValues init_values;
3,498✔
1836
        for (auto& it : column_accessors) {
28,200✔
1837
            auto col_key = it.first;
28,200✔
1838
            auto col_type = col_key.get_type();
28,200✔
1839
            auto nullable = col_key.get_attrs().test(col_attr_Nullable);
28,200✔
1840
            auto val = get_val_from_column(row_ndx, col_type, nullable, it.second.get());
28,200✔
1841
            init_values.insert(col_key, val);
28,200✔
1842
        }
28,200✔
1843
        for (auto& it : ts_accessors) {
3,288✔
1844
            init_values.insert(it.first, Mixed(it.second->get(row_ndx)));
3,078✔
1845
        }
3,078✔
1846

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

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

78✔
1892
    // Destroy values in the old columns that has been copied.
78✔
1893
    // This frees up space in the file
78✔
1894
    for (auto ndx : cols_to_destroy) {
516✔
1895
        Array::destroy_deep(to_ref(col_refs.get(ndx)), m_alloc);
516✔
1896
        col_refs.set(ndx, 0);
516✔
1897
    }
516✔
1898

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

78✔
1903
#if 0
1904
    if (fastrand(100) < 20) {
1905
        throw std::runtime_error("Upgrade interrupted"); // Can be used for testing
1906
    }
1907
#endif
1908
    return !has_link_columns;
156✔
1909
}
156✔
1910

1911
void Table::migrate_links()
1912
{
60✔
1913
    ref_type top_ref = m_top.get_as_ref(top_position_for_columns);
60✔
1914
    if (!top_ref) {
60✔
1915
        // All objects migrated
1916
        return;
×
1917
    }
×
1918

30✔
1919
    Array col_refs(m_alloc);
60✔
1920
    col_refs.set_parent(&m_top, top_position_for_columns);
60✔
1921
    col_refs.init_from_ref(top_ref);
60✔
1922

30✔
1923
    // Cache column accessors and other information
30✔
1924
    size_t nb_columns = m_spec.get_public_column_count();
60✔
1925
    std::vector<std::unique_ptr<BPlusTree<Int>>> link_column_accessors(nb_columns);
60✔
1926
    std::vector<ColKey> col_keys(nb_columns);
60✔
1927
    std::vector<ColumnType> col_types(nb_columns);
60✔
1928
    std::vector<Table*> target_tables(nb_columns);
60✔
1929
    std::vector<ColKey> opposite_orig_row_ndx_col(nb_columns);
60✔
1930
    for (size_t col_ndx = 0; col_ndx < nb_columns; col_ndx++) {
414✔
1931
        ColumnType col_type = m_spec.get_column_type(col_ndx);
354✔
1932

177✔
1933
        if (is_link_type(col_type)) {
354✔
1934
            link_column_accessors[col_ndx] = std::make_unique<BPlusTree<int64_t>>(m_alloc);
120✔
1935
            link_column_accessors[col_ndx]->set_parent(&col_refs, col_ndx);
120✔
1936
            link_column_accessors[col_ndx]->init_from_parent();
120✔
1937
            col_keys[col_ndx] = m_spec.get_key(col_ndx);
120✔
1938
            col_types[col_ndx] = col_type;
120✔
1939
            target_tables[col_ndx] = get_opposite_table(col_keys[col_ndx]).unchecked_ptr();
120✔
1940
            opposite_orig_row_ndx_col[col_ndx] = target_tables[col_ndx]->get_column_key("!ROW_INDEX");
120✔
1941
        }
120✔
1942
    }
354✔
1943

30✔
1944
    auto orig_row_ndx_col_key = get_column_key("!ROW_INDEX");
60✔
1945
    for (auto obj : *this) {
3,186✔
1946
        for (size_t col_ndx = 0; col_ndx < nb_columns; col_ndx++) {
46,314✔
1947
            if (col_keys[col_ndx]) {
43,128✔
1948
                // If no !ROW_INDEX column is found, the original row index number is
3,186✔
1949
                // equal to the ObjKey value
3,186✔
1950
                size_t orig_row_ndx =
6,372✔
1951
                    size_t(orig_row_ndx_col_key ? obj.get<Int>(orig_row_ndx_col_key) : obj.get_key().value);
6,372✔
1952
                // Get original link value
3,186✔
1953
                int64_t link_val = link_column_accessors[col_ndx]->get(orig_row_ndx);
6,372✔
1954

3,186✔
1955
                Table* target_table = target_tables[col_ndx];
6,372✔
1956
                ColKey search_col = opposite_orig_row_ndx_col[col_ndx];
6,372✔
1957
                auto get_target_key = [target_table, search_col](int64_t orig_link_val) -> ObjKey {
3,444✔
1958
                    if (search_col)
516✔
1959
                        return target_table->find_first_int(search_col, orig_link_val);
36✔
1960
                    else
480✔
1961
                        return ObjKey(orig_link_val);
480✔
1962
                };
516✔
1963

3,186✔
1964
                if (link_val) {
6,372✔
1965
                    if (col_types[col_ndx] == col_type_Link) {
240✔
1966
                        obj.set(col_keys[col_ndx], get_target_key(link_val - 1));
138✔
1967
                    }
138✔
1968
                    else {
102✔
1969
                        auto ll = obj.get_linklist(col_keys[col_ndx]);
102✔
1970
                        BPlusTree<Int> links(m_alloc);
102✔
1971
                        links.init_from_ref(ref_type(link_val));
102✔
1972
                        size_t nb_links = links.size();
102✔
1973
                        for (size_t j = 0; j < nb_links; j++) {
480✔
1974
                            ll.add(get_target_key(links.get(j)));
378✔
1975
                        }
378✔
1976
                    }
102✔
1977
                }
240✔
1978
            }
6,372✔
1979
        }
43,128✔
1980
    }
3,186✔
1981
}
60✔
1982

1983
void Table::finalize_migration(ColKey pk_col_key)
1984
{
282✔
1985
    if (ref_type ref = m_top.get_as_ref(top_position_for_columns)) {
282✔
1986
        Array::destroy_deep(ref, m_alloc);
234✔
1987
        m_top.set(top_position_for_columns, 0);
234✔
1988
    }
234✔
1989

141✔
1990
    if (auto orig_row_ndx_col = get_column_key("!ROW_INDEX")) {
282✔
1991
        remove_column(orig_row_ndx_col);
18✔
1992
    }
18✔
1993

141✔
1994
    if (auto oid_col = get_column_key("!OID")) {
282✔
1995
        remove_column(oid_col);
×
1996
    }
×
1997

141✔
1998
    REALM_ASSERT_RELEASE(!pk_col_key || valid_column(pk_col_key));
282✔
1999
    do_set_primary_key_column(pk_col_key);
282✔
2000
}
282✔
2001

2002
void Table::migrate_sets_and_dictionaries()
2003
{
180✔
2004
    std::vector<ColKey> to_migrate;
180✔
2005
    for (auto col : get_column_keys()) {
570✔
2006
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
2007
            to_migrate.push_back(col);
12✔
2008
        }
12✔
2009
    }
570✔
2010
    if (to_migrate.size()) {
180✔
2011
        for (auto obj : *this) {
6✔
2012
            for (auto col : to_migrate) {
12✔
2013
                if (col.is_set()) {
12✔
2014
                    auto set = obj.get_set<Mixed>(col);
6✔
2015
                    set.migrate();
6✔
2016
                }
6✔
2017
                else if (col.is_dictionary()) {
6✔
2018
                    auto dict = obj.get_dictionary(col);
6✔
2019
                    dict.migrate();
6✔
2020
                }
6✔
2021
            }
12✔
2022
        }
6✔
2023
    }
6✔
2024
}
180✔
2025

2026
StringData Table::get_name() const noexcept
2027
{
2,963,676✔
2028
    const Array& real_top = m_top;
2,963,676✔
2029
    ArrayParent* parent = real_top.get_parent();
2,963,676✔
2030
    if (!parent)
2,963,676✔
2031
        return StringData("");
54✔
2032
    REALM_ASSERT(dynamic_cast<Group*>(parent));
2,963,622✔
2033
    return static_cast<Group*>(parent)->get_table_name(get_key());
2,963,622✔
2034
}
2,963,622✔
2035

2036
StringData Table::get_class_name() const noexcept
2037
{
14,952✔
2038
    return Group::table_name_to_class_name(get_name());
14,952✔
2039
}
14,952✔
2040

2041
const char* Table::get_state() const noexcept
2042
{
42✔
2043
    switch (m_cookie) {
42✔
2044
        case cookie_created:
✔
2045
            return "created";
×
2046
        case cookie_transaction_ended:
6✔
2047
            return "transaction_ended";
6✔
2048
        case cookie_initialized:
✔
2049
            return "initialised";
×
2050
        case cookie_removed:
36✔
2051
            return "removed";
36✔
2052
        case cookie_void:
✔
2053
            return "void";
×
2054
        case cookie_deleted:
✔
2055
            return "deleted";
×
2056
    }
×
2057
    return "";
×
2058
}
×
2059

2060

2061
bool Table::is_nullable(ColKey col_key) const
2062
{
2,010,015✔
2063
    REALM_ASSERT_DEBUG(valid_column(col_key));
2,010,015✔
2064
    return col_key.get_attrs().test(col_attr_Nullable);
2,010,015✔
2065
}
2,010,015✔
2066

2067
bool Table::is_list(ColKey col_key) const
2068
{
172,062✔
2069
    REALM_ASSERT_DEBUG(valid_column(col_key));
172,062✔
2070
    return col_key.get_attrs().test(col_attr_List);
172,062✔
2071
}
172,062✔
2072

2073

2074
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
2075
{
351,750✔
2076
    Array top(alloc);
351,750✔
2077
    _impl::DeepArrayDestroyGuard dg(&top);
351,750✔
2078
    top.create(Array::type_HasRefs); // Throws
351,750✔
2079
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
351,750✔
2080

174,387✔
2081
    {
351,750✔
2082
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
351,750✔
2083
        dg_2.reset(mem.get_ref());
351,750✔
2084
        int_fast64_t v(from_ref(mem.get_ref()));
351,750✔
2085
        top.add(v); // Throws
351,750✔
2086
        dg_2.release();
351,750✔
2087
    }
351,750✔
2088
    top.add(0); // Old position for columns
351,750✔
2089
    {
351,750✔
2090
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
351,750✔
2091
        dg_2.reset(mem.get_ref());
351,750✔
2092
        int_fast64_t v(from_ref(mem.get_ref()));
351,750✔
2093
        top.add(v); // Throws
351,750✔
2094
        dg_2.release();
351,750✔
2095
    }
351,750✔
2096

174,387✔
2097
    // Table key value
174,387✔
2098
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
351,750✔
2099
    top.add(rot);
351,750✔
2100

174,387✔
2101
    // Search indexes
174,387✔
2102
    {
351,750✔
2103
        bool context_flag = false;
351,750✔
2104
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
351,750✔
2105
        dg_2.reset(mem.get_ref());
351,750✔
2106
        int_fast64_t v(from_ref(mem.get_ref()));
351,750✔
2107
        top.add(v); // Throws
351,750✔
2108
        dg_2.release();
351,750✔
2109
    }
351,750✔
2110
    rot = RefOrTagged::make_tagged(0);
351,750✔
2111
    top.add(rot); // Column key
351,750✔
2112
    top.add(rot); // Version
351,750✔
2113
    dg.release();
351,750✔
2114
    // Opposite keys (table and column)
174,387✔
2115
    {
351,750✔
2116
        bool context_flag = false;
351,750✔
2117
        {
351,750✔
2118
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
351,750✔
2119
            dg_2.reset(mem.get_ref());
351,750✔
2120
            int_fast64_t v(from_ref(mem.get_ref()));
351,750✔
2121
            top.add(v); // Throws
351,750✔
2122
            dg_2.release();
351,750✔
2123
        }
351,750✔
2124
        {
351,750✔
2125
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
351,750✔
2126
            dg_2.reset(mem.get_ref());
351,750✔
2127
            int_fast64_t v(from_ref(mem.get_ref()));
351,750✔
2128
            top.add(v); // Throws
351,750✔
2129
            dg_2.release();
351,750✔
2130
        }
351,750✔
2131
    }
351,750✔
2132
    top.add(0); // Sequence number
351,750✔
2133
    top.add(0); // Collision_map
351,750✔
2134
    top.add(0); // pk col key
351,750✔
2135
    top.add(0); // flags
351,750✔
2136
    top.add(0); // tombstones
351,750✔
2137

174,387✔
2138
    REALM_ASSERT(top.size() == top_array_size);
351,750✔
2139

174,387✔
2140
    return top.get_ref();
351,750✔
2141
}
351,750✔
2142

2143
void Table::ensure_graveyard()
2144
{
32,592✔
2145
    if (!m_tombstones) {
32,592✔
2146
        while (m_top.size() < top_position_for_tombstones)
10,638✔
2147
            m_top.add(0);
×
2148
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
10,638✔
2149
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
10,638✔
2150
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
10,638✔
2151
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
10,638✔
2152
        m_tombstones->init_from_parent();
10,638✔
2153
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
26,226✔
2154
            ts->insert_column(col);
26,226✔
2155
            return IteratorControl::AdvanceToNext;
26,226✔
2156
        });
26,226✔
2157
    }
10,638✔
2158
}
32,592✔
2159

2160
void Table::batch_erase_rows(const KeyColumn& keys)
2161
{
558✔
2162
    size_t num_objs = keys.size();
558✔
2163
    std::vector<ObjKey> vec;
558✔
2164
    vec.reserve(num_objs);
558✔
2165
    for (size_t i = 0; i < num_objs; ++i) {
2,898✔
2166
        ObjKey key = keys.get(i);
2,340✔
2167
        if (key != null_key && is_valid(key)) {
2,340✔
2168
            vec.push_back(key);
2,340✔
2169
        }
2,340✔
2170
    }
2,340✔
2171

279✔
2172
    sort(vec.begin(), vec.end());
558✔
2173
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
558✔
2174

279✔
2175
    batch_erase_objects(vec);
558✔
2176
}
558✔
2177

2178
void Table::batch_erase_objects(std::vector<ObjKey>& keys)
2179
{
5,574✔
2180
    Group* g = get_parent_group();
5,574✔
2181
    bool maybe_has_incoming_links = g && !is_asymmetric();
5,574✔
2182

2,787✔
2183
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
5,574✔
2184
        CascadeState state(CascadeState::Mode::Strong, g);
4,806✔
2185
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
3,297✔
2186
            state.m_to_be_deleted.emplace_back(m_key, k);
1,791✔
2187
        });
1,791✔
2188
        if (maybe_has_incoming_links)
4,806✔
2189
            nullify_links(state);
4,806✔
2190
        remove_recursive(state);
4,806✔
2191
    }
4,806✔
2192
    else {
768✔
2193
        CascadeState state(CascadeState::Mode::None, g);
768✔
2194
        for (auto k : keys) {
2,512,302✔
2195
            if (maybe_has_incoming_links) {
2,512,302✔
2196
                m_clusters.nullify_incoming_links(k, state);
2,512,224✔
2197
            }
2,512,224✔
2198
            m_clusters.erase(k, state);
2,512,302✔
2199
        }
2,512,302✔
2200
    }
768✔
2201
    keys.clear();
5,574✔
2202
}
5,574✔
2203

2204
void Table::clear()
2205
{
4,314✔
2206
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
4,314✔
2207
    m_clusters.clear(state);
4,314✔
2208
    free_collision_table();
4,314✔
2209
}
4,314✔
2210

2211

2212
Group* Table::get_parent_group() const noexcept
2213
{
59,898,540✔
2214
    if (!m_top.is_attached())
59,898,540✔
2215
        return 0;                             // Subtable with shared descriptor
×
2216
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
59,898,540✔
2217
    if (!parent)
59,898,540✔
2218
        return 0; // Free-standing table
25,723,920✔
2219

16,908,951✔
2220
    return static_cast<Group*>(parent);
34,174,620✔
2221
}
34,174,620✔
2222

2223
inline uint64_t Table::get_sync_file_id() const noexcept
2224
{
38,760,288✔
2225
    Group* g = get_parent_group();
38,760,288✔
2226
    return g ? g->get_sync_file_id() : 0;
32,143,542✔
2227
}
38,760,288✔
2228

2229
size_t Table::get_index_in_group() const noexcept
2230
{
42✔
2231
    if (!m_top.is_attached())
42✔
2232
        return realm::npos;                   // Subtable with shared descriptor
×
2233
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
42✔
2234
    if (!parent)
42✔
2235
        return realm::npos; // Free-standing table
×
2236
    return m_top.get_ndx_in_parent();
42✔
2237
}
42✔
2238

2239
uint64_t Table::allocate_sequence_number()
2240
{
19,939,386✔
2241
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
19,939,386✔
2242
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,809,600✔
2243
    rot = RefOrTagged::make_tagged(sn + 1);
19,939,386✔
2244
    m_top.set(top_position_for_sequence_number, rot);
19,939,386✔
2245

9,919,908✔
2246
    return sn;
19,939,386✔
2247
}
19,939,386✔
2248

2249
void Table::set_sequence_number(uint64_t seq)
2250
{
156✔
2251
    m_top.set(top_position_for_sequence_number, RefOrTagged::make_tagged(seq));
156✔
2252
}
156✔
2253

2254
void Table::set_collision_map(ref_type ref)
2255
{
×
2256
    m_top.set(top_position_for_collision_map, RefOrTagged::make_ref(ref));
×
2257
}
×
2258

2259
void Table::set_col_key_sequence_number(uint64_t seq)
2260
{
24✔
2261
    m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(seq));
24✔
2262
}
24✔
2263

2264
TableRef Table::get_link_target(ColKey col_key) noexcept
2265
{
320,340✔
2266
    return get_opposite_table(col_key);
320,340✔
2267
}
320,340✔
2268

2269
// count ----------------------------------------------
2270

2271
size_t Table::count_int(ColKey col_key, int64_t value) const
2272
{
12,006✔
2273
    if (auto index = this->get_search_index(col_key)) {
12,006✔
2274
        return index->count(value);
12,000✔
2275
    }
12,000✔
2276

3✔
2277
    return where().equal(col_key, value).count();
6✔
2278
}
6✔
2279
size_t Table::count_float(ColKey col_key, float value) const
2280
{
6✔
2281
    return where().equal(col_key, value).count();
6✔
2282
}
6✔
2283
size_t Table::count_double(ColKey col_key, double value) const
2284
{
6✔
2285
    return where().equal(col_key, value).count();
6✔
2286
}
6✔
2287
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
2288
{
18✔
2289
    ArrayDecimal128 leaf(get_alloc());
18✔
2290
    size_t cnt = 0;
18✔
2291
    bool null_value = value.is_null();
18✔
2292
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
2293
        // direct aggregate on the leaf
9✔
2294
        cluster->init_leaf(col_key, &leaf);
18✔
2295
        auto sz = leaf.size();
18✔
2296
        for (size_t i = 0; i < sz; i++) {
1,296✔
2297
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
2298
                cnt++;
24✔
2299
            }
24✔
2300
        }
1,278✔
2301
        return IteratorControl::AdvanceToNext;
18✔
2302
    };
18✔
2303

9✔
2304
    traverse_clusters(f);
18✔
2305

9✔
2306
    return cnt;
18✔
2307
}
18✔
2308
size_t Table::count_string(ColKey col_key, StringData value) const
2309
{
1,476✔
2310
    if (auto index = this->get_search_index(col_key)) {
1,476✔
2311
        return index->count(value);
732✔
2312
    }
732✔
2313
    return where().equal(col_key, value).count();
744✔
2314
}
744✔
2315

2316
template <typename T>
2317
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
2318
{
28,734✔
2319
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
28,734✔
2320
    LeafType leaf(get_alloc());
28,734✔
2321

14,340✔
2322
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
28,752✔
2323
        // direct aggregate on the leaf
14,349✔
2324
        cluster->init_leaf(column_key, &leaf);
28,752✔
2325
        st.m_key_offset = cluster->get_offset();
28,752✔
2326
        st.m_key_values = cluster->get_key_array();
28,752✔
2327
        st.set_payload_column(&leaf);
28,752✔
2328
        bool cont = true;
28,752✔
2329
        size_t sz = leaf.size();
28,752✔
2330
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
124,473✔
2331
            cont = st.match(local_index);
95,721✔
2332
        }
95,721✔
2333
        return IteratorControl::AdvanceToNext;
28,752✔
2334
    };
28,752✔
2335

14,340✔
2336
    traverse_clusters(f);
28,734✔
2337
}
28,734✔
2338

2339
// This template is also used by the query engine
2340
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
2341
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
2342
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
2343
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
2344
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
2345
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
2346
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
2347

2348
std::optional<Mixed> Table::sum(ColKey col_key) const
2349
{
756✔
2350
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
2351
}
756✔
2352

2353
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
2354
{
822✔
2355
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
2356
}
822✔
2357

2358
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
2359
{
1,170✔
2360
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
2361
}
1,170✔
2362

2363
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
2364
{
22,458✔
2365
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
22,458✔
2366
}
22,458✔
2367

2368
template <class T>
2369
ObjKey Table::find_first(ColKey col_key, T value) const
2370
{
34,815✔
2371
    check_column(col_key);
34,815✔
2372

17,364✔
2373
    if (!col_key.is_nullable() && value_is_null(value)) {
34,815!
2374
        return {}; // this is a precaution/optimization
6✔
2375
    }
6✔
2376
    // You cannot call GetIndexData on ObjKey
17,361✔
2377
    if constexpr (!std::is_same_v<T, ObjKey>) {
34,809✔
2378
        if (StringIndex* index = get_search_index(col_key)) {
34,791!
2379
            return index->find_first(value);
26,943✔
2380
        }
26,943✔
2381
        if (col_key == m_primary_key_col) {
7,848!
2382
            return find_primary_key(value);
×
2383
        }
×
2384
    }
7,848✔
2385

3,924✔
2386
    ObjKey key;
7,848✔
2387
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
7,848✔
2388
    LeafType leaf(get_alloc());
7,848✔
2389

3,924✔
2390
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
9,330✔
2391
        cluster->init_leaf(col_key, &leaf);
9,330✔
2392
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
2393
        if (row != realm::npos) {
9,330!
2394
            key = cluster->get_real_key(row);
7,692✔
2395
            return IteratorControl::Stop;
7,692✔
2396
        }
7,692✔
2397
        return IteratorControl::AdvanceToNext;
1,638✔
2398
    };
1,638✔
2399

3,924✔
2400
    traverse_clusters(f);
7,848✔
2401

3,924✔
2402
    return key;
7,848✔
2403
}
7,848✔
2404

2405
namespace realm {
2406

2407
template <>
2408
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
2409
{
×
2410
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
2411
}
×
2412

2413
template <>
2414
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
2415
{
×
2416
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
2417
}
×
2418

2419
template <>
2420
ObjKey Table::find_first(ColKey col_key, null) const
2421
{
×
2422
    return find_first_null(col_key);
×
2423
}
×
2424
} // namespace realm
2425

2426
// Explicitly instantiate the generic case of the template for the types we care about.
2427
template ObjKey Table::find_first(ColKey col_key, bool) const;
2428
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
2429
template ObjKey Table::find_first(ColKey col_key, float) const;
2430
template ObjKey Table::find_first(ColKey col_key, double) const;
2431
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
2432
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
2433
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
2434
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
2435
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
2436
template ObjKey Table::find_first(ColKey col_key, StringData) const;
2437
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
2438
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
2439
template ObjKey Table::find_first(ColKey col_key, UUID) const;
2440
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
2441
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
2442

2443
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
2444
{
7,740✔
2445
    if (is_nullable(col_key))
7,740✔
2446
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
2447
    else
7,704✔
2448
        return find_first<int64_t>(col_key, value);
7,704✔
2449
}
7,740✔
2450

2451
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
2452
{
78✔
2453
    if (is_nullable(col_key))
78✔
2454
        return find_first<util::Optional<bool>>(col_key, value);
36✔
2455
    else
42✔
2456
        return find_first<bool>(col_key, value);
42✔
2457
}
78✔
2458

2459
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
2460
{
108✔
2461
    return find_first(col_key, value);
108✔
2462
}
108✔
2463

2464
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
2465
{
6✔
2466
    return find_first(col_key, value);
6✔
2467
}
6✔
2468

2469
ObjKey Table::find_first_float(ColKey col_key, float value) const
2470
{
12✔
2471
    return find_first<Float>(col_key, value);
12✔
2472
}
12✔
2473

2474
ObjKey Table::find_first_double(ColKey col_key, double value) const
2475
{
12✔
2476
    return find_first<Double>(col_key, value);
12✔
2477
}
12✔
2478

2479
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
2480
{
×
2481
    return find_first<Decimal128>(col_key, value);
×
2482
}
×
2483

2484
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
2485
{
11,415✔
2486
    return find_first<StringData>(col_key, value);
11,415✔
2487
}
11,415✔
2488

2489
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
2490
{
×
2491
    return find_first<BinaryData>(col_key, value);
×
2492
}
×
2493

2494
ObjKey Table::find_first_null(ColKey col_key) const
2495
{
114✔
2496
    return where().equal(col_key, null{}).find();
114✔
2497
}
114✔
2498

2499
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
2500
{
18✔
2501
    return find_first(col_key, value);
18✔
2502
}
18✔
2503

2504
template <class T>
2505
TableView Table::find_all(ColKey col_key, T value)
2506
{
102✔
2507
    return where().equal(col_key, value).find_all();
102✔
2508
}
102✔
2509

2510
TableView Table::find_all_int(ColKey col_key, int64_t value)
2511
{
96✔
2512
    return find_all<int64_t>(col_key, value);
96✔
2513
}
96✔
2514

2515
TableView Table::find_all_int(ColKey col_key, int64_t value) const
2516
{
×
2517
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
2518
}
×
2519

2520
TableView Table::find_all_bool(ColKey col_key, bool value)
2521
{
×
2522
    return find_all<bool>(col_key, value);
×
2523
}
×
2524

2525
TableView Table::find_all_bool(ColKey col_key, bool value) const
2526
{
×
2527
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
2528
}
×
2529

2530

2531
TableView Table::find_all_float(ColKey col_key, float value)
2532
{
×
2533
    return find_all<float>(col_key, value);
×
2534
}
×
2535

2536
TableView Table::find_all_float(ColKey col_key, float value) const
2537
{
×
2538
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
2539
}
×
2540

2541
TableView Table::find_all_double(ColKey col_key, double value)
2542
{
6✔
2543
    return find_all<double>(col_key, value);
6✔
2544
}
6✔
2545

2546
TableView Table::find_all_double(ColKey col_key, double value) const
2547
{
×
2548
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
2549
}
×
2550

2551
TableView Table::find_all_string(ColKey col_key, StringData value)
2552
{
282✔
2553
    return where().equal(col_key, value).find_all();
282✔
2554
}
282✔
2555

2556
TableView Table::find_all_string(ColKey col_key, StringData value) const
2557
{
×
2558
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
2559
}
×
2560

2561
TableView Table::find_all_binary(ColKey, BinaryData)
2562
{
×
2563
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
2564
}
×
2565

2566
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
2567
{
×
2568
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
2569
}
×
2570

2571
TableView Table::find_all_null(ColKey col_key)
2572
{
×
2573
    return where().equal(col_key, null{}).find_all();
×
2574
}
×
2575

2576
TableView Table::find_all_null(ColKey col_key) const
2577
{
×
2578
    return const_cast<Table*>(this)->find_all_null(col_key);
×
2579
}
×
2580

2581
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
2582
{
6✔
2583
    return where().fulltext(col_key, terms).find_all();
6✔
2584
}
6✔
2585

2586
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
2587
{
72✔
2588
    TableView tv = where().find_all();
72✔
2589
    tv.sort(col_key, ascending);
72✔
2590
    return tv;
72✔
2591
}
72✔
2592

2593
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
2594
{
6✔
2595
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
2596
}
6✔
2597

2598
TableView Table::get_sorted_view(SortDescriptor order)
2599
{
126✔
2600
    TableView tv = where().find_all();
126✔
2601
    tv.sort(std::move(order));
126✔
2602
    return tv;
126✔
2603
}
126✔
2604

2605
TableView Table::get_sorted_view(SortDescriptor order) const
2606
{
×
2607
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
2608
}
×
2609

2610
util::Logger* Table::get_logger() const noexcept
2611
{
1,703,880✔
2612
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
2,148,360,358✔
2613
}
1,703,880✔
2614

2615
// Called after a commit. Table will effectively contain the same as before,
2616
// but now with new refs from the file
2617
void Table::update_from_parent() noexcept
2618
{
853,926✔
2619
    // There is no top for sub-tables sharing spec
423,984✔
2620
    if (m_top.is_attached()) {
853,926✔
2621
        m_top.update_from_parent();
853,920✔
2622
        m_spec.update_from_parent();
853,920✔
2623
        m_clusters.update_from_parent();
853,920✔
2624
        m_index_refs.update_from_parent();
853,920✔
2625
        for (auto&& index : m_index_accessors) {
2,823,438✔
2626
            if (index != nullptr) {
2,823,438✔
2627
                index->update_from_parent();
303,243✔
2628
            }
303,243✔
2629
        }
2,823,438✔
2630

423,984✔
2631
        m_opposite_table.update_from_parent();
853,920✔
2632
        m_opposite_column.update_from_parent();
853,920✔
2633
        if (m_top.size() > top_position_for_flags) {
853,920✔
2634
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
852,090✔
2635
            m_table_type = Type(flags & table_type_mask);
852,090✔
2636
        }
852,090✔
2637
        else {
1,830✔
2638
            m_table_type = Type::TopLevel;
1,830✔
2639
        }
1,830✔
2640
        if (m_tombstones)
853,920✔
2641
            m_tombstones->update_from_parent();
3,042✔
2642

423,984✔
2643
        refresh_content_version();
853,920✔
2644
        m_has_any_embedded_objects.reset();
853,920✔
2645
    }
853,920✔
2646
    m_alloc.bump_storage_version();
853,926✔
2647
}
853,926✔
2648

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

2711
void Table::to_json(std::ostream& out, size_t link_depth, const std::map<std::string, std::string>& renames,
2712
                    JSONOutputMode output_mode) const
2713
{
300✔
2714
    // Represent table as list of objects
150✔
2715
    out << "[";
300✔
2716
    bool first = true;
300✔
2717

150✔
2718
    for (auto& obj : *this) {
4,050✔
2719
        if (first) {
4,050✔
2720
            first = false;
294✔
2721
        }
294✔
2722
        else {
3,756✔
2723
            out << ",";
3,756✔
2724
        }
3,756✔
2725
        obj.to_json(out, link_depth, renames, output_mode);
4,050✔
2726
    }
4,050✔
2727

150✔
2728
    out << "]";
300✔
2729
}
300✔
2730

2731

2732
bool Table::operator==(const Table& t) const
2733
{
138✔
2734
    if (size() != t.size()) {
138✔
2735
        return false;
12✔
2736
    }
12✔
2737
    // Check columns
63✔
2738
    for (auto ck : this->get_column_keys()) {
522✔
2739
        auto name = get_column_name(ck);
522✔
2740
        auto other_ck = t.get_column_key(name);
522✔
2741
        auto attrs = ck.get_attrs();
522✔
2742
        if (search_index_type(ck) != t.search_index_type(other_ck))
522✔
2743
            return false;
×
2744

261✔
2745
        if (!other_ck || other_ck.get_attrs() != attrs) {
522✔
2746
            return false;
×
2747
        }
×
2748
    }
522✔
2749
    auto pk_col = get_primary_key_column();
126✔
2750
    for (auto o : *this) {
2,898✔
2751
        Obj other_o;
2,898✔
2752
        if (pk_col) {
2,898✔
2753
            auto pk = o.get_any(pk_col);
90✔
2754
            other_o = t.get_object_with_primary_key(pk);
90✔
2755
        }
90✔
2756
        else {
2,808✔
2757
            other_o = t.get_object(o.get_key());
2,808✔
2758
        }
2,808✔
2759
        if (!(other_o && o == other_o))
2,898✔
2760
            return false;
18✔
2761
    }
2,898✔
2762

63✔
2763
    return true;
117✔
2764
}
126✔
2765

2766

2767
void Table::flush_for_commit()
2768
{
3,966,696✔
2769
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
3,966,696✔
2770
        if (!m_top.is_read_only()) {
3,966,687✔
2771
            ++m_in_file_version_at_transaction_boundary;
1,259,064✔
2772
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
1,259,064✔
2773
            m_top.set(top_position_for_version, rot_version);
1,259,064✔
2774
        }
1,259,064✔
2775
    }
3,966,687✔
2776
}
3,966,696✔
2777

2778
void Table::refresh_content_version()
2779
{
1,213,185✔
2780
    REALM_ASSERT(m_top.is_attached());
1,213,185✔
2781
    if (m_top.size() >= top_position_for_version) {
1,213,185✔
2782
        // we have versioning info in the file. Use this to conditionally
644,061✔
2783
        // bump the version counter:
644,061✔
2784
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,212,996✔
2785
        REALM_ASSERT(rot_version.is_tagged());
1,212,996✔
2786
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,212,996✔
2787
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
239,205✔
2788
            bump_content_version();
239,205✔
2789
        }
239,205✔
2790
    }
1,212,996✔
2791
    else {
189✔
2792
        // assume the worst:
138✔
2793
        bump_content_version();
189✔
2794
    }
189✔
2795
}
1,213,185✔
2796

2797

2798
// Called when Group is moved to another version - either a rollback or an advance.
2799
// The content of the table is potentially different, so make no assumptions.
2800
void Table::refresh_accessor_tree()
2801
{
359,250✔
2802
    REALM_ASSERT(m_cookie == cookie_initialized);
359,250✔
2803
    REALM_ASSERT(m_top.is_attached());
359,250✔
2804
    m_top.init_from_parent();
359,250✔
2805
    m_spec.init_from_parent();
359,250✔
2806
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
359,250✔
2807
    m_clusters.init_from_parent();
359,250✔
2808
    m_index_refs.init_from_parent();
359,250✔
2809
    m_opposite_table.init_from_parent();
359,250✔
2810
    m_opposite_column.init_from_parent();
359,250✔
2811
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
359,250✔
2812
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
305,898✔
2813
    if (m_top.size() > top_position_for_flags) {
359,250✔
2814
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
358,980✔
2815
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
358,980✔
2816
    }
358,980✔
2817
    else {
270✔
2818
        m_table_type = Type::TopLevel;
270✔
2819
    }
270✔
2820
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
359,250✔
2821
        // Tombstones exists
1,056✔
2822
        if (!m_tombstones) {
2,112✔
2823
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
282✔
2824
        }
282✔
2825
        m_tombstones->init_from_parent();
2,112✔
2826
    }
2,112✔
2827
    else {
357,138✔
2828
        m_tombstones = nullptr;
357,138✔
2829
    }
357,138✔
2830
    refresh_content_version();
359,250✔
2831
    bump_storage_version();
359,250✔
2832
    build_column_mapping();
359,250✔
2833
    refresh_index_accessors();
359,250✔
2834
}
359,250✔
2835

2836
void Table::refresh_index_accessors()
2837
{
6,375,240✔
2838
    // Refresh search index accessors
3,444,120✔
2839

3,444,120✔
2840
    // First eliminate any index accessors for eliminated last columns
3,444,120✔
2841
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
6,375,240✔
2842
    m_index_accessors.resize(col_ndx_end);
6,375,240✔
2843

3,444,120✔
2844
    // Then eliminate/refresh/create accessors within column range
3,444,120✔
2845
    // we can not use for_each_column() here, since the columns may have changed
3,444,120✔
2846
    // and the index accessor vector is not updated correspondingly.
3,444,120✔
2847
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
28,052,757✔
2848
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
21,677,517✔
2849

11,278,371✔
2850
        if (ref == 0) {
21,677,517✔
2851
            // accessor drop
9,281,721✔
2852
            m_index_accessors[col_ndx].reset();
17,701,911✔
2853
        }
17,701,911✔
2854
        else {
3,975,606✔
2855
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
3,975,606✔
2856
            bool fulltext = attr.test(col_attr_FullText_Indexed);
3,975,606✔
2857
            auto col_key = m_leaf_ndx2colkey[col_ndx];
3,975,606✔
2858
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
3,975,591✔
2859

1,996,650✔
2860
            if (m_index_accessors[col_ndx]) { // still there, refresh:
3,975,606✔
2861
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
490,983✔
2862
            }
490,983✔
2863
            else { // new index!
3,484,623✔
2864
                m_index_accessors[col_ndx] =
3,484,623✔
2865
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
3,484,623✔
2866
            }
3,484,623✔
2867
        }
3,975,606✔
2868
    }
21,677,517✔
2869
}
6,375,240✔
2870

2871
bool Table::is_cross_table_link_target() const noexcept
2872
{
8,214✔
2873
    auto is_cross_link = [this](ColKey col_key) {
4,077✔
2874
        auto t = col_key.get_type();
60✔
2875
        // look for a backlink with a different target than ourselves
33✔
2876
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
60✔
2877
                   ? IteratorControl::Stop
42✔
2878
                   : IteratorControl::AdvanceToNext;
51✔
2879
    };
60✔
2880
    return for_each_backlink_column(is_cross_link);
8,214✔
2881
}
8,214✔
2882

2883
// LCOV_EXCL_START ignore debug functions
2884

2885
void Table::verify() const
2886
{
2,907,684✔
2887
#ifdef REALM_DEBUG
2,907,684✔
2888
    if (m_top.is_attached())
2,907,684✔
2889
        m_top.verify();
2,907,684✔
2890
    m_spec.verify();
2,907,684✔
2891
    m_clusters.verify();
2,907,684✔
2892
    if (nb_unresolved())
2,907,684✔
2893
        m_tombstones->verify();
774,744✔
2894
#endif
2,907,684✔
2895
}
2,907,684✔
2896

2897
#ifdef REALM_DEBUG
2898
MemStats Table::stats() const
2899
{
×
2900
    MemStats mem_stats;
×
2901
    m_top.stats(mem_stats);
×
2902
    return mem_stats;
×
2903
}
×
2904
#endif // LCOV_EXCL_STOP ignore debug functions
2905

2906
Obj Table::create_object(ObjKey key, const FieldValues& values)
2907
{
22,586,139✔
2908
    if (is_embedded())
22,586,139✔
2909
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2910
    if (m_primary_key_col)
22,586,091✔
2911
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2912
    if (key == null_key) {
22,586,091✔
2913
        GlobalKey object_id = allocate_object_id_squeezed();
19,447,731✔
2914
        key = object_id.get_local_key(get_sync_file_id());
19,447,731✔
2915
        // Check if this key collides with an already existing object
9,705,912✔
2916
        // This could happen if objects were at some point created with primary keys,
9,705,912✔
2917
        // but later primary key property was removed from the schema.
9,705,912✔
2918
        while (m_clusters.is_valid(key)) {
19,447,731✔
2919
            object_id = allocate_object_id_squeezed();
×
2920
            key = object_id.get_local_key(get_sync_file_id());
×
2921
        }
×
2922
        if (auto repl = get_repl())
19,447,731✔
2923
            repl->create_object(this, object_id);
4,317,087✔
2924
    }
19,447,731✔
2925

11,268,228✔
2926
    REALM_ASSERT(key.value >= 0);
22,586,091✔
2927

11,268,228✔
2928
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,586,091✔
2929

11,268,228✔
2930
    return obj;
22,586,091✔
2931
}
22,586,091✔
2932

2933
Obj Table::create_linked_object()
2934
{
40,266✔
2935
    REALM_ASSERT(is_embedded());
40,266✔
2936

20,028✔
2937
    GlobalKey object_id = allocate_object_id_squeezed();
40,266✔
2938
    ObjKey key = object_id.get_local_key(get_sync_file_id());
40,266✔
2939

20,028✔
2940
    REALM_ASSERT(key.value >= 0);
40,266✔
2941

20,028✔
2942
    Obj obj = m_clusters.insert(key, {});
40,266✔
2943

20,028✔
2944
    return obj;
40,266✔
2945
}
40,266✔
2946

2947
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2948
{
30✔
2949
    if (is_embedded())
30✔
2950
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2951
    if (m_primary_key_col)
30✔
2952
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2953
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2954

15✔
2955
    if (auto repl = get_repl())
30✔
2956
        repl->create_object(this, object_id);
30✔
2957

15✔
2958
    try {
30✔
2959
        Obj obj = m_clusters.insert(key, values);
30✔
2960
        // Check if tombstone exists
15✔
2961
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2962
            auto unres_key = key.get_unresolved();
6✔
2963
            // Copy links over
3✔
2964
            auto tombstone = m_tombstones->get(unres_key);
6✔
2965
            obj.assign_pk_and_backlinks(tombstone);
6✔
2966
            // If tombstones had no links to it, it may still be alive
3✔
2967
            if (m_tombstones->is_valid(unres_key)) {
6✔
2968
                CascadeState state(CascadeState::Mode::None);
6✔
2969
                m_tombstones->erase(unres_key, state);
6✔
2970
            }
6✔
2971
        }
6✔
2972

15✔
2973
        return obj;
30✔
2974
    }
30✔
2975
    catch (const KeyAlreadyUsed&) {
6✔
2976
        return m_clusters.get(key);
6✔
2977
    }
6✔
2978
}
30✔
2979

2980
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2981
                                          bool* did_create)
2982
{
599,187✔
2983
    auto primary_key_col = get_primary_key_column();
599,187✔
2984
    if (is_embedded() || !primary_key_col)
599,187✔
2985
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2986
                              util::format("Table has no primary key: %1", get_name()));
6✔
2987

288,948✔
2988
    DataType type = DataType(primary_key_col.get_type());
599,181✔
2989

288,948✔
2990
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
599,181✔
2991
        throw InvalidArgument(
6✔
2992
            ErrorCodes::PropertyNotNullable,
6✔
2993
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2994
    }
6✔
2995

288,945✔
2996
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
599,175✔
2997
        primary_key.get_type() != type) {
598,971✔
2998
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2999
                                                                     Group::table_name_to_class_name(get_name())));
6✔
3000
    }
6✔
3001

288,942✔
3002
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
599,169✔
3003

288,942✔
3004
    if (did_create)
599,169✔
3005
        *did_create = false;
81,990✔
3006

288,942✔
3007
    // Check for existing object
288,942✔
3008
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
599,169✔
3009
        if (mode == UpdateMode::never) {
80,757✔
3010
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
3011
        }
6✔
3012
        auto obj = m_clusters.get(key);
80,751✔
3013
        for (auto& val : field_values) {
40,536✔
3014
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
3015
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
3016
            }
12✔
3017
        }
12✔
3018
        return obj;
80,751✔
3019
    }
80,751✔
3020

248,409✔
3021
    ObjKey unres_key;
518,412✔
3022
    if (m_tombstones) {
518,412✔
3023
        // Check for potential tombstone
20,040✔
3024
        GlobalKey object_id{primary_key};
40,509✔
3025
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
40,509✔
3026

20,040✔
3027
        ObjKey key = object_key.get_unresolved();
40,509✔
3028
        if (auto obj = m_tombstones->try_get_obj(key)) {
40,509✔
3029
            auto existing_pk_value = obj.get_any(primary_key_col);
13,281✔
3030

6,699✔
3031
            // If the primary key is the same, the object should be resurrected below
6,699✔
3032
            if (existing_pk_value == primary_key) {
13,281✔
3033
                unres_key = key;
13,275✔
3034
            }
13,275✔
3035
        }
13,281✔
3036
    }
40,509✔
3037

248,409✔
3038
    ObjKey key = get_next_valid_key();
518,412✔
3039

248,409✔
3040
    auto repl = get_repl();
518,412✔
3041
    if (repl) {
518,412✔
3042
        repl->create_object_with_primary_key(this, key, primary_key);
457,317✔
3043
    }
457,317✔
3044
    if (did_create) {
518,412✔
3045
        *did_create = true;
45,684✔
3046
    }
45,684✔
3047

248,409✔
3048
    field_values.insert(primary_key_col, primary_key);
518,412✔
3049
    Obj ret = m_clusters.insert(key, field_values);
518,412✔
3050

248,409✔
3051
    // Check if unresolved exists
248,409✔
3052
    if (unres_key) {
518,412✔
3053
        auto tombstone = m_tombstones->get(unres_key);
13,275✔
3054
        ret.assign_pk_and_backlinks(tombstone);
13,275✔
3055
        // If tombstones had no links to it, it may still be alive
6,696✔
3056
        if (m_tombstones->is_valid(unres_key)) {
13,275✔
3057
            CascadeState state(CascadeState::Mode::None);
4,119✔
3058
            m_tombstones->erase(unres_key, state);
4,119✔
3059
        }
4,119✔
3060
    }
13,275✔
3061
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
518,412✔
3062
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
3063
    }
1,290✔
3064
    return ret;
518,412✔
3065
}
518,412✔
3066

3067
ObjKey Table::find_primary_key(Mixed primary_key) const
3068
{
921,972✔
3069
    auto primary_key_col = get_primary_key_column();
921,972✔
3070
    REALM_ASSERT(primary_key_col);
921,972✔
3071
    DataType type = DataType(primary_key_col.get_type());
921,972✔
3072
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
921,972✔
3073
                 primary_key.get_type() == type);
921,972✔
3074

462,240✔
3075
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
921,981✔
3076
        return index->find_first(primary_key);
921,945✔
3077
    }
921,945✔
3078

36✔
3079
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
36✔
3080
    // so try the old algorithm
36✔
3081
    GlobalKey object_id{primary_key};
2,147,483,683✔
3082
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
2,147,483,683✔
3083

36✔
3084
    // Check if existing
36✔
3085
    if (auto obj = m_clusters.try_get_obj(object_key)) {
2,147,483,683✔
3086
        auto existing_pk_value = obj.get_any(primary_key_col);
×
3087

3088
        if (existing_pk_value == primary_key) {
×
3089
            return object_key;
×
3090
        }
×
3091
    }
2,147,483,683✔
3092
    return {};
2,147,483,683✔
3093
}
2,147,483,683✔
3094

3095
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
3096
{
743,994✔
3097
    // Check if existing
373,668✔
3098
    if (auto key = find_primary_key(primary_key)) {
743,994✔
3099
        return key;
712,392✔
3100
    }
712,392✔
3101

15,996✔
3102
    // Object does not exist - create tombstone
15,996✔
3103
    GlobalKey object_id{primary_key};
31,602✔
3104
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
31,602✔
3105
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
31,602✔
3106
}
31,602✔
3107

3108
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
3109
{
18✔
3110
    REALM_ASSERT(!m_primary_key_col);
18✔
3111
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
3112

9✔
3113
    // Check if existing
9✔
3114
    if (m_clusters.is_valid(object_key)) {
18✔
3115
        return object_key;
12✔
3116
    }
12✔
3117

3✔
3118
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
3119
}
6✔
3120

3121
ObjKey Table::get_objkey(GlobalKey global_key) const
3122
{
18✔
3123
    ObjKey key;
18✔
3124
    REALM_ASSERT(!m_primary_key_col);
18✔
3125
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
3126
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
3127
        key = global_key.get_local_key(get_sync_file_id());
6✔
3128
    }
6✔
3129
    if (key && !is_valid(key)) {
18✔
3130
        key = realm::null_key;
6✔
3131
    }
6✔
3132
    return key;
18✔
3133
}
18✔
3134

3135
GlobalKey Table::get_object_id(ObjKey key) const
3136
{
144✔
3137
    auto col = get_primary_key_column();
144✔
3138
    if (col) {
144✔
3139
        const Obj obj = get_object(key);
24✔
3140
        auto val = obj.get_any(col);
24✔
3141
        return {val};
24✔
3142
    }
24✔
3143
    else {
120✔
3144
        return {key, get_sync_file_id()};
120✔
3145
    }
120✔
3146
    return {};
×
3147
}
×
3148

3149
Obj Table::get_object_with_primary_key(Mixed primary_key) const
3150
{
73,299✔
3151
    auto primary_key_col = get_primary_key_column();
73,299✔
3152
    REALM_ASSERT(primary_key_col);
73,299✔
3153
    DataType type = DataType(primary_key_col.get_type());
73,299✔
3154
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
73,299✔
3155
                 primary_key.get_type() == type);
73,299✔
3156
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
73,299✔
3157
    return k ? m_clusters.get(k) : Obj{};
73,203✔
3158
}
73,299✔
3159

3160
Mixed Table::get_primary_key(ObjKey key) const
3161
{
645,867✔
3162
    auto primary_key_col = get_primary_key_column();
645,867✔
3163
    REALM_ASSERT(primary_key_col);
645,867✔
3164
    if (key.is_unresolved()) {
645,867✔
3165
        REALM_ASSERT(m_tombstones);
360✔
3166
        return m_tombstones->get(key).get_any(primary_key_col);
360✔
3167
    }
360✔
3168
    else {
645,507✔
3169
        return m_clusters.get(key).get_any(primary_key_col);
645,507✔
3170
    }
645,507✔
3171
}
645,867✔
3172

3173
GlobalKey Table::allocate_object_id_squeezed()
3174
{
19,488,627✔
3175
    // m_client_file_ident will be zero if we haven't been in contact with
9,725,592✔
3176
    // the server yet.
9,725,592✔
3177
    auto peer_id = get_sync_file_id();
19,488,627✔
3178
    auto sequence = allocate_sequence_number();
19,488,627✔
3179
    return GlobalKey{peer_id, sequence};
19,488,627✔
3180
}
19,488,627✔
3181

3182
namespace {
3183

3184
/// Calculate optimistic local ID that may collide with others. It is up to
3185
/// the caller to ensure that collisions are detected and that
3186
/// allocate_local_id_after_collision() is called to obtain a non-colliding
3187
/// ID.
3188
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
3189
{
72,927✔
3190
#if REALM_EXERCISE_OBJECT_ID_COLLISION
3191
    const uint64_t optimistic_mask = 0xff;
3192
#else
3193
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
72,927✔
3194
#endif
72,927✔
3195
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
72,927✔
3196
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
72,927✔
3197
}
72,927✔
3198

3199
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
3200
{
12✔
3201
    REALM_ASSERT(!(sequence_number >> 62));
12✔
3202
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
3203
}
12✔
3204

3205
} // namespace
3206

3207
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
3208
{
72,927✔
3209
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
72,927✔
3210

36,444✔
3211
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
72,927✔
3212
        Allocator& alloc = m_top.get_alloc();
24✔
3213
        Array collision_map{alloc};
24✔
3214
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
3215

12✔
3216
        Array hi{alloc};
24✔
3217
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
3218

12✔
3219
        // Entries are ordered by hi,lo
12✔
3220
        size_t found = hi.find_first(object_id.hi());
24✔
3221
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
3222
            Array lo{alloc};
24✔
3223
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
3224
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
3225
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
3226
                Array local_id{alloc};
24✔
3227
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
3228
                return ObjKey{local_id.get(candidate)};
24✔
3229
            }
24✔
3230
        }
72,903✔
3231
    }
24✔
3232

36,432✔
3233
    return optimistic;
72,903✔
3234
}
72,903✔
3235

3236
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
3237
                                                     ObjKey colliding_local_id)
3238
{
12✔
3239
    // Possible optimization: Cache these accessors
6✔
3240
    Allocator& alloc = m_top.get_alloc();
12✔
3241
    Array collision_map{alloc};
12✔
3242
    Array hi{alloc};
12✔
3243
    Array lo{alloc};
12✔
3244
    Array local_id{alloc};
12✔
3245

6✔
3246
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
3247
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
3248
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
3249
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
3250

6✔
3251
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
3252
    if (collision_map_ref) {
12✔
3253
        collision_map.init_from_parent(); // Throws
×
3254
    }
×
3255
    else {
12✔
3256
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
3257
        collision_map.init_from_mem(mem);                                          // Throws
12✔
3258
        collision_map.update_parent();
12✔
3259

6✔
3260
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
3261
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
3262
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
3263
        collision_map.add(lo_ref);                                                                     // Throws
12✔
3264
        collision_map.add(hi_ref);                                                                     // Throws
12✔
3265
        collision_map.add(local_id_ref);                                                               // Throws
12✔
3266
    }
12✔
3267

6✔
3268
    hi.init_from_parent();       // Throws
12✔
3269
    lo.init_from_parent();       // Throws
12✔
3270
    local_id.init_from_parent(); // Throws
12✔
3271

6✔
3272
    size_t num_entries = hi.size();
12✔
3273
    REALM_ASSERT(lo.size() == num_entries);
12✔
3274
    REALM_ASSERT(local_id.size() == num_entries);
12✔
3275

6✔
3276
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
3277
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
3278
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
3279
            ++i;
6✔
3280
        return i;
24✔
3281
    };
24✔
3282

6✔
3283
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
3284
        size_t i = lower_bound_object_id(object_id);
24✔
3285
        if (i != num_entries) {
24✔
3286
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
3287
            if (existing == object_id) {
6✔
3288
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
3289
                return;
×
3290
            }
×
3291
        }
24✔
3292
        hi.insert(i, int64_t(object_id.hi()));
24✔
3293
        lo.insert(i, int64_t(object_id.lo()));
24✔
3294
        local_id.insert(i, new_local_id.value);
24✔
3295
        ++num_entries;
24✔
3296
    };
24✔
3297

6✔
3298
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
3299
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
3300
    insert_collision(incoming_id, new_local_id);
12✔
3301
    insert_collision(colliding_id, colliding_local_id);
12✔
3302

6✔
3303
    return new_local_id;
12✔
3304
}
12✔
3305

3306
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
3307
{
32,592✔
3308
    auto unres_key = key.get_unresolved();
32,592✔
3309

16,491✔
3310
    ensure_graveyard();
32,592✔
3311
    auto tombstone = m_tombstones->try_get_obj(unres_key);
32,592✔
3312
    if (tombstone) {
32,592✔
3313
        if (pk_col) {
3,192✔
3314
            auto existing_pk_value = tombstone.get_any(pk_col);
3,192✔
3315
            // It may just be the same object
1,584✔
3316
            if (existing_pk_value != pk_val) {
3,192✔
3317
                // We have a collision - create new ObjKey
6✔
3318
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
3319
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
3320
            }
12✔
3321
        }
3,180✔
3322
        return tombstone;
3,180✔
3323
    }
3,180✔
3324
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
29,400✔
3325
}
29,400✔
3326

3327
void Table::free_local_id_after_hash_collision(ObjKey key)
3328
{
5,031,813✔
3329
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,031,813✔
3330
        if (key.is_unresolved()) {
30✔
3331
            // Keys will always be inserted as resolved
12✔
3332
            key = key.get_unresolved();
24✔
3333
        }
24✔
3334
        // Possible optimization: Cache these accessors
15✔
3335
        Array collision_map{m_alloc};
30✔
3336
        Array local_id{m_alloc};
30✔
3337

15✔
3338
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
3339
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
3340
        collision_map.init_from_ref(collision_map_ref);
30✔
3341
        local_id.init_from_parent();
30✔
3342
        auto ndx = local_id.find_first(key.value);
30✔
3343
        if (ndx != realm::npos) {
30✔
3344
            Array hi{m_alloc};
24✔
3345
            Array lo{m_alloc};
24✔
3346

12✔
3347
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
3348
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
3349
            hi.init_from_parent();
24✔
3350
            lo.init_from_parent();
24✔
3351

12✔
3352
            hi.erase(ndx);
24✔
3353
            lo.erase(ndx);
24✔
3354
            local_id.erase(ndx);
24✔
3355
            if (hi.size() == 0) {
24✔
3356
                free_collision_table();
12✔
3357
            }
12✔
3358
        }
24✔
3359
    }
30✔
3360
}
5,031,813✔
3361

3362
void Table::free_collision_table()
3363
{
4,326✔
3364
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
4,326✔
3365
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
3366
        m_top.set(top_position_for_collision_map, 0);
12✔
3367
    }
12✔
3368
}
4,326✔
3369

3370
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
3371
{
3,876✔
3372
    while (number--) {
6,324,645✔
3373
        keys.push_back(create_object().get_key());
6,320,769✔
3374
    }
6,320,769✔
3375
}
3,876✔
3376

3377
void Table::create_objects(const std::vector<ObjKey>& keys)
3378
{
630✔
3379
    for (auto k : keys) {
5,616✔
3380
        create_object(k);
5,616✔
3381
    }
5,616✔
3382
}
630✔
3383

3384
void Table::dump_objects()
3385
{
×
3386
    m_clusters.dump_objects();
×
3387
    if (nb_unresolved())
×
3388
        m_tombstones->dump_objects();
×
3389
}
×
3390

3391
void Table::remove_object(ObjKey key)
3392
{
2,493,540✔
3393
    Group* g = get_parent_group();
2,493,540✔
3394

1,246,956✔
3395
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,493,540✔
3396
        CascadeState state(CascadeState::Mode::Strong, g);
2,643✔
3397
        state.m_to_be_deleted.emplace_back(m_key, key);
2,643✔
3398
        m_clusters.nullify_incoming_links(key, state);
2,643✔
3399
        remove_recursive(state);
2,643✔
3400
    }
2,643✔
3401
    else {
2,490,897✔
3402
        CascadeState state(CascadeState::Mode::None, g);
2,490,897✔
3403
        if (g) {
2,490,897✔
3404
            m_clusters.nullify_incoming_links(key, state);
2,408,889✔
3405
        }
2,408,889✔
3406
        m_clusters.erase(key, state);
2,490,897✔
3407
    }
2,490,897✔
3408
}
2,493,540✔
3409

3410
ObjKey Table::invalidate_object(ObjKey key)
3411
{
64,968✔
3412
    if (is_embedded())
64,968✔
3413
        throw IllegalOperation("Deletion of embedded object not allowed");
×
3414
    REALM_ASSERT(!key.is_unresolved());
64,968✔
3415

32,985✔
3416
    Obj tombstone;
64,968✔
3417
    auto obj = get_object(key);
64,968✔
3418
    if (obj.has_backlinks(false)) {
64,968✔
3419
        // If the object has backlinks, we should make a tombstone
483✔
3420
        // and make inward links point to it,
483✔
3421
        if (auto primary_key_col = get_primary_key_column()) {
966✔
3422
            auto pk = obj.get_any(primary_key_col);
810✔
3423
            GlobalKey object_id{pk};
810✔
3424
            auto unres_key = global_to_local_object_id_hashed(object_id);
810✔
3425
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
810✔
3426
        }
810✔
3427
        else {
156✔
3428
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
3429
        }
156✔
3430
        tombstone.assign_pk_and_backlinks(obj);
966✔
3431
    }
966✔
3432

32,985✔
3433
    remove_object(key);
64,968✔
3434

32,985✔
3435
    return tombstone.get_key();
64,968✔
3436
}
64,968✔
3437

3438
void Table::remove_object_recursive(ObjKey key)
3439
{
42✔
3440
    size_t table_ndx = get_index_in_group();
42✔
3441
    if (table_ndx != realm::npos) {
42✔
3442
        CascadeState state(CascadeState::Mode::All, get_parent_group());
42✔
3443
        state.m_to_be_deleted.emplace_back(m_key, key);
42✔
3444
        nullify_links(state);
42✔
3445
        remove_recursive(state);
42✔
3446
    }
42✔
3447
    else {
×
3448
        // No links in freestanding table
3449
        CascadeState state(CascadeState::Mode::None);
×
3450
        m_clusters.erase(key, state);
×
3451
    }
×
3452
}
42✔
3453

3454
Table::Iterator Table::begin() const
3455
{
729,582✔
3456
    return Iterator(m_clusters, 0);
729,582✔
3457
}
729,582✔
3458

3459
Table::Iterator Table::end() const
3460
{
9,640,989✔
3461
    return Iterator(m_clusters, size());
9,640,989✔
3462
}
9,640,989✔
3463

3464
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
3465
{
7,091,844✔
3466
    TableRef ret;
7,091,844✔
3467
    if (col_key) {
7,093,272✔
3468
        return table.get_opposite_table(col_key);
7,093,272✔
3469
    }
7,093,272✔
3470
    return ret;
4,294,967,294✔
3471
}
4,294,967,294✔
3472

3473
const uint64_t Table::max_num_columns;
3474

3475
void Table::build_column_mapping()
3476
{
6,398,469✔
3477
    // build column mapping from spec
3,456,084✔
3478
    // TODO: Optimization - Don't rebuild this for every change
3,456,084✔
3479
    m_spec_ndx2leaf_ndx.clear();
6,398,469✔
3480
    m_leaf_ndx2spec_ndx.clear();
6,398,469✔
3481
    m_leaf_ndx2colkey.clear();
6,398,469✔
3482
    size_t num_spec_cols = m_spec.get_column_count();
6,398,469✔
3483
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
6,398,469✔
3484
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
28,042,548✔
3485
        ColKey col_key = m_spec.get_key(spec_ndx);
21,644,079✔
3486
        unsigned leaf_ndx = col_key.get_index().val;
21,644,079✔
3487
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
21,644,079✔
3488
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
21,173,940✔
3489
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
21,173,940✔
3490
        }
21,173,940✔
3491
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
21,644,079✔
3492
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
21,644,079✔
3493
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
21,644,079✔
3494
    }
21,644,079✔
3495
}
6,398,469✔
3496

3497
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
3498
{
1,022,337✔
3499
    REALM_ASSERT(!attr.test(col_attr_Indexed));
1,022,337✔
3500
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
1,022,337✔
3501

504,540✔
3502
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
1,022,337✔
3503
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
1,022,337✔
3504

504,540✔
3505
    // reuse lowest available leaf ndx:
504,540✔
3506
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
1,022,337✔
3507
    // look for an unused entry:
504,540✔
3508
    for (unsigned idx = 0; idx < lower; ++idx) {
6,469,551✔
3509
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
5,447,298✔
3510
            lower = idx;
84✔
3511
            break;
84✔
3512
        }
84✔
3513
    }
5,447,298✔
3514
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
1,022,337✔
3515
}
1,022,337✔
3516

3517
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
3518
                                                  StringData origin_col_name) const noexcept
3519
{
×
3520
    BacklinkOrigin ret;
×
3521
    auto f = [&](ColKey backlink_col_key) {
×
3522
        auto origin_table = get_opposite_table(backlink_col_key);
×
3523
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
3524
        if (origin_table->get_name() == origin_table_name &&
×
3525
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
3526
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
3527
            return IteratorControl::Stop;
×
3528
        }
×
3529
        return IteratorControl::AdvanceToNext;
×
3530
    };
×
3531
    this->for_each_backlink_column(f);
×
3532
    return ret;
×
3533
}
×
3534

3535
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
3536
{
360✔
3537
    try {
360✔
3538
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
360✔
3539
        ColKey linked_column_key = get_opposite_column(backlink_col);
360✔
3540
        if (linked_table_key == m_key) {
360✔
3541
            return {{m_own_ref, linked_column_key}};
24✔
3542
        }
24✔
3543
        else {
336✔
3544
            Group* current_group = get_parent_group();
336✔
3545
            if (current_group) {
336✔
3546
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
3547
                return {{linked_table_ref, linked_column_key}};
336✔
3548
            }
336✔
3549
        }
×
3550
    }
360✔
3551
    catch (...) {
×
3552
        // backlink column not found, returning empty optional
3553
    }
×
3554
    return {};
180✔
3555
}
360✔
3556

3557
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
3558
{
×
3559
    std::vector<std::pair<TableKey, ColKey>> origins;
×
3560
    auto f = [&](ColKey backlink_col_key) {
×
3561
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
3562
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
3563
        origins.emplace_back(origin_table_key, origin_link_col);
×
3564
        return IteratorControl::AdvanceToNext;
×
3565
    };
×
3566
    this->for_each_backlink_column(f);
×
3567
    return origins;
×
3568
}
×
3569

3570
ColKey Table::get_primary_key_column() const
3571
{
19,981,086✔
3572
    return m_primary_key_col;
19,981,086✔
3573
}
19,981,086✔
3574

3575
void Table::set_primary_key_column(ColKey col_key)
3576
{
720✔
3577
    if (col_key == m_primary_key_col) {
720✔
3578
        return;
378✔
3579
    }
378✔
3580

171✔
3581
    if (Replication* repl = get_repl()) {
342✔
3582
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
3583
            throw RuntimeError(
×
3584
                ErrorCodes::BrokenInvariant,
×
3585
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
3586
        }
×
3587
    }
342✔
3588

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

171✔
3591
    if (col_key) {
342✔
3592
        check_column(col_key);
222✔
3593
        validate_column_is_unique(col_key);
222✔
3594
        do_set_primary_key_column(col_key);
222✔
3595
    }
222✔
3596
    else {
120✔
3597
        do_set_primary_key_column(col_key);
120✔
3598
    }
120✔
3599
}
342✔
3600

3601

3602
void Table::do_set_primary_key_column(ColKey col_key)
3603
{
144,798✔
3604
    if (col_key) {
144,798✔
3605
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
136,653✔
3606
        auto attr = m_spec.get_column_attr(spec_ndx);
136,653✔
3607
        if (attr.test(col_attr_FullText_Indexed)) {
136,653✔
3608
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
3609
        }
6✔
3610
    }
144,792✔
3611

71,601✔
3612
    if (m_primary_key_col) {
144,792✔
3613
        // If the search index has not been set explicitly on current pk col, we remove it again
3,972✔
3614
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
8,049✔
3615
        auto attr = m_spec.get_column_attr(spec_ndx);
8,049✔
3616
        if (!attr.test(col_attr_Indexed)) {
8,049✔
3617
            remove_search_index(m_primary_key_col);
8,037✔
3618
        }
8,037✔
3619
    }
8,049✔
3620

71,601✔
3621
    if (col_key) {
144,792✔
3622
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
136,647✔
3623
        do_add_search_index(col_key, IndexType::General);
136,647✔
3624
    }
136,647✔
3625
    else {
8,145✔
3626
        m_top.set(top_position_for_pk_col, 0);
8,145✔
3627
    }
8,145✔
3628

71,601✔
3629
    m_primary_key_col = col_key;
144,792✔
3630
}
144,792✔
3631

3632
bool Table::contains_unique_values(ColKey col) const
3633
{
834✔
3634
    if (search_index_type(col) == IndexType::General) {
834✔
3635
        auto search_index = get_search_index(col);
744✔
3636
        return !search_index->has_duplicate_values();
744✔
3637
    }
744✔
3638
    else {
90✔
3639
        TableView tv = where().find_all();
90✔
3640
        tv.distinct(col);
90✔
3641
        return tv.size() == size();
90✔
3642
    }
90✔
3643
}
834✔
3644

3645
void Table::validate_column_is_unique(ColKey col) const
3646
{
834✔
3647
    if (!contains_unique_values(col)) {
834✔
3648
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
3649
                                           get_class_name(), get_column_name(col)));
30✔
3650
    }
30✔
3651
}
834✔
3652

3653
void Table::validate_primary_column()
3654
{
1,812✔
3655
    if (ColKey col = get_primary_key_column()) {
1,812✔
3656
        validate_column_is_unique(col);
612✔
3657
    }
612✔
3658
}
1,812✔
3659

3660
ObjKey Table::get_next_valid_key()
3661
{
518,418✔
3662
    ObjKey key;
518,418✔
3663
    do {
518,424✔
3664
        key = ObjKey(allocate_sequence_number());
518,424✔
3665
    } while (m_clusters.is_valid(key));
518,424✔
3666

248,418✔
3667
    return key;
518,418✔
3668
}
518,418✔
3669

3670
namespace {
3671
template <class T>
3672
typename util::RemoveOptional<T>::type remove_optional(T val)
3673
{
87,939✔
3674
    return val;
87,939✔
3675
}
87,939✔
3676
template <>
3677
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
3678
{
5,472✔
3679
    return *val;
5,472✔
3680
}
5,472✔
3681
template <>
3682
bool remove_optional<Optional<bool>>(Optional<bool> val)
3683
{
11,460✔
3684
    return *val;
11,460✔
3685
}
11,460✔
3686
template <>
3687
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
3688
{
5,475✔
3689
    return *val;
5,475✔
3690
}
5,475✔
3691
template <>
3692
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
3693
{
6,060✔
3694
    return *val;
6,060✔
3695
}
6,060✔
3696
} // namespace
3697

3698
template <class F, class T>
3699
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
3700
{
162✔
3701
    Allocator& allocator = this->get_alloc();
162✔
3702
    bool from_nullability = is_nullable(key_from);
162✔
3703
    auto func = [&](Cluster* cluster) {
162✔
3704
        size_t sz = cluster->node_size();
162✔
3705

81✔
3706
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
3707
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
3708
        cluster->init_leaf(key_from, &from_arr);
162✔
3709
        cluster->init_leaf(key_to, &to_arr);
162✔
3710

81✔
3711
        for (size_t i = 0; i < sz; i++) {
1,512✔
3712
            if (from_nullability && from_arr.is_null(i)) {
1,356!
3713
                if (throw_on_null) {
75!
3714
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
3715
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
3716
                                                    get_column_name(key_from)));
6✔
3717
                }
6✔
3718
                else {
69✔
3719
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
69✔
3720
                }
69✔
3721
            }
75✔
3722
            else {
1,281✔
3723
                auto v = remove_optional(from_arr.get(i));
1,281✔
3724
                to_arr.set(i, v);
1,281✔
3725
            }
1,281✔
3726
        }
1,356✔
3727
    };
162✔
3728

81✔
3729
    m_clusters.update(func);
162✔
3730
}
162✔
3731

3732
template <class F, class T>
3733
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
3734
{
120✔
3735
    Allocator& allocator = this->get_alloc();
120✔
3736
    bool from_nullability = is_nullable(key_from);
120✔
3737
    auto func = [&](Cluster* cluster) {
120✔
3738
        size_t sz = cluster->node_size();
120✔
3739

60✔
3740
        ArrayInteger from_arr(allocator);
120✔
3741
        ArrayInteger to_arr(allocator);
120✔
3742
        cluster->init_leaf(key_from, &from_arr);
120✔
3743
        cluster->init_leaf(key_to, &to_arr);
120✔
3744

60✔
3745
        for (size_t i = 0; i < sz; i++) {
360✔
3746
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3747
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3748
            REALM_ASSERT(!ref_to);
240✔
3749

120✔
3750
            if (ref_from) {
240✔
3751
                BPlusTree<F> from_list(allocator);
120✔
3752
                BPlusTree<T> to_list(allocator);
120✔
3753
                from_list.init_from_ref(ref_from);
120✔
3754
                to_list.create();
120✔
3755
                size_t n = from_list.size();
120✔
3756
                for (size_t j = 0; j < n; j++) {
120,120✔
3757
                    auto v = from_list.get(j);
120,000✔
3758
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
3759
                        to_list.add(remove_optional(v));
115,125✔
3760
                    }
115,125✔
3761
                    else {
4,875✔
3762
                        if (throw_on_null) {
4,875!
3763
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3764
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3765
                                                            get_name(), get_column_name(key_from)));
×
3766
                        }
×
3767
                        else {
4,875✔
3768
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,875✔
3769
                        }
4,875✔
3770
                    }
4,875✔
3771
                }
120,000✔
3772
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3773
            }
120✔
3774
        }
240✔
3775
    };
120✔
3776

60✔
3777
    m_clusters.update(func);
120✔
3778
}
120✔
3779

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

3901

3902
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3903
{
522✔
3904
    if (col_key.is_nullable() == nullable)
522✔
3905
        return col_key;
240✔
3906

141✔
3907
    check_column(col_key);
282✔
3908

141✔
3909
    auto index_type = search_index_type(col_key);
282✔
3910
    std::string column_name(get_column_name(col_key));
282✔
3911
    auto type = col_key.get_type();
282✔
3912
    auto attr = col_key.get_attrs();
282✔
3913
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3914
    if (nullable) {
282✔
3915
        attr.set(col_attr_Nullable);
150✔
3916
    }
150✔
3917
    else {
132✔
3918
        attr.reset(col_attr_Nullable);
132✔
3919
    }
132✔
3920

141✔
3921
    ColKey new_col = generate_col_key(type, attr);
282✔
3922
    do_insert_root_column(new_col, type, "__temporary");
282✔
3923

141✔
3924
    try {
282✔
3925
        convert_column(col_key, new_col, throw_on_null);
282✔
3926
    }
282✔
3927
    catch (...) {
144✔
3928
        // remove any partially filled column
3✔
3929
        remove_column(new_col);
6✔
3930
        throw;
6✔
3931
    }
6✔
3932

138✔
3933
    if (is_pk_col) {
276✔
3934
        // If we go from non nullable to nullable, no values change,
6✔
3935
        // so it is safe to preserve the pk column. Otherwise it is not
6✔
3936
        // safe as a null entry might have been converted to default value.
6✔
3937
        do_set_primary_key_column(nullable ? new_col : ColKey{});
9✔
3938
    }
12✔
3939

138✔
3940
    erase_root_column(col_key);
276✔
3941
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3942

138✔
3943
    if (index_type != IndexType::None)
276✔
3944
        do_add_search_index(new_col, index_type);
30✔
3945

138✔
3946
    return new_col;
276✔
3947
}
276✔
3948

3949
bool Table::has_any_embedded_objects()
3950
{
2,499,084✔
3951
    if (!m_has_any_embedded_objects) {
2,499,084✔
3952
        m_has_any_embedded_objects = false;
113,343✔
3953
        for_each_public_column([&](ColKey col_key) {
229,782✔
3954
            auto target_table_key = get_opposite_table_key(col_key);
229,782✔
3955
            if (target_table_key && is_link_type(col_key.get_type())) {
229,782✔
3956
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
9,510✔
3957
                if (target_table->is_embedded()) {
9,510✔
3958
                    m_has_any_embedded_objects = true;
7,392✔
3959
                    return IteratorControl::Stop; // early out
7,392✔
3960
                }
7,392✔
3961
            }
222,390✔
3962
            return IteratorControl::AdvanceToNext;
222,390✔
3963
        });
222,390✔
3964
    }
113,343✔
3965
    return *m_has_any_embedded_objects;
2,499,084✔
3966
}
2,499,084✔
3967

3968
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3969
{
174,690✔
3970
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
174,690✔
3971
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
174,690✔
3972
}
174,690✔
3973

3974
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3975
{
46,926✔
3976
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
161,187✔
3977
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
153,507✔
3978
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
39,246✔
3979
        }
39,246✔
3980
    }
153,507✔
3981

23,346✔
3982
    return {};
27,186✔
3983
}
46,926✔
3984

3985
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3986
{
46,278✔
3987
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
46,278✔
3988

23,022✔
3989
    if (!backlink_col_key) {
46,278✔
3990
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
7,680✔
3991
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
7,680✔
3992

3,840✔
3993
        if (Replication* repl = get_repl())
7,680✔
3994
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
7,350✔
3995
                                    m_key); // Throws
7,350✔
3996
    }
7,680✔
3997

23,022✔
3998
    return backlink_col_key;
46,278✔
3999
}
46,278✔
4000

4001
TableKey Table::get_opposite_table_key(ColKey col_key) const
4002
{
14,536,338✔
4003
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,536,338✔
4004
}
14,536,338✔
4005

4006
bool Table::links_to_self(ColKey col_key) const
4007
{
58,608✔
4008
    return get_opposite_table_key(col_key) == m_key;
58,608✔
4009
}
58,608✔
4010

4011
TableRef Table::get_opposite_table(ColKey col_key) const
4012
{
7,737,486✔
4013
    if (auto k = get_opposite_table_key(col_key)) {
7,737,486✔
4014
        return get_parent_group()->get_table(k);
7,681,566✔
4015
    }
7,681,566✔
4016
    return {};
55,920✔
4017
}
55,920✔
4018

4019
ColKey Table::get_opposite_column(ColKey col_key) const
4020
{
7,059,798✔
4021
    return ColKey(m_opposite_column.get(col_key.get_index().val));
7,059,798✔
4022
}
7,059,798✔
4023

4024
ColKey Table::find_opposite_column(ColKey col_key) const
4025
{
×
4026
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
4027
        if (m_opposite_column.get(i) == col_key.value) {
×
4028
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
4029
        }
×
4030
    }
×
4031
    return ColKey();
×
4032
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc