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

realm / realm-core / github_pull_request_301264

30 Jul 2024 07:11PM UTC coverage: 91.111% (+0.009%) from 91.102%
github_pull_request_301264

Pull #7936

Evergreen

web-flow
Add support for multi-process subscription state change notifications (#7862)

As with the other multi-process notifications, the core idea here is to
eliminate the in-memory state and produce notifications based entirely on the
current state of the Realm file.

SubscriptionStore::update_state() has been replaced with separate functions for
the specific legal state transitions, which also take a write transaction as a
parameter. These functions are called by PendingBootstrapStore inside the same
write transaction as the bootstrap updates which changed the subscription
state. This is both a minor performance optimization (due to fewer writes) and
eliminates a brief window between the two writes where the Realm file was in an
inconsistent state.

There's a minor functional change here: previously old subscription sets were
superseded when the new one reached the Completed state, and now they are
superseded on AwaitingMark. This aligns it with when the new subscription set
becomes the one which is returned by get_active().
Pull Request #7936: Fix connection callback crashes when reloading with React Native

102800 of 181570 branches covered (56.62%)

216840 of 237996 relevant lines covered (91.11%)

5918493.47 hits per line

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

90.42
/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
{
191,853✔
268
    if (size() != other.size())
191,853✔
269
        return false;
18,408✔
270
    size_t sz = size();
173,445✔
271
    for (size_t i = 0; i < sz; i++) {
314,784✔
272
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
174,876✔
273
        if (this->at(i).second != other.at(i).second)
174,876✔
274
            return false;
33,537✔
275
    }
174,876✔
276
    return true;
139,908✔
277
}
173,445✔
278

279
namespace realm {
280
const char* get_data_type_name(DataType type) noexcept
281
{
1,116✔
282
    switch (type) {
1,116✔
283
        case type_Int:
198✔
284
            return "int";
198✔
285
        case type_Bool:
48✔
286
            return "bool";
48✔
287
        case type_Float:
90✔
288
            return "float";
90✔
289
        case type_Double:
120✔
290
            return "double";
120✔
291
        case type_String:
144✔
292
            return "string";
144✔
293
        case type_Binary:
42✔
294
            return "binary";
42✔
295
        case type_Timestamp:
114✔
296
            return "timestamp";
114✔
297
        case type_ObjectId:
78✔
298
            return "objectId";
78✔
299
        case type_Decimal:
66✔
300
            return "decimal128";
66✔
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
    }
1,116✔
319
    return "unknown";
×
320
}
1,116✔
321

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

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

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

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

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

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

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

399
    ColumnAttrMask attr;
517,287✔
400
    if (collection_type) {
517,287✔
401
        switch (*collection_type) {
150,570✔
402
            case CollectionType::List:
62,013✔
403
                attr.set(col_attr_List);
62,013✔
404
                break;
62,013✔
405
            case CollectionType::Set:
43,413✔
406
                attr.set(col_attr_Set);
43,413✔
407
                break;
43,413✔
408
            case CollectionType::Dictionary:
45,144✔
409
                attr.set(col_attr_Dictionary);
45,144✔
410
                break;
45,144✔
411
        }
150,570✔
412
    }
150,570✔
413
    if (nullable || type == type_Mixed)
517,287✔
414
        attr.set(col_attr_Nullable);
185,349✔
415
    ColKey col_key = generate_col_key(ColumnType(type), attr);
517,287✔
416

417
    Table* invalid_link = nullptr;
517,287✔
418
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
517,287✔
419
}
517,287✔
420

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

434
    m_has_any_embedded_objects.reset();
75,324✔
435

436
    DataType data_type = type_Link;
75,324✔
437
    ColumnAttrMask attr;
75,324✔
438
    if (collection_type) {
75,324✔
439
        switch (*collection_type) {
44,181✔
440
            case CollectionType::List:
22,083✔
441
                attr.set(col_attr_List);
22,083✔
442
                break;
22,083✔
443
            case CollectionType::Set:
10,734✔
444
                if (target.is_embedded())
10,734✔
445
                    throw IllegalOperation("Set of embedded objects not supported");
×
446
                attr.set(col_attr_Set);
10,734✔
447
                break;
10,734✔
448
            case CollectionType::Dictionary:
11,364✔
449
                attr.set(col_attr_Dictionary);
11,364✔
450
                attr.set(col_attr_Nullable);
11,364✔
451
                break;
11,364✔
452
        }
44,181✔
453
    }
44,181✔
454
    else {
31,143✔
455
        attr.set(col_attr_Nullable);
31,143✔
456
    }
31,143✔
457
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
75,324✔
458

459
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
75,324✔
460
}
75,324✔
461

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

468
    do {
240,264✔
469
        cascade_state.send_notifications();
240,264✔
470

471
        for (auto& l : cascade_state.m_to_be_nullified) {
240,264✔
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();
240,264✔
479

480
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
240,264✔
481
        for (auto obj : to_delete) {
240,264✔
482
            auto table = obj.first == m_key ? this : group->get_table_unchecked(obj.first);
232,575✔
483
            // This might add to the list of objects that should be deleted
484
            REALM_ASSERT(!obj.second.is_unresolved());
232,575✔
485
            table->m_clusters.erase(obj.second, cascade_state);
232,575✔
486
        }
232,575✔
487
        nullify_links(cascade_state);
240,264✔
488
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
240,264✔
489
}
192,699✔
490

491
void Table::nullify_links(CascadeState& cascade_state)
492
{
417,897✔
493
    Group* group = get_parent_group();
417,897✔
494
    REALM_ASSERT(group);
417,897✔
495
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
417,897✔
496
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
219,513✔
497
        if (!table->is_asymmetric())
219,513✔
498
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
219,513✔
499
    }
219,513✔
500
}
417,897✔
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
}
8,508✔
513

514
void Table::remove_columns()
515
{
2,484✔
516
    for (size_t i = get_column_count(); i > 0; --i) {
7,209✔
517
        ColKey col_key = spec_ndx2colkey(i - 1);
4,725✔
518
        remove_column(col_key);
4,725✔
519
    }
4,725✔
520
}
2,484✔
521

522
void Table::remove_column(ColKey col_key)
523
{
5,400✔
524
    check_column(col_key);
5,400✔
525

526
    if (Replication* repl = get_repl())
5,400✔
527
        repl->erase_column(this, col_key); // Throws
4,221✔
528

529
    if (col_key == m_primary_key_col) {
5,400✔
530
        do_set_primary_key_column(ColKey());
918✔
531
    }
918✔
532
    else {
4,482✔
533
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
4,482✔
534
    }
4,482✔
535

536
    erase_root_column(col_key); // Throws
5,400✔
537
    m_has_any_embedded_objects.reset();
5,400✔
538
}
5,400✔
539

540

541
void Table::rename_column(ColKey col_key, StringData name)
542
{
99✔
543
    check_column(col_key);
99✔
544

545
    auto col_ndx = colkey2spec_ndx(col_key);
99✔
546
    m_spec.rename_column(col_ndx, name); // Throws
99✔
547

548
    bump_content_version();
99✔
549
    bump_storage_version();
99✔
550

551
    if (Replication* repl = get_repl())
99✔
552
        repl->rename_column(this, col_key, name); // Throws
99✔
553
}
99✔
554

555

556
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
557
{
2,531,160✔
558
    // well, not quite "direct", more like "almost direct":
559
    Array table_top(alloc);
2,531,160✔
560
    table_top.init_from_ref(top_ref);
2,531,160✔
561
    if (table_top.size() > 3) {
2,531,187✔
562
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
2,528,862✔
563
        return TableKey(int32_t(rot.get_as_int()));
2,528,862✔
564
    }
2,528,862✔
565
    else {
2,147,485,972✔
566
        return TableKey();
2,147,485,972✔
567
    }
2,147,485,972✔
568
}
2,531,160✔
569

570

571
void Table::init(ref_type top_ref, ArrayParent* parent, size_t ndx_in_parent, bool is_writable, bool is_frzn)
572
{
2,075,265✔
573
    REALM_ASSERT(!(is_writable && is_frzn));
2,075,265✔
574
    m_is_frozen = is_frzn;
2,075,265✔
575
    m_alloc.set_read_only(!is_writable);
2,075,265✔
576
    // Load from allocated memory
577
    m_top.set_parent(parent, ndx_in_parent);
2,075,265✔
578
    m_top.init_from_ref(top_ref);
2,075,265✔
579

580
    m_spec.init_from_parent();
2,075,265✔
581

582
    while (m_top.size() <= top_position_for_pk_col) {
2,075,265✔
583
        m_top.add(0);
×
584
    }
×
585

586
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
2,075,265✔
587
        // This is an upgrade - create cluster
588
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
×
589
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
×
590
    }
×
591
    m_clusters.init_from_parent();
2,075,265✔
592

593
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
2,075,265✔
594
    if (!rot.is_tagged()) {
2,075,265✔
595
        // Create table key
596
        rot = RefOrTagged::make_tagged(ndx_in_parent);
×
597
        m_top.set(top_position_for_key, rot);
×
598
    }
×
599
    m_key = TableKey(int32_t(rot.get_as_int()));
2,075,265✔
600

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

634
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
2,075,265✔
635
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
2,075,265✔
636

637
    if (m_top.size() <= top_position_for_flags) {
2,075,265✔
638
        m_table_type = Type::TopLevel;
60✔
639
    }
60✔
640
    else {
2,075,205✔
641
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
2,075,205✔
642
        m_table_type = Type(flags & table_type_mask);
2,075,205✔
643
    }
2,075,205✔
644
    m_has_any_embedded_objects.reset();
2,075,265✔
645

646
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
2,075,265✔
647
        // Tombstones exists
648
        if (!m_tombstones) {
32,379✔
649
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
25,758✔
650
        }
25,758✔
651
        m_tombstones->init_from_parent();
32,379✔
652
    }
32,379✔
653
    else {
2,042,886✔
654
        m_tombstones = nullptr;
2,042,886✔
655
    }
2,042,886✔
656
    m_cookie = cookie_initialized;
2,075,265✔
657
}
2,075,265✔
658

659

660
ColKey Table::do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table, DataType key_type)
661
{
592,611✔
662
    col_key = do_insert_root_column(col_key, ColumnType(type), name, key_type); // Throws
592,611✔
663

664
    // When the inserted column is a link-type column, we must also add a
665
    // backlink column to the target table.
666

667
    if (target_table) {
592,611✔
668
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
75,318✔
669
        target_table->check_column(backlink_col_key);
75,318✔
670

671
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
75,318✔
672
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
75,318✔
673
    }
75,318✔
674

675
    if (Replication* repl = get_repl())
592,611✔
676
        repl->insert_column(this, col_key, type, name, target_table); // Throws
574,479✔
677

678
    return col_key;
592,611✔
679
}
592,611✔
680

