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

realm / realm-core / 2085

01 Mar 2024 12:26PM UTC coverage: 90.926% (-0.001%) from 90.927%
2085

push

Evergreen

jedelbo
Avoid doing unneeded logger work in Replication

Most of the replication log statements do some work including memory
allocations which are then thrown away if the log level it too high, so always
check the log level first. A few places don't actually benefit from this, but
it's easier to consistently check the log level every time.

93986 of 173116 branches covered (54.29%)

63 of 100 new or added lines in 2 files covered. (63.0%)

114 existing lines in 17 files now uncovered.

238379 of 262169 relevant lines covered (90.93%)

6007877.32 hits per line

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

90.81
/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
{
18,609✔
268
    if (size() != other.size())
18,609✔
269
        return false;
×
270
    size_t sz = size();
18,609✔
271
    for (size_t i = 0; i < sz; i++) {
29,184✔
272
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
18,741✔
273
        if (this->at(i).second != other.at(i).second)
18,741✔
274
            return false;
8,166✔
275
    }
18,741✔
276
    return true;
14,496✔
277
}
18,609✔
278

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

322
std::ostream& operator<<(std::ostream& o, Table::Type table_type)
323
{
270✔
324
    switch (table_type) {
270✔
325
        case Table::Type::TopLevel:
138✔
326
            return o << "TopLevel";
138✔
327
        case Table::Type::Embedded:
132✔
328
            return o << "Embedded";
132✔
UNCOV
329
        case Table::Type::TopLevelAsymmetric:
✔
UNCOV
330
            return o << "TopLevelAsymmetric";
×
331
    }
×
332
    return o << "Invalid table type: " << uint8_t(table_type);
×
333
}
×
334
} // namespace realm
335

336
bool LinkChain::add(ColKey ck)
337
{
804,039✔
338
    // Link column can be a single Link, LinkList, or BackLink.
402,036✔
339
    REALM_ASSERT(m_current_table->valid_column(ck));
804,039✔
340
    ColumnType type = ck.get_type();
804,039✔
341
    if (type == col_type_Link || type == col_type_BackLink) {
804,039✔
342
        m_current_table = m_current_table->get_opposite_table(ck);
88,167✔
343
        m_link_cols.push_back(ck);
88,167✔
344
        return true;
88,167✔
345
    }
88,167✔
346
    return false;
715,872✔
347
}
715,872✔
348

349
// -- Table ---------------------------------------------------------------------------------
350

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

1,776✔
367
    ref_type ref = create_empty_table(m_alloc); // Throws
3,552✔
368
    ArrayParent* parent = nullptr;
3,552✔
369
    size_t ndx_in_parent = 0;
3,552✔
370
    init(ref, parent, ndx_in_parent, true, false);
3,552✔
371
}
3,552✔
372

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

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

227,070✔
399
    ColumnAttrMask attr;
460,476✔
400
    if (collection_type) {
460,476✔
401
        switch (*collection_type) {
148,101✔
402
            case CollectionType::List:
60,870✔
403
                attr.set(col_attr_List);
60,870✔
404
                break;
60,870✔
405
            case CollectionType::Set:
43,479✔
406
                attr.set(col_attr_Set);
43,479✔
407
                break;
43,479✔
408
            case CollectionType::Dictionary:
43,752✔
409
                attr.set(col_attr_Dictionary);
43,752✔
410
                break;
43,752✔
411
        }
460,476✔
412
    }
460,476✔
413
    if (nullable || type == type_Mixed)
460,476✔
414
        attr.set(col_attr_Nullable);
153,699✔
415
    ColKey col_key = generate_col_key(ColumnType(type), attr);
460,476✔
416

227,070✔
417
    Table* invalid_link = nullptr;
460,476✔
418
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
460,476✔
419
}
460,476✔
420

421
ColKey Table::add_column(Table& target, StringData name, std::optional<CollectionType> collection_type,
422
                         DataType key_type)
423
{
73,425✔
424
    // Both origin and target must be group-level tables, and in the same group.
36,294✔
425
    Group* origin_group = get_parent_group();
73,425✔
426
    Group* target_group = target.get_parent_group();
73,425✔
427
    REALM_ASSERT_RELEASE(origin_group && target_group);
73,425✔
428
    REALM_ASSERT_RELEASE(origin_group == target_group);
73,425✔
429
    // Links to an asymmetric table are not allowed.
36,294✔
430
    if (target.is_asymmetric()) {
73,425✔
431
        throw IllegalOperation("Ephemeral objects not supported");
6✔
432
    }
6✔
433

36,291✔
434
    m_has_any_embedded_objects.reset();
73,419✔
435

36,291✔
436
    DataType data_type = type_Link;
73,419✔
437
    ColumnAttrMask attr;
73,419✔
438
    if (collection_type) {
73,419✔
439
        switch (*collection_type) {
43,245✔
440
            case CollectionType::List:
21,183✔
441
                attr.set(col_attr_List);
21,183✔
442
                break;
21,183✔
443
            case CollectionType::Set:
10,728✔
444
                if (target.is_embedded())
10,728✔
445
                    throw IllegalOperation("Set of embedded objects not supported");
×
446
                attr.set(col_attr_Set);
10,728✔
447
                break;
10,728✔
448
            case CollectionType::Dictionary:
11,334✔
449
                attr.set(col_attr_Dictionary);
11,334✔
450
                attr.set(col_attr_Nullable);
11,334✔
451
                break;
11,334✔
452
        }
30,174✔
453
    }
30,174✔
454
    else {
30,174✔
455
        attr.set(col_attr_Nullable);
30,174✔
456
    }
30,174✔
457
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
73,419✔
458

36,291✔
459
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
73,419✔
460
}
73,419✔
461

462
void Table::remove_recursive(CascadeState& cascade_state)
463
{
16,248✔
464
    Group* group = get_parent_group();
16,248✔
465
    REALM_ASSERT(group);
16,248✔
466
    cascade_state.m_group = group;
16,248✔
467

7,464✔
468
    do {
20,679✔
469
        cascade_state.send_notifications();
20,679✔
470

9,678✔
471
        for (auto& l : cascade_state.m_to_be_nullified) {
9,702✔
472
            Obj obj = group->get_table_unchecked(l.origin_table)->try_get_object(l.origin_key);
48✔
473
            REALM_ASSERT_DEBUG(obj);
48✔
474
            if (obj) {
48✔
475
                std::move(obj).nullify_link(l.origin_col_key, l.old_target_link);
48✔
476
            }
48✔
477
        }
48✔
478
        cascade_state.m_to_be_nullified.clear();
20,679✔
479

9,678✔
480
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
20,679✔
481
        for (auto obj : to_delete) {
17,682✔
482
            auto table = obj.first == m_key ? this : group->get_table_unchecked(obj.first);
13,425✔
483
            // This might add to the list of objects that should be deleted
7,971✔
484
            REALM_ASSERT(!obj.second.is_unresolved());
15,975✔
485
            table->m_clusters.erase(obj.second, cascade_state);
15,975✔
486
        }
15,975✔
487
        nullify_links(cascade_state);
20,679✔
488
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
20,679✔
489
}
16,248✔
490

491
void Table::nullify_links(CascadeState& cascade_state)
492
{
26,223✔
493
    Group* group = get_parent_group();
26,223✔
494
    REALM_ASSERT(group);
26,223✔
495
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
16,503✔
496
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
7,206✔
497
        if (!table->is_asymmetric())
8,088✔
498
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
8,088✔
499
    }
8,088✔
500
}
26,223✔
501

502
CollectionType Table::get_collection_type(ColKey col_key) const
503
{
9,132✔
504
    if (col_key.is_list()) {
9,132✔
505
        return CollectionType::List;
648✔
506
    }
648✔
507
    if (col_key.is_set()) {
8,484✔
508
        return CollectionType::Set;
1,056✔
509
    }
1,056✔
510
    REALM_ASSERT(col_key.is_dictionary());
7,428✔
511
    return CollectionType::Dictionary;
7,428✔
512
}
7,428✔
513

514
void Table::remove_column(ColKey col_key)
515
{
1,686✔
516
    check_column(col_key);
1,686✔
517

819✔
518
    if (Replication* repl = get_repl())
1,686✔
519
        repl->erase_column(this, col_key); // Throws
528✔
520

819✔
521
    if (col_key == m_primary_key_col) {
1,686✔
522
        do_set_primary_key_column(ColKey());
390✔
523
    }
390✔
524
    else {
1,296✔
525
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
1,296✔
526
    }
1,296✔
527

819✔
528
    erase_root_column(col_key); // Throws
1,686✔
529
    m_has_any_embedded_objects.reset();
1,686✔
530
}
1,686✔
531

532

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

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

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

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

547

548
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
549
{
2,213,013✔
550
    // well, not quite "direct", more like "almost direct":
1,354,386✔
551
    Array table_top(alloc);
2,213,013✔
552
    table_top.init_from_ref(top_ref);
2,213,013✔
553
    if (table_top.size() > 3) {
2,213,013✔
554
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
2,212,605✔
555
        return TableKey(int32_t(rot.get_as_int()));
2,212,605✔
556
    }
2,212,605✔
557
    else {
408✔
558
        return TableKey();
408✔
559
    }
408✔
560
}
2,213,013✔
561

562

563
void Table::init(ref_type top_ref, ArrayParent* parent, size_t ndx_in_parent, bool is_writable, bool is_frzn)
564
{
1,784,292✔
565
    REALM_ASSERT(!(is_writable && is_frzn));
1,784,292✔
566
    m_is_frozen = is_frzn;
1,784,292✔
567
    m_alloc.set_read_only(!is_writable);
1,784,292✔
568
    // Load from allocated memory
1,140,435✔
569
    m_top.set_parent(parent, ndx_in_parent);
1,784,292✔
570
    m_top.init_from_ref(top_ref);
1,784,292✔
571

1,140,435✔
572
    m_spec.init_from_parent();
1,784,292✔
573

1,140,435✔
574
    while (m_top.size() <= top_position_for_pk_col) {
1,784,292✔
575
        m_top.add(0);
×
576
    }
×
577

1,140,435✔
578
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
1,784,292✔
579
        // This is an upgrade - create cluster
580
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
×
581
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
×
582
    }
×
583
    m_clusters.init_from_parent();
1,784,292✔
584

1,140,435✔
585
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
1,784,292✔
586
    if (!rot.is_tagged()) {
1,784,292✔
587
        // Create table key
588
        rot = RefOrTagged::make_tagged(ndx_in_parent);
×
589
        m_top.set(top_position_for_key, rot);
×
590
    }
×
591
    m_key = TableKey(int32_t(rot.get_as_int()));
1,784,292✔
592

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

1,140,435✔
626
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
1,784,292✔
627
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
1,532,589✔
628

1,140,435✔
629
    if (m_top.size() <= top_position_for_flags) {
1,784,292✔
630
        m_table_type = Type::TopLevel;
60✔
631
    }
60✔
632
    else {
1,784,232✔
633
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
1,784,232✔
634
        m_table_type = Type(flags & table_type_mask);
1,784,232✔
635
    }
1,784,232✔
636
    m_has_any_embedded_objects.reset();
1,784,292✔
637

1,140,435✔
638
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
1,784,292✔
639
        // Tombstones exists
15,894✔
640
        if (!m_tombstones) {
30,351✔
641
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
23,757✔
642
        }
23,757✔
643
        m_tombstones->init_from_parent();
30,351✔
644
    }
30,351✔
645
    else {
1,753,941✔
646
        m_tombstones = nullptr;
1,753,941✔
647
    }
1,753,941✔
648
    m_cookie = cookie_initialized;
1,784,292✔
649
}
1,784,292✔
650

651

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

263,361✔
656
    // When the inserted column is a link-type column, we must also add a
263,361✔
657
    // backlink column to the target table.
263,361✔
658

263,361✔
659
    if (target_table) {
533,895✔
660
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
73,413✔
661
        target_table->check_column(backlink_col_key);
73,413✔
662

36,288✔
663
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
73,413✔
664
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
73,413✔
665
    }
73,413✔
666

263,361✔
667
    if (Replication* repl = get_repl())
533,895✔
668
        repl->insert_column(this, col_key, type, name, target_table); // Throws
516,183✔
669

263,361✔
670
    return col_key;
533,895✔
671
}
533,895✔
672

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

48,003✔
679
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
101,943✔
680
        cluster->init_leaf(col_key, &leaf);
101,943✔
681
        index->insert_bulk(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
101,943✔
682
        return IteratorControl::AdvanceToNext;
101,943✔
683
    };
101,943✔
684

48,003✔
685
    table->traverse_clusters(f);
96,855✔
686
}
96,855✔
687

688

