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

realm / realm-core / 2214

10 Apr 2024 11:21PM UTC coverage: 91.813% (-0.8%) from 92.623%
2214

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94848 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1815 existing lines in 77 files now uncovered.

242945 of 264608 relevant lines covered (91.81%)

6136478.37 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
{
19,032✔
268
    if (size() != other.size())
19,032✔
269
        return false;
×
270
    size_t sz = size();
19,032✔
271
    for (size_t i = 0; i < sz; i++) {
30,030✔
272
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
19,164✔
273
        if (this->at(i).second != other.at(i).second)
19,164✔
274
            return false;
8,166✔
275
    }
19,164✔
276
    return true;
14,919✔
277
}
19,032✔
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
{
64,758✔
324
    switch (table_type) {
64,758✔
325
        case Table::Type::TopLevel:
61,470✔
326
            return o << "TopLevel";
61,470✔
327
        case Table::Type::Embedded:
3,288✔
328
            return o << "Embedded";
3,288✔
329
        case Table::Type::TopLevelAsymmetric:
✔
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,264✔
338
    // Link column can be a single Link, LinkList, or BackLink.
402,093✔
339
    REALM_ASSERT(m_current_table->valid_column(ck));
804,264✔
340
    ColumnType type = ck.get_type();
804,264✔
341
    if (type == col_type_Link || type == col_type_BackLink) {
804,264✔
342
        m_current_table = m_current_table->get_opposite_table(ck);
88,227✔
343
        m_link_cols.push_back(ck);
88,227✔
344
        return true;
88,227✔
345
    }
88,227✔
346
    return false;
716,037✔
347
}
716,037✔
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
{
26,124✔
384
    m_spec.set_parent(&m_top, top_position_for_spec);
26,124✔
385
    m_index_refs.set_parent(&m_top, top_position_for_search_indexes);
26,124✔
386
    m_opposite_table.set_parent(&m_top, top_position_for_opposite_table);
26,124✔
387
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
26,124✔
388
    m_cookie = cookie_created;
26,124✔
389
}
26,124✔
390

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

227,604✔
399
    ColumnAttrMask attr;
461,751✔
400
    if (collection_type) {
461,751✔
401
        switch (*collection_type) {
147,504✔
402
            case CollectionType::List:
60,531✔
403
                attr.set(col_attr_List);
60,531✔
404
                break;
60,531✔
405
            case CollectionType::Set:
43,215✔
406
                attr.set(col_attr_Set);
43,215✔
407
                break;
43,215✔
408
            case CollectionType::Dictionary:
43,758✔
409
                attr.set(col_attr_Dictionary);
43,758✔
410
                break;
43,758✔
411
        }
461,751✔
412
    }
461,751✔
413
    if (nullable || type == type_Mixed)
461,751✔
414
        attr.set(col_attr_Nullable);
154,881✔
415
    ColKey col_key = generate_col_key(ColumnType(type), attr);
461,751✔
416

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

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

36,312✔
434
    m_has_any_embedded_objects.reset();
73,455✔
435

36,312✔
436
    DataType data_type = type_Link;
73,455✔
437
    ColumnAttrMask attr;
73,455✔
438
    if (collection_type) {
73,455✔
439
        switch (*collection_type) {
43,179✔
440
            case CollectionType::List:
21,117✔
441
                attr.set(col_attr_List);
21,117✔
442
                break;
21,117✔
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,276✔
453
    }
30,276✔
454
    else {
30,276✔
455
        attr.set(col_attr_Nullable);
30,276✔
456
    }
30,276✔
457
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
73,455✔
458

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

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

8,742✔
468
    do {
23,403✔
469
        cascade_state.send_notifications();
23,403✔
470

11,043✔
471
        for (auto& l : cascade_state.m_to_be_nullified) {
11,067✔
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();
23,403✔
479

11,043✔
480
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
23,403✔
481
        for (auto obj : to_delete) {
19,437✔
482
            auto table = obj.first == m_key ? this : group->get_table_unchecked(obj.first);
14,103✔
483
            // This might add to the list of objects that should be deleted
8,382✔
484
            REALM_ASSERT(!obj.second.is_unresolved());
16,776✔
485
            table->m_clusters.erase(obj.second, cascade_state);
16,776✔
486
        }
16,776✔
487
        nullify_links(cascade_state);
23,403✔
488
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
23,403✔
489
}
18,804✔
490

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

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

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

2,370✔
518
    if (Replication* repl = get_repl())
4,803✔
519
        repl->erase_column(this, col_key); // Throws
516✔
520

2,370✔
521
    if (col_key == m_primary_key_col) {
4,803✔
522
        do_set_primary_key_column(ColKey());
792✔
523
    }
792✔
524
    else {
4,011✔
525
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
4,011✔
526
    }
4,011✔
527

2,370✔
528
    erase_root_column(col_key); // Throws
4,803✔
529
    m_has_any_embedded_objects.reset();
4,803✔
530
}
4,803✔
531

532

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

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

51✔
540
    bump_content_version();
96✔
541
    bump_storage_version();
96✔
542

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

547

548
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
549
{
2,549,190✔
550
    // well, not quite "direct", more like "almost direct":
1,603,809✔
551
    Array table_top(alloc);
2,549,190✔
552
    table_top.init_from_ref(top_ref);
2,549,190✔
553
    if (table_top.size() > 3) {
2,549,244✔
554
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
2,549,226✔
555
        return TableKey(int32_t(rot.get_as_int()));
2,549,226✔
556
    }
2,549,226✔
557
    else {
2,147,483,665✔
558
        return TableKey();
2,147,483,665✔
559
    }
2,147,483,665✔
560
}
2,549,190✔
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,971,873✔
565
    REALM_ASSERT(!(is_writable && is_frzn));
1,971,873✔
566
    m_is_frozen = is_frzn;
1,971,873✔
567
    m_alloc.set_read_only(!is_writable);
1,971,873✔
568
    // Load from allocated memory
1,284,819✔
569
    m_top.set_parent(parent, ndx_in_parent);
1,971,873✔
570
    m_top.init_from_ref(top_ref);
1,971,873✔
571

1,284,819✔
572
    m_spec.init_from_parent();
1,971,873✔
573

1,284,819✔
574
    while (m_top.size() <= top_position_for_pk_col) {
1,971,873✔
575
        m_top.add(0);
×
576
    }
×
577

1,284,819✔
578
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
1,971,873✔
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,971,873✔
584

1,284,819✔
585
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
1,971,873✔
586
    if (!rot.is_tagged()) {
1,971,873✔
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,971,873✔
592

1,284,819✔
593
    // index setup relies on column mapping being up to date:
1,284,819✔
594
    build_column_mapping();
1,971,873✔
595
    if (m_top.get_as_ref(top_position_for_search_indexes) == 0) {
1,971,873✔
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,971,873✔
610
        m_opposite_table.init_from_parent();
1,971,873✔
611
        m_opposite_column.init_from_parent();
1,971,873✔
612
        m_index_refs.init_from_parent();
1,971,873✔
613
        m_index_accessors.resize(m_index_refs.size());
1,971,873✔
614
    }
1,971,873✔
615
    if (!m_top.get_as_ref_or_tagged(top_position_for_column_key).is_tagged()) {
1,971,873✔
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,971,873✔
619
    if (!rot_version.is_tagged()) {
1,971,873✔
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,971,873✔
624
        m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
1,971,873✔
625

1,284,819✔
626
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
1,971,873✔
627
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
1,707,516✔
628

1,284,819✔
629
    if (m_top.size() <= top_position_for_flags) {
1,971,873✔
630
        m_table_type = Type::TopLevel;
60✔
631
    }
60✔
632
    else {
1,971,813✔
633
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
1,971,813✔
634
        m_table_type = Type(flags & table_type_mask);
1,971,813✔
635
    }
1,971,813✔
636
    m_has_any_embedded_objects.reset();
1,971,873✔
637

1,284,819✔
638
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
1,971,873✔
639
        // Tombstones exists
19,821✔
640
        if (!m_tombstones) {
42,153✔
641
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
29,880✔
642
        }
29,880✔
643
        m_tombstones->init_from_parent();
42,153✔
644
    }
42,153✔
645
    else {
1,929,720✔
646
        m_tombstones = nullptr;
1,929,720✔
647
    }
1,929,720✔
648
    m_cookie = cookie_initialized;
1,971,873✔
649
}
1,971,873✔
650

651

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

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

263,916✔
659
    if (target_table) {
535,206✔
660
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
73,449✔
661
        target_table->check_column(backlink_col_key);
73,449✔
662

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

263,916✔
667
    if (Replication* repl = get_repl())
535,206✔
668
        repl->insert_column(this, col_key, type, name, target_table); // Throws
517,452✔
669

263,916✔
670
    return col_key;
535,206✔
671
}
535,206✔
672

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

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

49,128✔
685
    table->traverse_clusters(f);
99,129✔
686
}
99,129✔
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
{
99,153✔
704
    auto col_ndx = col_key.get_index().val;
99,153✔
705
    SearchIndex* index = m_index_accessors[col_ndx].get();
99,153✔
706
    DataType type = get_column_type(col_key);
99,153✔
707

49,140✔
708
    if (type == type_Int) {
99,153✔
709
        if (is_nullable(col_key)) {
48,726✔
710
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
13,791✔
711
        }
13,791✔
712
        else {
34,935✔
713
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
34,935✔
714
        }
34,935✔
715
    }
48,726✔
716
    else if (type == type_Bool) {
50,427✔
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) {
50,376✔
725
        if (col_key.is_list()) {
7,359✔
726
            do_bulk_insert_index_list(this, index, col_key, get_alloc());
24✔
727
        }
24✔
728
        else {
7,335✔
729
            do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
7,335✔
730
        }
7,335✔
731
    }
7,359✔
732
    else if (type == type_Timestamp) {
43,017✔
733
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
90✔
734
    }
90✔
735
    else if (type == type_ObjectId) {
42,927✔
736
        if (is_nullable(col_key)) {
41,349✔
737
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
984✔
738
        }
984✔
739
        else {
40,365✔
740
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
40,365✔
741
        }
40,365✔
742
    }
41,349✔
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✔
UNCOV
754
    else {
×
UNCOV
755
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
UNCOV
756
    }
×
757
}
99,153✔
758

759
void Table::erase_from_search_indexes(ObjKey key)
760
{
5,000,478✔
761
    // Tombstones do not use index - will crash if we try to erase values
2,500,155✔
762
    if (!key.is_unresolved()) {
5,000,478✔
763
        for (auto&& index : m_index_accessors) {
6,667,311✔
764
            if (index) {
6,667,311✔
765
                index->erase(key);
255,285✔
766
            }
255,285✔
767
        }
6,667,311✔
768
    }
4,989,438✔
769
}
5,000,478✔
770

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

11,517,255✔
778
    auto sz = m_index_accessors.size();
23,063,706✔
779
    // values are sorted by column index - there may be values missing
11,517,255✔
780
    auto value = values.begin();
23,063,706✔
781
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
56,967,639✔
782
        // Check if initial value is provided
16,820,949✔
783
        Mixed init_value;
33,904,344✔
784
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
33,904,344✔
785
            // Value for this column is provided
228,393✔
786
            init_value = value->value;
479,193✔
787
            ++value;
479,193✔
788
        }
479,193✔
789

16,820,949✔
790
        if (auto&& index = m_index_accessors[column_ndx]) {
33,904,344✔
791
            // There is an index for this column
513,387✔
792
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,048,785✔
793
            if (col_key.is_collection())
1,048,785✔
794
                continue;
102✔
795
            auto type = col_key.get_type();
1,048,683✔
796
            auto attr = col_key.get_attrs();
1,048,683✔
797
            bool nullable = attr.test(col_attr_Nullable);
1,048,683✔
798
            switch (type) {
1,048,683✔
799
                case col_type_Int:
478,521✔
800
                    if (init_value.is_null()) {
478,521✔
801
                        index->insert(key, ArrayIntNull::default_value(nullable));
165,741✔
802
                    }
165,741✔
803
                    else {
312,780✔
804
                        index->insert(key, init_value.get<int64_t>());
312,780✔
805
                    }
312,780✔
806
                    break;
478,521✔
807
                case col_type_Bool:
6,717✔
808
                    if (init_value.is_null()) {
6,717✔
809
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,717✔
810
                    }
6,717✔
811
                    else {
×
812
                        index->insert(key, init_value.get<bool>());
×
813
                    }
×
814
                    break;
6,717✔
815
                case col_type_String:
447,276✔
816
                    if (init_value.is_null()) {
447,276✔
817
                        index->insert(key, ArrayString::default_value(nullable));
431,172✔
818
                    }
431,172✔
819
                    else {
16,104✔
820
                        index->insert(key, init_value.get<String>());
16,104✔
821
                    }
16,104✔
822
                    break;
447,276✔
823
                case col_type_Timestamp:
6,192✔
824
                    if (init_value.is_null()) {
6,192✔
825
                        index->insert(key, ArrayTimestamp::default_value(nullable));
6,192✔
826
                    }
6,192✔
827
                    else {
×
828
                        index->insert(key, init_value.get<Timestamp>());
×
829
                    }
×
830
                    break;
6,192✔
831
                case col_type_ObjectId:
90,561✔
832
                    if (init_value.is_null()) {
90,561✔
833
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
834
                    }
6,120✔
835
                    else {
84,441✔
836
                        index->insert(key, init_value.get<ObjectId>());
84,441✔
837
                    }
84,441✔
838
                    break;
90,561✔
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,048,683✔
853
        }
1,048,683✔
854
    }
33,904,344✔
855
}
23,063,706✔
856