681
template <typename Type>
682
static void do_bulk_insert_index(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
683
{
114,576✔
684
    using LeafType = typename ColumnTypeTraits<Type>::cluster_leaf_type;
114,576✔
685
    LeafType leaf(alloc);
114,576✔
686

687
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
119,664✔
688
        cluster->init_leaf(col_key, &leaf);
119,664✔
689
        index->insert_bulk(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
119,664✔
690
        return IteratorControl::AdvanceToNext;
119,664✔
691
    };
119,664✔
692

693
    table->traverse_clusters(f);
114,576✔
694
}
114,576✔
695

696

697
static void do_bulk_insert_index_list(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
698
{
24✔
699
    ArrayInteger leaf(alloc);
24✔
700

701
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
258✔
702
        cluster->init_leaf(col_key, &leaf);
258✔
703
        index->insert_bulk_list(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
258✔
704
        return IteratorControl::AdvanceToNext;
258✔
705
    };
258✔
706

707
    table->traverse_clusters(f);
24✔
708
}
24✔
709

710
void Table::populate_search_index(ColKey col_key)
711
{
114,600✔
712
    auto col_ndx = col_key.get_index().val;
114,600✔
713
    SearchIndex* index = m_index_accessors[col_ndx].get();
114,600✔
714
    DataType type = get_column_type(col_key);
114,600✔
715

716
    if (type == type_Int) {
114,600✔
717
        if (is_nullable(col_key)) {
50,121✔
718
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
13,794✔
719
        }
13,794✔
720
        else {
36,327✔
721
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
36,327✔
722
        }
36,327✔
723
    }
50,121✔
724
    else if (type == type_Bool) {
64,479✔
725
        if (is_nullable(col_key)) {
48✔
726
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
727
        }
24✔
728
        else {
24✔
729
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
24✔
730
        }
24✔
731
    }
48✔
732
    else if (type == type_String) {
64,431✔
733
        if (col_key.is_list()) {
19,239✔
734
            do_bulk_insert_index_list(this, index, col_key, get_alloc());
24✔
735
        }
24✔
736
        else {
19,215✔
737
            do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
19,215✔
738
        }
19,215✔
739
    }
19,239✔
740
    else if (type == type_Timestamp) {
45,192✔
741
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
99✔
742
    }
99✔
743
    else if (type == type_ObjectId) {
45,093✔
744
        if (is_nullable(col_key)) {
43,503✔
745
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
996✔
746
        }
996✔
747
        else {
42,507✔
748
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
42,507✔
749
        }
42,507✔
750
    }
43,503✔
751
    else if (type == type_UUID) {
1,590✔
752
        if (is_nullable(col_key)) {
684✔
753
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
754
        }
516✔
755
        else {
168✔
756
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
168✔
757
        }
168✔
758
    }
684✔
759
    else if (type == type_Mixed) {
906✔
760
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
906✔
761
    }
906✔
762
    else {
×
763
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
764
    }
×
765
}
114,600✔
766

767
void Table::erase_from_search_indexes(ObjKey key)
768
{
5,323,074✔
769
    // Tombstones do not use index - will crash if we try to erase values
770
    if (!key.is_unresolved()) {
5,323,074✔
771
        for (auto&& index : m_index_accessors) {
7,294,251✔
772
            if (index) {
7,294,251✔
773
                index->erase(key);
544,929✔
774
            }
544,929✔
775
        }
7,294,251✔
776
    }
5,283,483✔
777
}
5,323,074✔
778

779
void Table::update_indexes(ObjKey key, const FieldValues& values)
780
{
23,855,703✔
781
    // Tombstones do not use index - will crash if we try to insert values
782
    if (key.is_unresolved()) {
23,855,703✔
783
        return;
41,109✔
784
    }
41,109✔
785

786
    auto sz = m_index_accessors.size();
23,814,594✔
787
    // values are sorted by column index - there may be values missing
788
    auto value = values.begin();
23,814,594✔
789
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
59,467,599✔
790
        // Check if initial value is provided
791
        Mixed init_value;
35,653,212✔
792
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
35,653,212✔
793
            // Value for this column is provided
794
            init_value = value->value;
959,889✔
795
            ++value;
959,889✔
796
        }
959,889✔
797

798
        if (auto&& index = m_index_accessors[column_ndx]) {
35,653,212✔
799
            // There is an index for this column
800
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,534,779✔
801
            if (col_key.is_collection())
1,534,779✔
802
                continue;
102✔
803
            auto type = col_key.get_type();
1,534,677✔
804
            auto attr = col_key.get_attrs();
1,534,677✔
805
            bool nullable = attr.test(col_attr_Nullable);
1,534,677✔
806
            switch (type) {
1,534,677✔
807
                case col_type_Int:
481,293✔
808
                    if (init_value.is_null()) {
481,293✔
809
                        index->insert(key, ArrayIntNull::default_value(nullable));
166,755✔
810
                    }
166,755✔
811
                    else {
314,538✔
812
                        index->insert(key, init_value.get<int64_t>());
314,538✔
813
                    }
314,538✔
814
                    break;
481,293✔
815
                case col_type_Bool:
6,171✔
816
                    if (init_value.is_null()) {
6,171✔
817
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,171✔
818
                    }
6,171✔
819
                    else {
×
820
                        index->insert(key, init_value.get<bool>());
×
821
                    }
×
822
                    break;
6,171✔
823
                case col_type_String:
887,142✔
824
                    if (init_value.is_null()) {
887,142✔
825
                        index->insert(key, ArrayString::default_value(nullable));
432,609✔
826
                    }
432,609✔
827
                    else {
454,533✔
828
                        index->insert(key, init_value.get<String>());
454,533✔
829
                    }
454,533✔
830
                    break;
887,142✔
831
                case col_type_Timestamp:
7,374✔
832
                    if (init_value.is_null()) {
7,374✔
833
                        index->insert(key, ArrayTimestamp::default_value(nullable));
7,374✔
834
                    }
7,374✔
835
                    else {
×
836
                        index->insert(key, init_value.get<Timestamp>());
×
837
                    }
×
838
                    break;
7,374✔
839
                case col_type_ObjectId:
130,881✔
840
                    if (init_value.is_null()) {
130,881✔
841
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
7,320✔
842
                    }
7,320✔
843
                    else {
123,561✔
844
                        index->insert(key, init_value.get<ObjectId>());
123,561✔
845
                    }
123,561✔
846
                    break;
130,881✔
847
                case col_type_Mixed:
2,286✔
848
                    index->insert(key, init_value);
2,286✔
849
                    break;
2,286✔
850
                case col_type_UUID:
19,542✔
851
                    if (init_value.is_null()) {
19,542✔
852
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
7,338✔
853
                    }
7,338✔
854
                    else {
12,204✔
855
                        index->insert(key, init_value.get<UUID>());
12,204✔
856
                    }
12,204✔
857
                    break;
19,542✔
858
                default:
✔
859
                    REALM_UNREACHABLE();
860
            }
1,534,677✔
861
        }
1,534,677✔
862
    }
35,653,212✔
863
}
23,814,594✔
864

865
void Table::clear_indexes()
866
{
6,429✔
867
    for (auto&& index : m_index_accessors) {
59,856✔
868
        if (index) {
59,856✔
869
            index->clear();
5,130✔
870
        }
5,130✔
871
    }
59,856✔
872
}
6,429✔
873

874
void Table::do_add_search_index(ColKey col_key, IndexType type)
875
{
114,792✔
876
    size_t column_ndx = col_key.get_index().val;
114,792✔
877

878
    // Early-out if already indexed
879
    if (m_index_accessors[column_ndx] != nullptr)
114,792✔
880
        return;
150✔
881

882
    if (!StringIndex::type_supported(DataType(col_key.get_type())) ||
114,642✔
883
        (col_key.is_collection() && !(col_key.is_list() && col_key.get_type() == col_type_String)) ||
114,642✔
884
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
114,642✔
885
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
886
        // it should probably be a type mismatch exception instead.
887
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
42✔
888
    }
42✔
889

890
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
891
    // index have 0-entries.
892
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
114,600✔
893
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
114,600✔
894

895
    // Create the index
896
    m_index_accessors[column_ndx] =
114,600✔
897
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
114,600✔
898
    SearchIndex* index = m_index_accessors[column_ndx].get();
114,600✔
899
    // Insert ref to index
900
    index->set_parent(&m_index_refs, column_ndx);
114,600✔
901

902
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
114,600✔
903

904
    populate_search_index(col_key);
114,600✔
905
}
114,600✔
906

907
void Table::add_search_index(ColKey col_key, IndexType type)
908
{
3,915✔
909
    check_column(col_key);
3,915✔
910

911
    // Check spec
912
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,915✔
913
    auto attr = m_spec.get_column_attr(spec_ndx);
3,915✔
914

915
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,915✔
916
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
917

918
    switch (type) {
3,909✔
919
        case IndexType::None:
✔
920
            remove_search_index(col_key);
×
921
            return;
×
922
        case IndexType::Fulltext:
54✔
923
            // Early-out if already indexed
924
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
925
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
926
                return;
×
927
            }
×
928
            if (attr.test(col_attr_Indexed)) {
54✔
929
                this->remove_search_index(col_key);
×
930
            }
×
931
            break;
54✔
932
        case IndexType::General:
3,855✔
933
            if (attr.test(col_attr_Indexed)) {
3,855✔
934
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
30✔
935
                return;
30✔
936
            }
30✔
937
            if (attr.test(col_attr_FullText_Indexed)) {
3,825✔
938
                this->remove_search_index(col_key);
×
939
            }
×
940
            break;
3,825✔
941
    }
3,909✔
942

943
    do_add_search_index(col_key, type);
3,879✔
944

945
    // Update spec
946
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,879✔
947
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,879✔
948
}
3,879✔
949

950
void Table::remove_search_index(ColKey col_key)
951
{
1,338✔
952
    check_column(col_key);
1,338✔
953
    auto column_ndx = col_key.get_index();
1,338✔
954

955
    // Early-out if non-indexed
956
    if (m_index_accessors[column_ndx.val] == nullptr)
1,338✔
957
        return;
81✔
958

959
    // Destroy and remove the index column
960
    auto& index = m_index_accessors[column_ndx.val];
1,257✔
961
    REALM_ASSERT(index != nullptr);
1,257✔
962
    index->destroy();
1,257✔
963
    index.reset();
1,257✔
964

965
    m_index_refs.set(column_ndx.val, 0);
1,257✔
966

967
    // update spec
968
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
1,257✔
969
    auto attr = m_spec.get_column_attr(spec_ndx);
1,257✔
970
    attr.reset(col_attr_Indexed);
1,257✔
971
    attr.reset(col_attr_FullText_Indexed);
1,257✔
972
    m_spec.set_column_attr(spec_ndx, attr); // Throws
1,257✔
973
}
1,257✔
974