689
static void do_bulk_insert_index_list(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
690
{
24✔
691
    ArrayInteger leaf(alloc);
24✔
692

12✔
693
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
258✔
694
        cluster->init_leaf(col_key, &leaf);
258✔
695
        index->insert_bulk_list(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
258✔
696
        return IteratorControl::AdvanceToNext;
258✔
697
    };
258✔
698

12✔
699
    table->traverse_clusters(f);
24✔
700
}
24✔
701

702
void Table::populate_search_index(ColKey col_key)
703
{
96,879✔
704
    auto col_ndx = col_key.get_index().val;
96,879✔
705
    SearchIndex* index = m_index_accessors[col_ndx].get();
96,879✔
706
    DataType type = get_column_type(col_key);
96,879✔
707

48,015✔
708
    if (type == type_Int) {
96,879✔
709
        if (is_nullable(col_key)) {
47,679✔
710
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
13,773✔
711
        }
13,773✔
712
        else {
33,906✔
713
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
33,906✔
714
        }
33,906✔
715
    }
47,679✔
716
    else if (type == type_Bool) {
49,200✔
717
        if (is_nullable(col_key)) {
51✔
718
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
719
        }
24✔
720
        else {
27✔
721
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
27✔
722
        }
27✔
723
    }
51✔
724
    else if (type == type_String) {
49,149✔
725
        if (col_key.is_list()) {
7,494✔
726
            do_bulk_insert_index_list(this, index, col_key, get_alloc());
24✔
727
        }
24✔
728
        else {
7,470✔
729
            do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
7,470✔
730
        }
7,470✔
731
    }
7,494✔
732
    else if (type == type_Timestamp) {
41,655✔
733
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
90✔
734
    }
90✔
735
    else if (type == type_ObjectId) {
41,565✔
736
        if (is_nullable(col_key)) {
39,987✔
737
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
972✔
738
        }
972✔
739
        else {
39,015✔
740
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
39,015✔
741
        }
39,015✔
742
    }
39,987✔
743
    else if (type == type_UUID) {
1,578✔
744
        if (is_nullable(col_key)) {
678✔
745
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
746
        }
516✔
747
        else {
162✔
748
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
162✔
749
        }
162✔
750
    }
678✔
751
    else if (type == type_Mixed) {
900✔
752
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
900✔
753
    }
900✔
754
    else {
×
755
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
756
    }
×
757
}
96,879✔
758

759
void Table::erase_from_search_indexes(ObjKey key)
760
{
4,999,593✔
761
    // Tombstones do not use index - will crash if we try to erase values
2,499,585✔
762
    if (!key.is_unresolved()) {
4,999,593✔
763
        for (auto&& index : m_index_accessors) {
6,662,976✔
764
            if (index) {
6,662,976✔
765
                index->erase(key);
255,348✔
766
            }
255,348✔
767
        }
6,662,976✔
768
    }
4,988,625✔
769
}
4,999,593✔
770

771
void Table::update_indexes(ObjKey key, const FieldValues& values)
772
{
23,179,986✔
773
    // Tombstones do not use index - will crash if we try to insert values
11,507,910✔
774
    if (key.is_unresolved()) {
23,179,986✔
775
        return;
12,441✔
776
    }
12,441✔
777

11,501,658✔
778
    auto sz = m_index_accessors.size();
23,167,545✔
779
    // values are sorted by column index - there may be values missing
11,501,658✔
780
    auto value = values.begin();
23,167,545✔
781
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
57,242,346✔
782
        // Check if initial value is provided
16,864,578✔
783
        Mixed init_value;
34,075,092✔
784
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
34,075,092✔
785
            // Value for this column is provided
226,620✔
786
            init_value = value->value;
475,236✔
787
            ++value;
475,236✔
788
        }
475,236✔
789

16,864,578✔
790
        if (auto&& index = m_index_accessors[column_ndx]) {
34,075,092✔
791
            // There is an index for this column
511,260✔
792
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,044,642✔
793
            if (col_key.is_collection())
1,044,642✔
794
                continue;
102✔
795
            auto type = col_key.get_type();
1,044,540✔
796
            auto attr = col_key.get_attrs();
1,044,540✔
797
            bool nullable = attr.test(col_attr_Nullable);
1,044,540✔
798
            switch (type) {
1,044,540✔
799
                case col_type_Int:
477,099✔
800
                    if (init_value.is_null()) {
477,099✔
801
                        index->insert(key, ArrayIntNull::default_value(nullable));
165,615✔
802
                    }
165,615✔
803
                    else {
311,484✔
804
                        index->insert(key, init_value.get<int64_t>());
311,484✔
805
                    }
311,484✔
806
                    break;
477,099✔
807
                case col_type_Bool:
5,973✔
808
                    if (init_value.is_null()) {
5,973✔
809
                        index->insert(key, ArrayBoolNull::default_value(nullable));
5,973✔
810
                    }
5,973✔
811
                    else {
×
812
                        index->insert(key, init_value.get<bool>());
×
813
                    }
×
814
                    break;
5,973✔
815
                case col_type_String:
447,474✔
816
                    if (init_value.is_null()) {
447,474✔
817
                        index->insert(key, ArrayString::default_value(nullable));
431,277✔
818
                    }
431,277✔
819
                    else {
16,197✔
820
                        index->insert(key, init_value.get<String>());
16,197✔
821
                    }
16,197✔
822
                    break;
447,474✔
823
                case col_type_Timestamp:
6,165✔
824
                    if (init_value.is_null()) {
6,165✔
825
                        index->insert(key, ArrayTimestamp::default_value(nullable));
6,165✔
826
                    }
6,165✔
827
                    else {
×
828
                        index->insert(key, init_value.get<Timestamp>());
×
829
                    }
×
830
                    break;
6,165✔
831
                case col_type_ObjectId:
88,578✔
832
                    if (init_value.is_null()) {
88,578✔
833
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
834
                    }
6,120✔
835
                    else {
82,458✔
836
                        index->insert(key, init_value.get<ObjectId>());
82,458✔
837
                    }
82,458✔
838
                    break;
88,578✔
839
                case col_type_Mixed:
1,086✔
840
                    index->insert(key, init_value);
1,086✔
841
                    break;
1,086✔
842
                case col_type_UUID:
18,342✔
843
                    if (init_value.is_null()) {
18,342✔
844
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
6,138✔
845
                    }
6,138✔
846
                    else {
12,204✔
847
                        index->insert(key, init_value.get<UUID>());
12,204✔
848
                    }
12,204✔
849
                    break;
18,342✔
850
                default:
✔
851
                    REALM_UNREACHABLE();
852
            }
1,044,540✔
853
        }
1,044,540✔
854
    }
34,075,092✔
855
}
23,167,545✔
856

857
void Table::clear_indexes()
858
{
4,365✔
859
    for (auto&& index : m_index_accessors) {
48,825✔
860
        if (index) {
48,825✔
861
            index->clear();
3,099✔
862
        }
3,099✔
863
    }
48,825✔
864
}
4,365✔
865

866
void Table::do_add_search_index(ColKey col_key, IndexType type)
867
{
97,068✔
868
    size_t column_ndx = col_key.get_index().val;
97,068✔
869

48,111✔
870
    // Early-out if already indexed
48,111✔
871
    if (m_index_accessors[column_ndx] != nullptr)
97,068✔
872
        return;
150✔
873

48,036✔
874
    if (!StringIndex::type_supported(DataType(col_key.get_type())) ||
96,918✔
875
        (col_key.is_collection() && !(col_key.is_list() && col_key.get_type() == col_type_String)) ||
96,906✔
876
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
96,900✔
877
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
21✔
878
        // it should probably be a type mismatch exception instead.
21✔
879
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
42✔
880
    }
42✔
881

48,015✔
882
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
48,015✔
883
    // index have 0-entries.
48,015✔
884
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
96,876✔
885
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
96,876✔
886

48,015✔
887
    // Create the index
48,015✔
888
    m_index_accessors[column_ndx] =
96,876✔
889
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
96,876✔
890
    SearchIndex* index = m_index_accessors[column_ndx].get();
96,876✔
891
    // Insert ref to index
48,015✔
892
    index->set_parent(&m_index_refs, column_ndx);
96,876✔
893

48,015✔
894
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
96,876✔
895

48,015✔
896
    populate_search_index(col_key);
96,876✔
897
}
96,876✔
898

899
void Table::add_search_index(ColKey col_key, IndexType type)
900
{
3,900✔
901
    check_column(col_key);
3,900✔
902

1,965✔
903
    // Check spec
1,965✔
904
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,900✔
905
    auto attr = m_spec.get_column_attr(spec_ndx);
3,900✔
906

1,965✔
907
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,900✔
908
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
909

1,962✔
910
    switch (type) {
3,894✔
911
        case IndexType::None:
✔
912
            remove_search_index(col_key);
×
913
            return;
×
914
        case IndexType::Fulltext:
54✔
915
            // Early-out if already indexed
27✔
916
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
917
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
918
                return;
×
919
            }
×
920
            if (attr.test(col_attr_Indexed)) {
54✔
921
                this->remove_search_index(col_key);
×
922
            }
×
923
            break;
54✔
924
        case IndexType::General:
3,840✔
925
            if (attr.test(col_attr_Indexed)) {
3,840✔
926
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
30✔
927
                return;
30✔
928
            }
30✔
929
            if (attr.test(col_attr_FullText_Indexed)) {
3,810✔
930
                this->remove_search_index(col_key);
×
931
            }
×
932
            break;
3,810✔
933
    }
3,864✔
934

1,947✔
935
    do_add_search_index(col_key, type);
3,864✔
936

1,947✔
937
    // Update spec
1,947✔
938
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,837✔
939
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,864✔
940
}
3,864✔
941

942
void Table::remove_search_index(ColKey col_key)
943
{
837✔
944
    check_column(col_key);
837✔
945
    auto column_ndx = col_key.get_index();
837✔
946

411✔
947
    // Early-out if non-indexed
411✔
948
    if (m_index_accessors[column_ndx.val] == nullptr)
837✔
949
        return;
75✔
950

381✔
951
    // Destroy and remove the index column
381✔
952
    auto& index = m_index_accessors[column_ndx.val];
762✔
953
    REALM_ASSERT(index != nullptr);
762✔
954
    index->destroy();
762✔
955
    index.reset();
762✔
956

381✔
957
    m_index_refs.set(column_ndx.val, 0);
762✔
958

381✔
959
    // update spec
381✔
960
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
762✔
961
    auto attr = m_spec.get_column_attr(spec_ndx);
762✔
962
    attr.reset(col_attr_Indexed);
762✔
963
    attr.reset(col_attr_FullText_Indexed);
762✔
964
    m_spec.set_column_attr(spec_ndx, attr); // Throws
762✔
965
}
762✔
966

967
void Table::enumerate_string_column(ColKey col_key)
968
{
1,308✔
969
    check_column(col_key);
1,308✔
970
    size_t column_ndx = colkey2spec_ndx(col_key);
1,308✔
971
    ColumnType type = col_key.get_type();
1,308✔
972
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,308✔
973
        m_clusters.enumerate_string_column(col_key);
693✔
974
    }
693✔
975
}
1,308✔
976

977
bool Table::is_enumerated(ColKey col_key) const noexcept
978
{
38,772✔
979
    size_t col_ndx = colkey2spec_ndx(col_key);
38,772✔
980
    return m_spec.is_string_enum_type(col_ndx);
38,772✔
981
}
38,772✔
982

983
size_t Table::get_num_unique_values(ColKey col_key) const
984
{
138✔
985
    if (!is_enumerated(col_key))
138✔
986
        return 0;
84✔
987

27✔
988
    ArrayParent* parent;
54✔
989
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
990
    BPlusTree<StringData> col(get_alloc());
54✔
991
    col.init_from_ref(ref);
54✔
992

27✔
993
    return col.size();
54✔
994
}
54✔
995

996

997
void Table::erase_root_column(ColKey col_key)
998
{
1,962✔
999
    ColumnType col_type = col_key.get_type();
1,962✔
1000
    if (is_link_type(col_type)) {
1,962✔
1001
        auto target_table = get_opposite_table(col_key);
255✔
1002
        auto target_column = get_opposite_column(col_key);
255✔
1003
        target_table->do_erase_root_column(target_column);
255✔
1004
    }
255✔
1005
    do_erase_root_column(col_key); // Throws
1,962✔
1006
}
1,962✔
1007

1008

1009
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
1010
{
708,123✔
1011
    // if col_key specifies a key, it must be unused
349,614✔
1012
    REALM_ASSERT(!col_key || !valid_column(col_key));
708,123✔
1013

349,614✔
1014
    // locate insertion point: ordinary columns must come before backlink columns
349,614✔
1015
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
667,179✔
1016

349,614✔
1017
    if (!col_key) {
708,123✔
1018
        col_key = generate_col_key(type, {});
81,051✔
1019
    }
81,051✔
1020

349,614✔
1021
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
708,123✔
1022
    if (col_key.is_dictionary()) {
708,123✔
1023
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
55,086✔
1024
    }
55,086✔
1025
    auto col_ndx = col_key.get_index().val;
708,123✔
1026
    build_column_mapping();
708,123✔
1027
    REALM_ASSERT(col_ndx <= m_index_refs.size());
708,123✔
1028
    if (col_ndx == m_index_refs.size()) {
708,123✔
1029
        m_index_refs.insert(col_ndx, 0);
707,874✔
1030
    }
707,874✔
1031
    else {
249✔
1032
        m_index_refs.set(col_ndx, 0);
249✔
1033
    }
249✔
1034
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
708,123✔
1035
    if (col_ndx == m_opposite_table.size()) {
708,123✔
1036
        // m_opposite_table and m_opposite_column are always resized together!
349,491✔
1037
        m_opposite_table.insert(col_ndx, TableKey().value);
707,868✔
1038
        m_opposite_column.insert(col_ndx, ColKey().value);
707,868✔
1039
    }
707,868✔
1040
    else {
255✔
1041
        m_opposite_table.set(col_ndx, TableKey().value);
255✔
1042
        m_opposite_column.set(col_ndx, ColKey().value);
255✔
1043
    }
255✔
1044
    refresh_index_accessors();
708,123✔
1045
    m_clusters.insert_column(col_key);
708,123✔
1046
    if (m_tombstones) {
708,123✔
1047
        m_tombstones->insert_column(col_key);
327✔
1048
    }
327✔
1049

349,614✔
1050
    bump_storage_version();
708,123✔
1051

349,614✔
1052
    return col_key;
708,123✔
1053
}
708,123✔
1054

1055

1056
void Table::do_erase_root_column(ColKey col_key)
1057
{
2,217✔
1058
    size_t col_ndx = col_key.get_index().val;
2,217✔
1059
    // If the column had a source index we have to remove and destroy that as well
1,062✔
1060
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
2,217✔
1061
    if (index_ref) {
2,217✔
1062
        Array::destroy_deep(index_ref, m_index_refs.get_alloc());
132✔
1063
        m_index_refs.set(col_ndx, 0);
132✔
1064
        m_index_accessors[col_ndx].reset();
132✔
1065
    }
132✔
1066
    m_opposite_table.set(col_ndx, TableKey().value);
2,217✔
1067
    m_opposite_column.set(col_ndx, ColKey().value);
2,217✔
1068
    m_index_accessors[col_ndx] = nullptr;
2,217✔
1069
    m_clusters.remove_column(col_key);
2,217✔
1070
    if (m_tombstones)
2,217✔
1071
        m_tombstones->remove_column(col_key);
285✔
1072
    size_t spec_ndx = colkey2spec_ndx(col_key);
2,217✔
1073
    m_spec.erase_column(spec_ndx);
2,217✔
1074
    m_top.adjust(top_position_for_column_key, 2);
2,217✔
1075

1,062✔
1076
    build_column_mapping();
2,217✔
1077
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
3,858✔
1078
        REALM_ASSERT(m_index_accessors.back() == nullptr);
1,641✔
1079
        m_index_accessors.pop_back();
1,641✔
1080
    }
1,641✔
1081
    bump_content_version();
2,217✔
1082
    bump_storage_version();
2,217✔
1083
}
2,217✔
1084