857
void Table::clear_indexes()
858
{
5,949✔
859
    for (auto&& index : m_index_accessors) {
56,730✔
860
        if (index) {
56,730✔
861
            index->clear();
4,683✔
862
        }
4,683✔
863
    }
56,730✔
864
}
5,949✔
865

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

49,236✔
870
    // Early-out if already indexed
49,236✔
871
    if (m_index_accessors[column_ndx] != nullptr)
99,342✔
872
        return;
150✔
873

49,161✔
874
    if (!StringIndex::type_supported(DataType(col_key.get_type())) ||
99,192✔
875
        (col_key.is_collection() && !(col_key.is_list() && col_key.get_type() == col_type_String)) ||
99,177✔
876
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
99,171✔
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

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

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

49,140✔
894
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
99,150✔
895

49,140✔
896
    populate_search_index(col_key);
99,150✔
897
}
99,150✔
898

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

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

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

1,965✔
910
    switch (type) {
3,927✔
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,873✔
925
            if (attr.test(col_attr_Indexed)) {
3,873✔
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,843✔
930
                this->remove_search_index(col_key);
×
931
            }
×
932
            break;
3,843✔
933
    }
3,897✔
934

1,950✔
935
    do_add_search_index(col_key, type);
3,897✔
936

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

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

609✔
947
    // Early-out if non-indexed