975
void Table::enumerate_string_column(ColKey col_key)
976
{
1,299✔
977
    check_column(col_key);
1,299✔
978
    size_t column_ndx = colkey2spec_ndx(col_key);
1,299✔
979
    ColumnType type = col_key.get_type();
1,299✔
980
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,299✔
981
        m_clusters.enumerate_string_column(col_key);
693✔
982
    }
693✔
983
}
1,299✔
984

985
bool Table::is_enumerated(ColKey col_key) const noexcept
986
{
50,358✔
987
    size_t col_ndx = colkey2spec_ndx(col_key);
50,358✔
988
    return m_spec.is_string_enum_type(col_ndx);
50,358✔
989
}
50,358✔
990

991
size_t Table::get_num_unique_values(ColKey col_key) const
992
{
138✔
993
    if (!is_enumerated(col_key))
138✔
994
        return 0;
84✔
995

996
    ArrayParent* parent;
54✔
997
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
998
    BPlusTree<StringData> col(get_alloc());
54✔
999
    col.init_from_ref(ref);
54✔
1000

1001
    return col.size();
54✔
1002
}
138✔
1003

1004

1005
void Table::erase_root_column(ColKey col_key)
1006
{
5,676✔
1007
    ColumnType col_type = col_key.get_type();
5,676✔
1008
    if (is_link_type(col_type)) {
5,676✔
1009
        auto target_table = get_opposite_table(col_key);
615✔
1010
        auto target_column = get_opposite_column(col_key);
615✔
1011
        target_table->do_erase_root_column(target_column);
615✔
1012
    }
615✔
1013
    do_erase_root_column(col_key); // Throws
5,676✔
1014
}
5,676✔
1015

1016

1017
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
1018
{
786,666✔
1019
    // if col_key specifies a key, it must be unused
1020
    REALM_ASSERT(!col_key || !valid_column(col_key));
786,666✔
1021

1022
    // locate insertion point: ordinary columns must come before backlink columns
1023
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
786,666✔
1024

1025
    if (!col_key) {
786,666✔
1026
        col_key = generate_col_key(type, {});
83,166✔
1027
    }
83,166✔
1028

1029
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
786,666✔
1030
    if (col_key.is_dictionary()) {
786,666✔
1031
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
56,508✔
1032
    }
56,508✔
1033
    auto col_ndx = col_key.get_index().val;
786,666✔
1034
    build_column_mapping();
786,666✔
1035
    REALM_ASSERT(col_ndx <= m_index_refs.size());
786,666✔
1036
    if (col_ndx == m_index_refs.size()) {
786,666✔
1037
        m_index_refs.insert(col_ndx, 0);
786,405✔
1038
    }
786,405✔
1039
    else {
261✔
1040
        m_index_refs.set(col_ndx, 0);
261✔
1041
    }
261✔
1042
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
786,666✔
1043
    if (col_ndx == m_opposite_table.size()) {
786,666✔
1044
        // m_opposite_table and m_opposite_column are always resized together!
1045
        m_opposite_table.insert(col_ndx, TableKey().value);
786,399✔
1046
        m_opposite_column.insert(col_ndx, ColKey().value);
786,399✔
1047
    }
786,399✔
1048
    else {
267✔
1049
        m_opposite_table.set(col_ndx, TableKey().value);
267✔
1050
        m_opposite_column.set(col_ndx, ColKey().value);
267✔
1051
    }
267✔
1052
    refresh_index_accessors();
786,666✔
1053
    m_clusters.insert_column(col_key);
786,666✔
1054
    if (m_tombstones) {
786,666✔
1055
        m_tombstones->insert_column(col_key);
267✔
1056
    }
267✔
1057

1058
    bump_storage_version();
786,666✔
1059

1060
    return col_key;
786,666✔
1061
}
786,666✔
1062

1063

1064
void Table::do_erase_root_column(ColKey col_key)
1065
{
6,291✔
1066
    size_t col_ndx = col_key.get_index().val;
6,291✔
1067
    // If the column had a source index we have to remove and destroy that as well
1068
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
6,291✔
1069
    if (index_ref) {
6,291✔
1070
        Array::destroy_deep(index_ref, m_index_refs.get_alloc());
132✔
1071
        m_index_refs.set(col_ndx, 0);
132✔
1072
        m_index_accessors[col_ndx].reset();
132✔
1073
    }
132✔
1074
    m_opposite_table.set(col_ndx, TableKey().value);
6,291✔
1075
    m_opposite_column.set(col_ndx, ColKey().value);
6,291✔
1076
    m_index_accessors[col_ndx] = nullptr;
6,291✔
1077
    m_clusters.remove_column(col_key);
6,291✔
1078
    if (m_tombstones)
6,291✔
1079
        m_tombstones->remove_column(col_key);
309✔
1080
    size_t spec_ndx = colkey2spec_ndx(col_key);
6,291✔
1081
    m_spec.erase_column(spec_ndx);
6,291✔
1082
    m_top.adjust(top_position_for_column_key, 2);
6,291✔
1083

1084
    build_column_mapping();
6,291✔
1085
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
12,009✔
1086
        REALM_ASSERT(m_index_accessors.back() == nullptr);
5,718✔
1087
        m_index_accessors.pop_back();
5,718✔
1088
    }
5,718✔
1089
    bump_content_version();
6,291✔
1090
    bump_storage_version();
6,291✔
1091
}
6,291✔
1092

1093
Query Table::where(const Dictionary& dict) const
1094
{
12✔
1095
    return Query(m_own_ref, dict.clone_as_obj_list());
12✔
1096
}
12✔
1097

1098
void Table::set_table_type(Type table_type, bool handle_backlinks)
1099
{
312✔
1100
    if (table_type == m_table_type) {
312✔
1101
        return;
×
1102
    }
×
1103

1104
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
1105
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1106
                                                                   get_class_name(), m_table_type, table_type));
×
1107
    }
×
1108

1109
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1110
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1111
}
312✔
1112

1113
void Table::set_embedded(bool embedded, bool handle_backlinks)
1114
{
312✔
1115
    if (embedded == false) {
312✔
1116
        do_set_table_type(Type::TopLevel);
24✔
1117
        return;
24✔
1118
    }
24✔
1119

1120
    // Embedded objects cannot have a primary key.
1121
    if (get_primary_key_column()) {
288✔
1122
        throw IllegalOperation(
6✔
1123
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1124
    }
6✔
1125

1126
    if (size() == 0) {
282✔
1127
        do_set_table_type(Type::Embedded);
42✔
1128
        return;
42✔
1129
    }
42✔
1130

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

1147
        for_each_backlink_column([&](ColKey col) {
606✔
1148
            cluster->init_leaf(col, &leaf);
606✔
1149
            // Width zero means all the values are zero and there can't be any backlinks
1150
            if (leaf.get_width() == 0) {
606✔
1151
                return IteratorControl::AdvanceToNext;
36✔
1152
            }
36✔
1153

1154
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1155
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1156
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1157
                    // ref of zero means there's no backlinks
1158
                    continue;
59,340✔
1159
                }
59,340✔
1160

1161
                if (value.is_ref()) {
960✔
1162
                    // Any other ref indicates an array of backlinks, which will
1163
                    // always have more than one entry
1164
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1165
                }
78✔
1166
                else {
882✔
1167
                    // Otherwise it's a tagged ref to the single linking object
1168
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1169
                        incoming_link_count[i] = LinkCount::One;
792✔
1170
                    }
792✔
1171
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1172
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1173
                    }
42✔
1174
                }
882✔
1175

1176
                auto source_col = get_opposite_column(col);
960✔
1177
                if (source_col.get_type() == col_type_Mixed) {
960✔
1178
                    auto source_table = get_opposite_table(col);
54✔
1179
                    throw IllegalOperation(util::format(
54✔
1180
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1181
                        "which does not support linking to embedded objects.",
54✔
1182
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1183
                }
54✔
1184
            }
960✔
1185
            return IteratorControl::AdvanceToNext;
516✔
1186
        });
570✔
1187

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

1207
        return IteratorControl::AdvanceToNext;
420✔
1208
    });
474✔
1209

1210
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
1211
    for (auto key : orphans) {
59,406✔
1212
        remove_object(key);
59,406✔
1213
    }
59,406✔
1214
    for (auto key : multiple_incoming_links) {
240✔
1215
        auto obj = get_object(key);
54✔
1216
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1217
        obj.remove();
54✔
1218
    }
54✔
1219

1220
    do_set_table_type(Type::Embedded);
240✔
1221
}
240✔
1222

1223
void Table::do_set_table_type(Type table_type)
1224
{
282,501✔
1225
    while (m_top.size() <= top_position_for_flags)
282,501✔
1226
        m_top.add(0);
×
1227

1228
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
282,501✔
1229
    // reset bits 0-1
1230
    flags &= ~table_type_mask;
282,501✔
1231
    // set table type
1232
    flags |= static_cast<uint8_t>(table_type);
282,501✔
1233
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
282,501✔
1234
    m_table_type = table_type;
282,501✔
1235
}
282,501✔
1236

1237

1238
void Table::detach(LifeCycleCookie cookie) noexcept
1239
{
2,067,624✔
1240
    m_cookie = cookie;
2,067,624✔
1241
    m_alloc.bump_instance_version();
2,067,624✔
1242
}
2,067,624✔
1243

1244
void Table::fully_detach() noexcept
1245
{
2,049,990✔
1246
    m_spec.detach();
2,049,990✔
1247
    m_top.detach();
2,049,990✔
1248
    m_index_refs.detach();
2,049,990✔
1249
    m_opposite_table.detach();
2,049,990✔
1250
    m_opposite_column.detach();
2,049,990✔
1251
    m_index_accessors.clear();
2,049,990✔
1252
}
2,049,990✔
1253

1254

1255
Table::~Table() noexcept
1256
{
3,588✔
1257
    if (m_top.is_attached()) {
3,588✔
1258
        // If destroyed as a standalone table, destroy all memory allocated
1259
        if (m_top.get_parent() == nullptr) {
3,588✔
1260
            m_top.destroy_deep();
3,588✔
1261
        }
3,588✔
1262
        fully_detach();
3,588✔
1263
    }
3,588✔
1264
    else {
×
1265
        REALM_ASSERT(m_index_accessors.size() == 0);
×
1266
    }
×
1267
    m_cookie = cookie_deleted;
3,588✔
1268
}
3,588✔
1269

1270

1271
IndexType Table::search_index_type(ColKey col_key) const noexcept
1272
{
3,915,741✔
1273
    if (m_index_accessors[col_key.get_index().val].get()) {
3,915,741✔
1274
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
914,235✔
1275
        bool fulltext = attr.test(col_attr_FullText_Indexed);
914,235✔
1276
        return fulltext ? IndexType::Fulltext : IndexType::General;
914,235✔
1277
    }
914,235✔
1278
    return IndexType::None;
3,001,506✔
1279
}
3,915,741✔
1280