1085
Query Table::where(const DictionaryLinkValues& dictionary_of_links) const
1086
{
1,524✔
1087
    return Query(m_own_ref, dictionary_of_links);
1,524✔
1088
}
1,524✔
1089

1090
void Table::set_table_type(Type table_type, bool handle_backlinks)
1091
{
312✔
1092
    if (table_type == m_table_type) {
312✔
1093
        return;
×
1094
    }
×
1095

156✔
1096
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
1097
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1098
                                                                   get_class_name(), m_table_type, table_type));
×
1099
    }
×
1100

156✔
1101
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1102
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1103
}
312✔
1104

1105
void Table::set_embedded(bool embedded, bool handle_backlinks)
1106
{
312✔
1107
    if (embedded == false) {
312✔
1108
        do_set_table_type(Type::TopLevel);
24✔
1109
        return;
24✔
1110
    }
24✔
1111

144✔
1112
    // Embedded objects cannot have a primary key.
144✔
1113
    if (get_primary_key_column()) {
288✔
1114
        throw IllegalOperation(
6✔
1115
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1116
    }
6✔
1117

141✔
1118
    if (size() == 0) {
282✔
1119
        do_set_table_type(Type::Embedded);
42✔
1120
        return;
42✔
1121
    }
42✔
1122

120✔
1123
    // Check all of the objects for invalid incoming links. Each embedded object
120✔
1124
    // must have exactly one incoming link, and it must be from a non-Mixed property.
120✔
1125
    // Objects with no incoming links are either deleted or an error (depending
120✔
1126
    // on `handle_backlinks`), and objects with multiple incoming links are either
120✔
1127
    // cloned for each of the incoming links or an error (again depending on `handle_backlinks`).
120✔
1128
    // Incoming links from a Mixed property are always an error, as those can't
120✔
1129
    // link to embedded objects
120✔
1130
    ArrayInteger leaf(get_alloc());
240✔
1131
    enum class LinkCount : int8_t { None, One, Multiple };
240✔
1132
    std::vector<LinkCount> incoming_link_count;
240✔
1133
    std::vector<ObjKey> orphans;
240✔
1134
    std::vector<ObjKey> multiple_incoming_links;
240✔
1135
    traverse_clusters([&](const Cluster* cluster) {
474✔
1136
        size_t size = cluster->node_size();
474✔
1137
        incoming_link_count.assign(size, LinkCount::None);
474✔
1138

237✔
1139
        for_each_backlink_column([&](ColKey col) {
606✔
1140
            cluster->init_leaf(col, &leaf);
606✔
1141
            // Width zero means all the values are zero and there can't be any backlinks
303✔
1142
            if (leaf.get_width() == 0) {
606✔
1143
                return IteratorControl::AdvanceToNext;
36✔
1144
            }
36✔
1145

285✔
1146
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1147
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1148
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1149
                    // ref of zero means there's no backlinks
29,670✔
1150
                    continue;
59,340✔
1151
                }
59,340✔
1152

480✔
1153
                if (value.is_ref()) {
960✔
1154
                    // Any other ref indicates an array of backlinks, which will
39✔
1155
                    // always have more than one entry
39✔
1156
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1157
                }
78✔
1158
                else {
882✔
1159
                    // Otherwise it's a tagged ref to the single linking object
441✔
1160
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1161
                        incoming_link_count[i] = LinkCount::One;
792✔
1162
                    }
792✔
1163
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1164
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1165
                    }
42✔
1166
                }
882✔
1167

480✔
1168
                auto source_col = get_opposite_column(col);
960✔
1169
                if (source_col.get_type() == col_type_Mixed) {
960✔
1170
                    auto source_table = get_opposite_table(col);
54✔
1171
                    throw IllegalOperation(util::format(
54✔
1172
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1173
                        "which does not support linking to embedded objects.",
54✔
1174
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1175
                }
54✔
1176
            }
960✔
1177
            return IteratorControl::AdvanceToNext;
543✔
1178
        });
570✔
1179

237✔
1180
        for (size_t i = 0; i < size; ++i) {
60,660✔
1181
            if (incoming_link_count[i] == LinkCount::None) {
60,240✔
1182
                if (!handle_backlinks) {
59,424✔
1183
                    throw IllegalOperation(util::format("Cannot convert '%1' to embedded: at least one object has no "
18✔
1184
                                                        "incoming links and would be deleted.",
18✔
1185
                                                        get_class_name()));
18✔
1186
                }
18✔
1187
                orphans.push_back(cluster->get_real_key(i));
59,406✔
1188
            }
59,406✔
1189
            else if (incoming_link_count[i] == LinkCount::Multiple) {
816✔
1190
                if (!handle_backlinks) {
90✔
1191
                    throw IllegalOperation(util::format(
36✔
1192
                        "Cannot convert '%1' to embedded: at least one object has more than one incoming link.",
36✔
1193
                        get_class_name()));
36✔
1194
                }
36✔
1195
                multiple_incoming_links.push_back(cluster->get_real_key(i));
54✔
1196
            }
54✔
1197
        }
60,240✔
1198

237✔
1199
        return IteratorControl::AdvanceToNext;
447✔
1200
    });
474✔
1201

120✔
1202
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
120✔
1203
    for (auto key : orphans) {
59,406✔
1204
        remove_object(key);
59,406✔
1205
    }
59,406✔
1206
    for (auto key : multiple_incoming_links) {
147✔
1207
        auto obj = get_object(key);
54✔
1208
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1209
        obj.remove();
54✔
1210
    }
54✔
1211

120✔
1212
    do_set_table_type(Type::Embedded);
240✔
1213
}
240✔
1214

1215
void Table::do_set_table_type(Type table_type)
1216
{
261,648✔
1217
    while (m_top.size() <= top_position_for_flags)
261,648✔
1218
        m_top.add(0);
×
1219

129,981✔
1220
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
261,648✔
1221
    // reset bits 0-1
129,981✔
1222
    flags &= ~table_type_mask;
261,648✔
1223
    // set table type
129,981✔
1224
    flags |= static_cast<uint8_t>(table_type);
261,648✔
1225
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
261,648✔
1226
    m_table_type = table_type;
261,648✔
1227
}
261,648✔
1228

1229

1230
void Table::detach(LifeCycleCookie cookie) noexcept
1231
{
1,773,033✔
1232
    m_cookie = cookie;
1,773,033✔
1233
    m_alloc.bump_instance_version();
1,773,033✔
1234
}
1,773,033✔
1235

1236
void Table::fully_detach() noexcept
1237
{
1,762,935✔
1238
    m_spec.detach();
1,762,935✔
1239
    m_top.detach();
1,762,935✔
1240
    m_index_refs.detach();
1,762,935✔
1241
    m_opposite_table.detach();
1,762,935✔
1242
    m_opposite_column.detach();
1,762,935✔
1243
    m_index_accessors.clear();
1,762,935✔
1244
}
1,762,935✔
1245

1246

1247
Table::~Table() noexcept
1248
{
3,552✔
1249
    if (m_top.is_attached()) {
3,552✔
1250
        // If destroyed as a standalone table, destroy all memory allocated
1,776✔
1251
        if (m_top.get_parent() == nullptr) {
3,552✔
1252
            m_top.destroy_deep();
3,552✔
1253
        }
3,552✔
1254
        fully_detach();
3,552✔
1255
    }
3,552✔
1256
    else {
×
1257
        REALM_ASSERT(m_index_accessors.size() == 0);
×
1258
    }
×
1259
    m_cookie = cookie_deleted;
3,552✔
1260
}
3,552✔
1261

1262

1263
IndexType Table::search_index_type(ColKey col_key) const noexcept
1264
{
5,503,362✔
1265
    if (m_index_accessors[col_key.get_index().val].get()) {
5,503,362✔
1266
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
936,168✔
1267
        bool fulltext = attr.test(col_attr_FullText_Indexed);
936,168✔
1268
        return fulltext ? IndexType::Fulltext : IndexType::General;
935,949✔
1269
    }
936,168✔
1270
    return IndexType::None;
4,567,194✔
1271
}
4,567,194✔
1272

1273

1274
void Table::migrate_sets_and_dictionaries()
1275
{
180✔
1276
    std::vector<ColKey> to_migrate;
180✔
1277
    for (auto col : get_column_keys()) {
570✔
1278
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
1279
            to_migrate.push_back(col);
12✔
1280
        }
12✔
1281
    }
570✔
1282
    if (to_migrate.size()) {
180✔
1283
        for (auto obj : *this) {
6✔
1284
            for (auto col : to_migrate) {
12✔
1285
                if (col.is_set()) {
12✔
1286
                    auto set = obj.get_set<Mixed>(col);
6✔
1287
                    set.migrate();
6✔
1288
                }
6✔
1289
                else if (col.is_dictionary()) {
6✔
1290
                    auto dict = obj.get_dictionary(col);
6✔
1291
                    dict.migrate();
6✔
1292
                }
6✔
1293
            }
12✔
1294
        }
6✔
1295
    }
6✔
1296
}
180✔
1297

1298
void Table::migrate_set_orderings()
1299
{
354✔
1300
    std::vector<ColKey> to_migrate;
354✔
1301
    for (auto col : get_column_keys()) {
918✔
1302
        if (col.is_set() && (col.get_type() == col_type_Mixed || col.get_type() == col_type_String ||
918✔
1303
                             col.get_type() == col_type_Binary)) {
30✔
1304
            to_migrate.push_back(col);
30✔
1305
        }
30✔
1306
    }
918✔
1307
    if (to_migrate.size()) {
354✔
1308
        for (auto obj : *this) {
90✔
1309
            for (auto col : to_migrate) {
102✔
1310
                if (col.get_type() == col_type_Mixed) {
102✔
1311
                    auto set = obj.get_set<Mixed>(col);
12✔
1312
                    set.migration_resort();
12✔
1313
                }
12✔
1314
                else if (col.get_type() == col_type_Binary) {
90✔
1315
                    auto set = obj.get_set<BinaryData>(col);
6✔
1316
                    set.migration_resort();
6✔
1317
                }
6✔
1318
                else {
84✔
1319
                    REALM_ASSERT_3(col.get_type(), ==, col_type_String);
84✔
1320
                    auto set = obj.get_set<String>(col);
84✔
1321
                    set.migration_resort();
84✔
1322
                }
84✔
1323
            }
102✔
1324
        }
90✔
1325
    }
18✔
1326
}
354✔
1327

1328
void Table::migrate_col_keys()
1329
{
354✔
1330
    if (m_spec.migrate_column_keys()) {
354✔
1331
        build_column_mapping();
24✔
1332
    }
24✔
1333

177✔
1334
    // Fix also m_opposite_column col_keys
177✔
1335
    ColumnType col_type_LinkList(13);
354✔
1336
    auto sz = m_opposite_column.size();
354✔
1337

177✔
1338
    for (size_t n = 0; n < sz; n++) {
1,386✔
1339
        ColKey col_key(m_opposite_column.get(n));
1,032✔
1340
        if (col_key.get_type() == col_type_LinkList) {
1,032✔
1341
            auto attrs = col_key.get_attrs();
24✔
1342
            REALM_ASSERT(attrs.test(col_attr_List));
24✔
1343
            ColKey new_key(col_key.get_index(), col_type_Link, attrs, col_key.get_tag());
24✔
1344
            m_opposite_column.set(n, new_key.value);
24✔
1345
        }
24✔
1346
    }
1,032✔
1347
}
354✔
1348

1349
StringData Table::get_name() const noexcept
1350
{
2,401,923✔
1351
    const Array& real_top = m_top;
2,401,923✔
1352
    ArrayParent* parent = real_top.get_parent();
2,401,923✔
1353
    if (!parent)
2,401,923✔
1354
        return StringData("");
51✔
1355
    REALM_ASSERT(dynamic_cast<Group*>(parent));
2,401,872✔
1356
    return static_cast<Group*>(parent)->get_table_name(get_key());
2,401,872✔
1357
}
2,401,872✔
1358

1359
StringData Table::get_class_name() const noexcept
1360
{
16,506✔
1361
    return Group::table_name_to_class_name(get_name());
16,506✔
1362
}
16,506✔
1363

1364
const char* Table::get_state() const noexcept
1365
{
42✔
1366
    switch (m_cookie) {
42✔
1367
        case cookie_created:
✔
1368
            return "created";
×
1369
        case cookie_transaction_ended:
6✔
1370
            return "transaction_ended";
6✔
1371
        case cookie_initialized:
✔
1372
            return "initialised";
×
1373
        case cookie_removed:
36✔
1374
            return "removed";
36✔
1375
        case cookie_void:
✔
1376
            return "void";
×
1377
        case cookie_deleted:
✔
1378
            return "deleted";
×
1379
    }
×
1380
    return "";
×
1381
}
×
1382

1383

1384
bool Table::is_nullable(ColKey col_key) const
1385
{
714,642✔
1386
    REALM_ASSERT_DEBUG(valid_column(col_key));
714,642✔
1387
    return col_key.get_attrs().test(col_attr_Nullable);
714,642✔
1388
}
714,642✔
1389

1390
bool Table::is_list(ColKey col_key) const
1391
{
22,881✔
1392
    REALM_ASSERT_DEBUG(valid_column(col_key));
22,881✔
1393
    return col_key.get_attrs().test(col_attr_List);
22,881✔
1394
}
22,881✔
1395

1396