609✔
948
    if (m_index_accessors[column_ndx.val] == nullptr)
1,251✔
949
        return;
60✔
950

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

579✔
957
    m_index_refs.set(column_ndx.val, 0);
1,191✔
958

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

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

977
bool Table::is_enumerated(ColKey col_key) const noexcept
978
{
50,610✔
979
    size_t col_ndx = colkey2spec_ndx(col_key);
50,610✔
980
    return m_spec.is_string_enum_type(col_ndx);
50,610✔
981
}
50,610✔
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
{
5,079✔
999
    ColumnType col_type = col_key.get_type();
5,079✔
1000
    if (is_link_type(col_type)) {
5,079✔
1001
        auto target_table = get_opposite_table(col_key);
459✔
1002
        auto target_column = get_opposite_column(col_key);
459✔
1003
        target_table->do_erase_root_column(target_column);
459✔
1004
    }
459✔
1005
    do_erase_root_column(col_key); // Throws
5,079✔
1006
}
5,079✔
1007

1008

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

351,318✔
1014
    // locate insertion point: ordinary columns must come before backlink columns
351,318✔
1015
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
670,755✔
1016

351,318✔
1017
    if (!col_key) {
711,714✔
1018
        col_key = generate_col_key(type, {});
81,087✔
1019
    }
81,087✔
1020

351,318✔
1021
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
711,714✔
1022
    if (col_key.is_dictionary()) {
711,714✔
1023
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
55,092✔
1024
    }
55,092✔
1025
    auto col_ndx = col_key.get_index().val;
711,714✔
1026
    build_column_mapping();
711,714✔
1027
    REALM_ASSERT(col_ndx <= m_index_refs.size());
711,714✔
1028
    if (col_ndx == m_index_refs.size()) {
711,714✔
1029
        m_index_refs.insert(col_ndx, 0);
711,468✔
1030
    }
711,468✔
1031
    else {
246✔
1032
        m_index_refs.set(col_ndx, 0);
246✔
1033
    }
246✔
1034
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
711,714✔
1035
    if (col_ndx == m_opposite_table.size()) {
711,714✔
1036
        // m_opposite_table and m_opposite_column are always resized together!
351,192✔
1037
        m_opposite_table.insert(col_ndx, TableKey().value);
711,462✔
1038
        m_opposite_column.insert(col_ndx, ColKey().value);
711,462✔
1039
    }