1281

1282
void Table::migrate_sets_and_dictionaries()
1283
{
180✔
1284
    std::vector<ColKey> to_migrate;
180✔
1285
    for (auto col : get_column_keys()) {
570✔
1286
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
1287
            to_migrate.push_back(col);
12✔
1288
        }
12✔
1289
    }
570✔
1290
    if (to_migrate.size()) {
180✔
1291
        for (auto obj : *this) {
6✔
1292
            for (auto col : to_migrate) {
12✔
1293
                if (col.is_set()) {
12✔
1294
                    auto set = obj.get_set<Mixed>(col);
6✔
1295
                    set.migrate();
6✔
1296
                }
6✔
1297
                else if (col.is_dictionary()) {
6✔
1298
                    auto dict = obj.get_dictionary(col);
6✔
1299
                    dict.migrate();
6✔
1300
                }
6✔
1301
            }
12✔
1302
        }
6✔
1303
    }
6✔
1304
}
180✔
1305

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

1336
void Table::migrate_col_keys()
1337
{
354✔
1338
    if (m_spec.migrate_column_keys()) {
354✔
1339
        build_column_mapping();
24✔
1340
    }
24✔
1341

1342
    // Fix also m_opposite_column col_keys
1343
    ColumnType col_type_LinkList(13);
354✔
1344
    auto sz = m_opposite_column.size();
354✔
1345

1346
    for (size_t n = 0; n < sz; n++) {
1,386✔
1347
        ColKey col_key(m_opposite_column.get(n));
1,032✔
1348
        if (col_key.get_type() == col_type_LinkList) {
1,032✔
1349
            auto attrs = col_key.get_attrs();
24✔
1350
            REALM_ASSERT(attrs.test(col_attr_List));
24✔
1351
            ColKey new_key(col_key.get_index(), col_type_Link, attrs, col_key.get_tag());
24✔
1352
            m_opposite_column.set(n, new_key.value);
24✔
1353
        }
24✔
1354
    }
1,032✔
1355
}
354✔
1356

1357
StringData Table::get_name() const noexcept
1358
{
3,196,161✔
1359
    const Array& real_top = m_top;
3,196,161✔
1360
    ArrayParent* parent = real_top.get_parent();
3,196,161✔
1361
    if (!parent)
3,196,161✔
1362
        return StringData("");
48✔
1363
    REALM_ASSERT(dynamic_cast<Group*>(parent));
3,196,113✔
1364
    return static_cast<Group*>(parent)->get_table_name(get_key());
3,196,113✔
1365
}
3,196,161✔
1366

1367
StringData Table::get_class_name() const noexcept
1368
{
630,939✔
1369
    return Group::table_name_to_class_name(get_name());
630,939✔
1370
}
630,939✔
1371

1372
const char* Table::get_state() const noexcept
1373
{
42✔
1374
    switch (m_cookie) {
42✔
1375
        case cookie_created:
✔
1376
            return "created";
×
1377
        case cookie_transaction_ended:
6✔
1378
            return "transaction_ended";
6✔
1379
        case cookie_initialized:
✔
1380
            return "initialised";
×
1381
        case cookie_removed:
36✔
1382
            return "removed";
36✔
1383
        case cookie_void:
✔
1384
            return "void";
×
1385
        case cookie_deleted:
✔
1386
            return "deleted";
×
1387
    }
42✔
1388
    return "";
×
1389
}
42✔
1390

1391

1392
bool Table::is_nullable(ColKey col_key) const
1393
{
845,535✔
1394
    REALM_ASSERT_DEBUG(valid_column(col_key));
845,535✔
1395
    return col_key.get_attrs().test(col_attr_Nullable);
845,535✔
1396
}
845,535✔
1397

1398
bool Table::is_list(ColKey col_key) const
1399
{
27,816✔
1400
    REALM_ASSERT_DEBUG(valid_column(col_key));
27,816✔
1401
    return col_key.get_attrs().test(col_attr_List);
27,816✔
1402
}
27,816✔
1403

1404

1405
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
1406
{
285,891✔
1407
    Array top(alloc);
285,891✔
1408
    _impl::DeepArrayDestroyGuard dg(&top);
285,891✔
1409
    top.create(Array::type_HasRefs); // Throws
285,891✔
1410
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
285,891✔
1411

1412
    {
285,891✔
1413
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
285,891✔
1414
        dg_2.reset(mem.get_ref());
285,891✔
1415
        int_fast64_t v(from_ref(mem.get_ref()));
285,891✔
1416
        top.add(v); // Throws
285,891✔
1417
        dg_2.release();
285,891✔
1418
    }
285,891✔
1419
    top.add(0); // Old position for columns
285,891✔
1420
    {
285,891✔
1421
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
285,891✔
1422
        dg_2.reset(mem.get_ref());
285,891✔
1423
        int_fast64_t v(from_ref(mem.get_ref()));
285,891✔
1424
        top.add(v); // Throws
285,891✔
1425
        dg_2.release();
285,891✔
1426
    }
285,891✔
1427

1428
    // Table key value
1429
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
285,891✔
1430
    top.add(rot);
285,891✔
1431

1432
    // Search indexes
1433
    {
285,891✔
1434
        bool context_flag = false;
285,891✔
1435
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
285,891✔
1436
        dg_2.reset(mem.get_ref());
285,891✔
1437
        int_fast64_t v(from_ref(mem.get_ref()));
285,891✔
1438
        top.add(v); // Throws
285,891✔
1439
        dg_2.release();
285,891✔
1440
    }
285,891✔
1441
    rot = RefOrTagged::make_tagged(0);
285,891✔
1442
    top.add(rot); // Column key
285,891✔
1443
    top.add(rot); // Version
285,891✔
1444
    dg.release();
285,891✔
1445
    // Opposite keys (table and column)
1446
    {
285,891✔
1447
        bool context_flag = false;
285,891✔
1448
        {
285,891✔
1449
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
285,891✔
1450
            dg_2.reset(mem.get_ref());
285,891✔
1451
            int_fast64_t v(from_ref(mem.get_ref()));
285,891✔
1452
            top.add(v); // Throws
285,891✔
1453
            dg_2.release();
285,891✔
1454
        }
285,891✔
1455
        {
285,891✔
1456
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
285,891✔
1457
            dg_2.reset(mem.get_ref());
285,891✔
1458
            int_fast64_t v(from_ref(mem.get_ref()));
285,891✔
1459
            top.add(v); // Throws
285,891✔
1460
            dg_2.release();
285,891✔
1461
        }
285,891✔
1462
    }
285,891✔
1463
    top.add(0); // Sequence number
285,891✔
1464
    top.add(0); // Collision_map
285,891✔
1465
    top.add(0); // pk col key
285,891✔
1466
    top.add(0); // flags
285,891✔
1467
    top.add(0); // tombstones
285,891✔
1468

1469
    REALM_ASSERT(top.size() == top_array_size);
285,891✔
1470

1471
    return top.get_ref();
285,891✔
1472
}
285,891✔
1473

1474
void Table::ensure_graveyard()
1475
{
41,469✔
1476
    if (!m_tombstones) {
41,469✔
1477
        while (m_top.size() < top_position_for_tombstones)
2,436✔
1478
            m_top.add(0);
×
1479
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
2,436✔
1480
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
2,436✔
1481
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
2,436✔
1482
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
2,436✔
1483
        m_tombstones->init_from_parent();
2,436✔
1484
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
9,171✔
1485
            ts->insert_column(col);
9,171✔
1486
            return IteratorControl::AdvanceToNext;
9,171✔
1487
        });
9,171✔
1488
    }
2,436✔
1489
}
41,469✔
1490

1491
void Table::batch_erase_rows(const KeyColumn& keys)
1492
{
558✔
1493
    size_t num_objs = keys.size();
558✔
1494
    std::vector<ObjKey> vec;
558✔
1495
    vec.reserve(num_objs);
558✔
1496
    for (size_t i = 0; i < num_objs; ++i) {
2,898✔
1497
        ObjKey key = keys.get(i);
2,340✔
1498
        if (key != null_key && is_valid(key)) {
2,340✔
1499
            vec.push_back(key);
2,340✔
1500
        }
2,340✔
1501
    }
2,340✔
1502

1503
    sort(vec.begin(), vec.end());
558✔
1504
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
558✔
1505

1506
    batch_erase_objects(vec);
558✔
1507
}
558✔
1508

1509
void Table::batch_erase_objects(std::vector<ObjKey>& keys)
1510
{
10,356✔
1511
    Group* g = get_parent_group();
10,356✔
1512
    bool maybe_has_incoming_links = g && !is_asymmetric();
10,356✔
1513

1514
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
10,356✔
1515
        CascadeState state(CascadeState::Mode::Strong, g);
9,588✔
1516
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
9,588✔
1517
            state.m_to_be_deleted.emplace_back(m_key, k);
2,076✔
1518
        });
2,076✔
1519
        if (maybe_has_incoming_links)
9,588✔
1520
            nullify_links(state);
9,588✔
1521
        remove_recursive(state);
9,588✔
1522
    }
9,588✔
1523
    else {
768✔
1524
        CascadeState state(CascadeState::Mode::None, g);
768✔
1525
        for (auto k : keys) {
2,512,302✔
1526
            if (maybe_has_incoming_links) {
2,512,302✔
1527
                m_clusters.nullify_incoming_links(k, state);
2,512,224✔
1528
            }
2,512,224✔
1529
            m_clusters.erase(k, state);
2,512,302✔
1530
        }
2,512,302✔
1531
    }
768✔
1532
    keys.clear();
10,356✔
1533
}
10,356✔
1534

1535
void Table::clear()
1536
{
6,429✔
1537
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
6,429✔
1538
    m_clusters.clear(state);
6,429✔
1539
    free_collision_table();
6,429✔
1540
}
6,429✔
1541

1542

1543
Group* Table::get_parent_group() const noexcept
1544
{
62,975,757✔
1545
    if (!m_top.is_attached())
62,975,757✔
1546
        return 0;                             // Subtable with shared descriptor
×
1547
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
62,975,757✔
1548
    if (!parent)
62,975,757✔
1549
        return 0; // Free-standing table
26,290,755✔
1550

1551
    return static_cast<Group*>(parent);
36,685,002✔
1552
}
62,975,757✔
1553

1554
inline uint64_t Table::get_sync_file_id() const noexcept
1555
{
39,540,537✔
1556
    Group* g = get_parent_group();
39,540,537✔
1557
    return g ? g->get_sync_file_id() : 0;
39,540,537✔
1558
}
39,540,537✔
1559

1560
size_t Table::get_index_in_group() const noexcept
1561
{
14,353,371✔
1562
    if (!m_top.is_attached())
14,353,371✔
1563
        return realm::npos;                   // Subtable with shared descriptor
×
1564
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
14,353,371✔
1565
    if (!parent)
14,353,371✔
1566
        return realm::npos; // Free-standing table
×
1567
    return m_top.get_ndx_in_parent();
14,353,371✔
1568
}
14,353,371✔
1569