1397
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
1398
{
265,008✔
1399
    Array top(alloc);
265,008✔
1400
    _impl::DeepArrayDestroyGuard dg(&top);
265,008✔
1401
    top.create(Array::type_HasRefs); // Throws
265,008✔
1402
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
265,008✔
1403

131,661✔
1404
    {
265,008✔
1405
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
265,008✔
1406
        dg_2.reset(mem.get_ref());
265,008✔
1407
        int_fast64_t v(from_ref(mem.get_ref()));
265,008✔
1408
        top.add(v); // Throws
265,008✔
1409
        dg_2.release();
265,008✔
1410
    }
265,008✔
1411
    top.add(0); // Old position for columns
265,008✔
1412
    {
265,008✔
1413
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
265,008✔
1414
        dg_2.reset(mem.get_ref());
265,008✔
1415
        int_fast64_t v(from_ref(mem.get_ref()));
265,008✔
1416
        top.add(v); // Throws
265,008✔
1417
        dg_2.release();
265,008✔
1418
    }
265,008✔
1419

131,661✔
1420
    // Table key value
131,661✔
1421
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
265,008✔
1422
    top.add(rot);
265,008✔
1423

131,661✔
1424
    // Search indexes
131,661✔
1425
    {
265,008✔
1426
        bool context_flag = false;
265,008✔
1427
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
265,008✔
1428
        dg_2.reset(mem.get_ref());
265,008✔
1429
        int_fast64_t v(from_ref(mem.get_ref()));
265,008✔
1430
        top.add(v); // Throws
265,008✔
1431
        dg_2.release();
265,008✔
1432
    }
265,008✔
1433
    rot = RefOrTagged::make_tagged(0);
265,008✔
1434
    top.add(rot); // Column key
265,008✔
1435
    top.add(rot); // Version
265,008✔
1436
    dg.release();
265,008✔
1437
    // Opposite keys (table and column)
131,661✔
1438
    {
265,008✔
1439
        bool context_flag = false;
265,008✔
1440
        {
265,008✔
1441
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
265,008✔
1442
            dg_2.reset(mem.get_ref());
265,008✔
1443
            int_fast64_t v(from_ref(mem.get_ref()));
265,008✔
1444
            top.add(v); // Throws
265,008✔
1445
            dg_2.release();
265,008✔
1446
        }
265,008✔
1447
        {
265,008✔
1448
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
265,008✔
1449
            dg_2.reset(mem.get_ref());
265,008✔
1450
            int_fast64_t v(from_ref(mem.get_ref()));
265,008✔
1451
            top.add(v); // Throws
265,008✔
1452
            dg_2.release();
265,008✔
1453
        }
265,008✔
1454
    }
265,008✔
1455
    top.add(0); // Sequence number
265,008✔
1456
    top.add(0); // Collision_map
265,008✔
1457
    top.add(0); // pk col key
265,008✔
1458
    top.add(0); // flags
265,008✔
1459
    top.add(0); // tombstones
265,008✔
1460

131,661✔
1461
    REALM_ASSERT(top.size() == top_array_size);
265,008✔
1462

131,661✔
1463
    return top.get_ref();
265,008✔
1464
}
265,008✔
1465

1466
void Table::ensure_graveyard()
1467
{
12,807✔
1468
    if (!m_tombstones) {
12,807✔
1469
        while (m_top.size() < top_position_for_tombstones)
2,160✔
1470
            m_top.add(0);
×
1471
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
2,160✔
1472
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
2,160✔
1473
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
2,160✔
1474
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
2,160✔
1475
        m_tombstones->init_from_parent();
2,160✔
1476
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
7,233✔
1477
            ts->insert_column(col);
7,233✔
1478
            return IteratorControl::AdvanceToNext;
7,233✔
1479
        });
7,233✔
1480
    }
2,160✔
1481
}
12,807✔
1482

1483
void Table::batch_erase_rows(const KeyColumn& keys)
1484
{
558✔
1485
    size_t num_objs = keys.size();
558✔
1486
    std::vector<ObjKey> vec;
558✔
1487
    vec.reserve(num_objs);
558✔
1488
    for (size_t i = 0; i < num_objs; ++i) {
2,898✔
1489
        ObjKey key = keys.get(i);
2,340✔
1490
        if (key != null_key && is_valid(key)) {
2,340✔
1491
            vec.push_back(key);
2,340✔
1492
        }
2,340✔
1493
    }
2,340✔
1494

279✔
1495
    sort(vec.begin(), vec.end());
558✔
1496
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
558✔
1497

279✔
1498
    batch_erase_objects(vec);
558✔
1499
}
558✔
1500

1501
void Table::batch_erase_objects(std::vector<ObjKey>& keys)
1502
{
6,273✔
1503
    Group* g = get_parent_group();
6,273✔
1504
    bool maybe_has_incoming_links = g && !is_asymmetric();
6,273✔
1505

3,138✔
1506
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
6,273✔
1507
        CascadeState state(CascadeState::Mode::Strong, g);
5,505✔
1508
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
3,579✔
1509
            state.m_to_be_deleted.emplace_back(m_key, k);
1,653✔
1510
        });
1,653✔
1511
        if (maybe_has_incoming_links)
5,505✔
1512
            nullify_links(state);
5,505✔
1513
        remove_recursive(state);
5,505✔
1514
    }
5,505✔
1515
    else {
768✔
1516
        CascadeState state(CascadeState::Mode::None, g);
768✔
1517
        for (auto k : keys) {
2,512,302✔
1518
            if (maybe_has_incoming_links) {
2,512,302✔
1519
                m_clusters.nullify_incoming_links(k, state);
2,512,224✔
1520
            }
2,512,224✔
1521
            m_clusters.erase(k, state);
2,512,302✔
1522
        }
2,512,302✔
1523
    }
768✔
1524
    keys.clear();
6,273✔
1525
}
6,273✔
1526

1527
void Table::clear()
1528
{
4,365✔
1529
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
4,365✔
1530
    m_clusters.clear(state);
4,365✔
1531
    free_collision_table();
4,365✔
1532
}
4,365✔
1533

1534

1535
Group* Table::get_parent_group() const noexcept
1536
{
60,144,894✔
1537
    if (!m_top.is_attached())
60,144,894✔
1538
        return 0;                             // Subtable with shared descriptor
×
1539
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
60,144,894✔
1540
    if (!parent)
60,144,894✔
1541
        return 0; // Free-standing table
25,989,042✔
1542

16,861,527✔
1543
    return static_cast<Group*>(parent);
34,155,852✔
1544
}
34,155,852✔
1545

1546
inline uint64_t Table::get_sync_file_id() const noexcept
1547
{
39,064,794✔
1548
    Group* g = get_parent_group();
39,064,794✔
1549
    return g ? g->get_sync_file_id() : 0;
32,407,059✔
1550
}
39,064,794✔
1551

1552
size_t Table::get_index_in_group() const noexcept
1553
{
39✔
1554
    if (!m_top.is_attached())
39✔
1555
        return realm::npos;                   // Subtable with shared descriptor
×
1556
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
39✔
1557
    if (!parent)
39✔
1558
        return realm::npos; // Free-standing table
×
1559
    return m_top.get_ndx_in_parent();
39✔
1560
}
39✔
1561

1562
uint64_t Table::allocate_sequence_number()
1563
{
20,006,904✔
1564
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
20,006,904✔
1565
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,907,244✔
1566
    rot = RefOrTagged::make_tagged(sn + 1);
20,006,904✔
1567
    m_top.set(top_position_for_sequence_number, rot);
20,006,904✔
1568

9,938,373✔
1569
    return sn;
20,006,904✔
1570
}
20,006,904✔
1571

1572
void Table::set_sequence_number(uint64_t seq)
1573
{
×
1574
    m_top.set(top_position_for_sequence_number, RefOrTagged::make_tagged(seq));
×
1575
}
×
1576

1577
void Table::set_collision_map(ref_type ref)
1578
{
×
1579
    m_top.set(top_position_for_collision_map, RefOrTagged::make_ref(ref));
×
1580
}
×
1581

1582
void Table::set_col_key_sequence_number(uint64_t seq)
1583
{
24✔
1584
    m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(seq));
24✔
1585
}
24✔
1586

1587
TableRef Table::get_link_target(ColKey col_key) noexcept
1588
{
263,229✔
1589
    return get_opposite_table(col_key);
263,229✔
1590
}
263,229✔
1591

1592
// count ----------------------------------------------
1593

1594
size_t Table::count_int(ColKey col_key, int64_t value) const
1595
{
12,006✔
1596
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1597
        return index->count(value);
12,000✔
1598
    }
12,000✔
1599

3✔
1600
    return where().equal(col_key, value).count();
6✔
1601
}
6✔
1602
size_t Table::count_float(ColKey col_key, float value) const
1603
{
6✔
1604
    return where().equal(col_key, value).count();
6✔
1605
}
6✔
1606
size_t Table::count_double(ColKey col_key, double value) const
1607
{
6✔
1608
    return where().equal(col_key, value).count();
6✔
1609
}
6✔
1610
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
1611
{
18✔
1612
    ArrayDecimal128 leaf(get_alloc());
18✔
1613
    size_t cnt = 0;
18✔
1614
    bool null_value = value.is_null();
18✔
1615
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
1616
        // direct aggregate on the leaf
9✔
1617
        cluster->init_leaf(col_key, &leaf);
18✔
1618
        auto sz = leaf.size();
18✔
1619
        for (size_t i = 0; i < sz; i++) {
1,296✔
1620
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
1621
                cnt++;
24✔
1622
            }
24✔
1623
        }
1,278✔
1624
        return IteratorControl::AdvanceToNext;
18✔
1625
    };
18✔
1626

9✔
1627
    traverse_clusters(f);
18✔
1628

9✔
1629
    return cnt;
18✔
1630
}
18✔
1631
size_t Table::count_string(ColKey col_key, StringData value) const
1632
{
1,476✔
1633
    if (auto index = this->get_search_index(col_key)) {
1,476✔
1634
        return index->count(value);
732✔
1635
    }
732✔
1636
    return where().equal(col_key, value).count();
744✔
1637
}
744✔
1638

1639
template <typename T>
1640
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1641
{
32,037✔
1642
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
32,037✔
1643
    LeafType leaf(get_alloc());
32,037✔
1644

16,005✔
1645
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
32,055✔
1646
        // direct aggregate on the leaf
16,014✔
1647
        cluster->init_leaf(column_key, &leaf);
32,055✔
1648
        st.m_key_offset = cluster->get_offset();
32,055✔
1649
        st.m_key_values = cluster->get_key_array();
32,055✔
1650
        st.set_payload_column(&leaf);
32,055✔
1651
        bool cont = true;
32,055✔
1652
        size_t sz = leaf.size();
32,055✔
1653
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
130,923✔
1654
            cont = st.match(local_index);
98,868✔
1655
        }
98,868✔
1656
        return IteratorControl::AdvanceToNext;
32,055✔
1657
    };
32,055✔
1658

16,005✔
1659
    traverse_clusters(f);
32,037✔
1660
}
32,037✔
1661

1662
// This template is also used by the query engine
1663
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1664
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1665
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1666
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1667
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1668
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1669
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1670

1671
std::optional<Mixed> Table::sum(ColKey col_key) const
1672
{
756✔
1673
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1674
}
756✔
1675

1676
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1677
{
822✔
1678
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1679
}
822✔
1680

1681
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1682
{
1,170✔
1683
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
1684
}
1,170✔
1685

1686
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1687
{
25,761✔
1688
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
25,761✔
1689
}
25,761✔
1690

1691

1692
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1693
{
30,140,238✔
1694
    check_column(col);
30,140,238✔
1695
    return m_index_accessors[col.get_index().val].get();
30,140,238✔
1696
}
30,140,238✔
1697

1698
StringIndex* Table::get_string_index(ColKey col) const noexcept
1699
{
689,919✔
1700
    check_column(col);
689,919✔
1701
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
689,919✔
1702
}
689,919✔
1703

1704
template <class T>
1705
ObjKey Table::find_first(ColKey col_key, T value) const
1706
{
34,947✔
1707
    check_column(col_key);
34,947✔
1708

17,409✔
1709
    if (!col_key.is_nullable() && value_is_null(value)) {
34,947!
1710
        return {}; // this is a precaution/optimization
6✔
1711
    }
6✔
1712
    // You cannot call GetIndexData on ObjKey
17,406✔
1713
    if constexpr (!std::is_same_v<T, ObjKey>) {
34,941✔
1714
        if (SearchIndex* index = get_search_index(col_key)) {
34,923!
1715
            return index->find_first(value);
27,075✔
1716
        }
27,075✔
1717
        if (col_key == m_primary_key_col) {
7,848!
1718
            return find_primary_key(value);
×
1719
        }
×
1720
    }
7,848✔
1721

3,924✔
1722
    ObjKey key;
7,848✔
1723
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
7,848✔
1724
    LeafType leaf(get_alloc());
7,848✔
1725

3,924✔
1726
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
9,330✔
1727
        cluster->init_leaf(col_key, &leaf);
9,330✔
1728
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
1729
        if (row != realm::npos) {
9,330!
1730
            key = cluster->get_real_key(row);
7,692✔
1731
            return IteratorControl::Stop;
7,692✔
1732
        }
7,692✔
1733
        return IteratorControl::AdvanceToNext;
1,638✔
1734
    };
1,638✔
1735

3,924✔
1736
    traverse_clusters(f);
7,848✔
1737

3,924✔
1738
    return key;
7,848✔
1739
}
7,848✔
1740

1741
namespace realm {
1742

1743
template <>
1744
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1745
{
×
1746
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1747
}
×
1748

1749
template <>
1750
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
1751
{
×
1752
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1753
}
×
1754

1755
template <>
1756
ObjKey Table::find_first(ColKey col_key, null) const
1757
{
×
1758
    return find_first_null(col_key);
×
1759
}
×
1760
} // namespace realm
1761

1762
// Explicitly instantiate the generic case of the template for the types we care about.
1763
template ObjKey Table::find_first(ColKey col_key, bool) const;
1764
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
1765
template ObjKey Table::find_first(ColKey col_key, float) const;
1766
template ObjKey Table::find_first(ColKey col_key, double) const;
1767
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
1768
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
1769
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
1770
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
1771
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
1772
template ObjKey Table::find_first(ColKey col_key, StringData) const;
1773
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
1774
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
1775
template ObjKey Table::find_first(ColKey col_key, UUID) const;
1776
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
1777
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
1778

1779
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1780
{
7,698✔
1781
    if (is_nullable(col_key))
7,698✔
1782
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1783
    else
7,662✔
1784
        return find_first<int64_t>(col_key, value);
7,662✔
1785
}
7,698✔
1786

1787
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1788
{
78✔
1789
    if (is_nullable(col_key))
78✔
1790
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1791
    else
42✔
1792
        return find_first<bool>(col_key, value);
42✔
1793
}
78✔
1794

1795
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1796
{
108✔
1797
    return find_first(col_key, value);
108✔
1798
}
108✔
1799

1800
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1801
{
6✔
1802
    return find_first(col_key, value);
6✔
1803
}
6✔
1804

1805
ObjKey Table::find_first_float(ColKey col_key, float value) const
1806
{
12✔
1807
    return find_first<Float>(col_key, value);
12✔
1808
}
12✔
1809

1810
ObjKey Table::find_first_double(ColKey col_key, double value) const
1811
{
12✔
1812
    return find_first<Double>(col_key, value);
12✔
1813
}
12✔
1814