711,462✔
1040
    else {
252✔
1041
        m_opposite_table.set(col_ndx, TableKey().value);
252✔
1042
        m_opposite_column.set(col_ndx, ColKey().value);
252✔
1043
    }
252✔
1044
    refresh_index_accessors();
711,714✔
1045
    m_clusters.insert_column(col_key);
711,714✔
1046
    if (m_tombstones) {
711,714✔
1047
        m_tombstones->insert_column(col_key);
309✔
1048
    }
309✔
1049

351,318✔
1050
    bump_storage_version();
711,714✔
1051

351,318✔
1052
    return col_key;
711,714✔
1053
}
711,714✔
1054

1055

1056
void Table::do_erase_root_column(ColKey col_key)
1057
{
5,538✔
1058
    size_t col_ndx = col_key.get_index().val;
5,538✔
1059
    // If the column had a source index we have to remove and destroy that as well
2,730✔
1060
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
5,538✔
1061
    if (index_ref) {
5,538✔
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);
5,538✔
1067
    m_opposite_column.set(col_ndx, ColKey().value);
5,538✔
1068
    m_index_accessors[col_ndx] = nullptr;
5,538✔
1069
    m_clusters.remove_column(col_key);
5,538✔
1070
    if (m_tombstones)
5,538✔
1071
        m_tombstones->remove_column(col_key);
333✔
1072
    size_t spec_ndx = colkey2spec_ndx(col_key);
5,538✔
1073
    m_spec.erase_column(spec_ndx);
5,538✔
1074
    m_top.adjust(top_position_for_column_key, 2);
5,538✔
1075

2,730✔
1076
    build_column_mapping();
5,538✔
1077
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
10,497✔
1078
        REALM_ASSERT(m_index_accessors.back() == nullptr);
4,959✔
1079
        m_index_accessors.pop_back();
4,959✔
1080
    }
4,959✔
1081
    bump_content_version();
5,538✔
1082
    bump_storage_version();
5,538✔
1083
}
5,538✔
1084

1085
Query Table::where(const Dictionary& dict) const
1086
{
12✔
1087
    return Query(m_own_ref, dict.clone_as_obj_list());
12✔
1088
}
12✔
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
{
263,745✔
1217
    while (m_top.size() <= top_position_for_flags)
263,745✔
1218
        m_top.add(0);
×
1219

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

1229

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

1236
void Table::fully_detach() noexcept
1237
{
1,946,187✔
1238
    m_spec.detach();
1,946,187✔
1239
    m_top.detach();
1,946,187✔
1240
    m_index_refs.detach();
1,946,187✔
1241
    m_opposite_table.detach();
1,946,187✔
1242
    m_opposite_column.detach();
1,946,187✔
1243
    m_index_accessors.clear();
1,946,187✔
1244
}
1,946,187✔
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,775,972✔
1265
    if (m_index_accessors[col_key.get_index().val].get()) {
5,775,972✔
1266
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
955,410✔
1267
        bool fulltext = attr.test(col_attr_FullText_Indexed);
955,410✔
1268
        return fulltext ? IndexType::Fulltext : IndexType::General;
955,191✔
1269
    }
955,410✔
1270
    return IndexType::None;
4,820,562✔
1271
}
4,820,562✔
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
{
3,085,302✔
1351
    const Array& real_top = m_top;
3,085,302✔
1352
    ArrayParent* parent = real_top.get_parent();
3,085,302✔
1353
    if (!parent)
3,085,302✔
1354
        return StringData("");
48✔
1355
    REALM_ASSERT(dynamic_cast<Group*>(parent));
3,085,254✔
1356
    return static_cast<Group*>(parent)->get_table_name(get_key());
3,085,254✔
1357
}
3,085,254✔
1358

1359
StringData Table::get_class_name() const noexcept
1360
{
670,167✔
1361
    return Group::table_name_to_class_name(get_name());
670,167✔
1362
}
670,167✔
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
{
727,200✔
1386
    REALM_ASSERT_DEBUG(valid_column(col_key));
727,200✔
1387
    return col_key.get_attrs().test(col_attr_Nullable);
727,200✔
1388
}
727,200✔
1389

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

1396

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

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

132,699✔
1420
    // Table key value
132,699✔
1421
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
267,096✔
1422
    top.add(rot);
267,096✔
1423

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

132,699✔
1461
    REALM_ASSERT(top.size() == top_array_size);
267,096✔
1462

132,699✔
1463
    return top.get_ref();
267,096✔
1464
}
267,096✔
1465

1466
void Table::ensure_graveyard()
1467
{
12,996✔
1468
    if (!m_tombstones) {
12,996✔
1469
        while (m_top.size() < top_position_for_tombstones)
2,199✔
1470
            m_top.add(0);
×
1471
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
2,199✔
1472
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
2,199✔
1473
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
2,199✔
1474
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
2,199✔
1475
        m_tombstones->init_from_parent();
2,199✔
1476
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
7,347✔
1477
            ts->insert_column(col);
7,347✔
1478
            return IteratorControl::AdvanceToNext;
7,347✔
1479
        });
7,347✔
1480
    }
2,199✔
1481
}
12,996✔
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,696✔
1503
    Group* g = get_parent_group();
6,696✔
1504
    bool maybe_has_incoming_links = g && !is_asymmetric();
6,696✔
1505

3,348✔
1506
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
6,696✔
1507
        CascadeState state(CascadeState::Mode::Strong, g);
5,928✔
1508
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
3,828✔
1509
            state.m_to_be_deleted.emplace_back(m_key, k);
1,731✔
1510
        });
1,731✔
1511
        if (maybe_has_incoming_links)
5,928✔
1512
            nullify_links(state);
5,928✔
1513
        remove_recursive(state);
5,928✔
1514
    }
5,928✔
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,696✔
1525
}
6,696✔
1526

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

1534

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

16,781,247✔
1543
    return static_cast<Group*>(parent);
33,919,812✔
1544
}
33,919,812✔
1545