1570
uint64_t Table::allocate_sequence_number()
1571
{
20,689,527✔
1572
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
20,689,527✔
1573
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
20,689,527✔
1574
    rot = RefOrTagged::make_tagged(sn + 1);
20,689,527✔
1575
    m_top.set(top_position_for_sequence_number, rot);
20,689,527✔
1576

1577
    return sn;
20,689,527✔
1578
}
20,689,527✔
1579

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

1585
TableRef Table::get_link_target(ColKey col_key) noexcept
1586
{
250,437✔
1587
    return get_opposite_table(col_key);
250,437✔
1588
}
250,437✔
1589

1590
// count ----------------------------------------------
1591

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

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

1625
    traverse_clusters(f);
18✔
1626

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

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

1643
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
36,819✔
1644
        // direct aggregate on the leaf
1645
        cluster->init_leaf(column_key, &leaf);
36,819✔
1646
        st.m_key_offset = cluster->get_offset();
36,819✔
1647
        st.m_key_values = cluster->get_key_array();
36,819✔
1648
        st.set_payload_column(&leaf);
36,819✔
1649
        bool cont = true;
36,819✔
1650
        size_t sz = leaf.size();
36,819✔
1651
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
133,953✔
1652
            cont = st.match(local_index);
97,134✔
1653
        }
97,134✔
1654
        return IteratorControl::AdvanceToNext;
36,819✔
1655
    };
36,819✔
1656

1657
    traverse_clusters(f);
36,801✔
1658
}
36,801✔
1659

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

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

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

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

1684
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1685
{
30,519✔
1686
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
30,519✔
1687
}
30,519✔
1688

1689

1690
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1691
{
30,284,322✔
1692
    check_column(col);
30,284,322✔
1693
    return m_index_accessors[col.get_index().val].get();
30,284,322✔
1694
}
30,284,322✔
1695

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

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

1707
    if (!col_key.is_nullable() && value_is_null(value)) {
35,166!
1708
        return {}; // this is a precaution/optimization
6✔
1709
    }
6✔
1710
    // You cannot call GetIndexData on ObjKey
1711
    if constexpr (!std::is_same_v<T, ObjKey>) {
35,160✔
1712
        if (SearchIndex* index = get_search_index(col_key)) {
35,142✔
1713
            return index->find_first(value);
27,294✔
1714
        }
27,294✔
1715
        if (col_key == m_primary_key_col) {
7,848✔
1716
            return find_primary_key(value);
×
1717
        }
×
1718
    }
7,848✔
1719

1720
    ObjKey key;
7,848✔
1721
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
21,597✔
1722
    LeafType leaf(get_alloc());
21,597✔
1723

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

1734
    traverse_clusters(f);
21,597✔
1735

1736
    return key;
21,597✔
1737
}
35,154✔
1738

1739
namespace realm {
1740

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1864

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1944
util::Logger* Table::get_logger() const noexcept
1945
{
1,901,514✔
1946
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,901,514✔
1947
}
1,901,514✔
1948

1949
// Called after a commit. Table will effectively contain the same as before,
1950
// but now with new refs from the file
1951
void Table::update_from_parent() noexcept
1952
{
750,918✔
1953
    // There is no top for sub-tables sharing spec
1954
    if (m_top.is_attached()) {
750,918✔
1955
        m_top.update_from_parent();
750,918✔
1956
        m_spec.update_from_parent();
750,918✔
1957
        m_clusters.update_from_parent();
750,918✔
1958
        m_index_refs.update_from_parent();
750,918✔
1959
        for (auto&& index : m_index_accessors) {
2,253,414✔
1960
            if (index != nullptr) {
2,253,414✔
1961
                index->update_from_parent();
332,859✔
1962
            }
332,859✔
1963
        }
2,253,414✔
1964

1965
        m_opposite_table.update_from_parent();
750,918✔
1966
        m_opposite_column.update_from_parent();
750,918✔
1967
        if (m_top.size() > top_position_for_flags) {
750,918✔
1968
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
750,909✔
1969
            m_table_type = Type(flags & table_type_mask);
750,909✔
1970
        }
750,909✔
1971
        else {
9✔
1972
            m_table_type = Type::TopLevel;
9✔
1973
        }
9✔
1974
        if (m_tombstones)
750,918✔
1975
            m_tombstones->update_from_parent();
4,110✔
1976

1977
        refresh_content_version();
750,918✔
1978
        m_has_any_embedded_objects.reset();
750,918✔
1979
    }
750,918✔
1980
    m_alloc.bump_storage_version();
750,918✔
1981
}
750,918✔
1982

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

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

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

2070
    return true;
108✔
2071
}
126✔
2072

2073

2074
void Table::flush_for_commit()
2075
{
1,068,318✔
2076
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
1,068,318✔
2077
        if (!m_top.is_read_only()) {
1,068,312✔
2078
            ++m_in_file_version_at_transaction_boundary;
812,445✔
2079
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
812,445✔
2080
            m_top.set(top_position_for_version, rot_version);
812,445✔
2081
        }
812,445✔
2082
    }
1,068,312✔
2083
}
1,068,318✔
2084

2085
void Table::refresh_content_version()
2086
{
1,096,410✔
2087
    REALM_ASSERT(m_top.is_attached());
1,096,410✔
2088
    if (m_top.size() >= top_position_for_version) {
1,096,410✔
2089
        // we have versioning info in the file. Use this to conditionally
2090
        // bump the version counter:
2091
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,096,248✔
2092
        REALM_ASSERT(rot_version.is_tagged());
1,096,248✔
2093
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,096,248✔
2094
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
241,005✔
2095
            bump_content_version();
241,005✔
2096
        }
241,005✔
2097
    }
1,096,248✔
2098
    else {
162✔
2099
        // assume the worst:
2100
        bump_content_version();
162✔
2101
    }
162✔
2102
}
1,096,410✔
2103

2104

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

2143
void Table::refresh_index_accessors()
2144
{
3,201,030✔
2145
    // Refresh search index accessors
2146

2147
    // First eliminate any index accessors for eliminated last columns
2148
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
3,201,030✔
2149
    m_index_accessors.resize(col_ndx_end);
3,201,030✔
2150

2151
    // Then eliminate/refresh/create accessors within column range
2152
    // we can not use for_each_column() here, since the columns may have changed
2153
    // and the index accessor vector is not updated correspondingly.
2154
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
17,222,307✔
2155
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
14,021,277✔
2156

2157
        if (ref == 0) {
14,021,277✔
2158
            // accessor drop
2159
            m_index_accessors[col_ndx].reset();
13,004,217✔
2160
        }
13,004,217✔
2161
        else {
1,017,060✔
2162
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
1,017,060✔
2163
            bool fulltext = attr.test(col_attr_FullText_Indexed);
1,017,060✔
2164
            auto col_key = m_leaf_ndx2colkey[col_ndx];
1,017,060✔
2165
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
1,017,060✔
2166

2167
            if (m_index_accessors[col_ndx]) { // still there, refresh:
1,017,060✔
2168
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
409,044✔
2169
            }
409,044✔
2170
            else { // new index!
608,016✔
2171
                m_index_accessors[col_ndx] =
608,016✔
2172
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
608,016✔
2173
            }
608,016✔
2174
        }
1,017,060✔
2175
    }
14,021,277✔
2176
}
3,201,030✔
2177

2178
bool Table::is_cross_table_link_target() const noexcept
2179
{
1,569✔
2180
    auto is_cross_link = [this](ColKey col_key) {
1,569✔
2181
        auto t = col_key.get_type();
69✔
2182
        // look for a backlink with a different target than ourselves
2183
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
69✔
2184
                   ? IteratorControl::Stop
69✔
2185
                   : IteratorControl::AdvanceToNext;
69✔
2186
    };
69✔
2187
    return for_each_backlink_column(is_cross_link);
1,569✔
2188
}
1,569✔
2189

2190
// LCOV_EXCL_START ignore debug functions
2191

2192
void Table::verify() const
2193
{
237,633✔
2194
#ifdef REALM_DEBUG
237,633✔
2195
    if (m_top.is_attached())
237,633✔
2196
        m_top.verify();
237,633✔
2197
    m_spec.verify();
237,633✔
2198
    m_clusters.verify();
237,633✔
2199
    if (nb_unresolved())
237,633✔
2200
        m_tombstones->verify();
28,524✔
2201
#endif
237,633✔
2202
}
237,633✔
2203

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

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

2233
    REALM_ASSERT(key.value >= 0);
22,918,875✔
2234

2235
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,918,875✔
2236

2237
    return obj;
22,918,875✔
2238
}
22,918,875✔
2239

2240
Obj Table::create_linked_object()
2241
{
47,841✔
2242
    REALM_ASSERT(is_embedded());
47,841✔
2243

2244
    GlobalKey object_id = allocate_object_id_squeezed();
47,841✔
2245
    ObjKey key = object_id.get_local_key(get_sync_file_id());
47,841✔
2246
    REALM_ASSERT(key.value >= 0);
47,841✔
2247

2248
    if (auto repl = get_repl())
47,841✔
2249
        repl->create_linked_object(this, key);
46,527✔
2250

2251
    Obj obj = m_clusters.insert(key, {});
47,841✔
2252

2253
    return obj;
47,841✔
2254
}
47,841✔
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

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

2267
    try {
30✔
2268
        Obj obj = m_clusters.insert(key, values);
30✔
2269
        // Check if tombstone exists
2270
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2271
            auto unres_key = key.get_unresolved();
6✔
2272
            // Copy links over
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
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

2282
        return obj;
30✔
2283
    }