1815
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
1816
{
×
1817
    return find_first<Decimal128>(col_key, value);
×
1818
}
×
1819

1820
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1821
{
11,313✔
1822
    return find_first<StringData>(col_key, value);
11,313✔
1823
}
11,313✔
1824

1825
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
1826
{
×
1827
    return find_first<BinaryData>(col_key, value);
×
1828
}
×
1829

1830
ObjKey Table::find_first_null(ColKey col_key) const
1831
{
114✔
1832
    return where().equal(col_key, null{}).find();
114✔
1833
}
114✔
1834

1835
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1836
{
18✔
1837
    return find_first(col_key, value);
18✔
1838
}
18✔
1839

1840
template <class T>
1841
TableView Table::find_all(ColKey col_key, T value)
1842
{
108✔
1843
    return where().equal(col_key, value).find_all();
108✔
1844
}
108✔
1845

1846
TableView Table::find_all_int(ColKey col_key, int64_t value)
1847
{
102✔
1848
    return find_all<int64_t>(col_key, value);
102✔
1849
}
102✔
1850

1851
TableView Table::find_all_int(ColKey col_key, int64_t value) const
1852
{
×
1853
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1854
}
×
1855

1856
TableView Table::find_all_bool(ColKey col_key, bool value)
1857
{
×
1858
    return find_all<bool>(col_key, value);
×
1859
}
×
1860

1861
TableView Table::find_all_bool(ColKey col_key, bool value) const
1862
{
×
1863
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1864
}
×
1865

1866

1867
TableView Table::find_all_float(ColKey col_key, float value)
1868
{
×
1869
    return find_all<float>(col_key, value);
×
1870
}
×
1871

1872
TableView Table::find_all_float(ColKey col_key, float value) const
1873
{
×
1874
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
1875
}
×
1876

1877
TableView Table::find_all_double(ColKey col_key, double value)
1878
{
6✔
1879
    return find_all<double>(col_key, value);
6✔
1880
}
6✔
1881

1882
TableView Table::find_all_double(ColKey col_key, double value) const
1883
{
×
1884
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
1885
}
×
1886

1887
TableView Table::find_all_string(ColKey col_key, StringData value)
1888
{
282✔
1889
    return where().equal(col_key, value).find_all();
282✔
1890
}
282✔
1891

1892
TableView Table::find_all_string(ColKey col_key, StringData value) const
1893
{
×
1894
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
1895
}
×
1896

1897
TableView Table::find_all_binary(ColKey, BinaryData)
1898
{
×
1899
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
1900
}
×
1901

1902
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
1903
{
×
1904
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
1905
}
×
1906

1907
TableView Table::find_all_null(ColKey col_key)
1908
{
×
1909
    return where().equal(col_key, null{}).find_all();
×
1910
}
×
1911

1912
TableView Table::find_all_null(ColKey col_key) const
1913
{
×
1914
    return const_cast<Table*>(this)->find_all_null(col_key);
×
1915
}
×
1916

1917
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1918
{
6✔
1919
    return where().fulltext(col_key, terms).find_all();
6✔
1920
}
6✔
1921

1922
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1923
{
72✔
1924
    TableView tv = where().find_all();
72✔
1925
    tv.sort(col_key, ascending);
72✔
1926
    return tv;
72✔
1927
}
72✔
1928

1929
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1930
{
6✔
1931
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1932
}
6✔
1933

1934
TableView Table::get_sorted_view(SortDescriptor order)
1935
{
126✔
1936
    TableView tv = where().find_all();
126✔
1937
    tv.sort(std::move(order));
126✔
1938
    return tv;
126✔
1939
}
126✔
1940

1941
TableView Table::get_sorted_view(SortDescriptor order) const
1942
{
×
1943
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
1944
}
×
1945

1946
util::Logger* Table::get_logger() const noexcept
1947
{
1,796,043✔
1948
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,699,551✔
1949
}
1,796,043✔
1950

1951
// Called after a commit. Table will effectively contain the same as before,
1952
// but now with new refs from the file
1953
void Table::update_from_parent() noexcept
1954
{
705,543✔
1955
    // There is no top for sub-tables sharing spec
350,613✔
1956
    if (m_top.is_attached()) {
705,543✔
1957
        m_top.update_from_parent();
705,543✔
1958
        m_spec.update_from_parent();
705,543✔
1959
        m_clusters.update_from_parent();
705,543✔
1960
        m_index_refs.update_from_parent();
705,543✔
1961
        for (auto&& index : m_index_accessors) {
2,114,109✔
1962
            if (index != nullptr) {
2,114,109✔
1963
                index->update_from_parent();
276,831✔
1964
            }
276,831✔
1965
        }
2,114,109✔
1966

350,613✔
1967
        m_opposite_table.update_from_parent();
705,543✔
1968
        m_opposite_column.update_from_parent();
705,543✔
1969
        if (m_top.size() > top_position_for_flags) {
705,543✔
1970
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
705,534✔
1971
            m_table_type = Type(flags & table_type_mask);
705,534✔
1972
        }
705,534✔
1973
        else {
9✔
1974
            m_table_type = Type::TopLevel;
9✔
1975
        }
9✔
1976
        if (m_tombstones)
705,543✔
1977
            m_tombstones->update_from_parent();
3,216✔
1978

350,613✔
1979
        refresh_content_version();
705,543✔
1980
        m_has_any_embedded_objects.reset();
705,543✔
1981
    }
705,543✔
1982
    m_alloc.bump_storage_version();
705,543✔
1983
}
705,543✔
1984

1985
void Table::schema_to_json(std::ostream& out) const
1986
{
12✔
1987
    out << "{";
12✔
1988
    auto name = get_name();
12✔
1989
    out << "\"name\":\"" << name << "\"";
12✔
1990
    if (this->m_primary_key_col) {
12✔
1991
        out << ",";
×
1992
        out << "\"primaryKey\":\"" << this->get_column_name(m_primary_key_col) << "\"";
×
1993
    }
×
1994
    out << ",\"tableType\":\"" << this->get_table_type() << "\"";
12✔
1995
    out << ",\"properties\":[";
12✔
1996
    auto col_keys = get_column_keys();
12✔
1997
    int sz = int(col_keys.size());
12✔
1998
    for (int i = 0; i < sz; ++i) {
54✔
1999
        auto col_key = col_keys[i];
42✔
2000
        name = get_column_name(col_key);
42✔
2001
        auto type = col_key.get_type();
42✔
2002
        out << "{";
42✔
2003
        out << "\"name\":\"" << name << "\"";
42✔
2004
        if (this->is_link_type(type)) {
42✔
2005
            out << ",\"type\":\"object\"";
6✔
2006
            name = this->get_opposite_table(col_key)->get_name();
6✔
2007
            out << ",\"objectType\":\"" << name << "\"";
6✔
2008
        }
6✔
2009
        else {
36✔
2010
            out << ",\"type\":\"" << get_data_type_name(DataType(type)) << "\"";
36✔
2011
        }
36✔
2012
        if (col_key.is_list()) {
42✔
2013
            out << ",\"isArray\":true";
12✔
2014
        }
12✔
2015
        else if (col_key.is_set()) {
30✔
2016
            out << ",\"isSet\":true";
×
2017
        }
×
2018
        else if (col_key.is_dictionary()) {
30✔
2019
            out << ",\"isMap\":true";
6✔
2020
            auto key_type = get_dictionary_key_type(col_key);
6✔
2021
            out << ",\"keyType\":\"" << get_data_type_name(key_type) << "\"";
6✔
2022
        }
6✔
2023
        if (col_key.is_nullable()) {
42✔
2024
            out << ",\"isOptional\":true";
12✔
2025
        }
12✔
2026
        auto index_type = search_index_type(col_key);
42✔
2027
        if (index_type == IndexType::General) {
42✔
2028
            out << ",\"isIndexed\":true";
×
2029
        }
×
2030
        if (index_type == IndexType::Fulltext) {
42✔
2031
            out << ",\"isFulltextIndexed\":true";
×
2032
        }
×
2033
        out << "}";
42✔
2034
        if (i < sz - 1) {
42✔
2035
            out << ",";
30✔
2036
        }
30✔
2037
    }
42✔
2038
    out << "]}";
12✔
2039
}
12✔
2040

2041
bool Table::operator==(const Table& t) const
2042
{
138✔
2043
    if (size() != t.size()) {
138✔
2044
        return false;
12✔
2045
    }
12✔
2046
    // Check columns
63✔
2047
    for (auto ck : this->get_column_keys()) {
534✔
2048
        auto name = get_column_name(ck);
534✔
2049
        auto other_ck = t.get_column_key(name);
534✔
2050
        auto attrs = ck.get_attrs();
534✔
2051
        if (search_index_type(ck) != t.search_index_type(other_ck))
534✔
2052
            return false;
×
2053

267✔
2054
        if (!other_ck || other_ck.get_attrs() != attrs) {
534✔
2055
            return false;
×
2056
        }
×
2057
    }
534✔
2058
    auto pk_col = get_primary_key_column();
126✔
2059
    for (auto o : *this) {
2,898✔
2060
        Obj other_o;
2,898✔
2061
        if (pk_col) {
2,898✔
2062
            auto pk = o.get_any(pk_col);
90✔
2063
            other_o = t.get_object_with_primary_key(pk);
90✔
2064
        }
90✔
2065
        else {
2,808✔
2066
            other_o = t.get_object(o.get_key());
2,808✔
2067
        }
2,808✔
2068
        if (!(other_o && o == other_o))
2,898✔
2069
            return false;
18✔
2070
    }
2,898✔
2071

63✔
2072
    return true;
117✔
2073
}
126✔
2074

2075

2076
void Table::flush_for_commit()
2077
{
994,695✔
2078
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
994,701✔
2079
        if (!m_top.is_read_only()) {
994,683✔
2080
            ++m_in_file_version_at_transaction_boundary;
757,608✔
2081
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
757,608✔
2082
            m_top.set(top_position_for_version, rot_version);
757,608✔
2083
        }
757,608✔
2084
    }
994,683✔
2085
}
994,695✔
2086

2087
void Table::refresh_content_version()
2088
{
976,590✔
2089
    REALM_ASSERT(m_top.is_attached());
976,590✔
2090
    if (m_top.size() >= top_position_for_version) {
976,590✔
2091
        // we have versioning info in the file. Use this to conditionally
489,969✔
2092
        // bump the version counter:
489,969✔
2093
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
976,536✔
2094
        REALM_ASSERT(rot_version.is_tagged());
976,536✔
2095
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
976,536✔
2096
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
168,717✔
2097
            bump_content_version();
168,717✔
2098
        }
168,717✔
2099
    }
976,536✔
2100
    else {
54✔
2101
        // assume the worst:
3✔
2102
        bump_content_version();
54✔
2103
    }
54✔
2104
}
976,590✔
2105

2106

2107
// Called when Group is moved to another version - either a rollback or an advance.
2108
// The content of the table is potentially different, so make no assumptions.
2109
void Table::refresh_accessor_tree()
2110
{
271,026✔
2111
    REALM_ASSERT(m_cookie == cookie_initialized);
271,026✔
2112
    REALM_ASSERT(m_top.is_attached());
271,026✔
2113
    m_top.init_from_parent();
271,026✔
2114
    m_spec.init_from_parent();
271,026✔
2115
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
271,026✔
2116
    m_clusters.init_from_parent();
271,026✔
2117
    m_index_refs.init_from_parent();
271,026✔
2118
    m_opposite_table.init_from_parent();
271,026✔
2119
    m_opposite_column.init_from_parent();
271,026✔
2120
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
271,026✔
2121
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
223,881✔
2122
    if (m_top.size() > top_position_for_flags) {
271,029✔
2123
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
270,999✔
2124
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
270,999✔
2125
    }
270,999✔
2126
    else {
2,147,483,677✔
2127
        m_table_type = Type::TopLevel;
2,147,483,677✔
2128
    }
2,147,483,677✔
2129
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
271,035✔
2130
        // Tombstones exists
1,029✔
2131
        if (!m_tombstones) {
2,058✔
2132
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
294✔
2133
        }
294✔
2134
        m_tombstones->init_from_parent();
2,058✔
2135
    }
2,058✔
2136
    else {
268,968✔
2137
        m_tombstones = nullptr;
268,968✔
2138
    }
268,968✔
2139
    refresh_content_version();
271,026✔
2140
    bump_storage_version();
271,026✔
2141
    build_column_mapping();
271,026✔
2142
    refresh_index_accessors();
271,026✔
2143
}
271,026✔
2144

2145
void Table::refresh_index_accessors()
2146
{
2,756,391✔
2147
    // Refresh search index accessors
1,624,179✔
2148

1,624,179✔
2149
    // First eliminate any index accessors for eliminated last columns
1,624,179✔
2150
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
2,756,391✔
2151
    m_index_accessors.resize(col_ndx_end);
2,756,391✔
2152

1,624,179✔
2153
    // Then eliminate/refresh/create accessors within column range
1,624,179✔
2154
    // we can not use for_each_column() here, since the columns may have changed
1,624,179✔
2155
    // and the index accessor vector is not updated correspondingly.
1,624,179✔
2156
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
15,622,779✔
2157
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
12,866,388✔
2158

6,845,664✔
2159
        if (ref == 0) {
12,866,388✔
2160
            // accessor drop
6,424,872✔
2161
            m_index_accessors[col_ndx].reset();
12,010,845✔
2162
        }
12,010,845✔
2163
        else {
855,543✔
2164
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
855,543✔
2165
            bool fulltext = attr.test(col_attr_FullText_Indexed);
855,543✔
2166
            auto col_key = m_leaf_ndx2colkey[col_ndx];
855,543✔
2167
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
855,528✔
2168

420,792✔
2169
            if (m_index_accessors[col_ndx]) { // still there, refresh:
855,543✔
2170
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
356,250✔
2171
            }
356,250✔
2172
            else { // new index!
499,293✔
2173
                m_index_accessors[col_ndx] =
499,293✔
2174
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
499,293✔
2175
            }
499,293✔
2176
        }
855,543✔
2177
    }
12,866,388✔
2178
}
2,756,391✔
2179