1546
inline uint64_t Table::get_sync_file_id() const noexcept
1547
{
38,921,211✔
1548
    Group* g = get_parent_group();
38,921,211✔
1549
    return g ? g->get_sync_file_id() : 0;
32,274,906✔
1550
}
38,921,211✔
1551

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

1562
uint64_t Table::allocate_sequence_number()
1563
{
19,905,270✔
1564
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
19,905,270✔
1565
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,806,591✔
1566
    rot = RefOrTagged::make_tagged(sn + 1);
19,905,270✔
1567
    m_top.set(top_position_for_sequence_number, rot);
19,905,270✔
1568

9,954,750✔
1569
    return sn;
19,905,270✔
1570
}
19,905,270✔
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
{
270,993✔
1589
    return get_opposite_table(col_key);
270,993✔
1590
}
270,993✔
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
{
34,290✔
1642
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
34,290✔
1643
    LeafType leaf(get_alloc());
34,290✔
1644

17,133✔
1645
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
34,308✔
1646
        // direct aggregate on the leaf
17,142✔
1647
        cluster->init_leaf(column_key, &leaf);
34,308✔
1648
        st.m_key_offset = cluster->get_offset();
34,308✔
1649
        st.m_key_values = cluster->get_key_array();
34,308✔
1650
        st.set_payload_column(&leaf);
34,308✔
1651
        bool cont = true;
34,308✔
1652
        size_t sz = leaf.size();
34,308✔
1653
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
138,474✔
1654
            cont = st.match(local_index);
104,166✔
1655
        }
104,166✔
1656
        return IteratorControl::AdvanceToNext;
34,308✔
1657
    };
34,308✔
1658

17,133✔
1659
    traverse_clusters(f);
34,290✔
1660
}
34,290✔
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
{
28,014✔
1688
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
28,014✔
1689
}
28,014✔
1690

1691

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

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

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

17,433✔
1709
    if (!col_key.is_nullable() && value_is_null(value)) {
35,025!
1710
        return {}; // this is a precaution/optimization
6✔
1711
    }
6✔
1712
    // You cannot call GetIndexData on ObjKey
17,430✔
1713
    if constexpr (!std::is_same_v<T, ObjKey>) {
35,019✔
1714
        if (SearchIndex* index = get_search_index(col_key)) {
35,001!
1715
            return index->find_first(value);
27,153✔
1716
        }
27,153✔
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,391✔
1822
    return find_first<StringData>(col_key, value);
11,391✔
1823
}
11,391✔
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,780,050✔
1948
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,680,579✔
1949
}
1,780,050✔
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
{
695,364✔
1955
    // There is no top for sub-tables sharing spec
345,858✔
1956
    if (m_top.is_attached()) {
695,364✔
1957
        m_top.update_from_parent();
695,364✔
1958
        m_spec.update_from_parent();
695,364✔
1959
        m_clusters.update_from_parent();
695,364✔
1960
        m_index_refs.update_from_parent();
695,364✔
1961
        for (auto&& index : m_index_accessors) {
2,053,788✔
1962
            if (index != nullptr) {
2,053,788✔
1963
                index->update_from_parent();
284,265✔
1964
            }
284,265✔
1965
        }
2,053,788✔
1966

345,858✔
1967
        m_opposite_table.update_from_parent();
695,364✔
1968
        m_opposite_column.update_from_parent();
695,364✔
1969
        if (m_top.size() > top_position_for_flags) {
695,370✔
1970
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
695,370✔
1971
            m_table_type = Type(flags & table_type_mask);
695,370✔
1972
        }
695,370✔
1973
        else {
2,147,483,647✔
1974
            m_table_type = Type::TopLevel;
2,147,483,647✔
1975
        }
2,147,483,647✔
1976
        if (m_tombstones)
695,364✔
1977
            m_tombstones->update_from_parent();
3,426✔
1978

345,858✔
1979
        refresh_content_version();
695,364✔
1980
        m_has_any_embedded_objects.reset();
695,364✔
1981
    }
695,364✔
1982
    m_alloc.bump_storage_version();
695,364✔
1983
}
695,364✔
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
{
998,454✔
2078
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
998,454✔
2079
        if (!m_top.is_read_only()) {
998,442✔
2080
            ++m_in_file_version_at_transaction_boundary;
755,877✔
2081
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
755,877✔
2082
            m_top.set(top_position_for_version, rot_version);
755,877✔
2083
        }
755,877✔
2084
    }
998,442✔
2085
}
998,454✔
2086

2087
void Table::refresh_content_version()
2088
{
1,030,509✔
2089
    REALM_ASSERT(m_top.is_attached());
1,030,509✔
2090
    if (m_top.size() >= top_position_for_version) {
1,030,509✔
2091
        // we have versioning info in the file. Use this to conditionally
549,957✔
2092
        // bump the version counter:
549,957✔
2093
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,030,257✔
2094
        REALM_ASSERT(rot_version.is_tagged());
1,030,257✔
2095
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,030,257✔
2096
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
232,767✔
2097
            bump_content_version();
232,767✔
2098
        }
232,767✔
2099
    }
1,030,257✔
2100
    else {
252✔
2101
        // assume the worst:
240✔
2102
        bump_content_version();
252✔
2103
    }
252✔
2104
}
1,030,509✔
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
{
335,208✔
2111
    REALM_ASSERT(m_cookie == cookie_initialized);
335,208✔
2112
    REALM_ASSERT(m_top.is_attached());
335,208✔
2113
    m_top.init_from_parent();
335,208✔
2114
    m_spec.init_from_parent();
335,208✔
2115
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
335,208✔
2116
    m_clusters.init_from_parent();
335,208✔
2117
    m_index_refs.init_from_parent();
335,208✔
2118
    m_opposite_table.init_from_parent();
335,208✔
2119
    m_opposite_column.init_from_parent();
335,208✔
2120
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
335,208✔
2121
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
287,301✔
2122
    if (m_top.size() > top_position_for_flags) {
335,208✔
2123
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
334,905✔
2124
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
334,905✔
2125
    }
334,905✔
2126
    else {
303✔
2127
        m_table_type = Type::TopLevel;
303✔
2128
    }
303✔
2129
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
335,208✔
2130
        // Tombstones exists
1,032✔
2131
        if (!m_tombstones) {
2,064✔
2132
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
288✔
2133
        }
288✔
2134
        m_tombstones->init_from_parent();
2,064✔
2135
    }