30✔
2284
    catch (const KeyAlreadyUsed&) {
30✔
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
{
1,081,182✔
2292
    auto primary_key_col = get_primary_key_column();
1,081,182✔
2293
    if (is_embedded() || !primary_key_col)
1,081,182✔
2294
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2295
                              util::format("Table has no primary key: %1", get_name()));
6✔
2296

2297
    DataType type = DataType(primary_key_col.get_type());
1,081,176✔
2298

2299
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
1,081,176✔
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

2305
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
1,081,170✔
2306
        primary_key.get_type() != type) {
1,081,170✔
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

2311
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
1,081,164✔
2312

2313
    if (did_create)
1,081,164✔
2314
        *did_create = false;
97,362✔
2315

2316
    // Check for existing object
2317
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
1,081,164✔
2318
        if (mode == UpdateMode::never) {
176,100✔
2319
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2320
        }
6✔
2321
        auto obj = m_clusters.get(key);
176,094✔
2322
        for (auto& val : field_values) {
176,094✔
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;
176,094✔
2328
    }
176,100✔
2329

2330
    ObjKey unres_key;
905,064✔
2331
    if (m_tombstones) {
905,064✔
2332
        // Check for potential tombstone
2333
        GlobalKey object_id{primary_key};
46,011✔
2334
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
46,011✔
2335

2336
        ObjKey key = object_key.get_unresolved();
46,011✔
2337
        if (auto obj = m_tombstones->try_get_obj(key)) {
46,011✔
2338
            auto existing_pk_value = obj.get_any(primary_key_col);
39,345✔
2339

2340
            // If the primary key is the same, the object should be resurrected below
2341
            if (existing_pk_value == primary_key) {
39,345✔
2342
                unres_key = key;
39,339✔
2343
            }
39,339✔
2344
        }
39,345✔
2345
    }
46,011✔
2346

2347
    ObjKey key = get_next_valid_key();
905,064✔
2348

2349
    auto repl = get_repl();
905,064✔
2350
    if (repl) {
905,064✔
2351
        repl->create_object_with_primary_key(this, key, primary_key);
357,996✔
2352
    }
357,996✔
2353
    if (did_create) {
905,064✔
2354
        *did_create = true;
56,622✔
2355
    }
56,622✔
2356

2357
    field_values.insert(primary_key_col, primary_key);
905,064✔
2358
    Obj ret = m_clusters.insert(key, field_values);
905,064✔
2359

2360
    // Check if unresolved exists
2361
    if (unres_key) {
905,064✔
2362
        if (Replication* repl = get_repl()) {
39,339✔
2363
            if (auto logger = repl->would_log(util::Logger::Level::debug)) {
39,249✔
2364
                logger->log(LogCategory::object, util::Logger::Level::debug, "Cancel tombstone on '%1': %2",
36✔
2365
                            get_class_name(), unres_key);
36✔
2366
            }
36✔
2367
        }
39,249✔
2368

2369
        auto tombstone = m_tombstones->get(unres_key);
39,339✔
2370
        ret.assign_pk_and_backlinks(tombstone);
39,339✔
2371
        // If tombstones had no links to it, it may still be alive
2372
        if (m_tombstones->is_valid(unres_key)) {
39,339✔
2373
            CascadeState state(CascadeState::Mode::None);
39,279✔
2374
            m_tombstones->erase(unres_key, state);
39,279✔
2375
        }
39,279✔
2376
    }
39,339✔
2377
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
905,064✔
2378
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
2379
    }
1,290✔
2380
    return ret;
905,064✔
2381
}
1,081,164✔
2382

2383
ObjKey Table::find_primary_key(Mixed primary_key) const
2384
{
764,880✔
2385
    auto primary_key_col = get_primary_key_column();
764,880✔
2386
    REALM_ASSERT(primary_key_col);
764,880✔
2387
    DataType type = DataType(primary_key_col.get_type());
764,880✔
2388
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
764,880✔
2389
                 primary_key.get_type() == type);
764,880✔
2390

2391
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
764,883✔
2392
        return index->find_first(primary_key);
764,865✔
2393
    }
764,865✔
2394

2395
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
2396
    // so try the old algorithm
2397
    GlobalKey object_id{primary_key};
2,147,483,665✔
2398
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
2,147,483,665✔
2399

2400
    // Check if existing
2401
    if (auto obj = m_clusters.try_get_obj(object_key)) {
2,147,483,665✔
2402
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2403

2404
        if (existing_pk_value == primary_key) {
×
2405
            return object_key;
×
2406
        }
×
2407
    }
×
2408
    return {};
2,147,483,665✔
2409
}
2,147,483,665✔
2410

2411
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2412
{
643,884✔
2413
    // Check if existing
2414
    if (auto key = find_primary_key(primary_key)) {
643,884✔
2415
        return key;
603,387✔
2416
    }
603,387✔
2417

2418
    // Object does not exist - create tombstone
2419
    GlobalKey object_id{primary_key};
40,497✔
2420
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
40,497✔
2421
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
40,497✔
2422
}
643,884✔
2423

2424
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2425
{
18✔
2426
    REALM_ASSERT(!m_primary_key_col);
18✔
2427
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2428

2429
    // Check if existing
2430
    if (m_clusters.is_valid(object_key)) {
18✔
2431
        return object_key;
12✔
2432
    }
12✔
2433

2434
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2435
}
18✔
2436

2437
ObjKey Table::get_objkey(GlobalKey global_key) const
2438
{
18✔
2439
    ObjKey key;
18✔
2440
    REALM_ASSERT(!m_primary_key_col);
18✔
2441
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
2442
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
2443
        key = global_key.get_local_key(get_sync_file_id());
6✔
2444
    }
6✔
2445
    if (key && !is_valid(key)) {
18✔
2446
        key = realm::null_key;
6✔
2447
    }
6✔
2448
    return key;
18✔
2449
}
18✔
2450

2451
GlobalKey Table::get_object_id(ObjKey key) const
2452
{
144✔
2453
    auto col = get_primary_key_column();
144✔
2454
    if (col) {
144✔
2455
        const Obj obj = get_object(key);
24✔
2456
        auto val = obj.get_any(col);
24✔
2457
        return {val};
24✔
2458
    }
24✔
2459
    else {
120✔
2460
        return {key, get_sync_file_id()};
120✔
2461
    }
120✔
2462
    return {};
×
2463
}
144✔
2464

2465
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2466
{
116,004✔
2467
    auto primary_key_col = get_primary_key_column();
116,004✔
2468
    REALM_ASSERT(primary_key_col);
116,004✔
2469
    DataType type = DataType(primary_key_col.get_type());
116,004✔
2470
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
116,004✔
2471
                 primary_key.get_type() == type);
116,004✔
2472
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
116,004✔
2473
    return k ? m_clusters.get(k) : Obj{};
116,004✔
2474
}
116,004✔
2475

2476
Mixed Table::get_primary_key(ObjKey key) const
2477
{
703,182✔
2478
    auto primary_key_col = get_primary_key_column();
703,182✔
2479
    REALM_ASSERT(primary_key_col);
703,182✔
2480
    if (key.is_unresolved()) {
703,182✔
2481
        REALM_ASSERT(m_tombstones);
792✔
2482
        return m_tombstones->get(key).get_any(primary_key_col);
792✔
2483
    }
792✔
2484
    else {
702,390✔
2485
        return m_clusters.get(key).get_any(primary_key_col);
702,390✔
2486
    }
702,390✔
2487
}
703,182✔
2488

2489
GlobalKey Table::allocate_object_id_squeezed()
2490
{
19,822,752✔
2491
    // m_client_file_ident will be zero if we haven't been in contact with
2492
    // the server yet.
2493
    auto peer_id = get_sync_file_id();
19,822,752✔
2494
    auto sequence = allocate_sequence_number();
19,822,752✔
2495
    return GlobalKey{peer_id, sequence};
19,822,752✔
2496
}
19,822,752✔
2497

2498
namespace {
2499

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

2515
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2516
{
12✔
2517
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2518
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2519
}
12✔
2520

2521
} // namespace
2522

2523
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2524
{
87,306✔
2525
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
87,306✔
2526

2527
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
87,306✔
2528
        Allocator& alloc = m_top.get_alloc();
24✔
2529
        Array collision_map{alloc};
24✔
2530
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2531

2532
        Array hi{alloc};
24✔
2533
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2534

2535
        // Entries are ordered by hi,lo
2536
        size_t found = hi.find_first(object_id.hi());
24✔
2537
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
2538
            Array lo{alloc};
24✔
2539
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
2540
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
2541
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
2542
                Array local_id{alloc};
24✔
2543
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
2544
                return ObjKey{local_id.get(candidate)};
24✔
2545
            }
24✔
2546
        }
24✔
2547
    }
24✔
2548

2549
    return optimistic;
87,282✔
2550
}
87,306✔
2551

2552
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2553
                                                     ObjKey colliding_local_id)
2554
{
12✔
2555
    // Possible optimization: Cache these accessors
2556
    Allocator& alloc = m_top.get_alloc();
12✔
2557
    Array collision_map{alloc};
12✔
2558
    Array hi{alloc};
12✔
2559
    Array lo{alloc};
12✔
2560
    Array local_id{alloc};
12✔
2561

2562
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2563
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2564
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2565
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2566

2567
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2568
    if (collision_map_ref) {
12✔
2569
        collision_map.init_from_parent(); // Throws
×
2570
    }
×
2571
    else {
12✔
2572
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2573
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2574
        collision_map.update_parent();
12✔
2575

2576
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2577
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2578
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2579
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2580
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2581
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2582
    }
12✔
2583

2584
    hi.init_from_parent();       // Throws
12✔
2585
    lo.init_from_parent();       // Throws
12✔
2586
    local_id.init_from_parent(); // Throws
12✔
2587

2588
    size_t num_entries = hi.size();
12✔
2589
    REALM_ASSERT(lo.size() == num_entries);
12✔
2590
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2591

2592
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2593
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2594
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2595
            ++i;
6✔
2596
        return i;
24✔
2597
    };
24✔
2598

2599
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
2600
        size_t i = lower_bound_object_id(object_id);
24✔
2601
        if (i != num_entries) {
24✔
2602
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
2603
            if (existing == object_id) {
6✔
2604
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
2605
                return;
×
2606
            }
×
2607
        }
6✔
2608
        hi.insert(i, int64_t(object_id.hi()));
24✔
2609
        lo.insert(i, int64_t(object_id.lo()));
24✔
2610
        local_id.insert(i, new_local_id.value);
24✔
2611
        ++num_entries;
24✔
2612
    };
24✔
2613

2614
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2615
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2616
    insert_collision(incoming_id, new_local_id);
12✔
2617
    insert_collision(colliding_id, colliding_local_id);
12✔
2618

2619
    return new_local_id;
12✔
2620
}
12✔
2621

2622
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2623
{
41,469✔
2624
    auto unres_key = key.get_unresolved();
41,469✔
2625

2626
    ensure_graveyard();
41,469✔
2627
    auto tombstone = m_tombstones->try_get_obj(unres_key);
41,469✔
2628
    if (tombstone) {
41,469✔
2629
        if (pk_col) {
360✔
2630
            auto existing_pk_value = tombstone.get_any(pk_col);
360✔
2631
            // It may just be the same object
2632
            if (existing_pk_value != pk_val) {
360✔
2633
                // We have a collision - create new ObjKey
2634
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
2635
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
2636
            }
12✔
2637
        }
360✔
2638
        return tombstone;
348✔
2639
    }
360✔
2640
    if (Replication* repl = get_repl()) {
41,109✔
2641
        if (auto logger = repl->would_log(util::Logger::Level::debug)) {
40,839✔
2642
            logger->log(LogCategory::object, util::Logger::Level::debug,
576✔
2643
                        "Create tombstone for object '%1' with primary key %2 : %3", get_class_name(), pk_val,
576✔
2644
                        unres_key);
576✔
2645
        }
576✔
2646
    }
40,839✔
2647
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
41,109✔
2648
}
41,469✔
2649