2180
bool Table::is_cross_table_link_target() const noexcept
2181
{
645✔
2182
    auto is_cross_link = [this](ColKey col_key) {
348✔
2183
        auto t = col_key.get_type();
81✔
2184
        // look for a backlink with a different target than ourselves
27✔
2185
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
81✔
2186
                   ? IteratorControl::Stop
36✔
2187
                   : IteratorControl::AdvanceToNext;
72✔
2188
    };
81✔
2189
    return for_each_backlink_column(is_cross_link);
645✔
2190
}
645✔
2191

2192
// LCOV_EXCL_START ignore debug functions
2193

2194
void Table::verify() const
2195
{
226,707✔
2196
#ifdef REALM_DEBUG
226,707✔
2197
    if (m_top.is_attached())
226,707✔
2198
        m_top.verify();
226,707✔
2199
    m_spec.verify();
226,707✔
2200
    m_clusters.verify();
226,707✔
2201
    if (nb_unresolved())
226,707✔
2202
        m_tombstones->verify();
25,815✔
2203
#endif
226,707✔
2204
}
226,707✔
2205

2206
#ifdef REALM_DEBUG
2207
MemStats Table::stats() const
2208
{
×
2209
    MemStats mem_stats;
×
2210
    m_top.stats(mem_stats);
×
2211
    return mem_stats;
×
2212
}
×
2213
#endif // LCOV_EXCL_STOP ignore debug functions
2214

2215
Obj Table::create_object(ObjKey key, const FieldValues& values)
2216
{
22,805,679✔
2217
    if (is_embedded())
22,805,679✔
2218
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2219
    if (m_primary_key_col)
22,805,631✔
2220
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2221
    if (key == null_key) {
22,805,631✔
2222
        GlobalKey object_id = allocate_object_id_squeezed();
19,640,895✔
2223
        key = object_id.get_local_key(get_sync_file_id());
19,640,895✔
2224
        // Check if this key collides with an already existing object
9,781,308✔
2225
        // This could happen if objects were at some point created with primary keys,
9,781,308✔
2226
        // but later primary key property was removed from the schema.
9,781,308✔
2227
        while (m_clusters.is_valid(key)) {
19,640,895✔
2228
            object_id = allocate_object_id_squeezed();
×
2229
            key = object_id.get_local_key(get_sync_file_id());
×
2230
        }
×
2231
        if (auto repl = get_repl())
19,640,895✔
2232
            repl->create_object(this, object_id);
4,293,951✔
2233
    }
19,640,895✔
2234

11,369,844✔
2235
    REALM_ASSERT(key.value >= 0);
22,805,631✔
2236

11,369,844✔
2237
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,805,631✔
2238

11,369,844✔
2239
    return obj;
22,805,631✔
2240
}
22,805,631✔
2241

2242
Obj Table::create_linked_object()
2243
{
41,988✔
2244
    REALM_ASSERT(is_embedded());
41,988✔
2245

20,898✔
2246
    GlobalKey object_id = allocate_object_id_squeezed();
41,988✔
2247
    ObjKey key = object_id.get_local_key(get_sync_file_id());
41,988✔
2248

20,898✔
2249
    REALM_ASSERT(key.value >= 0);
41,988✔
2250

20,898✔
2251
    Obj obj = m_clusters.insert(key, {});
41,988✔
2252

20,898✔
2253
    return obj;
41,988✔
2254
}
41,988✔
2255

2256
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2257
{
30✔
2258
    if (is_embedded())
30✔
2259
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2260
    if (m_primary_key_col)
30✔
2261
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2262
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2263

15✔
2264
    if (auto repl = get_repl())
30✔
2265
        repl->create_object(this, object_id);
30✔
2266

15✔
2267
    try {
30✔
2268
        Obj obj = m_clusters.insert(key, values);
30✔
2269
        // Check if tombstone exists
15✔
2270
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2271
            auto unres_key = key.get_unresolved();
6✔
2272
            // Copy links over
3✔
2273
            auto tombstone = m_tombstones->get(unres_key);
6✔
2274
            obj.assign_pk_and_backlinks(tombstone);
6✔
2275
            // If tombstones had no links to it, it may still be alive
3✔
2276
            if (m_tombstones->is_valid(unres_key)) {
6✔
2277
                CascadeState state(CascadeState::Mode::None);
6✔
2278
                m_tombstones->erase(unres_key, state);
6✔
2279
            }
6✔
2280
        }
6✔
2281

15✔
2282
        return obj;
30✔
2283
    }
30✔
2284
    catch (const KeyAlreadyUsed&) {
6✔
2285
        return m_clusters.get(key);
6✔
2286
    }
6✔
2287
}
30✔
2288

2289
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2290
                                          bool* did_create)
2291
{
468,603✔
2292
    auto primary_key_col = get_primary_key_column();
468,603✔
2293
    if (is_embedded() || !primary_key_col)
468,603✔
2294
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2295
                              util::format("Table has no primary key: %1", get_name()));
6✔
2296

223,122✔
2297
    DataType type = DataType(primary_key_col.get_type());
468,597✔
2298

223,122✔
2299
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
468,597✔
2300
        throw InvalidArgument(
6✔
2301
            ErrorCodes::PropertyNotNullable,
6✔
2302
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2303
    }
6✔
2304

223,119✔
2305
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
468,591✔
2306
        primary_key.get_type() != type) {
468,384✔
2307
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2308
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2309
    }
6✔
2310

223,116✔
2311
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
468,585✔
2312

223,116✔
2313
    if (did_create)
468,585✔
2314
        *did_create = false;
82,866✔
2315

223,116✔
2316
    // Check for existing object
223,116✔
2317
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
468,585✔
2318
        if (mode == UpdateMode::never) {
46,122✔
2319
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2320
        }
6✔
2321
        auto obj = m_clusters.get(key);
46,116✔
2322
        for (auto& val : field_values) {
22,881✔
2323
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2324
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2325
            }
12✔
2326
        }
12✔
2327
        return obj;
46,116✔
2328
    }
46,116✔
2329

200,238✔
2330
    ObjKey unres_key;
422,463✔
2331
    if (m_tombstones) {
422,463✔
2332
        // Check for potential tombstone
5,925✔
2333
        GlobalKey object_id{primary_key};
11,898✔
2334
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
11,898✔
2335

5,925✔
2336
        ObjKey key = object_key.get_unresolved();
11,898✔
2337
        if (auto obj = m_tombstones->try_get_obj(key)) {
11,898✔
2338
            auto existing_pk_value = obj.get_any(primary_key_col);
10,740✔
2339

5,373✔
2340
            // If the primary key is the same, the object should be resurrected below
5,373✔
2341
            if (existing_pk_value == primary_key) {
10,740✔
2342
                unres_key = key;
10,734✔
2343
            }
10,734✔
2344
        }
10,740✔
2345
    }
11,898✔
2346

200,238✔
2347
    ObjKey key = get_next_valid_key();
422,463✔
2348

200,238✔
2349
    auto repl = get_repl();
422,463✔
2350
    if (repl) {
422,463✔
2351
        repl->create_object_with_primary_key(this, key, primary_key);
301,467✔
2352
    }
301,467✔
2353
    if (did_create) {
422,463✔
2354
        *did_create = true;
46,266✔
2355
    }
46,266✔
2356

200,238✔
2357
    field_values.insert(primary_key_col, primary_key);
422,463✔
2358
    Obj ret = m_clusters.insert(key, field_values);
422,463✔
2359

200,238✔
2360
    // Check if unresolved exists
200,238✔
2361
    if (unres_key) {
422,463✔
2362
        auto tombstone = m_tombstones->get(unres_key);
10,734✔
2363
        ret.assign_pk_and_backlinks(tombstone);
10,734✔
2364
        // If tombstones had no links to it, it may still be alive
5,370✔
2365
        if (m_tombstones->is_valid(unres_key)) {
10,734✔
2366
            CascadeState state(CascadeState::Mode::None);
10,674✔
2367
            m_tombstones->erase(unres_key, state);
10,674✔
2368
        }
10,674✔
2369
    }
10,734✔
2370
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
422,463✔
2371
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
2372
    }
1,290✔
2373
    return ret;
422,463✔
2374
}
422,463✔
2375

2376
ObjKey Table::find_primary_key(Mixed primary_key) const
2377
{
714,771✔
2378
    auto primary_key_col = get_primary_key_column();
714,771✔
2379
    REALM_ASSERT(primary_key_col);
714,771✔
2380
    DataType type = DataType(primary_key_col.get_type());
714,771✔
2381
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
714,771✔
2382
                 primary_key.get_type() == type);
714,771✔
2383

358,185✔
2384
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
714,771✔
2385
        return index->find_first(primary_key);
713,592✔
2386
    }
713,592✔
2387

1,179✔
2388
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
1,179✔
2389
    // so try the old algorithm
1,179✔
2390
    GlobalKey object_id{primary_key};
1,179✔
2391
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
1,179✔
2392

1,179✔
2393
    // Check if existing
1,179✔
2394
    if (auto obj = m_clusters.try_get_obj(object_key)) {
1,179!
2395
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2396

2397
        if (existing_pk_value == primary_key) {
×
2398
            return object_key;
×
2399
        }
×
2400
    }
1,179✔
2401
    return {};
1,179✔
2402
}
1,179✔
2403

2404
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2405
{
603,180✔
2406
    // Check if existing
301,533✔
2407
    if (auto key = find_primary_key(primary_key)) {
603,180✔
2408
        return key;
591,000✔
2409
    }
591,000✔
2410

6,267✔
2411
    // Object does not exist - create tombstone
6,267✔
2412
    GlobalKey object_id{primary_key};
12,180✔
2413
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
12,180✔
2414
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
12,180✔
2415
}
12,180✔
2416

2417
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2418
{
18✔
2419
    REALM_ASSERT(!m_primary_key_col);
18✔
2420
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2421

9✔
2422
    // Check if existing
9✔
2423
    if (m_clusters.is_valid(object_key)) {
18✔
2424
        return object_key;
12✔
2425
    }
12✔
2426

3✔
2427
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2428
}
6✔
2429

2430
ObjKey Table::get_objkey(GlobalKey global_key) const
2431
{
18✔
2432
    ObjKey key;
18✔
2433
    REALM_ASSERT(!m_primary_key_col);
18✔
2434
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
2435
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
2436
        key = global_key.get_local_key(get_sync_file_id());
6✔
2437
    }
6✔
2438
    if (key && !is_valid(key)) {
18✔
2439
        key = realm::null_key;
6✔
2440
    }
6✔
2441
    return key;
18✔
2442
}
18✔
2443

2444
GlobalKey Table::get_object_id(ObjKey key) const
2445
{
144✔
2446
    auto col = get_primary_key_column();
144✔
2447
    if (col) {
144✔
2448
        const Obj obj = get_object(key);
24✔
2449
        auto val = obj.get_any(col);
24✔
2450
        return {val};
24✔
2451
    }
24✔
2452
    else {
120✔
2453
        return {key, get_sync_file_id()};
120✔
2454
    }
120✔
2455
    return {};
×
2456
}
×
2457

2458
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2459
{
75,747✔
2460
    auto primary_key_col = get_primary_key_column();
75,747✔
2461
    REALM_ASSERT(primary_key_col);
75,747✔
2462
    DataType type = DataType(primary_key_col.get_type());
75,747✔
2463
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
75,747✔
2464
                 primary_key.get_type() == type);
75,747✔
2465
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
75,747✔
2466
    return k ? m_clusters.get(k) : Obj{};
75,651✔
2467
}
75,747✔
2468

2469
Mixed Table::get_primary_key(ObjKey key) const
2470
{
590,406✔
2471
    auto primary_key_col = get_primary_key_column();
590,406✔
2472
    REALM_ASSERT(primary_key_col);
590,406✔
2473
    if (key.is_unresolved()) {
590,406✔
2474
        REALM_ASSERT(m_tombstones);
360✔
2475
        return m_tombstones->get(key).get_any(primary_key_col);
360✔
2476
    }
360✔
2477
    else {
590,046✔
2478
        return m_clusters.get(key).get_any(primary_key_col);
590,046✔
2479
    }
590,046✔
2480
}
590,406✔
2481

2482
GlobalKey Table::allocate_object_id_squeezed()
2483
{
19,674,996✔
2484
    // m_client_file_ident will be zero if we haven't been in contact with
9,795,213✔
2485
    // the server yet.
9,795,213✔
2486
    auto peer_id = get_sync_file_id();
19,674,996✔
2487
    auto sequence = allocate_sequence_number();
19,674,996✔
2488
    return GlobalKey{peer_id, sequence};
19,674,996✔
2489
}
19,674,996✔
2490

2491
namespace {
2492

2493
/// Calculate optimistic local ID that may collide with others. It is up to
2494
/// the caller to ensure that collisions are detected and that
2495
/// allocate_local_id_after_collision() is called to obtain a non-colliding
2496
/// ID.
2497
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
2498
{
24,531✔
2499
#if REALM_EXERCISE_OBJECT_ID_COLLISION
2500
    const uint64_t optimistic_mask = 0xff;
2501
#else
2502
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
24,531✔
2503
#endif
24,531✔
2504
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
24,531✔
2505
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
24,531✔
2506
}
24,531✔
2507

2508
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2509
{
12✔
2510
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2511
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2512
}
12✔
2513

2514
} // namespace
2515

2516
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2517
{
24,531✔
2518
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
24,531✔
2519

12,255✔
2520
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
24,531✔
2521
        Allocator& alloc = m_top.get_alloc();
24✔
2522
        Array collision_map{alloc};
24✔
2523
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2524

12✔
2525
        Array hi{alloc};
24✔
2526
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2527

12✔
2528
        // Entries are ordered by hi,lo
12✔
2529
        size_t found = hi.find_first(object_id.hi());
24✔
2530
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
2531
            Array lo{alloc};
24✔
2532
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
2533
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
2534
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
2535
                Array local_id{alloc};
24✔
2536
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
2537
                return ObjKey{local_id.get(candidate)};
24✔
2538
            }
24✔
2539
        }
24,507✔
2540
    }
24✔
2541

12,243✔
2542
    return optimistic;
24,507✔
2543
}
24,507✔
2544

2545
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2546
                                                     ObjKey colliding_local_id)