2,064✔
2136
    else {
333,144✔
2137
        m_tombstones = nullptr;
333,144✔
2138
    }
333,144✔
2139
    refresh_content_version();
335,208✔
2140
    bump_storage_version();
335,208✔
2141
    build_column_mapping();
335,208✔
2142
    refresh_index_accessors();
335,208✔
2143
}
335,208✔
2144

2145
void Table::refresh_index_accessors()
2146
{
3,008,412✔
2147
    // Refresh search index accessors
1,831,989✔
2148

1,831,989✔
2149
    // First eliminate any index accessors for eliminated last columns
1,831,989✔
2150
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
3,008,412✔
2151
    m_index_accessors.resize(col_ndx_end);
3,008,412✔
2152

1,831,989✔
2153
    // Then eliminate/refresh/create accessors within column range
1,831,989✔
2154
    // we can not use for_each_column() here, since the columns may have changed
1,831,989✔
2155
    // and the index accessor vector is not updated correspondingly.
1,831,989✔
2156
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
16,563,468✔
2157
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
13,555,056✔
2158

7,346,064✔
2159
        if (ref == 0) {
13,555,056✔
2160
            // accessor drop
6,887,409✔
2161
            m_index_accessors[col_ndx].reset();
12,646,911✔
2162
        }
12,646,911✔
2163
        else {
908,145✔
2164
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
908,145✔
2165
            bool fulltext = attr.test(col_attr_FullText_Indexed);
908,145✔
2166
            auto col_key = m_leaf_ndx2colkey[col_ndx];
908,145✔
2167
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
908,130✔
2168

458,655✔
2169
            if (m_index_accessors[col_ndx]) { // still there, refresh:
908,145✔
2170
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
360,165✔
2171
            }
360,165✔
2172
            else { // new index!
547,980✔
2173
                m_index_accessors[col_ndx] =
547,980✔
2174
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
547,980✔
2175
            }
547,980✔
2176
        }
908,145✔
2177
    }
13,555,056✔
2178
}
3,008,412✔
2179

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

2192
// LCOV_EXCL_START ignore debug functions
2193

2194
void Table::verify() const
2195
{
236,976✔
2196
#ifdef REALM_DEBUG
236,976✔
2197
    if (m_top.is_attached())
236,976✔
2198
        m_top.verify();
236,967✔
2199
    m_spec.verify();
236,976✔
2200
    m_clusters.verify();
236,976✔
2201
    if (nb_unresolved())
236,976✔
2202
        m_tombstones->verify();
34,512✔
2203
#endif
236,976✔
2204
}
236,976✔
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,654,644✔
2217
    if (is_embedded())
22,654,644✔
2218
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2219
    if (m_primary_key_col)
22,654,596✔
2220
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2221
    if (key == null_key) {
22,654,596✔
2222
        GlobalKey object_id = allocate_object_id_squeezed();
19,502,862✔
2223
        key = object_id.get_local_key(get_sync_file_id());
19,502,862✔
2224
        // Check if this key collides with an already existing object
9,766,644✔
2225
        // This could happen if objects were at some point created with primary keys,
9,766,644✔
2226
        // but later primary key property was removed from the schema.
9,766,644✔
2227
        while (m_clusters.is_valid(key)) {
19,502,862✔
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,502,862✔
2232
            repl->create_object(this, object_id);
4,298,334✔
2233
    }
19,502,862✔
2234

11,345,487✔
2235
    REALM_ASSERT(key.value >= 0);
22,654,596✔
2236

11,345,487✔
2237
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,654,596✔
2238

11,345,487✔
2239
    return obj;
22,654,596✔
2240
}
22,654,596✔
2241

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

21,132✔
2246
    GlobalKey object_id = allocate_object_id_squeezed();
42,450✔
2247
    ObjKey key = object_id.get_local_key(get_sync_file_id());
42,450✔
2248

21,132✔
2249
    REALM_ASSERT(key.value >= 0);
42,450✔
2250

21,132✔
2251
    Obj obj = m_clusters.insert(key, {});
42,450✔
2252

21,132✔
2253
    return obj;
42,450✔
2254
}
42,450✔
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
{
472,842✔
2292
    auto primary_key_col = get_primary_key_column();
472,842✔
2293
    if (is_embedded() || !primary_key_col)
472,848✔
2294
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2295
                              util::format("Table has no primary key: %1", get_name()));
6✔
2296

225,105✔
2297
    DataType type = DataType(primary_key_col.get_type());
472,836✔
2298

225,105✔
2299
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
472,836✔
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