2650
void Table::free_local_id_after_hash_collision(ObjKey key)
2651
{
5,323,152✔
2652
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,323,152✔
2653
        if (key.is_unresolved()) {
30✔
2654
            // Keys will always be inserted as resolved
2655
            key = key.get_unresolved();
24✔
2656
        }
24✔
2657
        // Possible optimization: Cache these accessors
2658
        Array collision_map{m_alloc};
30✔
2659
        Array local_id{m_alloc};
30✔
2660

2661
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2662
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2663
        collision_map.init_from_ref(collision_map_ref);
30✔
2664
        local_id.init_from_parent();
30✔
2665
        auto ndx = local_id.find_first(key.value);
30✔
2666
        if (ndx != realm::npos) {
30✔
2667
            Array hi{m_alloc};
24✔
2668
            Array lo{m_alloc};
24✔
2669

2670
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2671
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2672
            hi.init_from_parent();
24✔
2673
            lo.init_from_parent();
24✔
2674

2675
            hi.erase(ndx);
24✔
2676
            lo.erase(ndx);
24✔
2677
            local_id.erase(ndx);
24✔
2678
            if (hi.size() == 0) {
24✔
2679
                free_collision_table();
12✔
2680
            }
12✔
2681
        }
24✔
2682
    }
30✔
2683
}
5,323,152✔
2684

2685
void Table::free_collision_table()
2686
{
6,441✔
2687
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
6,441✔
2688
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2689
        m_top.set(top_position_for_collision_map, 0);
12✔
2690
    }
12✔
2691
}
6,441✔
2692

2693
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2694
{
3,891✔
2695
    while (number--) {
6,327,264✔
2696
        keys.push_back(create_object().get_key());
6,323,373✔
2697
    }
6,323,373✔
2698
}
3,891✔
2699

2700
void Table::create_objects(const std::vector<ObjKey>& keys)
2701
{
630✔
2702
    for (auto k : keys) {
5,616✔
2703
        create_object(k);
5,616✔
2704
    }
5,616✔
2705
}
630✔
2706

2707
void Table::dump_objects()
2708
{
×
2709
    m_clusters.dump_objects();
×
2710
    if (nb_unresolved())
×
2711
        m_tombstones->dump_objects();
×
2712
}
×
2713

2714
void Table::remove_object(ObjKey key)
2715
{
2,542,740✔
2716
    Group* g = get_parent_group();
2,542,740✔
2717

2718
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,542,740✔
2719
        CascadeState state(CascadeState::Mode::Strong, g);
4,002✔
2720
        state.m_to_be_deleted.emplace_back(m_key, key);
4,002✔
2721
        m_clusters.nullify_incoming_links(key, state);
4,002✔
2722
        remove_recursive(state);
4,002✔
2723
    }
4,002✔
2724
    else {
2,538,738✔
2725
        CascadeState state(CascadeState::Mode::None, g);
2,538,738✔
2726
        if (g) {
2,538,738✔
2727
            m_clusters.nullify_incoming_links(key, state);
2,456,817✔
2728
        }
2,456,817✔
2729
        m_clusters.erase(key, state);
2,538,738✔
2730
    }
2,538,738✔
2731
}
2,542,740✔
2732

2733
ObjKey Table::invalidate_object(ObjKey key)
2734
{
8,706✔
2735
    if (is_embedded())
8,706✔
2736
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2737
    REALM_ASSERT(!key.is_unresolved());
8,706✔
2738

2739
    Obj tombstone;
8,706✔
2740
    auto obj = get_object(key);
8,706✔
2741
    if (obj.has_backlinks(false)) {
8,706✔
2742
        // If the object has backlinks, we should make a tombstone
2743
        // and make inward links point to it,
2744
        if (auto primary_key_col = get_primary_key_column()) {
984✔
2745
            auto pk = obj.get_any(primary_key_col);
828✔
2746
            GlobalKey object_id{pk};
828✔
2747
            auto unres_key = global_to_local_object_id_hashed(object_id);
828✔
2748
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
828✔
2749
        }
828✔
2750
        else {
156✔
2751
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
2752
        }
156✔
2753
        tombstone.assign_pk_and_backlinks(obj);
984✔
2754
    }
984✔
2755

2756
    remove_object(key);
8,706✔
2757

2758
    return tombstone.get_key();
8,706✔
2759
}
8,706✔
2760

2761
void Table::remove_object_recursive(ObjKey key)
2762
{
168,036✔
2763
    size_t table_ndx = get_index_in_group();
168,036✔
2764
    if (table_ndx != realm::npos) {
168,036✔
2765
        CascadeState state(CascadeState::Mode::All, get_parent_group());
168,036✔
2766
        state.m_to_be_deleted.emplace_back(m_key, key);
168,036✔
2767
        nullify_links(state);
168,036✔
2768
        remove_recursive(state);
168,036✔
2769
    }
168,036✔
2770
    else {
×
2771
        // No links in freestanding table
2772
        CascadeState state(CascadeState::Mode::None);
×
2773
        m_clusters.erase(key, state);
×
2774
    }
×
2775
}
168,036✔
2776

2777
Table::Iterator Table::begin() const
2778
{
511,671✔
2779
    return Iterator(m_clusters, 0);
511,671✔
2780
}
511,671✔
2781

2782
Table::Iterator Table::end() const
2783
{
9,713,229✔
2784
    return Iterator(m_clusters, size());
9,713,229✔
2785
}
9,713,229✔
2786

2787
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2788
{
7,168,953✔
2789
    TableRef ret;
7,168,953✔
2790
    if (col_key) {
7,168,953✔
2791
        return table.get_opposite_table(col_key);
7,168,722✔
2792
    }
7,168,722✔
2793
    return ret;
231✔
2794
}
7,168,953✔
2795

2796
const uint64_t Table::max_num_columns;
2797

2798
void Table::build_column_mapping()
2799
{
3,204,609✔
2800
    // build column mapping from spec
2801
    // TODO: Optimization - Don't rebuild this for every change
2802
    m_spec_ndx2leaf_ndx.clear();
3,204,609✔
2803
    m_leaf_ndx2spec_ndx.clear();
3,204,609✔
2804
    m_leaf_ndx2colkey.clear();
3,204,609✔
2805
    size_t num_spec_cols = m_spec.get_column_count();
3,204,609✔
2806
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
3,204,609✔
2807
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
17,187,378✔
2808
        ColKey col_key = m_spec.get_key(spec_ndx);
13,982,769✔
2809
        unsigned leaf_ndx = col_key.get_index().val;
13,982,769✔
2810
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
13,982,769✔
2811
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
13,479,879✔
2812
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
13,479,879✔
2813
        }
13,479,879✔
2814
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
13,982,769✔
2815
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
13,982,769✔
2816
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
13,982,769✔
2817
    }
13,982,769✔
2818
}
3,204,609✔
2819

2820
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2821
{
786,663✔
2822
    REALM_ASSERT(!attr.test(col_attr_Indexed));
786,663✔
2823
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
786,663✔
2824

2825
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
786,663✔
2826
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
786,663✔
2827

2828
    // reuse lowest available leaf ndx:
2829
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
786,663✔
2830
    // look for an unused entry:
2831
    for (unsigned idx = 0; idx < lower; ++idx) {
5,734,173✔
2832
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
4,947,597✔
2833
            lower = idx;
87✔
2834
            break;
87✔
2835
        }
87✔
2836
    }
4,947,597✔
2837
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
786,663✔
2838
}
786,663✔
2839

2840
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2841
                                                  StringData origin_col_name) const noexcept
2842
{
×
2843
    BacklinkOrigin ret;
×
2844
    auto f = [&](ColKey backlink_col_key) {
×
2845
        auto origin_table = get_opposite_table(backlink_col_key);
×
2846
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2847
        if (origin_table->get_name() == origin_table_name &&
×
2848
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
2849
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
2850
            return IteratorControl::Stop;
×
2851
        }
×
2852
        return IteratorControl::AdvanceToNext;
×
2853
    };
×
2854
    this->for_each_backlink_column(f);
×
2855
    return ret;
×
2856
}
×
2857

2858
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2859
{
354✔
2860
    try {
354✔
2861
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
354✔
2862
        ColKey linked_column_key = get_opposite_column(backlink_col);
354✔
2863
        if (linked_table_key == m_key) {
354✔
2864
            return {{m_own_ref, linked_column_key}};
18✔
2865
        }
18✔
2866
        else {
336✔
2867
            Group* current_group = get_parent_group();
336✔
2868
            if (current_group) {
336✔
2869
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2870
                return {{linked_table_ref, linked_column_key}};
336✔
2871
            }
336✔
2872
        }
336✔
2873
    }
354✔
2874
    catch (...) {
354✔
2875
        // backlink column not found, returning empty optional
2876
    }
×
2877
    return {};
×
2878
}
354✔
2879

2880
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
2881
{
×
2882
    std::vector<std::pair<TableKey, ColKey>> origins;
×
2883
    auto f = [&](ColKey backlink_col_key) {
×
2884
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
2885
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2886
        origins.emplace_back(origin_table_key, origin_link_col);
×
2887
        return IteratorControl::AdvanceToNext;
×
2888
    };
×
2889
    this->for_each_backlink_column(f);
×
2890
    return origins;
×
2891
}
×
2892

2893
ColKey Table::get_primary_key_column() const
2894
{
19,776,225✔
2895
    return m_primary_key_col;
19,776,225✔
2896
}
19,776,225✔
2897

2898
void Table::set_primary_key_column(ColKey col_key)
2899
{
720✔
2900
    if (col_key == m_primary_key_col) {
720✔
2901
        return;
378✔
2902
    }
378✔
2903

2904
    if (Replication* repl = get_repl()) {
342✔
2905
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
2906
            throw RuntimeError(
×
2907
                ErrorCodes::BrokenInvariant,
×
2908
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
2909
        }
×
2910
    }
240✔
2911

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

2914
    if (col_key) {
342✔
2915
        check_column(col_key);
222✔
2916
        validate_column_is_unique(col_key);
222✔
2917
        do_set_primary_key_column(col_key);
222✔
2918
    }
222✔
2919
    else {
120✔
2920
        do_set_primary_key_column(col_key);
120✔
2921
    }
120✔
2922
}
342✔
2923

2924