2547
{
12✔
2548
    // Possible optimization: Cache these accessors
6✔
2549
    Allocator& alloc = m_top.get_alloc();
12✔
2550
    Array collision_map{alloc};
12✔
2551
    Array hi{alloc};
12✔
2552
    Array lo{alloc};
12✔
2553
    Array local_id{alloc};
12✔
2554

6✔
2555
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2556
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2557
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2558
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2559

6✔
2560
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2561
    if (collision_map_ref) {
12✔
2562
        collision_map.init_from_parent(); // Throws
×
2563
    }
×
2564
    else {
12✔
2565
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2566
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2567
        collision_map.update_parent();
12✔
2568

6✔
2569
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2570
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2571
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2572
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2573
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2574
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2575
    }
12✔
2576

6✔
2577
    hi.init_from_parent();       // Throws
12✔
2578
    lo.init_from_parent();       // Throws
12✔
2579
    local_id.init_from_parent(); // Throws
12✔
2580

6✔
2581
    size_t num_entries = hi.size();
12✔
2582
    REALM_ASSERT(lo.size() == num_entries);
12✔
2583
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2584

6✔
2585
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2586
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2587
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2588
            ++i;
6✔
2589
        return i;
24✔
2590
    };
24✔
2591

6✔
2592
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
2593
        size_t i = lower_bound_object_id(object_id);
24✔
2594
        if (i != num_entries) {
24✔
2595
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
2596
            if (existing == object_id) {
6✔
2597
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
2598
                return;
×
2599
            }
×
2600
        }
24✔
2601
        hi.insert(i, int64_t(object_id.hi()));
24✔
2602
        lo.insert(i, int64_t(object_id.lo()));
24✔
2603
        local_id.insert(i, new_local_id.value);
24✔
2604
        ++num_entries;
24✔
2605
    };
24✔
2606

6✔
2607
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2608
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2609
    insert_collision(incoming_id, new_local_id);
12✔
2610
    insert_collision(colliding_id, colliding_local_id);
12✔
2611

6✔
2612
    return new_local_id;
12✔
2613
}
12✔
2614

2615
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2616
{
12,807✔
2617
    auto unres_key = key.get_unresolved();
12,807✔
2618

6,417✔
2619
    ensure_graveyard();
12,807✔
2620
    auto tombstone = m_tombstones->try_get_obj(unres_key);
12,807✔
2621
    if (tombstone) {
12,807✔
2622
        if (pk_col) {
366✔
2623
            auto existing_pk_value = tombstone.get_any(pk_col);
366✔
2624
            // It may just be the same object
165✔
2625
            if (existing_pk_value != pk_val) {
366✔
2626
                // We have a collision - create new ObjKey
6✔
2627
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
2628
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
2629
            }
12✔
2630
        }
354✔
2631
        return tombstone;
354✔
2632
    }
354✔
2633
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
12,441✔
2634
}
12,441✔
2635

2636
void Table::free_local_id_after_hash_collision(ObjKey key)
2637
{
4,999,569✔
2638
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
4,999,569✔
2639
        if (key.is_unresolved()) {
30✔
2640
            // Keys will always be inserted as resolved
12✔
2641
            key = key.get_unresolved();
24✔
2642
        }
24✔
2643
        // Possible optimization: Cache these accessors
15✔
2644
        Array collision_map{m_alloc};
30✔
2645
        Array local_id{m_alloc};
30✔
2646

15✔
2647
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2648
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2649
        collision_map.init_from_ref(collision_map_ref);
30✔
2650
        local_id.init_from_parent();
30✔
2651
        auto ndx = local_id.find_first(key.value);
30✔
2652
        if (ndx != realm::npos) {
30✔
2653
            Array hi{m_alloc};
24✔
2654
            Array lo{m_alloc};
24✔
2655

12✔
2656
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2657
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2658
            hi.init_from_parent();
24✔
2659
            lo.init_from_parent();
24✔
2660

12✔
2661
            hi.erase(ndx);
24✔
2662
            lo.erase(ndx);
24✔
2663
            local_id.erase(ndx);
24✔
2664
            if (hi.size() == 0) {
24✔
2665
                free_collision_table();
12✔
2666
            }
12✔
2667
        }
24✔
2668
    }
30✔
2669
}
4,999,569✔
2670

2671
void Table::free_collision_table()
2672
{
4,377✔
2673
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
4,377✔
2674
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2675
        m_top.set(top_position_for_collision_map, 0);
12✔
2676
    }
12✔
2677
}
4,377✔
2678

2679
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2680
{
3,840✔
2681
    while (number--) {
6,320,208✔
2682
        keys.push_back(create_object().get_key());
6,316,368✔
2683
    }
6,316,368✔
2684
}
3,840✔
2685

2686
void Table::create_objects(const std::vector<ObjKey>& keys)
2687
{
630✔
2688
    for (auto k : keys) {
5,616✔
2689
        create_object(k);
5,616✔
2690
    }
5,616✔
2691
}
630✔
2692

2693
void Table::dump_objects()
2694
{
×
2695
    m_clusters.dump_objects();
×
2696
    if (nb_unresolved())
×
2697
        m_tombstones->dump_objects();
×
2698
}
×
2699

2700
void Table::remove_object(ObjKey key)
2701
{
2,463,657✔
2702
    Group* g = get_parent_group();
2,463,657✔
2703

1,231,749✔
2704
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,463,657✔
2705
        CascadeState state(CascadeState::Mode::Strong, g);
3,015✔
2706
        state.m_to_be_deleted.emplace_back(m_key, key);
3,015✔
2707
        m_clusters.nullify_incoming_links(key, state);
3,015✔
2708
        remove_recursive(state);
3,015✔
2709
    }
3,015✔
2710
    else {
2,460,642✔
2711
        CascadeState state(CascadeState::Mode::None, g);
2,460,642✔
2712
        if (g) {
2,460,642✔
2713
            m_clusters.nullify_incoming_links(key, state);
2,378,805✔
2714
        }
2,378,805✔
2715
        m_clusters.erase(key, state);
2,460,642✔
2716
    }
2,460,642✔
2717
}
2,463,657✔
2718

2719
ObjKey Table::invalidate_object(ObjKey key)
2720
{
4,272✔
2721
    if (is_embedded())
4,272✔
2722
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2723
    REALM_ASSERT(!key.is_unresolved());
4,272✔
2724

2,103✔
2725
    Obj tombstone;
4,272✔
2726
    auto obj = get_object(key);
4,272✔
2727
    if (obj.has_backlinks(false)) {
4,272✔
2728
        // If the object has backlinks, we should make a tombstone
489✔
2729
        // and make inward links point to it,
489✔
2730
        if (auto primary_key_col = get_primary_key_column()) {
978✔
2731
            auto pk = obj.get_any(primary_key_col);
822✔
2732
            GlobalKey object_id{pk};
822✔
2733
            auto unres_key = global_to_local_object_id_hashed(object_id);
822✔
2734
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
822✔
2735
        }
822✔
2736
        else {
156✔
2737
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
2738
        }
156✔
2739
        tombstone.assign_pk_and_backlinks(obj);
978✔
2740
    }
978✔
2741

2,103✔
2742
    remove_object(key);
4,272✔
2743

2,103✔
2744
    return tombstone.get_key();
4,272✔
2745
}
4,272✔
2746

2747
void Table::remove_object_recursive(ObjKey key)
2748
{
39✔
2749
    size_t table_ndx = get_index_in_group();
39✔
2750
    if (table_ndx != realm::npos) {
39✔
2751
        CascadeState state(CascadeState::Mode::All, get_parent_group());
39✔
2752
        state.m_to_be_deleted.emplace_back(m_key, key);
39✔
2753
        nullify_links(state);
39✔
2754
        remove_recursive(state);
39✔
2755
    }
39✔
2756
    else {
×
2757
        // No links in freestanding table
2758
        CascadeState state(CascadeState::Mode::None);
×
2759
        m_clusters.erase(key, state);
×
2760
    }
×
2761
}
39✔
2762

2763
Table::Iterator Table::begin() const
2764
{
472,935✔
2765
    return Iterator(m_clusters, 0);
472,935✔
2766
}
472,935✔
2767

2768
Table::Iterator Table::end() const
2769
{
9,462,267✔
2770
    return Iterator(m_clusters, size());
9,462,267✔
2771
}
9,462,267✔
2772

2773
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2774
{
7,093,584✔
2775
    TableRef ret;
7,093,584✔
2776
    if (col_key) {
7,093,770✔
2777
        return table.get_opposite_table(col_key);
7,093,764✔
2778
    }
7,093,764✔
2779
    return ret;
2,147,483,653✔
2780
}
2,147,483,653✔
2781

2782
const uint64_t Table::max_num_columns;
2783

2784
void Table::build_column_mapping()
2785
{
2,762,814✔
2786
    // build column mapping from spec
1,627,629✔
2787
    // TODO: Optimization - Don't rebuild this for every change
1,627,629✔
2788
    m_spec_ndx2leaf_ndx.clear();
2,762,814✔
2789
    m_leaf_ndx2spec_ndx.clear();
2,762,814✔
2790
    m_leaf_ndx2colkey.clear();
2,762,814✔
2791
    size_t num_spec_cols = m_spec.get_column_count();
2,762,814✔
2792
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
2,762,814✔
2793
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
15,583,836✔
2794
        ColKey col_key = m_spec.get_key(spec_ndx);
12,821,022✔
2795
        unsigned leaf_ndx = col_key.get_index().val;
12,821,022✔
2796
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
12,821,022✔
2797
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
12,346,581✔
2798
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
12,346,581✔
2799
        }
12,346,581✔
2800
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
12,821,022✔
2801
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
12,821,022✔
2802
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
12,821,022✔
2803
    }
12,821,022✔
2804
}
2,762,814✔
2805

2806
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2807
{
708,123✔
2808
    REALM_ASSERT(!attr.test(col_attr_Indexed));
708,123✔
2809
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
708,123✔
2810

349,617✔
2811
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
708,123✔
2812
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
708,123✔
2813

349,617✔
2814
    // reuse lowest available leaf ndx:
349,617✔
2815
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
708,123✔
2816
    // look for an unused entry:
349,617✔
2817
    for (unsigned idx = 0; idx < lower; ++idx) {
5,479,953✔
2818
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
4,771,914✔
2819
            lower = idx;
84✔
2820
            break;
84✔
2821
        }
84✔
2822
    }
4,771,914✔
2823
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
708,123✔
2824
}
708,123✔
2825

2826
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2827
                                                  StringData origin_col_name) const noexcept
2828
{
×
2829
    BacklinkOrigin ret;
×
2830
    auto f = [&](ColKey backlink_col_key) {
×
2831
        auto origin_table = get_opposite_table(backlink_col_key);
×
2832
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2833
        if (origin_table->get_name() == origin_table_name &&
×
2834
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
2835
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
2836
            return IteratorControl::Stop;
×
2837
        }
×
2838
        return IteratorControl::AdvanceToNext;
×
2839
    };
×
2840
    this->for_each_backlink_column(f);
×
2841
    return ret;
×
2842
}
×
2843

2844
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2845
{
354✔
2846
    try {
354✔
2847
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
354✔
2848
        ColKey linked_column_key = get_opposite_column(backlink_col);
354✔
2849
        if (linked_table_key == m_key) {
354✔
2850
            return {{m_own_ref, linked_column_key}};
18✔
2851
        }
18✔
2852
        else {
336✔
2853
            Group* current_group = get_parent_group();
336✔
2854
            if (current_group) {
336✔
2855
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2856
                return {{linked_table_ref, linked_column_key}};
336✔
2857
            }
336✔
2858
        }
×
2859
    }
354✔
2860
    catch (...) {
×
2861
        // backlink column not found, returning empty optional
2862
    }
×
2863
    return {};
177✔
2864
}
354✔
2865

2866
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
2867
{
×
2868
    std::vector<std::pair<TableKey, ColKey>> origins;
×
2869
    auto f = [&](ColKey backlink_col_key) {
×
2870
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
2871
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2872
        origins.emplace_back(origin_table_key, origin_link_col);
×
2873
        return IteratorControl::AdvanceToNext;
×
2874
    };
×
2875
    this->for_each_backlink_column(f);
×
2876
    return origins;
×
2877
}
×
2878

2879
ColKey Table::get_primary_key_column() const
2880
{
18,327,012✔
2881
    return m_primary_key_col;
18,327,012✔
2882
}
18,327,012✔
2883

2884
void Table::set_primary_key_column(ColKey col_key)
2885
{
720✔
2886
    if (col_key == m_primary_key_col) {
720✔
2887
        return;
378✔
2888
    }
378✔
2889

171✔
2890
    if (Replication* repl = get_repl()) {
342✔
2891
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
2892
            throw RuntimeError(
×
2893
                ErrorCodes::BrokenInvariant,
×
2894
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
2895
        }
×
2896
    }
342✔
2897

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

171✔
2900
    if (col_key) {
342✔
2901
        check_column(col_key);
222✔
2902
        validate_column_is_unique(col_key);
222✔
2903
        do_set_primary_key_column(col_key);
222✔
2904
    }
222✔
2905
    else {
120✔
2906
        do_set_primary_key_column(col_key);
120✔
2907
    }
120✔
2908
}
342✔
2909

2910

2911
void Table::do_set_primary_key_column(ColKey col_key)
2912
{
93,621✔
2913
    if (col_key) {
93,621✔
2914
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
93,105✔
2915
        auto attr = m_spec.get_column_attr(spec_ndx);
93,105✔
2916
        if (attr.test(col_attr_FullText_Indexed)) {
93,105✔
2917
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
2918
        }
6✔
2919
    }
93,615✔
2920

46,353✔
2921
    if (m_primary_key_col) {
93,615✔
2922
        // If the search index has not been set explicitly on current pk col, we remove it again
255✔
2923
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
540✔
2924
        auto attr = m_spec.get_column_attr(spec_ndx);
540✔
2925
        if (!attr.test(col_attr_Indexed)) {
540✔
2926
            remove_search_index(m_primary_key_col);
528✔
2927
        }
528✔
2928
    }
540✔
2929

46,353✔
2930
    if (col_key) {
93,615✔
2931
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
93,099✔
2932
        do_add_search_index(col_key, IndexType::General);
93,099✔
2933
    }
93,099✔
2934
    else {
516✔
2935
        m_top.set(top_position_for_pk_col, 0);
516✔
2936
    }
516✔
2937

46,353✔
2938
    m_primary_key_col = col_key;
93,615✔
2939
}
93,615✔
2940