225,102✔
2305
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
472,830✔
2306
        primary_key.get_type() != type) {
472,638✔
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

225,099✔
2311
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
472,824✔
2312

225,099✔
2313
    if (did_create)
472,824✔
2314
        *did_create = false;
84,003✔
2315

225,099✔
2316
    // Check for existing object
225,099✔
2317
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
472,824✔
2318
        if (mode == UpdateMode::never) {
47,187✔
2319
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2320
        }
6✔
2321
        auto obj = m_clusters.get(key);
47,181✔
2322
        for (auto& val : field_values) {
23,481✔
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;
47,181✔
2328
    }
47,181✔
2329

201,621✔
2330
    ObjKey unres_key;
425,637✔
2331
    if (m_tombstones) {
425,637✔
2332
        // Check for potential tombstone
6,228✔
2333
        GlobalKey object_id{primary_key};
12,648✔
2334
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
12,648✔
2335

6,228✔
2336
        ObjKey key = object_key.get_unresolved();
12,648✔
2337
        if (auto obj = m_tombstones->try_get_obj(key)) {
12,648✔
2338
            auto existing_pk_value = obj.get_any(primary_key_col);
10,830✔
2339

5,409✔
2340
            // If the primary key is the same, the object should be resurrected below
5,409✔
2341
            if (existing_pk_value == primary_key) {
10,830✔
2342
                unres_key = key;
10,824✔
2343
            }
10,824✔
2344
        }
10,830✔
2345
    }
12,648✔
2346

201,621✔
2347
    ObjKey key = get_next_valid_key();
425,637✔
2348

201,621✔
2349
    auto repl = get_repl();
425,637✔
2350
    if (repl) {
425,637✔
2351
        repl->create_object_with_primary_key(this, key, primary_key);
304,596✔
2352
    }
304,596✔
2353
    if (did_create) {
425,637✔
2354
        *did_create = true;
46,851✔
2355
    }
46,851✔
2356

201,621✔
2357
    field_values.insert(primary_key_col, primary_key);
425,637✔
2358
    Obj ret = m_clusters.insert(key, field_values);
425,637✔
2359

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

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

360,099✔
2384
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
720,408✔
2385
        return index->find_first(primary_key);
720,354✔
2386
    }
720,354✔
2387

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

30✔
2393
    // Check if existing
30✔
2394
    if (auto obj = m_clusters.try_get_obj(object_key)) {
54✔
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
    }
54✔
2401
    return {};
54✔
2402
}
54✔
2403

2404
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2405
{
607,311✔
2406
    // Check if existing
303,780✔
2407
    if (auto key = find_primary_key(primary_key)) {
607,311✔
2408
        return key;
595,302✔
2409
    }
595,302✔
2410

6,021✔
2411
    // Object does not exist - create tombstone
6,021✔
2412
    GlobalKey object_id{primary_key};
12,009✔
2413
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
12,009✔
2414
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
12,009✔
2415
}
12,009✔
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
{
79,551✔
2460
    auto primary_key_col = get_primary_key_column();
79,551✔
2461
    REALM_ASSERT(primary_key_col);
79,551✔
2462
    DataType type = DataType(primary_key_col.get_type());
79,551✔
2463
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
79,551✔
2464
                 primary_key.get_type() == type);
79,551✔
2465
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
79,551✔
2466
    return k ? m_clusters.get(k) : Obj{};
79,455✔
2467
}
79,551✔
2468

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

2482
GlobalKey Table::allocate_object_id_squeezed()
2483
{
19,543,206✔
2484
    // m_client_file_ident will be zero if we haven't been in contact with
9,785,778✔
2485
    // the server yet.
9,785,778✔
2486
    auto peer_id = get_sync_file_id();
19,543,206✔
2487
    auto sequence = allocate_sequence_number();
19,543,206✔
2488
    return GlobalKey{peer_id, sequence};
19,543,206✔
2489
}
19,543,206✔
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
{
25,470✔
2499
#if REALM_EXERCISE_OBJECT_ID_COLLISION
2500
    const uint64_t optimistic_mask = 0xff;
2501
#else
2502
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
25,470✔
2503
#endif
25,470✔
2504
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
25,470✔
2505
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
25,470✔
2506
}
25,470✔
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
{
25,470✔
2518
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
25,470✔
2519

12,654✔
2520
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
25,470✔
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
        }
25,446✔
2540
    }
24✔
2541

12,642✔
2542
    return optimistic;
25,446✔
2543
}
25,446✔
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,996✔
2617
    auto unres_key = key.get_unresolved();
12,996✔
2618

6,513✔
2619
    ensure_graveyard();
12,996✔
2620
    auto tombstone = m_tombstones->try_get_obj(unres_key);
12,996✔
2621
    if (tombstone) {
12,996✔
2622
        if (pk_col) {
411✔
2623
            auto existing_pk_value = tombstone.get_any(pk_col);
411✔
2624
            // It may just be the same object
204✔
2625
            if (existing_pk_value != pk_val) {
411✔
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
        }
399✔
2631
        return tombstone;
399✔
2632
    }
399✔
2633
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
12,585✔
2634
}
12,585✔
2635

2636
void Table::free_local_id_after_hash_collision(ObjKey key)
2637
{
5,000,487✔
2638
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,000,487✔
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
}
5,000,487✔
2670

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

2679
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2680
{
3,864✔
2681
    while (number--) {
6,324,429✔
2682
        keys.push_back(create_object().get_key());
6,320,565✔
2683
    }
6,320,565✔
2684
}
3,864✔
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,552✔
2702
    Group* g = get_parent_group();
2,463,552✔
2703

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

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

2,127✔
2725
    Obj tombstone;
4,260✔
2726
    auto obj = get_object(key);
4,260✔
2727
    if (obj.has_backlinks(false)) {
4,260✔
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,127✔
2742
    remove_object(key);
4,260✔
2743

2,127✔
2744
    return tombstone.get_key();
4,260✔
2745
}
4,260✔
2746

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

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

2768
Table::Iterator Table::end() const
2769
{
10,019,520✔
2770
    return Iterator(m_clusters, size());
10,019,520✔
2771
}
10,019,520✔
2772

2773
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2774
{
7,103,394✔
2775
    TableRef ret;
7,103,394✔
2776
    if (col_key) {
7,103,526✔
2777
        return table.get_opposite_table(col_key);
7,103,526✔
2778
    }
7,103,526✔
2779
    return ret;
4,294,967,294✔
2780
}
4,294,967,294✔
2781

2782
const uint64_t Table::max_num_columns;
2783