2925
void Table::do_set_primary_key_column(ColKey col_key)
2926
{
111,855✔
2927
    if (col_key) {
111,855✔
2928
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
110,811✔
2929
        auto attr = m_spec.get_column_attr(spec_ndx);
110,811✔
2930
        if (attr.test(col_attr_FullText_Indexed)) {
110,811✔
2931
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
2932
        }
6✔
2933
    }
110,811✔
2934

2935
    if (m_primary_key_col) {
111,849✔
2936
        // If the search index has not been set explicitly on current pk col, we remove it again
2937
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
1,068✔
2938
        auto attr = m_spec.get_column_attr(spec_ndx);
1,068✔
2939
        if (!attr.test(col_attr_Indexed)) {
1,068✔
2940
            remove_search_index(m_primary_key_col);
1,056✔
2941
        }
1,056✔
2942
    }
1,068✔
2943

2944
    if (col_key) {
111,849✔
2945
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
110,805✔
2946
        do_add_search_index(col_key, IndexType::General);
110,805✔
2947
    }
110,805✔
2948
    else {
1,044✔
2949
        m_top.set(top_position_for_pk_col, 0);
1,044✔
2950
    }
1,044✔
2951

2952
    m_primary_key_col = col_key;
111,849✔
2953
}
111,849✔
2954

2955
bool Table::contains_unique_values(ColKey col) const
2956
{
834✔
2957
    if (search_index_type(col) == IndexType::General) {
834✔
2958
        auto search_index = get_search_index(col);
744✔
2959
        return !search_index->has_duplicate_values();
744✔
2960
    }
744✔
2961
    else {
90✔
2962
        TableView tv = where().find_all();
90✔
2963
        tv.distinct(col);
90✔
2964
        return tv.size() == size();
90✔
2965
    }
90✔
2966
}
834✔
2967

2968
void Table::validate_column_is_unique(ColKey col) const
2969
{
834✔
2970
    if (!contains_unique_values(col)) {
834✔
2971
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
2972
                                           get_class_name(), get_column_name(col)));
30✔
2973
    }
30✔
2974
}
834✔
2975

2976
void Table::validate_primary_column()
2977
{
1,812✔
2978
    if (ColKey col = get_primary_key_column()) {
1,812✔
2979
        validate_column_is_unique(col);
612✔
2980
    }
612✔
2981
}
1,812✔
2982

2983
ObjKey Table::get_next_valid_key()
2984
{
905,088✔
2985
    ObjKey key;
905,088✔
2986
    do {
905,094✔
2987
        key = ObjKey(allocate_sequence_number());
905,094✔
2988
    } while (m_clusters.is_valid(key));
905,094✔
2989

2990
    return key;
905,088✔
2991
}
905,088✔
2992

2993
namespace {
2994
template <class T>
2995
typename util::RemoveOptional<T>::type remove_optional(T val)
2996
{
88,011✔
2997
    return val;
88,011✔
2998
}
88,011✔
2999
template <>
3000
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
3001
{
5,445✔
3002
    return *val;
5,445✔
3003
}
5,445✔
3004
template <>
3005
bool remove_optional<Optional<bool>>(Optional<bool> val)
3006
{
11,556✔
3007
    return *val;
11,556✔
3008
}
11,556✔
3009
template <>
3010
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
3011
{
5,484✔
3012
    return *val;
5,484✔
3013
}
5,484✔
3014
template <>
3015
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
3016
{
6,060✔
3017
    return *val;
6,060✔
3018
}
6,060✔
3019
} // namespace
3020

3021
template <class F, class T>
3022
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
3023
{
162✔
3024
    Allocator& allocator = this->get_alloc();
162✔
3025
    bool from_nullability = is_nullable(key_from);
162✔
3026
    auto func = [&](Cluster* cluster) {
162✔
3027
        size_t sz = cluster->node_size();
162✔
3028

3029
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
3030
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
3031
        cluster->init_leaf(key_from, &from_arr);
162✔
3032
        cluster->init_leaf(key_to, &to_arr);
162✔
3033

3034
        for (size_t i = 0; i < sz; i++) {
1,512✔
3035
            if (from_nullability && from_arr.is_null(i)) {
1,356!
3036
                if (throw_on_null) {
42!
3037
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
3038
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
3039
                                                    get_column_name(key_from)));
6✔
3040
                }
6✔
3041
                else {
36✔
3042
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
36✔
3043
                }
36✔
3044
            }
42✔
3045
            else {
1,314✔
3046
                auto v = remove_optional(from_arr.get(i));
1,314✔
3047
                to_arr.set(i, v);
1,314✔
3048
            }
1,314✔
3049
        }
1,356✔
3050
    };
162✔
3051

3052
    m_clusters.update(func);
162✔
3053
}
162✔
3054

3055
template <class F, class T>
3056
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
3057
{
120✔
3058
    Allocator& allocator = this->get_alloc();
120✔
3059
    bool from_nullability = is_nullable(key_from);
120✔
3060
    auto func = [&](Cluster* cluster) {
120✔
3061
        size_t sz = cluster->node_size();
120✔
3062

3063
        ArrayInteger from_arr(allocator);
120✔
3064
        ArrayInteger to_arr(allocator);
120✔
3065
        cluster->init_leaf(key_from, &from_arr);
120✔
3066
        cluster->init_leaf(key_to, &to_arr);
120✔
3067

3068
        for (size_t i = 0; i < sz; i++) {
360✔
3069
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3070
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3071
            REALM_ASSERT(!ref_to);
240✔
3072

3073
            if (ref_from) {
240✔
3074
                BPlusTree<F> from_list(allocator);
120✔
3075
                BPlusTree<T> to_list(allocator);
120✔
3076
                from_list.init_from_ref(ref_from);
120✔
3077
                to_list.create();
120✔
3078
                size_t n = from_list.size();
120✔
3079
                for (size_t j = 0; j < n; j++) {
120,120✔
3080
                    auto v = from_list.get(j);
120,000✔
3081
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000✔
3082
                        to_list.add(remove_optional(v));
115,242✔
3083
                    }
115,242✔
3084
                    else {
4,758✔
3085
                        if (throw_on_null) {
4,758!
3086
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3087
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3088
                                                            get_name(), get_column_name(key_from)));
×
3089
                        }
×
3090
                        else {
4,758✔
3091
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,758✔
3092
                        }
4,758✔
3093
                    }
4,758✔
3094
                }
120,000✔
3095
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3096
            }
120✔
3097
        }
240✔
3098
    };
120✔
3099

3100
    m_clusters.update(func);
120✔
3101
}
120✔
3102

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

3221

3222
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3223
{
522✔
3224
    if (col_key.is_nullable() == nullable)
522✔
3225
        return col_key;
240✔
3226

3227
    check_column(col_key);
282✔
3228

3229
    auto index_type = search_index_type(col_key);
282✔
3230
    std::string column_name(get_column_name(col_key));
282✔
3231
    auto type = col_key.get_type();
282✔
3232
    auto attr = col_key.get_attrs();
282✔
3233
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3234
    if (nullable) {
282✔
3235
        attr.set(col_attr_Nullable);
150✔
3236
    }
150✔
3237
    else {
132✔
3238
        attr.reset(col_attr_Nullable);
132✔
3239
    }
132✔
3240

3241
    ColKey new_col = generate_col_key(type, attr);
282✔
3242
    do_insert_root_column(new_col, type, "__temporary");
282✔
3243

3244
    try {
282✔
3245
        convert_column(col_key, new_col, throw_on_null);
282✔
3246
    }
282✔
3247
    catch (...) {
282✔
3248
        // remove any partially filled column
3249
        remove_column(new_col);
6✔
3250
        throw;
6✔
3251
    }
6✔
3252

3253
    if (is_pk_col) {
276✔
3254
        // If we go from non nullable to nullable, no values change,
3255
        // so it is safe to preserve the pk column. Otherwise it is not
3256
        // safe as a null entry might have been converted to default value.
3257
        do_set_primary_key_column(nullable ? new_col : ColKey{});
12✔
3258
    }
12✔
3259

3260
    erase_root_column(col_key);
276✔
3261
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3262

3263
    if (index_type != IndexType::None)
276✔
3264
        do_add_search_index(new_col, index_type);
30✔
3265

3266
    return new_col;
276✔
3267
}
282✔
3268

3269
bool Table::has_any_embedded_objects()
3270
{
2,553,036✔
3271
    if (!m_has_any_embedded_objects) {
2,553,036✔
3272
        m_has_any_embedded_objects = false;
29,190✔
3273
        for_each_public_column([&](ColKey col_key) {
71,367✔
3274
            auto target_table_key = get_opposite_table_key(col_key);
71,367✔
3275
            if (target_table_key && is_link_type(col_key.get_type())) {
71,367✔
3276
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
15,681✔
3277
                if (target_table->is_embedded()) {
15,681✔
3278
                    m_has_any_embedded_objects = true;
13,542✔
3279
                    return IteratorControl::Stop; // early out
13,542✔
3280
                }
13,542✔
3281
            }
15,681✔
3282
            return IteratorControl::AdvanceToNext;
57,825✔
3283
        });
71,367✔
3284
    }
29,190✔
3285
    return *m_has_any_embedded_objects;
2,553,036✔
3286
}
2,553,036✔
3287

3288
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3289
{
158,484✔
3290
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
158,484✔
3291
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
158,484✔
3292
}
158,484✔
3293

3294
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3295
{
623,574✔
3296
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
1,315,191✔
3297
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
1,307,403✔
3298
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
615,786✔
3299
        }
615,786✔
3300
    }
1,307,403✔
3301

3302
    return {};
7,788✔
3303
}
623,574✔
3304

3305
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3306
{
623,526✔
3307
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
623,526✔
3308

3309
    if (!backlink_col_key) {
623,526✔
3310
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
7,848✔
3311
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
7,848✔
3312

3313
        if (Replication* repl = get_repl())
7,848✔
3314
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
7,452✔
3315
                                    m_key); // Throws
7,452✔
3316
    }
7,848✔
3317

3318
    return backlink_col_key;
623,526✔
3319
}
623,526✔
3320

3321
TableKey Table::get_opposite_table_key(ColKey col_key) const
3322
{
14,604,615✔
3323
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,604,615✔
3324
}
14,604,615✔
3325

3326
bool Table::links_to_self(ColKey col_key) const
3327
{
69,864✔
3328
    return get_opposite_table_key(col_key) == m_key;
69,864✔
3329
}
69,864✔
3330

3331
TableRef Table::get_opposite_table(ColKey col_key) const
3332
{
7,890,123✔
3333
    if (auto k = get_opposite_table_key(col_key)) {
7,890,123✔
3334
        return get_parent_group()->get_table(k);
7,828,920✔
3335
    }
7,828,920✔
3336
    return {};
61,203✔
3337
}
7,890,123✔
3338

3339
ColKey Table::get_opposite_column(ColKey col_key) const
3340
{
16,313,961✔
3341
    return ColKey(m_opposite_column.get(col_key.get_index().val));
16,313,961✔
3342
}
16,313,961✔
3343

3344
ColKey Table::find_opposite_column(ColKey col_key) const
3345
{
×
3346
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3347
        if (m_opposite_column.get(i) == col_key.value) {
×
3348
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3349
        }
×
3350
    }
×
3351
    return ColKey();
×
3352
}
×
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