2941
bool Table::contains_unique_values(ColKey col) const
2942
{
816✔
2943
    if (search_index_type(col) == IndexType::General) {
816✔
2944
        auto search_index = get_search_index(col);
726✔
2945
        return !search_index->has_duplicate_values();
726✔
2946
    }
726✔
2947
    else {
90✔
2948
        TableView tv = where().find_all();
90✔
2949
        tv.distinct(col);
90✔
2950
        return tv.size() == size();
90✔
2951
    }
90✔
2952
}
816✔
2953

2954
void Table::validate_column_is_unique(ColKey col) const
2955
{
816✔
2956
    if (!contains_unique_values(col)) {
816✔
2957
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
2958
                                           get_class_name(), get_column_name(col)));
30✔
2959
    }
30✔
2960
}
816✔
2961

2962
void Table::validate_primary_column()
2963
{
1,794✔
2964
    if (ColKey col = get_primary_key_column()) {
1,794✔
2965
        validate_column_is_unique(col);
594✔
2966
    }
594✔
2967
}
1,794✔
2968

2969
ObjKey Table::get_next_valid_key()
2970
{
422,475✔
2971
    ObjKey key;
422,475✔
2972
    do {
422,481✔
2973
        key = ObjKey(allocate_sequence_number());
422,481✔
2974
    } while (m_clusters.is_valid(key));
422,481✔
2975

200,241✔
2976
    return key;
422,475✔
2977
}
422,475✔
2978

2979
namespace {
2980
template <class T>
2981
typename util::RemoveOptional<T>::type remove_optional(T val)
2982
{
87,903✔
2983
    return val;
87,903✔
2984
}
87,903✔
2985
template <>
2986
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
2987
{
5,424✔
2988
    return *val;
5,424✔
2989
}
5,424✔
2990
template <>
2991
bool remove_optional<Optional<bool>>(Optional<bool> val)
2992
{
11,529✔
2993
    return *val;
11,529✔
2994
}
11,529✔
2995
template <>
2996
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
2997
{
5,442✔
2998
    return *val;
5,442✔
2999
}
5,442✔
3000
template <>
3001
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
3002
{
6,060✔
3003
    return *val;
6,060✔
3004
}
6,060✔
3005
} // namespace
3006

3007
template <class F, class T>
3008
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
3009
{
162✔
3010
    Allocator& allocator = this->get_alloc();
162✔
3011
    bool from_nullability = is_nullable(key_from);
162✔
3012
    auto func = [&](Cluster* cluster) {
162✔
3013
        size_t sz = cluster->node_size();
162✔
3014

81✔
3015
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
3016
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
3017
        cluster->init_leaf(key_from, &from_arr);
162✔
3018
        cluster->init_leaf(key_to, &to_arr);
162✔
3019

81✔
3020
        for (size_t i = 0; i < sz; i++) {
1,512✔
3021
            if (from_nullability && from_arr.is_null(i)) {
1,356!
3022
                if (throw_on_null) {
66!
3023
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
3024
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
3025
                                                    get_column_name(key_from)));
6✔
3026
                }
6✔
3027
                else {
60✔
3028
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
60✔
3029
                }
60✔
3030
            }
66✔
3031
            else {
1,290✔
3032
                auto v = remove_optional(from_arr.get(i));
1,290✔
3033
                to_arr.set(i, v);
1,290✔
3034
            }
1,290✔
3035
        }
1,356✔
3036
    };
162✔
3037

81✔
3038
    m_clusters.update(func);
162✔
3039
}
162✔
3040

3041
template <class F, class T>
3042
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
3043
{
120✔
3044
    Allocator& allocator = this->get_alloc();
120✔
3045
    bool from_nullability = is_nullable(key_from);
120✔
3046
    auto func = [&](Cluster* cluster) {
120✔
3047
        size_t sz = cluster->node_size();
120✔
3048

60✔
3049
        ArrayInteger from_arr(allocator);
120✔
3050
        ArrayInteger to_arr(allocator);
120✔
3051
        cluster->init_leaf(key_from, &from_arr);
120✔
3052
        cluster->init_leaf(key_to, &to_arr);
120✔
3053

60✔
3054
        for (size_t i = 0; i < sz; i++) {
360✔
3055
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3056
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3057
            REALM_ASSERT(!ref_to);
240✔
3058

120✔
3059
            if (ref_from) {
240✔
3060
                BPlusTree<F> from_list(allocator);
120✔
3061
                BPlusTree<T> to_list(allocator);
120✔
3062
                from_list.init_from_ref(ref_from);
120✔
3063
                to_list.create();
120✔
3064
                size_t n = from_list.size();
120✔
3065
                for (size_t j = 0; j < n; j++) {
120,120✔
3066
                    auto v = from_list.get(j);
120,000✔
3067
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
3068
                        to_list.add(remove_optional(v));
115,068✔
3069
                    }
115,068✔
3070
                    else {
4,932✔
3071
                        if (throw_on_null) {
4,932!
3072
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3073
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3074
                                                            get_name(), get_column_name(key_from)));
×
3075
                        }
×
3076
                        else {
4,932✔
3077
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,932✔
3078
                        }
4,932✔
3079
                    }
4,932✔
3080
                }
120,000✔
3081
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3082
            }
120✔
3083
        }
240✔
3084
    };
120✔
3085

60✔
3086
    m_clusters.update(func);
120✔
3087
}
120✔
3088

3089
void Table::convert_column(ColKey from, ColKey to, bool throw_on_null)
3090
{
282✔
3091
    realm::DataType type_id = get_column_type(from);
282✔
3092
    bool _is_list = is_list(from);
282✔
3093
    if (_is_list) {
282✔
3094
        switch (type_id) {
120✔
3095
            case type_Int:
12✔
3096
                if (is_nullable(from)) {
12✔
3097
                    change_nullability_list<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3098
                }
6✔
3099
                else {
6✔
3100
                    change_nullability_list<int64_t, Optional<int64_t>>(from, to, throw_on_null);
6✔
3101
                }
6✔
3102
                break;
12✔
3103
            case type_Float:
12✔
3104
                change_nullability_list<float, float>(from, to, throw_on_null);
12✔
3105
                break;
12✔
3106
            case type_Double:
12✔
3107
                change_nullability_list<double, double>(from, to, throw_on_null);
12✔
3108
                break;
12✔
3109
            case type_Bool:
12✔
3110
                change_nullability_list<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3111
                break;
12✔
3112
            case type_String:
12✔
3113
                change_nullability_list<StringData, StringData>(from, to, throw_on_null);
12✔
3114
                break;
12✔
3115
            case type_Binary:
12✔
3116
                change_nullability_list<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3117
                break;
12✔
3118
            case type_Timestamp:
12✔
3119
                change_nullability_list<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3120
                break;
12✔
3121
            case type_ObjectId:
12✔
3122
                if (is_nullable(from)) {
12✔
3123
                    change_nullability_list<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3124
                }
6✔
3125
                else {
6✔
3126
                    change_nullability_list<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3127
                }
6✔
3128
                break;
12✔
3129
            case type_Decimal:
12✔
3130
                change_nullability_list<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3131
                break;
12✔
3132
            case type_UUID:
12✔
3133
                if (is_nullable(from)) {
12✔
3134
                    change_nullability_list<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3135
                }
6✔
3136
                else {
6✔
3137
                    change_nullability_list<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3138
                }
6✔
3139
                break;
12✔
3140
            case type_Link:
✔
3141
            case type_TypedLink:
✔
3142
                // Can't have lists of these types
3143
            case type_Mixed:
✔
3144
                // These types are no longer supported at all
3145
                REALM_UNREACHABLE();
3146
                break;
×
3147
        }
162✔
3148
    }
162✔
3149
    else {
162✔
3150
        switch (type_id) {
162✔
3151
            case type_Int:
36✔
3152
                if (is_nullable(from)) {
36✔
3153
                    change_nullability<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3154
                }
6✔
3155
                else {
30✔
3156
                    change_nullability<int64_t, Optional<int64_t>>(from, to, throw_on_null);
30✔
3157
                }
30✔
3158
                break;
36✔
3159
            case type_Float:
12✔
3160
                change_nullability<float, float>(from, to, throw_on_null);
12✔
3161
                break;
12✔
3162
            case type_Double:
12✔
3163
                change_nullability<double, double>(from, to, throw_on_null);
12✔
3164
                break;
12✔
3165
            case type_Bool:
12✔
3166
                change_nullability<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3167
                break;
12✔
3168
            case type_String:
30✔
3169
                change_nullability<StringData, StringData>(from, to, throw_on_null);
30✔
3170
                break;
30✔
3171
            case type_Binary:
12✔
3172
                change_nullability<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3173
                break;
12✔
3174
            case type_Timestamp:
12✔
3175
                change_nullability<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3176
                break;
12✔
3177
            case type_ObjectId:
12✔
3178
                if (is_nullable(from)) {
12✔
3179
                    change_nullability<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3180
                }
6✔
3181
                else {
6✔
3182
                    change_nullability<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3183
                }
6✔
3184
                break;
12✔
3185
            case type_Decimal:
12✔
3186
                change_nullability<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3187
                break;
12✔
3188
            case type_UUID:
12✔
3189
                if (is_nullable(from)) {
12✔
3190
                    change_nullability<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3191
                }
6✔
3192
                else {
6✔
3193
                    change_nullability<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3194
                }
6✔
3195
                break;
12✔
3196
            case type_TypedLink:
✔
3197
            case type_Link:
✔
3198
                // Always nullable, so can't convert
3199
            case type_Mixed:
✔
3200
                // These types are no longer supported at all
3201
                REALM_UNREACHABLE();
3202
                break;
×
3203
        }
162✔
3204
    }
162✔
3205
}
282✔
3206

3207

3208
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3209
{
522✔
3210
    if (col_key.is_nullable() == nullable)
522✔
3211
        return col_key;
240✔
3212

141✔
3213
    check_column(col_key);
282✔
3214

141✔
3215
    auto index_type = search_index_type(col_key);
282✔
3216
    std::string column_name(get_column_name(col_key));
282✔
3217
    auto type = col_key.get_type();
282✔
3218
    auto attr = col_key.get_attrs();
282✔
3219
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3220
    if (nullable) {
282✔
3221
        attr.set(col_attr_Nullable);
150✔
3222
    }
150✔
3223
    else {
132✔
3224
        attr.reset(col_attr_Nullable);
132✔
3225
    }
132✔
3226

141✔
3227
    ColKey new_col = generate_col_key(type, attr);
282✔
3228
    do_insert_root_column(new_col, type, "__temporary");
282✔
3229

141✔
3230
    try {
282✔
3231
        convert_column(col_key, new_col, throw_on_null);
282✔
3232
    }
282✔
3233
    catch (...) {
144✔
3234
        // remove any partially filled column
3✔
3235
        remove_column(new_col);
6✔
3236
        throw;
6✔
3237
    }
6✔
3238

138✔
3239
    if (is_pk_col) {
276✔
3240
        // If we go from non nullable to nullable, no values change,
6✔
3241
        // so it is safe to preserve the pk column. Otherwise it is not
6✔
3242
        // safe as a null entry might have been converted to default value.
6✔
3243
        do_set_primary_key_column(nullable ? new_col : ColKey{});
9✔
3244
    }
12✔
3245

138✔
3246
    erase_root_column(col_key);
276✔
3247
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3248

138✔
3249
    if (index_type != IndexType::None)
276✔
3250
        do_add_search_index(new_col, index_type);
30✔
3251

138✔
3252
    return new_col;
276✔
3253
}
276✔
3254

3255
bool Table::has_any_embedded_objects()
3256
{
2,469,921✔
3257
    if (!m_has_any_embedded_objects) {
2,469,921✔
3258
        m_has_any_embedded_objects = false;
24,489✔
3259
        for_each_public_column([&](ColKey col_key) {
57,222✔
3260
            auto target_table_key = get_opposite_table_key(col_key);
57,222✔
3261
            if (target_table_key && is_link_type(col_key.get_type())) {
57,222✔
3262
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
10,611✔
3263
                if (target_table->is_embedded()) {
10,611✔
3264
                    m_has_any_embedded_objects = true;
8,472✔
3265
                    return IteratorControl::Stop; // early out
8,472✔
3266
                }
8,472✔
3267
            }
48,750✔
3268
            return IteratorControl::AdvanceToNext;
48,750✔
3269
        });
48,750✔
3270
    }
24,489✔
3271
    return *m_has_any_embedded_objects;
2,469,921✔
3272
}
2,469,921✔
3273

3274
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3275
{
154,464✔
3276
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
154,464✔
3277
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
154,464✔
3278
}
154,464✔
3279

3280
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3281
{
47,154✔
3282
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
162,453✔
3283
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
154,815✔
3284
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
39,516✔
3285
        }
39,516✔
3286
    }
154,815✔
3287

23,460✔
3288
    return {};
27,279✔
3289
}
47,154✔
3290

3291
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3292
{
47,082✔
3293
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
47,082✔
3294

23,424✔
3295
    if (!backlink_col_key) {
47,082✔
3296
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
7,638✔
3297
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
7,638✔
3298

3,819✔
3299
        if (Replication* repl = get_repl())
7,638✔
3300
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
7,332✔
3301
                                    m_key); // Throws
7,332✔
3302
    }
7,638✔
3303

23,424✔
3304
    return backlink_col_key;
47,082✔
3305
}
47,082✔
3306

3307
TableKey Table::get_opposite_table_key(ColKey col_key) const
3308
{
14,399,919✔
3309
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,399,919✔
3310
}
14,399,919✔
3311

3312
bool Table::links_to_self(ColKey col_key) const
3313
{
58,839✔
3314
    return get_opposite_table_key(col_key) == m_key;
58,839✔
3315
}
58,839✔
3316

3317
TableRef Table::get_opposite_table(ColKey col_key) const
3318
{
7,761,417✔
3319
    if (auto k = get_opposite_table_key(col_key)) {
7,761,417✔
3320
        return get_parent_group()->get_table(k);
7,702,287✔
3321
    }
7,702,287✔
3322
    return {};
59,130✔
3323
}
59,130✔
3324

3325
ColKey Table::get_opposite_column(ColKey col_key) const
3326
{
16,244,763✔
3327
    return ColKey(m_opposite_column.get(col_key.get_index().val));
16,244,763✔
3328
}
16,244,763✔
3329

3330
ColKey Table::find_opposite_column(ColKey col_key) const
3331
{
×
3332
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3333
        if (m_opposite_column.get(i) == col_key.value) {
×
3334
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3335
        }
×
3336
    }
×
3337
    return ColKey();
×
3338
}
×
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