2784
void Table::build_column_mapping()
2785
{
3,018,114✔
2786
    // build column mapping from spec
1,837,128✔
2787
    // TODO: Optimization - Don't rebuild this for every change
1,837,128✔
2788
    m_spec_ndx2leaf_ndx.clear();
3,018,114✔
2789
    m_leaf_ndx2spec_ndx.clear();
3,018,114✔
2790
    m_leaf_ndx2colkey.clear();
3,018,114✔
2791
    size_t num_spec_cols = m_spec.get_column_count();
3,018,114✔
2792
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
3,018,114✔
2793
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
16,536,555✔
2794
        ColKey col_key = m_spec.get_key(spec_ndx);
13,518,441✔
2795
        unsigned leaf_ndx = col_key.get_index().val;
13,518,441✔
2796
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
13,518,441✔
2797
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
13,033,056✔
2798
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
13,033,056✔
2799
        }
13,033,056✔
2800
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
13,518,441✔
2801
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
13,518,441✔
2802
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
13,518,441✔
2803
    }
13,518,441✔
2804
}
3,018,114✔
2805

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

351,318✔
2811
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
711,714✔
2812
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
711,714✔
2813

351,318✔
2814
    // reuse lowest available leaf ndx:
351,318✔
2815
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
711,714✔
2816
    // look for an unused entry:
351,318✔
2817
    for (unsigned idx = 0; idx < lower; ++idx) {
5,481,624✔
2818
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
4,769,994✔
2819
            lower = idx;
84✔
2820
            break;
84✔
2821
        }
84✔
2822
    }
4,769,994✔
2823
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
711,714✔
2824
}
711,714✔
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,865,182✔
2881
    return m_primary_key_col;
18,865,182✔
2882
}
18,865,182✔
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
{
96,264✔
2913
    if (col_key) {
96,264✔
2914
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
95,346✔
2915
        auto attr = m_spec.get_column_attr(spec_ndx);
95,346✔
2916
        if (attr.test(col_attr_FullText_Indexed)) {
95,346✔
2917
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
2918
        }
6✔
2919
    }
96,258✔
2920

47,676✔
2921
    if (m_primary_key_col) {
96,258✔
2922
        // If the search index has not been set explicitly on current pk col, we remove it again
456✔
2923
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
942✔
2924
        auto attr = m_spec.get_column_attr(spec_ndx);
942✔
2925
        if (!attr.test(col_attr_Indexed)) {
942✔
2926
            remove_search_index(m_primary_key_col);
930✔
2927
        }
930✔
2928
    }
942✔
2929

47,676✔
2930
    if (col_key) {
96,258✔
2931
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
95,337✔
2932
        do_add_search_index(col_key, IndexType::General);
95,337✔
2933
    }
95,337✔
2934
    else {
921✔
2935
        m_top.set(top_position_for_pk_col, 0);
921✔
2936
    }
921✔
2937

47,676✔
2938
    m_primary_key_col = col_key;
96,258✔
2939
}
96,258✔
2940

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

2954
void Table::validate_column_is_unique(ColKey col) const
2955
{
834✔
2956
    if (!contains_unique_values(col)) {
834✔
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
}
834✔
2961

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

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

201,615✔
2976
    return key;
425,634✔
2977
}
425,634✔
2978

2979
namespace {
2980
template <class T>
2981
typename util::RemoveOptional<T>::type remove_optional(T val)
2982
{
87,819✔
2983
    return val;
87,819✔
2984
}
87,819✔
2985
template <>
2986
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
2987
{
5,418✔
2988
    return *val;
5,418✔
2989
}
5,418✔
2990
template <>
2991
bool remove_optional<Optional<bool>>(Optional<bool> val)
2992
{
11,508✔
2993
    return *val;
11,508✔
2994
}
11,508✔
2995
template <>
2996
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
2997
{
5,406✔
2998
    return *val;
5,406✔
2999
}
5,406✔
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));
114,921✔
3069
                    }
114,921✔
3070
                    else {
5,079✔
3071
                        if (throw_on_null) {
5,079!
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 {
5,079✔
3077
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
5,079✔
3078
                        }
5,079✔
3079
                    }
5,079✔
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,470,248✔
3257
    if (!m_has_any_embedded_objects) {
2,470,248✔
3258
        m_has_any_embedded_objects = false;
24,894✔
3259
        for_each_public_column([&](ColKey col_key) {
58,782✔
3260
            auto target_table_key = get_opposite_table_key(col_key);
58,782✔
3261
            if (target_table_key && is_link_type(col_key.get_type())) {
58,782✔
3262
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
11,205✔
3263
                if (target_table->is_embedded()) {
11,205✔
3264
                    m_has_any_embedded_objects = true;
9,072✔
3265
                    return IteratorControl::Stop; // early out
9,072✔
3266
                }
9,072✔
3267
            }
49,710✔
3268
            return IteratorControl::AdvanceToNext;
49,710✔
3269
        });
49,710✔
3270
    }
24,894✔
3271
    return *m_has_any_embedded_objects;
2,470,248✔
3272
}
2,470,248✔
3273

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

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

23,496✔
3288
    return {};
27,315✔
3289
}
47,226✔
3290

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

23,460✔
3295
    if (!backlink_col_key) {
47,154✔
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,460✔
3304
    return backlink_col_key;
47,154✔
3305
}
47,154✔
3306

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

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

3317
TableRef Table::get_opposite_table(ColKey col_key) const
3318
{
7,808,478✔
3319
    if (auto k = get_opposite_table_key(col_key)) {
7,808,478✔
3320
        return get_parent_group()->get_table(k);
7,749,234✔
3321
    }
7,749,234✔
3322
    return {};
59,244✔
3323
}
59,244✔
3324

3325
ColKey Table::get_opposite_column(ColKey col_key) const
3326
{
16,304,808✔
3327
    return ColKey(m_opposite_column.get(col_key.get_index().val));
16,304,808✔
3328
}
16,304,808✔
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

© 2025 Coveralls, Inc