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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

90.49
/src/realm/table.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <realm/table.hpp>
20

21
#include <realm/alloc_slab.hpp>
22
#include <realm/array_binary.hpp>
23
#include <realm/array_bool.hpp>
24
#include <realm/array_decimal128.hpp>
25
#include <realm/array_fixed_bytes.hpp>
26
#include <realm/array_string.hpp>
27
#include <realm/array_timestamp.hpp>
28
#include <realm/db.hpp>
29
#include <realm/dictionary.hpp>
30
#include <realm/exceptions.hpp>
31
#include <realm/impl/destroy_guard.hpp>
32
#include <realm/index_string.hpp>
33
#include <realm/query_conditions_tpl.hpp>
34
#include <realm/replication.hpp>
35
#include <realm/table_view.hpp>
36
#include <realm/util/features.h>
37
#include <realm/util/miscellaneous.hpp>
38
#include <realm/util/serializer.hpp>
39

40
#include <stdexcept>
41

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

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

261

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

265
Replication* Table::g_dummy_replication = nullptr;
266

267
bool TableVersions::operator==(const TableVersions& other) const
268
{
16,911✔
269
    if (size() != other.size())
16,911✔
270
        return false;
×
271
    size_t sz = size();
16,911✔
272
    for (size_t i = 0; i < sz; i++) {
26,490✔
273
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
17,043✔
274
        if (this->at(i).second != other.at(i).second)
17,043✔
275
            return false;
7,464✔
276
    }
17,043✔
277
    return true;
13,149✔
278
}
16,911✔
279

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

325
std::ostream& operator<<(std::ostream& o, Table::Type table_type)
326
{
118,404✔
327
    switch (table_type) {
118,404✔
328
        case Table::Type::TopLevel:
99,297✔
329
            return o << "TopLevel";
99,297✔
330
        case Table::Type::Embedded:
18,999✔
331
            return o << "Embedded";
18,999✔
332
        case Table::Type::TopLevelAsymmetric:
108✔
333
            return o << "TopLevelAsymmetric";
108✔
334
    }
×
335
    return o << "Invalid table type: " << uint8_t(table_type);
×
336
}
×
337
} // namespace realm
338

339
bool LinkChain::add(ColKey ck)
340
{
799,041✔
341
    // Link column can be a single Link, LinkList, or BackLink.
399,564✔
342
    REALM_ASSERT(m_current_table->valid_column(ck));
799,041✔
343
    ColumnType type = ck.get_type();
799,041✔
344
    if (type == col_type_LinkList || type == col_type_Link || type == col_type_BackLink) {
799,041✔
345
        m_current_table = m_current_table->get_opposite_table(ck);
88,011✔
346
        m_link_cols.push_back(ck);
88,011✔
347
        return true;
88,011✔
348
    }
88,011✔
349
    return false;
711,030✔
350
}
711,030✔
351

352
// -- Table ---------------------------------------------------------------------------------
353

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

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

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

394
ColKey Table::add_column(DataType type, StringData name, bool nullable, std::optional<CollectionType> collection_type,
395
                         DataType key_type)
396
{
681,459✔
397
    REALM_ASSERT(!is_link_type(ColumnType(type)));
681,459✔
398
    if (type == type_TypedLink) {
681,459✔
NEW
399
        throw IllegalOperation("TypedLink properties not yet supported");
×
UNCOV
400
    }
×
401

335,448✔
402
    ColumnAttrMask attr;
681,459✔
403
    if (collection_type) {
681,459✔
404
        switch (*collection_type) {
175,308✔
405
            case CollectionType::List:
80,283✔
406
                attr.set(col_attr_List);
80,283✔
407
                break;
80,283✔
408
            case CollectionType::Set:
52,527✔
409
                attr.set(col_attr_Set);
52,527✔
410
                break;
52,527✔
411
            case CollectionType::Dictionary:
42,498✔
412
                attr.set(col_attr_Dictionary);
42,498✔
413
                break;
42,498✔
414
        }
681,459✔
415
    }
681,459✔
416
    if (nullable || type == type_Mixed)
681,459✔
417
        attr.set(col_attr_Nullable);
185,403✔
418
    ColKey col_key = generate_col_key(ColumnType(type), attr);
681,459✔
419

335,448✔
420
    Table* invalid_link = nullptr;
681,459✔
421
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
681,459✔
422
}
681,459✔
423

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

40,197✔
437
    m_has_any_embedded_objects.reset();
81,390✔
438

40,197✔
439
    DataType data_type = type_Link;
81,390✔
440
    ColumnAttrMask attr;
81,390✔
441
    if (collection_type) {
81,390✔
442
        switch (*collection_type) {
51,840✔
443
            case CollectionType::List:
30,306✔
444
                attr.set(col_attr_List);
30,306✔
445
                data_type = type_LinkList;
30,306✔
446
                break;
30,306✔
447
            case CollectionType::Set:
10,452✔
448
                if (target.is_embedded())
10,452✔
NEW
449
                    throw IllegalOperation("Set of embedded objects not supported");
×
450
                attr.set(col_attr_Set);
10,452✔
451
                break;
10,452✔
452
            case CollectionType::Dictionary:
11,082✔
453
                attr.set(col_attr_Dictionary);
11,082✔
454
                attr.set(col_attr_Nullable);
11,082✔
455
                break;
11,082✔
456
        }
29,550✔
457
    }
29,550✔
458
    else {
29,550✔
459
        attr.set(col_attr_Nullable);
29,550✔
460
    }
29,550✔
461
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
81,390✔
462

40,197✔
463
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
81,390✔
464
}
81,390✔
465

466
void Table::remove_recursive(CascadeState& cascade_state)
467
{
14,058✔
468
    Group* group = get_parent_group();
14,058✔
469
    REALM_ASSERT(group);
14,058✔
470
    cascade_state.m_group = group;
14,058✔
471

6,369✔
472
    do {
18,084✔
473
        cascade_state.send_notifications();
18,084✔
474

8,382✔
475
        for (auto& l : cascade_state.m_to_be_nullified) {
8,406✔
476
            Obj obj = group->get_table_unchecked(l.origin_table)->try_get_object(l.origin_key);
48✔
477
            REALM_ASSERT_DEBUG(obj);
48✔
478
            if (obj) {
48✔
479
                std::move(obj).nullify_link(l.origin_col_key, l.old_target_link);
48✔
480
            }
48✔
481
        }
48✔
482
        cascade_state.m_to_be_nullified.clear();
18,084✔
483

8,382✔
484
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
18,084✔
485
        for (auto obj : to_delete) {
15,741✔
486
            auto table = obj.first == m_key ? this : group->get_table_unchecked(obj.first);
12,372✔
487
            // This might add to the list of objects that should be deleted
7,350✔
488
            REALM_ASSERT(!obj.second.is_unresolved());
14,709✔
489
            table->m_clusters.erase(obj.second, cascade_state);
14,709✔
490
        }
14,709✔
491
        nullify_links(cascade_state);
18,084✔
492
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
18,084✔
493
}
14,058✔
494

495
void Table::nullify_links(CascadeState& cascade_state)
496
{
22,680✔
497
    Group* group = get_parent_group();
22,680✔
498
    REALM_ASSERT(group);
22,680✔
499
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
14,634✔
500
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
6,963✔
501
        if (!table->is_asymmetric())
7,890✔
502
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
7,890✔
503
    }
7,890✔
504
}
22,680✔
505

506
CollectionType Table::get_collection_type(ColKey col_key) const
507
{
7,932✔
508
    if (col_key.is_list()) {
7,932✔
509
        return CollectionType::List;
648✔
510
    }
648✔
511
    if (col_key.is_set()) {
7,284✔
512
        return CollectionType::Set;
1,056✔
513
    }
1,056✔
514
    REALM_ASSERT(col_key.is_dictionary());
6,228✔
515
    return CollectionType::Dictionary;
6,228✔
516
}
6,228✔
517

518
void Table::remove_column(ColKey col_key)
519
{
18,213✔
520
    check_column(col_key);
18,213✔
521

8,676✔
522
    if (Replication* repl = get_repl())
18,213✔
523
        repl->erase_column(this, col_key); // Throws
525✔
524

8,676✔
525
    if (col_key == m_primary_key_col) {
18,213✔
526
        do_set_primary_key_column(ColKey());
7,737✔
527
    }
7,737✔
528
    else {
10,476✔
529
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
10,476✔
530
    }
10,476✔
531

8,676✔
532
    erase_root_column(col_key); // Throws
18,213✔
533
    m_has_any_embedded_objects.reset();
18,213✔
534
}
18,213✔
535

536

537
void Table::rename_column(ColKey col_key, StringData name)
538
{
84✔
539
    check_column(col_key);
84✔
540

42✔
541
    auto col_ndx = colkey2spec_ndx(col_key);
84✔
542
    m_spec.rename_column(col_ndx, name); // Throws
84✔
543

42✔
544
    bump_content_version();
84✔
545
    bump_storage_version();
84✔
546

42✔
547
    if (Replication* repl = get_repl())
84✔
548
        repl->rename_column(this, col_key, name); // Throws
84✔
549
}
84✔
550

551

552
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
553
{
10,181,622✔
554
    // well, not quite "direct", more like "almost direct":
5,558,322✔
555
    Array table_top(alloc);
10,181,622✔
556
    table_top.init_from_ref(top_ref);
10,181,622✔
557
    if (table_top.size() > 3) {
10,182,480✔
558
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
10,182,375✔
559
        return TableKey(int32_t(rot.get_as_int()));
10,182,375✔
560
    }
10,182,375✔
561
    else {
2,147,483,752✔
562
        return TableKey();
2,147,483,752✔
563
    }
2,147,483,752✔
564
}
10,181,622✔
565

566

567
void Table::init(ref_type top_ref, ArrayParent* parent, size_t ndx_in_parent, bool is_writable, bool is_frzn)
568
{
5,460,687✔
569
    REALM_ASSERT(!(is_writable && is_frzn));
5,460,687✔
570
    m_is_frozen = is_frzn;
5,460,687✔
571
    m_alloc.set_read_only(!is_writable);
5,460,687✔
572
    // Load from allocated memory
3,175,215✔
573
    m_top.set_parent(parent, ndx_in_parent);
5,460,687✔
574
    m_top.init_from_ref(top_ref);
5,460,687✔
575

3,175,215✔
576
    m_spec.init_from_parent();
5,460,687✔
577

3,175,215✔
578
    while (m_top.size() <= top_position_for_pk_col) {
5,460,687✔
UNCOV
579
        m_top.add(0);
×
UNCOV
580
    }
×
581

3,175,215✔
582
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
5,460,687✔
583
        // This is an upgrade - create cluster
UNCOV
584
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
×
UNCOV
585
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
×
UNCOV
586
    }
×
587
    m_clusters.init_from_parent();
5,460,687✔
588

3,175,215✔
589
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
5,460,687✔
590
    if (!rot.is_tagged()) {
5,460,687✔
591
        // Create table key
UNCOV
592
        rot = RefOrTagged::make_tagged(ndx_in_parent);
×
UNCOV
593
        m_top.set(top_position_for_key, rot);
×
UNCOV
594
    }
×
595
    m_key = TableKey(int32_t(rot.get_as_int()));
5,460,687✔
596

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

3,175,215✔
630
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
5,460,687✔
631
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
4,903,494✔
632

3,175,215✔
633
    if (m_top.size() <= top_position_for_flags) {
5,460,687✔
634
        m_table_type = Type::TopLevel;
60✔
635
    }
60✔
636
    else {
5,460,627✔
637
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
5,460,627✔
638
        m_table_type = Type(flags & table_type_mask);
5,460,627✔
639
    }
5,460,627✔
640
    m_has_any_embedded_objects.reset();
5,460,687✔
641

3,175,215✔
642
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
5,460,687✔
643
        // Tombstones exists
432,609✔
644
        if (!m_tombstones) {
847,548✔
645
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
533,277✔
646
        }
533,277✔
647
        m_tombstones->init_from_parent();
847,548✔
648
    }
847,548✔
649
    else {
4,613,139✔
650
        m_tombstones = nullptr;
4,613,139✔
651
    }
4,613,139✔
652
    m_cookie = cookie_initialized;
5,460,687✔
653
}
5,460,687✔
654

655

656
ColKey Table::do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table, DataType key_type)
657
{
762,849✔
658
    col_key = do_insert_root_column(col_key, ColumnType(type), name, key_type); // Throws
762,849✔
659

375,645✔
660
    // When the inserted column is a link-type column, we must also add a
375,645✔
661
    // backlink column to the target table.
375,645✔
662

375,645✔
663
    if (target_table) {
762,849✔
664
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
81,384✔
665
        target_table->check_column(backlink_col_key);
81,384✔
666

40,194✔
667
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
81,384✔
668
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
81,384✔
669
    }
81,384✔
670

375,645✔
671
    if (Replication* repl = get_repl())
762,849✔
672
        repl->insert_column(this, col_key, type, name, target_table); // Throws
745,323✔
673

375,645✔
674
    return col_key;
762,849✔
675
}
762,849✔
676

677
template <typename Type>
678
void do_bulk_insert_index(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
679
{
132,993✔
680
    using LeafType = typename ColumnTypeTraits<Type>::cluster_leaf_type;
132,993✔
681
    LeafType leaf(alloc);
132,993✔
682

65,796✔
683
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
138,081✔
684
        cluster->init_leaf(col_key, &leaf);
138,081✔
685
        index->insert_bulk(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
138,081✔
686
        return IteratorControl::AdvanceToNext;
138,081✔
687
    };
138,081✔
688

65,796✔
689
    table->traverse_clusters(f);
132,993✔
690
}
132,993✔
691

692
void Table::populate_search_index(ColKey col_key)
693
{
132,993✔
694
    auto col_ndx = col_key.get_index().val;
132,993✔
695
    SearchIndex* index = m_index_accessors[col_ndx].get();
132,993✔
696
    DataType type = get_column_type(col_key);
132,993✔
697

65,796✔
698
    if (type == type_Int) {
132,993✔
699
        if (is_nullable(col_key)) {
73,560✔
700
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
10,797✔
701
        }
10,797✔
702
        else {
62,763✔
703
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
62,763✔
704
        }
62,763✔
705
    }
73,560✔
706
    else if (type == type_Bool) {
59,433✔
707
        if (is_nullable(col_key)) {
48✔
708
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
709
        }
24✔
710
        else {
24✔
711
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
24✔
712
        }
24✔
713
    }
48✔
714
    else if (type == type_String) {
59,385✔
715
        do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
21,072✔
716
    }
21,072✔
717
    else if (type == type_Timestamp) {
38,313✔
718
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
90✔
719
    }
90✔
720
    else if (type == type_ObjectId) {
38,223✔
721
        if (is_nullable(col_key)) {
36,651✔
722
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
948✔
723
        }
948✔
724
        else {
35,703✔
725
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
35,703✔
726
        }
35,703✔
727
    }
36,651✔
728
    else if (type == type_UUID) {
1,572✔
729
        if (is_nullable(col_key)) {
678✔
730
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
731
        }
516✔
732
        else {
162✔
733
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
162✔
734
        }
162✔
735
    }
678✔
736
    else if (type == type_Mixed) {
894✔
737
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
894✔
738
    }
894✔
NEW
739
    else {
×
NEW
740
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
NEW
741
    }
×
742
}
132,993✔
743

744
void Table::erase_from_search_indexes(ObjKey key)
745
{
5,086,761✔
746
    // Tombstones do not use index - will crash if we try to erase values
2,543,511✔
747
    if (!key.is_unresolved()) {
5,086,761✔
748
        for (auto&& index : m_index_accessors) {
6,823,824✔
749
            if (index) {
6,823,824✔
750
                index->erase(key);
340,782✔
751
            }
340,782✔
752
        }
6,823,824✔
753
    }
5,073,531✔
754
}
5,086,761✔
755

756
void Table::update_indexes(ObjKey key, const FieldValues& values)
757
{
23,123,427✔
758
    // Tombstones do not use index - will crash if we try to insert values
11,472,981✔
759
    if (key.is_unresolved()) {
23,123,427✔
760
        return;
28,098✔
761
    }
28,098✔
762

11,458,959✔
763
    auto sz = m_index_accessors.size();
23,095,329✔
764
    // values are sorted by column index - there may be values missing
11,458,959✔
765
    auto value = values.begin();
23,095,329✔
766
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
57,087,804✔
767
        // Check if initial value is provided
16,779,144✔
768
        Mixed init_value;
33,992,562✔
769
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
33,992,562✔
770
            // Value for this column is provided
266,043✔
771
            init_value = value->value;
554,433✔
772
            ++value;
554,433✔
773
        }
554,433✔
774

16,779,144✔
775
        if (auto&& index = m_index_accessors[column_ndx]) {
33,992,562✔
776
            // There is an index for this column
552,195✔
777
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,126,974✔
778
            auto type = col_key.get_type();
1,126,974✔
779
            auto attr = col_key.get_attrs();
1,126,974✔
780
            bool nullable = attr.test(col_attr_Nullable);
1,126,974✔
781
            switch (type) {
1,126,974✔
782
                case col_type_Int:
532,185✔
783
                    if (init_value.is_null()) {
532,185✔
784
                        index->insert(key, ArrayIntNull::default_value(nullable));
165,681✔
785
                    }
165,681✔
786
                    else {
366,504✔
787
                        index->insert(key, init_value.get<int64_t>());
366,504✔
788
                    }
366,504✔
789
                    break;
532,185✔
790
                case col_type_Bool:
5,976✔
791
                    if (init_value.is_null()) {
5,976✔
792
                        index->insert(key, ArrayBoolNull::default_value(nullable));
5,976✔
793
                    }
5,976✔
UNCOV
794
                    else {
×
UNCOV
795
                        index->insert(key, init_value.get<bool>());
×
UNCOV
796
                    }
×
797
                    break;
5,976✔
798
                case col_type_String:
477,603✔
799
                    if (init_value.is_null()) {
477,603✔
800
                        index->insert(key, ArrayString::default_value(nullable));
431,301✔
801
                    }
431,301✔
802
                    else {
46,302✔
803
                        index->insert(key, init_value.get<String>());
46,302✔
804
                    }
46,302✔
805
                    break;
477,603✔
806
                case col_type_Timestamp:
6,162✔
807
                    if (init_value.is_null()) {
6,162✔
808
                        index->insert(key, ArrayTimestamp::default_value(nullable));
6,162✔
809
                    }
6,162✔
UNCOV
810
                    else {
×
UNCOV
811
                        index->insert(key, init_value.get<Timestamp>());
×
UNCOV
812
                    }
×
813
                    break;
6,162✔
814
                case col_type_ObjectId:
85,698✔
815
                    if (init_value.is_null()) {
85,698✔
816
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
817
                    }
6,120✔
818
                    else {
79,578✔
819
                        index->insert(key, init_value.get<ObjectId>());
79,578✔
820
                    }
79,578✔
821
                    break;
85,698✔
822
                case col_type_Mixed:
1,074✔
823
                    index->insert(key, init_value);
1,074✔
824
                    break;
1,074✔
825
                case col_type_UUID:
18,342✔
826
                    if (init_value.is_null()) {
18,342✔
827
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
6,138✔
828
                    }
6,138✔
829
                    else {
12,204✔
830
                        index->insert(key, init_value.get<UUID>());
12,204✔
831
                    }
12,204✔
832
                    break;
18,342✔
833
                default:
✔
834
                    REALM_UNREACHABLE();
×
835
            }
1,126,974✔
836
        }
1,126,974✔
837
    }
33,992,562✔
838
}
23,095,329✔
839

840
void Table::clear_indexes()
841
{
4,197✔
842
    for (auto&& index : m_index_accessors) {
48,180✔
843
        if (index) {
48,180✔
844
            index->clear();
2,940✔
845
        }
2,940✔
846
    }
48,180✔
847
}
4,197✔
848

849
void Table::do_add_search_index(ColKey col_key, IndexType type)
850
{
133,191✔
851
    size_t column_ndx = col_key.get_index().val;
133,191✔
852

65,895✔
853
    // Early-out if already indexed
65,895✔
854
    if (m_index_accessors[column_ndx] != nullptr)
133,191✔
855
        return;
150✔
856

65,820✔
857
    if (!StringIndex::type_supported(DataType(col_key.get_type())) || col_key.is_collection() ||
133,041✔
858
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
133,017✔
859
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
24✔
860
        // it should probably be a type mismatch exception instead.
24✔
861
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
48✔
862
    }
48✔
863

65,796✔
864
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
65,796✔
865
    // index have 0-entries.
65,796✔
866
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
132,993✔
867
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
132,993✔
868

65,796✔
869
    // Create the index
65,796✔
870
    m_index_accessors[column_ndx] =
132,993✔
871
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
132,993✔
872
    SearchIndex* index = m_index_accessors[column_ndx].get();
132,993✔
873
    // Insert ref to index
65,796✔
874
    index->set_parent(&m_index_refs, column_ndx);
132,993✔
875

65,796✔
876
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
132,993✔
877

65,796✔
878
    populate_search_index(col_key);
132,993✔
879
}
132,993✔
880

881
void Table::add_search_index(ColKey col_key, IndexType type)
882
{
3,870✔
883
    check_column(col_key);
3,870✔
884

1,932✔
885
    // Check spec
1,932✔
886
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,870✔
887
    auto attr = m_spec.get_column_attr(spec_ndx);
3,870✔
888

1,932✔
889
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,870✔
890
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
891

1,929✔
892
    switch (type) {
3,864✔
893
        case IndexType::None:
✔
894
            remove_search_index(col_key);
×
895
            return;
×
896
        case IndexType::Fulltext:
54✔
897
            // Early-out if already indexed
27✔
898
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
899
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
900
                return;
×
901
            }
×
902
            if (attr.test(col_attr_Indexed)) {
54✔
903
                this->remove_search_index(col_key);
×
904
            }
×
905
            break;
54✔
906
        case IndexType::General:
3,810✔
907
            if (attr.test(col_attr_Indexed)) {
3,810✔
908
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
24✔
909
                return;
24✔
910
            }
24✔
911
            if (attr.test(col_attr_FullText_Indexed)) {
3,786✔
912
                this->remove_search_index(col_key);
×
913
            }
×
914
            break;
3,786✔
915
    }
3,840✔
916

1,917✔
917
    do_add_search_index(col_key, type);
3,840✔
918

1,917✔
919
    // Update spec
1,917✔
920
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,813✔
921
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,840✔
922
}
3,840✔
923

924
void Table::remove_search_index(ColKey col_key)
925
{
8,172✔
926
    check_column(col_key);
8,172✔
927
    auto column_ndx = col_key.get_index();
8,172✔
928

3,879✔
929
    // Early-out if non-indexed
3,879✔
930
    if (m_index_accessors[column_ndx.val] == nullptr)
8,172✔
931
        return;
63✔
932

3,849✔
933
    // Destroy and remove the index column
3,849✔
934
    auto& index = m_index_accessors[column_ndx.val];
8,109✔
935
    REALM_ASSERT(index != nullptr);
8,109✔
936
    index->destroy();
8,109✔
937
    index.reset();
8,109✔
938

3,849✔
939
    m_index_refs.set(column_ndx.val, 0);
8,109✔
940

3,849✔
941
    // update spec
3,849✔
942
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
8,109✔
943
    auto attr = m_spec.get_column_attr(spec_ndx);
8,109✔
944
    attr.reset(col_attr_Indexed);
8,109✔
945
    attr.reset(col_attr_FullText_Indexed);
8,109✔
946
    m_spec.set_column_attr(spec_ndx, attr); // Throws
8,109✔
947
}
8,109✔
948

949
void Table::enumerate_string_column(ColKey col_key)
950
{
1,293✔
951
    check_column(col_key);
1,293✔
952
    size_t column_ndx = colkey2spec_ndx(col_key);
1,293✔
953
    ColumnType type = col_key.get_type();
1,293✔
954
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,293✔
955
        m_clusters.enumerate_string_column(col_key);
690✔
956
    }
690✔
957
}
1,293✔
958

959
bool Table::is_enumerated(ColKey col_key) const noexcept
960
{
58,605✔
961
    size_t col_ndx = colkey2spec_ndx(col_key);
58,605✔
962
    return m_spec.is_string_enum_type(col_ndx);
58,605✔
963
}
58,605✔
964

965
size_t Table::get_num_unique_values(ColKey col_key) const
966
{
138✔
967
    if (!is_enumerated(col_key))
138✔
968
        return 0;
84✔
969

27✔
970
    ArrayParent* parent;
54✔
971
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
972
    BPlusTree<StringData> col(get_alloc());
54✔
973
    col.init_from_ref(ref);
54✔
974

27✔
975
    return col.size();
54✔
976
}
54✔
977

978

979
void Table::erase_root_column(ColKey col_key)
980
{
18,489✔
981
    ColumnType col_type = col_key.get_type();
18,489✔
982
    if (is_link_type(col_type)) {
18,489✔
983
        auto target_table = get_opposite_table(col_key);
228✔
984
        auto target_column = get_opposite_column(col_key);
228✔
985
        target_table->do_erase_root_column(target_column);
228✔
986
    }
228✔
987
    do_erase_root_column(col_key); // Throws
18,489✔
988
}
18,489✔
989

990

991
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
992
{
980,442✔
993
    // if col_key specifies a key, it must be unused
483,246✔
994
    REALM_ASSERT(!col_key || !valid_column(col_key));
980,442✔
995

483,246✔
996
    // locate insertion point: ordinary columns must come before backlink columns
483,246✔
997
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
935,811✔
998

483,246✔
999
    if (!col_key) {
980,442✔
1000
        col_key = generate_col_key(type, {});
88,266✔
1001
    }
88,266✔
1002

483,246✔
1003
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
980,442✔
1004
    if (col_key.is_dictionary()) {
980,442✔
1005
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
53,580✔
1006
    }
53,580✔
1007
    auto col_ndx = col_key.get_index().val;
980,442✔
1008
    build_column_mapping();
980,442✔
1009
    REALM_ASSERT(col_ndx <= m_index_refs.size());
980,442✔
1010
    if (col_ndx == m_index_refs.size()) {
980,442✔
1011
        m_index_refs.insert(col_ndx, 0);
980,193✔
1012
    }
980,193✔
1013
    else {
249✔
1014
        m_index_refs.set(col_ndx, 0);
249✔
1015
    }
249✔
1016
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
980,442✔
1017
    if (col_ndx == m_opposite_table.size()) {
980,442✔
1018
        // m_opposite_table and m_opposite_column are always resized together!
483,123✔
1019
        m_opposite_table.insert(col_ndx, TableKey().value);
980,193✔
1020
        m_opposite_column.insert(col_ndx, ColKey().value);
980,193✔
1021
    }
980,193✔
1022
    else {
249✔
1023
        m_opposite_table.set(col_ndx, TableKey().value);
249✔
1024
        m_opposite_column.set(col_ndx, ColKey().value);
249✔
1025
    }
249✔
1026
    refresh_index_accessors();
980,442✔
1027
    m_clusters.insert_column(col_key);
980,442✔
1028
    if (m_tombstones) {
980,442✔
1029
        m_tombstones->insert_column(col_key);
6,906✔
1030
    }
6,906✔
1031

483,246✔
1032
    bump_storage_version();
980,442✔
1033

483,246✔
1034
    return col_key;
980,442✔
1035
}
980,442✔
1036

1037

1038
void Table::do_erase_root_column(ColKey col_key)
1039
{
18,717✔
1040
    size_t col_ndx = col_key.get_index().val;
18,717✔
1041
    // If the column had a source index we have to remove and destroy that as well
8,925✔
1042
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
18,717✔
1043
    if (index_ref) {
18,717✔
1044
        Array::destroy_deep(index_ref, m_index_refs.get_alloc());
132✔
1045
        m_index_refs.set(col_ndx, 0);
132✔
1046
        m_index_accessors[col_ndx].reset();
132✔
1047
    }
132✔
1048
    m_opposite_table.set(col_ndx, TableKey().value);
18,717✔
1049
    m_opposite_column.set(col_ndx, ColKey().value);
18,717✔
1050
    m_index_accessors[col_ndx] = nullptr;
18,717✔
1051
    m_clusters.remove_column(col_key);
18,717✔
1052
    if (m_tombstones)
18,717✔
1053
        m_tombstones->remove_column(col_key);
7,335✔
1054
    size_t spec_ndx = colkey2spec_ndx(col_key);
18,717✔
1055
    m_spec.erase_column(spec_ndx);
18,717✔
1056
    m_top.adjust(top_position_for_column_key, 2);
18,717✔
1057

8,925✔
1058
    build_column_mapping();
18,717✔
1059
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
36,849✔
1060
        REALM_ASSERT(m_index_accessors.back() == nullptr);
18,132✔
1061
        m_index_accessors.pop_back();
18,132✔
1062
    }
18,132✔
1063
    bump_content_version();
18,717✔
1064
    bump_storage_version();
18,717✔
1065
}
18,717✔
1066

1067
Query Table::where(const DictionaryLinkValues& dictionary_of_links) const
1068
{
1,524✔
1069
    return Query(m_own_ref, dictionary_of_links);
1,524✔
1070
}
1,524✔
1071

1072
void Table::set_table_type(Type table_type, bool handle_backlinks)
1073
{
312✔
1074
    if (table_type == m_table_type) {
312✔
1075
        return;
×
1076
    }
×
1077

156✔
1078
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
1079
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1080
                                                                   get_class_name(), m_table_type, table_type));
×
1081
    }
×
1082

156✔
1083
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1084
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1085
}
312✔
1086

1087
void Table::set_embedded(bool embedded, bool handle_backlinks)
1088
{
312✔
1089
    if (embedded == false) {
312✔
1090
        do_set_table_type(Type::TopLevel);
24✔
1091
        return;
24✔
1092
    }
24✔
1093

144✔
1094
    // Embedded objects cannot have a primary key.
144✔
1095
    if (get_primary_key_column()) {
288✔
1096
        throw IllegalOperation(
6✔
1097
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1098
    }
6✔
1099

141✔
1100
    if (size() == 0) {
282✔
1101
        do_set_table_type(Type::Embedded);
42✔
1102
        return;
42✔
1103
    }
42✔
1104

120✔
1105
    // Check all of the objects for invalid incoming links. Each embedded object
120✔
1106
    // must have exactly one incoming link, and it must be from a non-Mixed property.
120✔
1107
    // Objects with no incoming links are either deleted or an error (depending
120✔
1108
    // on `handle_backlinks`), and objects with multiple incoming links are either
120✔
1109
    // cloned for each of the incoming links or an error (again depending on `handle_backlinks`).
120✔
1110
    // Incoming links from a Mixed property are always an error, as those can't
120✔
1111
    // link to embedded objects
120✔
1112
    ArrayInteger leaf(get_alloc());
240✔
1113
    enum class LinkCount : int8_t { None, One, Multiple };
240✔
1114
    std::vector<LinkCount> incoming_link_count;
240✔
1115
    std::vector<ObjKey> orphans;
240✔
1116
    std::vector<ObjKey> multiple_incoming_links;
240✔
1117
    traverse_clusters([&](const Cluster* cluster) {
474✔
1118
        size_t size = cluster->node_size();
474✔
1119
        incoming_link_count.assign(size, LinkCount::None);
474✔
1120

237✔
1121
        for_each_backlink_column([&](ColKey col) {
606✔
1122
            cluster->init_leaf(col, &leaf);
606✔
1123
            // Width zero means all the values are zero and there can't be any backlinks
303✔
1124
            if (leaf.get_width() == 0) {
606✔
1125
                return IteratorControl::AdvanceToNext;
36✔
1126
            }
36✔
1127

285✔
1128
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1129
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1130
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1131
                    // ref of zero means there's no backlinks
29,670✔
1132
                    continue;
59,340✔
1133
                }
59,340✔
1134

480✔
1135
                if (value.is_ref()) {
960✔
1136
                    // Any other ref indicates an array of backlinks, which will
39✔
1137
                    // always have more than one entry
39✔
1138
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1139
                }
78✔
1140
                else {
882✔
1141
                    // Otherwise it's a tagged ref to the single linking object
441✔
1142
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1143
                        incoming_link_count[i] = LinkCount::One;
792✔
1144
                    }
792✔
1145
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1146
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1147
                    }
42✔
1148
                }
882✔
1149

480✔
1150
                auto source_col = get_opposite_column(col);
960✔
1151
                if (source_col.get_type() == col_type_Mixed) {
960✔
1152
                    auto source_table = get_opposite_table(col);
54✔
1153
                    throw IllegalOperation(util::format(
54✔
1154
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1155
                        "which does not support linking to embedded objects.",
54✔
1156
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1157
                }
54✔
1158
            }
960✔
1159
            return IteratorControl::AdvanceToNext;
543✔
1160
        });
570✔
1161

237✔
1162
        for (size_t i = 0; i < size; ++i) {
60,660✔
1163
            if (incoming_link_count[i] == LinkCount::None) {
60,240✔
1164
                if (!handle_backlinks) {
59,424✔
1165
                    throw IllegalOperation(util::format("Cannot convert '%1' to embedded: at least one object has no "
18✔
1166
                                                        "incoming links and would be deleted.",
18✔
1167
                                                        get_class_name()));
18✔
1168
                }
18✔
1169
                orphans.push_back(cluster->get_real_key(i));
59,406✔
1170
            }
59,406✔
1171
            else if (incoming_link_count[i] == LinkCount::Multiple) {
816✔
1172
                if (!handle_backlinks) {
90✔
1173
                    throw IllegalOperation(util::format(
36✔
1174
                        "Cannot convert '%1' to embedded: at least one object has more than one incoming link.",
36✔
1175
                        get_class_name()));
36✔
1176
                }
36✔
1177
                multiple_incoming_links.push_back(cluster->get_real_key(i));
54✔
1178
            }
54✔
1179
        }
60,240✔
1180

237✔
1181
        return IteratorControl::AdvanceToNext;
447✔
1182
    });
474✔
1183

120✔
1184
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
120✔
1185
    for (auto key : orphans) {
59,406✔
1186
        remove_object(key);
59,406✔
1187
    }
59,406✔
1188
    for (auto key : multiple_incoming_links) {
147✔
1189
        auto obj = get_object(key);
54✔
1190
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1191
        obj.remove();
54✔
1192
    }
54✔
1193

120✔
1194
    do_set_table_type(Type::Embedded);
240✔
1195
}
240✔
1196

1197
void Table::do_set_table_type(Type table_type)
1198
{
336,015✔
1199
    while (m_top.size() <= top_position_for_flags)
336,015✔
1200
        m_top.add(0);
×
1201

166,584✔
1202
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
336,015✔
1203
    // reset bits 0-1
166,584✔
1204
    flags &= ~table_type_mask;
336,015✔
1205
    // set table type
166,584✔
1206
    flags |= static_cast<uint8_t>(table_type);
336,015✔
1207
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
336,015✔
1208
    m_table_type = table_type;
336,015✔
1209
}
336,015✔
1210

1211

1212
void Table::detach(LifeCycleCookie cookie) noexcept
1213
{
5,442,816✔
1214
    m_cookie = cookie;
5,442,816✔
1215
    m_alloc.bump_instance_version();
5,442,816✔
1216
}
5,442,816✔
1217

1218
void Table::fully_detach() noexcept
1219
{
5,431,593✔
1220
    m_spec.detach();
5,431,593✔
1221
    m_top.detach();
5,431,593✔
1222
    m_index_refs.detach();
5,431,593✔
1223
    m_opposite_table.detach();
5,431,593✔
1224
    m_opposite_column.detach();
5,431,593✔
1225
    m_index_accessors.clear();
5,431,593✔
1226
}
5,431,593✔
1227

1228

1229
Table::~Table() noexcept
1230
{
3,552✔
1231
    if (m_top.is_attached()) {
3,552✔
1232
        // If destroyed as a standalone table, destroy all memory allocated
1,776✔
1233
        if (m_top.get_parent() == nullptr) {
3,552✔
1234
            m_top.destroy_deep();
3,552✔
1235
        }
3,552✔
1236
        fully_detach();
3,552✔
1237
    }
3,552✔
1238
    else {
×
1239
        REALM_ASSERT(m_index_accessors.size() == 0);
×
1240
    }
×
1241
    m_cookie = cookie_deleted;
3,552✔
1242
}
3,552✔
1243

1244

1245
IndexType Table::search_index_type(ColKey col_key) const noexcept
1246
{
7,958,382✔
1247
    if (m_index_accessors[col_key.get_index().val].get()) {
7,958,382✔
1248
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
1,161,708✔
1249
        bool fulltext = attr.test(col_attr_FullText_Indexed);
1,161,708✔
1250
        return fulltext ? IndexType::Fulltext : IndexType::General;
1,161,498✔
1251
    }
1,161,708✔
1252
    return IndexType::None;
6,796,674✔
1253
}
6,796,674✔
1254

1255

1256
void Table::migrate_sets_and_dictionaries()
1257
{
180✔
1258
    std::vector<ColKey> to_migrate;
180✔
1259
    for (auto col : get_column_keys()) {
570✔
1260
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
1261
            to_migrate.push_back(col);
12✔
1262
        }
12✔
1263
    }
570✔
1264
    if (to_migrate.size()) {
180✔
1265
        for (auto obj : *this) {
6✔
1266
            for (auto col : to_migrate) {
12✔
1267
                if (col.is_set()) {
12✔
1268
                    auto set = obj.get_set<Mixed>(col);
6✔
1269
                    set.migrate();
6✔
1270
                }
6✔
1271
                else if (col.is_dictionary()) {
6✔
1272
                    auto dict = obj.get_dictionary(col);
6✔
1273
                    dict.migrate();
6✔
1274
                }
6✔
1275
            }
12✔
1276
        }
6✔
1277
    }
6✔
1278
}
180✔
1279

1280
void Table::migrate_set_orderings()
1281
{
354✔
1282
    std::vector<ColKey> to_migrate;
354✔
1283
    for (auto col : get_column_keys()) {
918✔
1284
        if (col.is_set() && (col.get_type() == col_type_Mixed || col.get_type() == col_type_String ||
918✔
1285
                             col.get_type() == col_type_Binary)) {
30✔
1286
            to_migrate.push_back(col);
30✔
1287
        }
30✔
1288
    }
918✔
1289
    if (to_migrate.size()) {
354✔
1290
        for (auto obj : *this) {
90✔
1291
            for (auto col : to_migrate) {
102✔
1292
                if (col.get_type() == col_type_Mixed) {
102✔
1293
                    auto set = obj.get_set<Mixed>(col);
12✔
1294
                    set.migration_resort();
12✔
1295
                }
12✔
1296
                else if (col.get_type() == col_type_Binary) {
90✔
1297
                    auto set = obj.get_set<BinaryData>(col);
6✔
1298
                    set.migration_resort();
6✔
1299
                }
6✔
1300
                else {
84✔
1301
                    REALM_ASSERT_3(col.get_type(), ==, col_type_String);
84✔
1302
                    auto set = obj.get_set<String>(col);
84✔
1303
                    set.migration_resort();
84✔
1304
                }
84✔
1305
            }
102✔
1306
        }
90✔
1307
    }
18✔
1308
}
354✔
1309

1310
StringData Table::get_name() const noexcept
1311
{
8,820,894✔
1312
    const Array& real_top = m_top;
8,820,894✔
1313
    ArrayParent* parent = real_top.get_parent();
8,820,894✔
1314
    if (!parent)
8,820,894✔
1315
        return StringData("");
54✔
1316
    REALM_ASSERT(dynamic_cast<Group*>(parent));
8,820,840✔
1317
    return static_cast<Group*>(parent)->get_table_name(get_key());
8,820,840✔
1318
}
8,820,840✔
1319

1320
StringData Table::get_class_name() const noexcept
1321
{
6,125,571✔
1322
    return Group::table_name_to_class_name(get_name());
6,125,571✔
1323
}
6,125,571✔
1324

1325
const char* Table::get_state() const noexcept
1326
{
42✔
1327
    switch (m_cookie) {
42✔
1328
        case cookie_created:
✔
1329
            return "created";
×
1330
        case cookie_transaction_ended:
6✔
1331
            return "transaction_ended";
6✔
1332
        case cookie_initialized:
✔
1333
            return "initialised";
×
1334
        case cookie_removed:
36✔
1335
            return "removed";
36✔
1336
        case cookie_void:
✔
1337
            return "void";
×
1338
        case cookie_deleted:
✔
1339
            return "deleted";
×
1340
    }
×
1341
    return "";
×
1342
}
×
1343

1344

1345
bool Table::is_nullable(ColKey col_key) const
1346
{
908,376✔
1347
    REALM_ASSERT_DEBUG(valid_column(col_key));
908,376✔
1348
    return col_key.get_attrs().test(col_attr_Nullable);
908,376✔
1349
}
908,376✔
1350

1351
bool Table::is_list(ColKey col_key) const
1352
{
174,372✔
1353
    REALM_ASSERT_DEBUG(valid_column(col_key));
174,372✔
1354
    return col_key.get_attrs().test(col_attr_List);
174,372✔
1355
}
174,372✔
1356

1357

1358
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
1359
{
339,375✔
1360
    Array top(alloc);
339,375✔
1361
    _impl::DeepArrayDestroyGuard dg(&top);
339,375✔
1362
    top.create(Array::type_HasRefs); // Throws
339,375✔
1363
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
339,375✔
1364

168,267✔
1365
    {
339,375✔
1366
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
339,375✔
1367
        dg_2.reset(mem.get_ref());
339,375✔
1368
        int_fast64_t v(from_ref(mem.get_ref()));
339,375✔
1369
        top.add(v); // Throws
339,375✔
1370
        dg_2.release();
339,375✔
1371
    }
339,375✔
1372
    top.add(0); // Old position for columns
339,375✔
1373
    {
339,375✔
1374
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
339,375✔
1375
        dg_2.reset(mem.get_ref());
339,375✔
1376
        int_fast64_t v(from_ref(mem.get_ref()));
339,375✔
1377
        top.add(v); // Throws
339,375✔
1378
        dg_2.release();
339,375✔
1379
    }
339,375✔
1380

168,267✔
1381
    // Table key value
168,267✔
1382
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
339,375✔
1383
    top.add(rot);
339,375✔
1384

168,267✔
1385
    // Search indexes
168,267✔
1386
    {
339,375✔
1387
        bool context_flag = false;
339,375✔
1388
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
339,375✔
1389
        dg_2.reset(mem.get_ref());
339,375✔
1390
        int_fast64_t v(from_ref(mem.get_ref()));
339,375✔
1391
        top.add(v); // Throws
339,375✔
1392
        dg_2.release();
339,375✔
1393
    }
339,375✔
1394
    rot = RefOrTagged::make_tagged(0);
339,375✔
1395
    top.add(rot); // Column key
339,375✔
1396
    top.add(rot); // Version
339,375✔
1397
    dg.release();
339,375✔
1398
    // Opposite keys (table and column)
168,267✔
1399
    {
339,375✔
1400
        bool context_flag = false;
339,375✔
1401
        {
339,375✔
1402
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
339,375✔
1403
            dg_2.reset(mem.get_ref());
339,375✔
1404
            int_fast64_t v(from_ref(mem.get_ref()));
339,375✔
1405
            top.add(v); // Throws
339,375✔
1406
            dg_2.release();
339,375✔
1407
        }
339,375✔
1408
        {
339,375✔
1409
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
339,375✔
1410
            dg_2.reset(mem.get_ref());
339,375✔
1411
            int_fast64_t v(from_ref(mem.get_ref()));
339,375✔
1412
            top.add(v); // Throws
339,375✔
1413
            dg_2.release();
339,375✔
1414
        }
339,375✔
1415
    }
339,375✔
1416
    top.add(0); // Sequence number
339,375✔
1417
    top.add(0); // Collision_map
339,375✔
1418
    top.add(0); // pk col key
339,375✔
1419
    top.add(0); // flags
339,375✔
1420
    top.add(0); // tombstones
339,375✔
1421

168,267✔
1422
    REALM_ASSERT(top.size() == top_array_size);
339,375✔
1423

168,267✔
1424
    return top.get_ref();
339,375✔
1425
}
339,375✔
1426

1427
void Table::ensure_graveyard()
1428
{
30,831✔
1429
    if (!m_tombstones) {
30,831✔
1430
        while (m_top.size() < top_position_for_tombstones)
10,236✔
1431
            m_top.add(0);
×
1432
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
10,236✔
1433
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
10,236✔
1434
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
10,236✔
1435
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
10,236✔
1436
        m_tombstones->init_from_parent();
10,236✔
1437
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
25,101✔
1438
            ts->insert_column(col);
25,101✔
1439
            return IteratorControl::AdvanceToNext;
25,101✔
1440
        });
25,101✔
1441
    }
10,236✔
1442
}
30,831✔
1443

1444
void Table::batch_erase_rows(const KeyColumn& keys)
1445
{
558✔
1446
    size_t num_objs = keys.size();
558✔
1447
    std::vector<ObjKey> vec;
558✔
1448
    vec.reserve(num_objs);
558✔
1449
    for (size_t i = 0; i < num_objs; ++i) {
2,898✔
1450
        ObjKey key = keys.get(i);
2,340✔
1451
        if (key != null_key && is_valid(key)) {
2,340✔
1452
            vec.push_back(key);
2,340✔
1453
        }
2,340✔
1454
    }
2,340✔
1455

279✔
1456
    sort(vec.begin(), vec.end());
558✔
1457
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
558✔
1458

279✔
1459
    batch_erase_objects(vec);
558✔
1460
}
558✔
1461

1462
void Table::batch_erase_objects(std::vector<ObjKey>& keys)
1463
{
5,325✔
1464
    Group* g = get_parent_group();
5,325✔
1465
    bool maybe_has_incoming_links = g && !is_asymmetric();
5,325✔
1466

2,664✔
1467
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
5,325✔
1468
        CascadeState state(CascadeState::Mode::Strong, g);
4,557✔
1469
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
3,159✔
1470
            state.m_to_be_deleted.emplace_back(m_key, k);
1,758✔
1471
        });
1,758✔
1472
        if (maybe_has_incoming_links)
4,557✔
1473
            nullify_links(state);
4,557✔
1474
        remove_recursive(state);
4,557✔
1475
    }
4,557✔
1476
    else {
768✔
1477
        CascadeState state(CascadeState::Mode::None, g);
768✔
1478
        for (auto k : keys) {
2,512,302✔
1479
            if (maybe_has_incoming_links) {
2,512,302✔
1480
                m_clusters.nullify_incoming_links(k, state);
2,512,224✔
1481
            }
2,512,224✔
1482
            m_clusters.erase(k, state);
2,512,302✔
1483
        }
2,512,302✔
1484
    }
768✔
1485
    keys.clear();
5,325✔
1486
}
5,325✔
1487

1488
void Table::clear()
1489
{
4,197✔
1490
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
4,197✔
1491
    m_clusters.clear(state);
4,197✔
1492
    free_collision_table();
4,197✔
1493
}
4,197✔
1494

1495

1496
Group* Table::get_parent_group() const noexcept
1497
{
60,169,428✔
1498
    if (!m_top.is_attached())
60,169,428✔
1499
        return 0;                             // Subtable with shared descriptor
×
1500
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
60,169,428✔
1501
    if (!parent)
60,169,428✔
1502
        return 0; // Free-standing table
25,787,094✔
1503

17,065,977✔
1504
    return static_cast<Group*>(parent);
34,382,334✔
1505
}
34,382,334✔
1506

1507
inline uint64_t Table::get_sync_file_id() const noexcept
1508
{
38,812,329✔
1509
    Group* g = get_parent_group();
38,812,329✔
1510
    return g ? g->get_sync_file_id() : 0;
32,189,241✔
1511
}
38,812,329✔
1512

1513
size_t Table::get_index_in_group() const noexcept
1514
{
39✔
1515
    if (!m_top.is_attached())
39✔
1516
        return realm::npos;                   // Subtable with shared descriptor
×
1517
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
39✔
1518
    if (!parent)
39✔
1519
        return realm::npos; // Free-standing table
×
1520
    return m_top.get_ndx_in_parent();
39✔
1521
}
39✔
1522

1523
uint64_t Table::allocate_sequence_number()
1524
{
19,959,636✔
1525
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
19,959,636✔
1526
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,829,730✔
1527
    rot = RefOrTagged::make_tagged(sn + 1);
19,959,636✔
1528
    m_top.set(top_position_for_sequence_number, rot);
19,959,636✔
1529

9,903,903✔
1530
    return sn;
19,959,636✔
1531
}
19,959,636✔
1532

1533
void Table::set_sequence_number(uint64_t seq)
UNCOV
1534
{
×
UNCOV
1535
    m_top.set(top_position_for_sequence_number, RefOrTagged::make_tagged(seq));
×
UNCOV
1536
}
×
1537

1538
void Table::set_collision_map(ref_type ref)
1539
{
×
1540
    m_top.set(top_position_for_collision_map, RefOrTagged::make_ref(ref));
×
1541
}
×
1542

1543
TableRef Table::get_link_target(ColKey col_key) noexcept
1544
{
309,705✔
1545
    return get_opposite_table(col_key);
309,705✔
1546
}
309,705✔
1547

1548
// count ----------------------------------------------
1549

1550
size_t Table::count_int(ColKey col_key, int64_t value) const
1551
{
12,006✔
1552
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1553
        return index->count(value);
12,000✔
1554
    }
12,000✔
1555

3✔
1556
    return where().equal(col_key, value).count();
6✔
1557
}
6✔
1558
size_t Table::count_float(ColKey col_key, float value) const
1559
{
6✔
1560
    return where().equal(col_key, value).count();
6✔
1561
}
6✔
1562
size_t Table::count_double(ColKey col_key, double value) const
1563
{
6✔
1564
    return where().equal(col_key, value).count();
6✔
1565
}
6✔
1566
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
1567
{
18✔
1568
    ArrayDecimal128 leaf(get_alloc());
18✔
1569
    size_t cnt = 0;
18✔
1570
    bool null_value = value.is_null();
18✔
1571
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
1572
        // direct aggregate on the leaf
9✔
1573
        cluster->init_leaf(col_key, &leaf);
18✔
1574
        auto sz = leaf.size();
18✔
1575
        for (size_t i = 0; i < sz; i++) {
1,296✔
1576
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
1577
                cnt++;
24✔
1578
            }
24✔
1579
        }
1,278✔
1580
        return IteratorControl::AdvanceToNext;
18✔
1581
    };
18✔
1582

9✔
1583
    traverse_clusters(f);
18✔
1584

9✔
1585
    return cnt;
18✔
1586
}
18✔
1587
size_t Table::count_string(ColKey col_key, StringData value) const
1588
{
1,476✔
1589
    if (auto index = this->get_search_index(col_key)) {
1,476✔
1590
        return index->count(value);
732✔
1591
    }
732✔
1592
    return where().equal(col_key, value).count();
744✔
1593
}
744✔
1594

1595
template <typename T>
1596
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1597
{
27,801✔
1598
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
27,801✔
1599
    LeafType leaf(get_alloc());
27,801✔
1600

13,920✔
1601
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
27,819✔
1602
        // direct aggregate on the leaf
13,929✔
1603
        cluster->init_leaf(column_key, &leaf);
27,819✔
1604
        st.m_key_offset = cluster->get_offset();
27,819✔
1605
        st.m_key_values = cluster->get_key_array();
27,819✔
1606

13,929✔
1607
        bool cont = true;
27,819✔
1608
        size_t sz = leaf.size();
27,819✔
1609
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
122,838✔
1610
            auto v = leaf.get(local_index);
95,019✔
1611
            cont = st.match(local_index, v);
95,019✔
1612
        }
95,019✔
1613
        return IteratorControl::AdvanceToNext;
27,819✔
1614
    };
27,819✔
1615

13,920✔
1616
    traverse_clusters(f);
27,801✔
1617
}
27,801✔
1618

1619
// This template is also used by the query engine
1620
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1621
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1622
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1623
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1624
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1625
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1626
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1627

1628
std::optional<Mixed> Table::sum(ColKey col_key) const
1629
{
756✔
1630
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1631
}
756✔
1632

1633
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1634
{
822✔
1635
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1636
}
822✔
1637

1638
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1639
{
1,170✔
1640
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
1641
}
1,170✔
1642

1643
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1644
{
21,525✔
1645
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
21,525✔
1646
}
21,525✔
1647

1648

1649
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1650
{
29,930,355✔
1651
    check_column(col);
29,930,355✔
1652
    return m_index_accessors[col.get_index().val].get();
29,930,355✔
1653
}
29,930,355✔
1654

1655
StringIndex* Table::get_string_index(ColKey col) const noexcept
1656
{
696✔
1657
    check_column(col);
696✔
1658
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
696✔
1659
}
696✔
1660

1661
template <class T>
1662
ObjKey Table::find_first(ColKey col_key, T value) const
1663
{
35,142✔
1664
    check_column(col_key);
35,142✔
1665

17,562✔
1666
    if (!col_key.is_nullable() && value_is_null(value)) {
35,142!
1667
        return {}; // this is a precaution/optimization
6✔
1668
    }
6✔
1669
    // You cannot call GetIndexData on ObjKey
17,559✔
1670
    if constexpr (!std::is_same_v<T, ObjKey>) {
35,136✔
1671
        if (SearchIndex* index = get_search_index(col_key)) {
35,118!
1672
            return index->find_first(value);
27,270✔
1673
        }
27,270✔
1674
        if (col_key == m_primary_key_col) {
7,848!
1675
            return find_primary_key(value);
×
1676
        }
×
1677
    }
7,848✔
1678

3,924✔
1679
    ObjKey key;
7,848✔
1680
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
7,848✔
1681
    LeafType leaf(get_alloc());
7,848✔
1682

3,924✔
1683
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
9,330✔
1684
        cluster->init_leaf(col_key, &leaf);
9,330✔
1685
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
1686
        if (row != realm::npos) {
9,330!
1687
            key = cluster->get_real_key(row);
7,692✔
1688
            return IteratorControl::Stop;
7,692✔
1689
        }
7,692✔
1690
        return IteratorControl::AdvanceToNext;
1,638✔
1691
    };
1,638✔
1692

3,924✔
1693
    traverse_clusters(f);
7,848✔
1694

3,924✔
1695
    return key;
7,848✔
1696
}
7,848✔
1697

1698
namespace realm {
1699

1700
template <>
1701
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1702
{
×
1703
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1704
}
×
1705

1706
template <>
1707
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
1708
{
×
1709
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1710
}
×
1711

1712
template <>
1713
ObjKey Table::find_first(ColKey col_key, null) const
1714
{
×
1715
    return find_first_null(col_key);
×
1716
}
×
1717
} // namespace realm
1718

1719
// Explicitly instantiate the generic case of the template for the types we care about.
1720
template ObjKey Table::find_first(ColKey col_key, bool) const;
1721
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
1722
template ObjKey Table::find_first(ColKey col_key, float) const;
1723
template ObjKey Table::find_first(ColKey col_key, double) const;
1724
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
1725
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
1726
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
1727
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
1728
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
1729
template ObjKey Table::find_first(ColKey col_key, StringData) const;
1730
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
1731
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
1732
template ObjKey Table::find_first(ColKey col_key, UUID) const;
1733
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
1734
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
1735

1736
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1737
{
7,698✔
1738
    if (is_nullable(col_key))
7,698✔
1739
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1740
    else
7,662✔
1741
        return find_first<int64_t>(col_key, value);
7,662✔
1742
}
7,698✔
1743

1744
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1745
{
78✔
1746
    if (is_nullable(col_key))
78✔
1747
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1748
    else
42✔
1749
        return find_first<bool>(col_key, value);
42✔
1750
}
78✔
1751

1752
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1753
{
108✔
1754
    return find_first(col_key, value);
108✔
1755
}
108✔
1756

1757
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1758
{
6✔
1759
    return find_first(col_key, value);
6✔
1760
}
6✔
1761

1762
ObjKey Table::find_first_float(ColKey col_key, float value) const
1763
{
12✔
1764
    return find_first<Float>(col_key, value);
12✔
1765
}
12✔
1766

1767
ObjKey Table::find_first_double(ColKey col_key, double value) const
1768
{
12✔
1769
    return find_first<Double>(col_key, value);
12✔
1770
}
12✔
1771

1772
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
1773
{
×
1774
    return find_first<Decimal128>(col_key, value);
×
1775
}
×
1776

1777
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1778
{
11,508✔
1779
    return find_first<StringData>(col_key, value);
11,508✔
1780
}
11,508✔
1781

1782
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
1783
{
×
1784
    return find_first<BinaryData>(col_key, value);
×
1785
}
×
1786

1787
ObjKey Table::find_first_null(ColKey col_key) const
1788
{
114✔
1789
    return where().equal(col_key, null{}).find();
114✔
1790
}
114✔
1791

1792
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1793
{
18✔
1794
    return find_first(col_key, value);
18✔
1795
}
18✔
1796

1797
template <class T>
1798
TableView Table::find_all(ColKey col_key, T value)
1799
{
108✔
1800
    return where().equal(col_key, value).find_all();
108✔
1801
}
108✔
1802

1803
TableView Table::find_all_int(ColKey col_key, int64_t value)
1804
{
102✔
1805
    return find_all<int64_t>(col_key, value);
102✔
1806
}
102✔
1807

1808
TableView Table::find_all_int(ColKey col_key, int64_t value) const
1809
{
×
1810
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1811
}
×
1812

1813
TableView Table::find_all_bool(ColKey col_key, bool value)
1814
{
×
1815
    return find_all<bool>(col_key, value);
×
1816
}
×
1817

1818
TableView Table::find_all_bool(ColKey col_key, bool value) const
1819
{
×
1820
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1821
}
×
1822

1823

1824
TableView Table::find_all_float(ColKey col_key, float value)
1825
{
×
1826
    return find_all<float>(col_key, value);
×
1827
}
×
1828

1829
TableView Table::find_all_float(ColKey col_key, float value) const
1830
{
×
1831
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
1832
}
×
1833

1834
TableView Table::find_all_double(ColKey col_key, double value)
1835
{
6✔
1836
    return find_all<double>(col_key, value);
6✔
1837
}
6✔
1838

1839
TableView Table::find_all_double(ColKey col_key, double value) const
1840
{
×
1841
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
1842
}
×
1843

1844
TableView Table::find_all_string(ColKey col_key, StringData value)
1845
{
282✔
1846
    return where().equal(col_key, value).find_all();
282✔
1847
}
282✔
1848

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

1854
TableView Table::find_all_binary(ColKey, BinaryData)
1855
{
×
1856
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
1857
}
×
1858

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

1864
TableView Table::find_all_null(ColKey col_key)
1865
{
×
1866
    return where().equal(col_key, null{}).find_all();
×
1867
}
×
1868

1869
TableView Table::find_all_null(ColKey col_key) const
1870
{
×
1871
    return const_cast<Table*>(this)->find_all_null(col_key);
×
1872
}
×
1873

1874
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1875
{
6✔
1876
    return where().fulltext(col_key, terms).find_all();
6✔
1877
}
6✔
1878

1879
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1880
{
72✔
1881
    TableView tv = where().find_all();
72✔
1882
    tv.sort(col_key, ascending);
72✔
1883
    return tv;
72✔
1884
}
72✔
1885

1886
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1887
{
6✔
1888
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1889
}
6✔
1890

1891
TableView Table::get_sorted_view(SortDescriptor order)
1892
{
126✔
1893
    TableView tv = where().find_all();
126✔
1894
    tv.sort(std::move(order));
126✔
1895
    return tv;
126✔
1896
}
126✔
1897

1898
TableView Table::get_sorted_view(SortDescriptor order) const
1899
{
×
1900
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
1901
}
×
1902

1903
util::Logger* Table::get_logger() const noexcept
1904
{
1,808,853✔
1905
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,714,818✔
1906
}
1,808,853✔
1907

1908
// Called after a commit. Table will effectively contain the same as before,
1909
// but now with new refs from the file
1910
void Table::update_from_parent() noexcept
1911
{
810,885✔
1912
    // There is no top for sub-tables sharing spec
402,993✔
1913
    if (m_top.is_attached()) {
810,885✔
1914
        m_top.update_from_parent();
810,885✔
1915
        m_spec.update_from_parent();
810,885✔
1916
        m_clusters.update_from_parent();
810,885✔
1917
        m_index_refs.update_from_parent();
810,885✔
1918
        for (auto&& index : m_index_accessors) {
2,651,460✔
1919
            if (index != nullptr) {
2,651,460✔
1920
                index->update_from_parent();
272,511✔
1921
            }
272,511✔
1922
        }
2,651,460✔
1923

402,993✔
1924
        m_opposite_table.update_from_parent();
810,885✔
1925
        m_opposite_column.update_from_parent();
810,885✔
1926
        if (m_top.size() > top_position_for_flags) {
810,885✔
1927
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
810,876✔
1928
            m_table_type = Type(flags & table_type_mask);
810,876✔
1929
        }
810,876✔
1930
        else {
9✔
1931
            m_table_type = Type::TopLevel;
9✔
1932
        }
9✔
1933
        if (m_tombstones)
810,885✔
1934
            m_tombstones->update_from_parent();
1,278✔
1935

402,993✔
1936
        refresh_content_version();
810,885✔
1937
        m_has_any_embedded_objects.reset();
810,885✔
1938
    }
810,885✔
1939
    m_alloc.bump_storage_version();
810,885✔
1940
}
810,885✔
1941

1942
void Table::schema_to_json(std::ostream& out, const std::map<std::string, std::string>& renames) const
1943
{
12✔
1944
    out << "{";
12✔
1945
    auto name = get_name();
12✔
1946
    if (renames.count(name))
12✔
1947
        name = renames.at(name);
×
1948
    out << "\"name\":\"" << name << "\"";
12✔
1949
    if (this->m_primary_key_col) {
12✔
1950
        out << ",";
×
1951
        out << "\"primaryKey\":\"" << this->get_column_name(m_primary_key_col) << "\"";
×
1952
    }
×
1953
    out << ",\"tableType\":\"" << this->get_table_type() << "\"";
12✔
1954
    out << ",\"properties\":[";
12✔
1955
    auto col_keys = get_column_keys();
12✔
1956
    int sz = int(col_keys.size());
12✔
1957
    for (int i = 0; i < sz; ++i) {
54✔
1958
        auto col_key = col_keys[i];
42✔
1959
        name = get_column_name(col_key);
42✔
1960
        auto type = col_key.get_type();
42✔
1961
        if (renames.count(name))
42✔
1962
            name = renames.at(name);
×
1963
        out << "{";
42✔
1964
        out << "\"name\":\"" << name << "\"";
42✔
1965
        if (this->is_link_type(type)) {
42✔
1966
            out << ",\"type\":\"object\"";
6✔
1967
            name = this->get_opposite_table(col_key)->get_name();
6✔
1968
            if (renames.count(name))
6✔
1969
                name = renames.at(name);
×
1970
            out << ",\"objectType\":\"" << name << "\"";
6✔
1971
        }
6✔
1972
        else {
36✔
1973
            out << ",\"type\":\"" << get_data_type_name(DataType(type)) << "\"";
36✔
1974
        }
36✔
1975
        if (col_key.is_list()) {
42✔
1976
            out << ",\"isArray\":true";
12✔
1977
        }
12✔
1978
        else if (col_key.is_set()) {
30✔
1979
            out << ",\"isSet\":true";
×
1980
        }
×
1981
        else if (col_key.is_dictionary()) {
30✔
1982
            out << ",\"isMap\":true";
6✔
1983
            auto key_type = get_dictionary_key_type(col_key);
6✔
1984
            out << ",\"keyType\":\"" << get_data_type_name(key_type) << "\"";
6✔
1985
        }
6✔
1986
        if (col_key.is_nullable()) {
42✔
1987
            out << ",\"isOptional\":true";
12✔
1988
        }
12✔
1989
        auto index_type = search_index_type(col_key);
42✔
1990
        if (index_type == IndexType::General) {
42✔
1991
            out << ",\"isIndexed\":true";
×
1992
        }
×
1993
        if (index_type == IndexType::Fulltext) {
42✔
1994
            out << ",\"isFulltextIndexed\":true";
×
1995
        }
×
1996
        out << "}";
42✔
1997
        if (i < sz - 1) {
42✔
1998
            out << ",";
30✔
1999
        }
30✔
2000
    }
42✔
2001
    out << "]}";
12✔
2002
}
12✔
2003

2004
bool Table::operator==(const Table& t) const
2005
{
138✔
2006
    if (size() != t.size()) {
138✔
2007
        return false;
12✔
2008
    }
12✔
2009
    // Check columns
63✔
2010
    for (auto ck : this->get_column_keys()) {
534✔
2011
        auto name = get_column_name(ck);
534✔
2012
        auto other_ck = t.get_column_key(name);
534✔
2013
        auto attrs = ck.get_attrs();
534✔
2014
        if (search_index_type(ck) != t.search_index_type(other_ck))
534✔
2015
            return false;
×
2016

267✔
2017
        if (!other_ck || other_ck.get_attrs() != attrs) {
534✔
2018
            return false;
×
2019
        }
×
2020
    }
534✔
2021
    auto pk_col = get_primary_key_column();
126✔
2022
    for (auto o : *this) {
2,898✔
2023
        Obj other_o;
2,898✔
2024
        if (pk_col) {
2,898✔
2025
            auto pk = o.get_any(pk_col);
90✔
2026
            other_o = t.get_object_with_primary_key(pk);
90✔
2027
        }
90✔
2028
        else {
2,808✔
2029
            other_o = t.get_object(o.get_key());
2,808✔
2030
        }
2,808✔
2031
        if (!(other_o && o == other_o))
2,898✔
2032
            return false;
18✔
2033
    }
2,898✔
2034

63✔
2035
    return true;
117✔
2036
}
126✔
2037

2038

2039
void Table::flush_for_commit()
2040
{
3,951,630✔
2041
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
3,951,636✔
2042
        if (!m_top.is_read_only()) {
3,951,630✔
2043
            ++m_in_file_version_at_transaction_boundary;
1,236,771✔
2044
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
1,236,771✔
2045
            m_top.set(top_position_for_version, rot_version);
1,236,771✔
2046
        }
1,236,771✔
2047
    }
3,951,630✔
2048
}
3,951,630✔
2049

2050
void Table::refresh_content_version()
2051
{
1,123,959✔
2052
    REALM_ASSERT(m_top.is_attached());
1,123,959✔
2053
    if (m_top.size() >= top_position_for_version) {
1,123,959✔
2054
        // we have versioning info in the file. Use this to conditionally
571,341✔
2055
        // bump the version counter:
571,341✔
2056
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,123,836✔
2057
        REALM_ASSERT(rot_version.is_tagged());
1,123,836✔
2058
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,123,836✔
2059
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
180,132✔
2060
            bump_content_version();
180,132✔
2061
        }
180,132✔
2062
    }
1,123,836✔
2063
    else {
123✔
2064
        // assume the worst:
99✔
2065
        bump_content_version();
123✔
2066
    }
123✔
2067
}
1,123,959✔
2068

2069

2070
// Called when Group is moved to another version - either a rollback or an advance.
2071
// The content of the table is potentially different, so make no assumptions.
2072
void Table::refresh_accessor_tree()
2073
{
313,020✔
2074
    REALM_ASSERT(m_cookie == cookie_initialized);
313,020✔
2075
    REALM_ASSERT(m_top.is_attached());
313,020✔
2076
    m_top.init_from_parent();
313,020✔
2077
    m_spec.init_from_parent();
313,020✔
2078
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
313,020✔
2079
    m_clusters.init_from_parent();
313,020✔
2080
    m_index_refs.init_from_parent();
313,020✔
2081
    m_opposite_table.init_from_parent();
313,020✔
2082
    m_opposite_column.init_from_parent();
313,020✔
2083
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
313,020✔
2084
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
258,231✔
2085
    if (m_top.size() > top_position_for_flags) {
313,020✔
2086
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
312,996✔
2087
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
312,996✔
2088
    }
312,996✔
2089
    else {
24✔
2090
        m_table_type = Type::TopLevel;
24✔
2091
    }
24✔
2092
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
313,032✔
2093
        // Tombstones exists
417✔
2094
        if (!m_tombstones) {
834✔
2095
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
240✔
2096
        }
240✔
2097
        m_tombstones->init_from_parent();
834✔
2098
    }
834✔
2099
    else {
312,186✔
2100
        m_tombstones = nullptr;
312,186✔
2101
    }
312,186✔
2102
    refresh_content_version();
313,020✔
2103
    bump_storage_version();
313,020✔
2104
    build_column_mapping();
313,020✔
2105
    refresh_index_accessors();
313,020✔
2106
}
313,020✔
2107

2108
void Table::refresh_index_accessors()
2109
{
6,743,664✔
2110
    // Refresh search index accessors
3,818,193✔
2111

3,818,193✔
2112
    // First eliminate any index accessors for eliminated last columns
3,818,193✔
2113
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
6,743,664✔
2114
    m_index_accessors.resize(col_ndx_end);
6,743,664✔
2115

3,818,193✔
2116
    // Then eliminate/refresh/create accessors within column range
3,818,193✔
2117
    // we can not use for_each_column() here, since the columns may have changed
3,818,193✔
2118
    // and the index accessor vector is not updated correspondingly.
3,818,193✔
2119
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
29,037,636✔
2120
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
22,293,972✔
2121

11,925,210✔
2122
        if (ref == 0) {
22,293,972✔
2123
            // accessor drop
9,945,588✔
2124
            m_index_accessors[col_ndx].reset();
18,342,117✔
2125
        }
18,342,117✔
2126
        else {
3,951,855✔
2127
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
3,951,855✔
2128
            bool fulltext = attr.test(col_attr_FullText_Indexed);
3,951,855✔
2129
            auto col_key = m_leaf_ndx2colkey[col_ndx];
3,951,855✔
2130
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
3,951,840✔
2131

1,979,622✔
2132
            if (m_index_accessors[col_ndx]) { // still there, refresh:
3,951,855✔
2133
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
476,067✔
2134
            }
476,067✔
2135
            else { // new index!
3,475,788✔
2136
                m_index_accessors[col_ndx] =
3,475,788✔
2137
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
3,475,788✔
2138
            }
3,475,788✔
2139
        }
3,951,855✔
2140
    }
22,293,972✔
2141
}
6,743,664✔
2142

2143
bool Table::is_cross_table_link_target() const noexcept
2144
{
7,968✔
2145
    auto is_cross_link = [this](ColKey col_key) {
3,816✔
2146
        auto t = col_key.get_type();
66✔
2147
        // look for a backlink with a different target than ourselves
30✔
2148
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
66✔
2149
                   ? IteratorControl::Stop
42✔
2150
                   : IteratorControl::AdvanceToNext;
54✔
2151
    };
66✔
2152
    return for_each_backlink_column(is_cross_link);
7,968✔
2153
}
7,968✔
2154

2155
// LCOV_EXCL_START ignore debug functions
2156

2157
void Table::verify() const
2158
{
2,935,968✔
2159
#ifdef REALM_DEBUG
2,935,968✔
2160
    if (m_top.is_attached())
2,935,968✔
2161
        m_top.verify();
2,935,965✔
2162
    m_spec.verify();
2,935,968✔
2163
    m_clusters.verify();
2,935,968✔
2164
    if (nb_unresolved())
2,935,968✔
2165
        m_tombstones->verify();
760,566✔
2166
#endif
2,935,968✔
2167
}
2,935,968✔
2168

2169
#ifdef REALM_DEBUG
2170
MemStats Table::stats() const
2171
{
×
2172
    MemStats mem_stats;
×
2173
    m_top.stats(mem_stats);
×
2174
    return mem_stats;
×
2175
}
×
2176
#endif // LCOV_EXCL_STOP ignore debug functions
2177

2178
Obj Table::create_object(ObjKey key, const FieldValues& values)
2179
{
22,625,361✔
2180
    if (is_embedded())
22,625,361✔
2181
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2182
    if (m_primary_key_col)
22,625,313✔
2183
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2184
    if (key == null_key) {
22,625,313✔
2185
        GlobalKey object_id = allocate_object_id_squeezed();
19,465,254✔
2186
        key = object_id.get_local_key(get_sync_file_id());
19,465,254✔
2187
        // Check if this key collides with an already existing object
9,680,427✔
2188
        // This could happen if objects were at some point created with primary keys,
9,680,427✔
2189
        // but later primary key property was removed from the schema.
9,680,427✔
2190
        while (m_clusters.is_valid(key)) {
19,465,254✔
2191
            object_id = allocate_object_id_squeezed();
×
2192
            key = object_id.get_local_key(get_sync_file_id());
×
2193
        }
×
2194
        if (auto repl = get_repl())
19,465,254✔
2195
            repl->create_object(this, object_id);
4,311,489✔
2196
    }
19,465,254✔
2197

11,263,023✔
2198
    REALM_ASSERT(key.value >= 0);
22,625,313✔
2199

11,263,023✔
2200
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,625,313✔
2201

11,263,023✔
2202
    return obj;
22,625,313✔
2203
}
22,625,313✔
2204

2205
Obj Table::create_linked_object()
2206
{
39,720✔
2207
    REALM_ASSERT(is_embedded());
39,720✔
2208

19,767✔
2209
    GlobalKey object_id = allocate_object_id_squeezed();
39,720✔
2210
    ObjKey key = object_id.get_local_key(get_sync_file_id());
39,720✔
2211

19,767✔
2212
    REALM_ASSERT(key.value >= 0);
39,720✔
2213

19,767✔
2214
    Obj obj = m_clusters.insert(key, {});
39,720✔
2215

19,767✔
2216
    return obj;
39,720✔
2217
}
39,720✔
2218

2219
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2220
{
30✔
2221
    if (is_embedded())
30✔
2222
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2223
    if (m_primary_key_col)
30✔
2224
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2225
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2226

15✔
2227
    if (auto repl = get_repl())
30✔
2228
        repl->create_object(this, object_id);
30✔
2229

15✔
2230
    try {
30✔
2231
        Obj obj = m_clusters.insert(key, values);
30✔
2232
        // Check if tombstone exists
15✔
2233
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2234
            auto unres_key = key.get_unresolved();
6✔
2235
            // Copy links over
3✔
2236
            auto tombstone = m_tombstones->get(unres_key);
6✔
2237
            obj.assign_pk_and_backlinks(tombstone);
6✔
2238
            // If tombstones had no links to it, it may still be alive
3✔
2239
            if (m_tombstones->is_valid(unres_key)) {
6✔
2240
                CascadeState state(CascadeState::Mode::None);
6✔
2241
                m_tombstones->erase(unres_key, state);
6✔
2242
            }
6✔
2243
        }
6✔
2244

15✔
2245
        return obj;
30✔
2246
    }
30✔
2247
    catch (const KeyAlreadyUsed&) {
6✔
2248
        return m_clusters.get(key);
6✔
2249
    }
6✔
2250
}
30✔
2251

2252
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2253
                                          bool* did_create)
2254
{
585,681✔
2255
    auto primary_key_col = get_primary_key_column();
585,681✔
2256
    if (is_embedded() || !primary_key_col)
585,684✔
2257
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2258
                              util::format("Table has no primary key: %1", get_name()));
6✔
2259

281,367✔
2260
    DataType type = DataType(primary_key_col.get_type());
585,675✔
2261

281,367✔
2262
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
585,675✔
2263
        throw InvalidArgument(
6✔
2264
            ErrorCodes::PropertyNotNullable,
6✔
2265
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2266
    }
6✔
2267

281,364✔
2268
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
585,669✔
2269
        primary_key.get_type() != type) {
585,450✔
2270
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2271
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2272
    }
6✔
2273

281,361✔
2274
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
585,663✔
2275

281,361✔
2276
    if (did_create)
585,663✔
2277
        *did_create = false;
74,457✔
2278

281,361✔
2279
    // Check for existing object
281,361✔
2280
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
585,663✔
2281
        if (mode == UpdateMode::never) {
80,970✔
2282
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2283
        }
6✔
2284
        auto obj = m_clusters.get(key);
80,964✔
2285
        for (auto& val : field_values) {
40,191✔
2286
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2287
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2288
            }
12✔
2289
        }
12✔
2290
        return obj;
80,964✔
2291
    }
80,964✔
2292

241,173✔
2293
    ObjKey unres_key;
504,693✔
2294
    if (m_tombstones) {
504,693✔
2295
        // Check for potential tombstone
19,938✔
2296
        GlobalKey object_id{primary_key};
39,777✔
2297
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
39,777✔
2298

19,938✔
2299
        ObjKey key = object_key.get_unresolved();
39,777✔
2300
        if (auto obj = m_tombstones->try_get_obj(key)) {
39,777✔
2301
            auto existing_pk_value = obj.get_any(primary_key_col);
13,176✔
2302

6,633✔
2303
            // If the primary key is the same, the object should be resurrected below
6,633✔
2304
            if (existing_pk_value == primary_key) {
13,176✔
2305
                unres_key = key;
13,170✔
2306
            }
13,170✔
2307
        }
13,176✔
2308
    }
39,777✔
2309

241,173✔
2310
    ObjKey key = get_next_valid_key();
504,693✔
2311

241,173✔
2312
    auto repl = get_repl();
504,693✔
2313
    if (repl) {
504,693✔
2314
        repl->create_object_with_primary_key(this, key, primary_key);
443,739✔
2315
    }
443,739✔
2316
    if (did_create) {
504,693✔
2317
        *did_create = true;
36,393✔
2318
    }
36,393✔
2319

241,173✔
2320
    field_values.insert(primary_key_col, primary_key);
504,693✔
2321
    Obj ret = m_clusters.insert(key, field_values);
504,693✔
2322

241,173✔
2323
    // Check if unresolved exists
241,173✔
2324
    if (unres_key) {
504,693✔
2325
        auto tombstone = m_tombstones->get(unres_key);
13,170✔
2326
        ret.assign_pk_and_backlinks(tombstone);
13,170✔
2327
        // If tombstones had no links to it, it may still be alive
6,630✔
2328
        if (m_tombstones->is_valid(unres_key)) {
13,170✔
2329
            CascadeState state(CascadeState::Mode::None);
4,002✔
2330
            m_tombstones->erase(unres_key, state);
4,002✔
2331
        }
4,002✔
2332
    }
13,170✔
2333
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
504,693✔
2334
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
2335
    }
1,290✔
2336
    return ret;
504,693✔
2337
}
504,693✔
2338

2339
ObjKey Table::find_primary_key(Mixed primary_key) const
2340
{
915,702✔
2341
    auto primary_key_col = get_primary_key_column();
915,702✔
2342
    REALM_ASSERT(primary_key_col);
915,702✔
2343
    DataType type = DataType(primary_key_col.get_type());
915,702✔
2344
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
915,702✔
2345
                 primary_key.get_type() == type);
915,702✔
2346

463,053✔
2347
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
915,702✔
2348
        return index->find_first(primary_key);
915,645✔
2349
    }
915,645✔
2350

27✔
2351
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
27✔
2352
    // so try the old algorithm
27✔
2353
    GlobalKey object_id{primary_key};
57✔
2354
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
57✔
2355

27✔
2356
    // Check if existing
27✔
2357
    if (auto obj = m_clusters.try_get_obj(object_key)) {
57✔
2358
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2359

2360
        if (existing_pk_value == primary_key) {
×
2361
            return object_key;
×
2362
        }
×
2363
    }
57✔
2364
    return {};
57✔
2365
}
57✔
2366

2367
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2368
{
747,759✔
2369
    // Check if existing
378,156✔
2370
    if (auto key = find_primary_key(primary_key)) {
747,759✔
2371
        return key;
717,480✔
2372
    }
717,480✔
2373

15,066✔
2374
    // Object does not exist - create tombstone
15,066✔
2375
    GlobalKey object_id{primary_key};
30,279✔
2376
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
30,279✔
2377
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
30,279✔
2378
}
30,279✔
2379

2380
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2381
{
18✔
2382
    REALM_ASSERT(!m_primary_key_col);
18✔
2383
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2384

9✔
2385
    // Check if existing
9✔
2386
    if (m_clusters.is_valid(object_key)) {
18✔
2387
        return object_key;
12✔
2388
    }
12✔
2389

3✔
2390
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2391
}
6✔
2392

2393
ObjKey Table::get_objkey(GlobalKey global_key) const
2394
{
18✔
2395
    ObjKey key;
18✔
2396
    REALM_ASSERT(!m_primary_key_col);
18✔
2397
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
2398
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
2399
        key = global_key.get_local_key(get_sync_file_id());
6✔
2400
    }
6✔
2401
    if (key && !is_valid(key)) {
18✔
2402
        key = realm::null_key;
6✔
2403
    }
6✔
2404
    return key;
18✔
2405
}
18✔
2406

2407
GlobalKey Table::get_object_id(ObjKey key) const
2408
{
144✔
2409
    auto col = get_primary_key_column();
144✔
2410
    if (col) {
144✔
2411
        const Obj obj = get_object(key);
24✔
2412
        auto val = obj.get_any(col);
24✔
2413
        return {val};
24✔
2414
    }
24✔
2415
    else {
120✔
2416
        return {key, get_sync_file_id()};
120✔
2417
    }
120✔
2418
    return {};
×
2419
}
×
2420

2421
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2422
{
62,814✔
2423
    auto primary_key_col = get_primary_key_column();
62,814✔
2424
    REALM_ASSERT(primary_key_col);
62,814✔
2425
    DataType type = DataType(primary_key_col.get_type());
62,814✔
2426
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
62,814✔
2427
                 primary_key.get_type() == type);
62,814✔
2428
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
62,814✔
2429
    return k ? m_clusters.get(k) : Obj{};
62,808✔
2430
}
62,814✔
2431

2432
Mixed Table::get_primary_key(ObjKey key) const
2433
{
719,568✔
2434
    auto primary_key_col = get_primary_key_column();
719,568✔
2435
    REALM_ASSERT(primary_key_col);
719,568✔
2436
    if (key.is_unresolved()) {
719,568✔
2437
        REALM_ASSERT(m_tombstones);
72✔
2438
        return m_tombstones->get(key).get_any(primary_key_col);
72✔
2439
    }
72✔
2440
    else {
719,496✔
2441
        return m_clusters.get(key).get_any(primary_key_col);
719,496✔
2442
    }
719,496✔
2443
}
719,568✔
2444

2445
GlobalKey Table::allocate_object_id_squeezed()
2446
{
19,502,634✔
2447
    // m_client_file_ident will be zero if we haven't been in contact with
9,698,280✔
2448
    // the server yet.
9,698,280✔
2449
    auto peer_id = get_sync_file_id();
19,502,634✔
2450
    auto sequence = allocate_sequence_number();
19,502,634✔
2451
    return GlobalKey{peer_id, sequence};
19,502,634✔
2452
}
19,502,634✔
2453

2454
namespace {
2455

2456
/// Calculate optimistic local ID that may collide with others. It is up to
2457
/// the caller to ensure that collisions are detected and that
2458
/// allocate_local_id_after_collision() is called to obtain a non-colliding
2459
/// ID.
2460
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
2461
{
70,434✔
2462
#if REALM_EXERCISE_OBJECT_ID_COLLISION
2463
    const uint64_t optimistic_mask = 0xff;
2464
#else
2465
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
70,434✔
2466
#endif
70,434✔
2467
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
70,434✔
2468
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
70,434✔
2469
}
70,434✔
2470

2471
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2472
{
12✔
2473
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2474
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2475
}
12✔
2476

2477
} // namespace
2478

2479
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2480
{
70,434✔
2481
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
70,434✔
2482

35,187✔
2483
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
70,434✔
2484
        Allocator& alloc = m_top.get_alloc();
24✔
2485
        Array collision_map{alloc};
24✔
2486
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2487

12✔
2488
        Array hi{alloc};
24✔
2489
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2490

12✔
2491
        // Entries are ordered by hi,lo
12✔
2492
        size_t found = hi.find_first(object_id.hi());
24✔
2493
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
2494
            Array lo{alloc};
24✔
2495
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
2496
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
2497
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
2498
                Array local_id{alloc};
24✔
2499
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
2500
                return ObjKey{local_id.get(candidate)};
24✔
2501
            }
24✔
2502
        }
70,410✔
2503
    }
24✔
2504

35,175✔
2505
    return optimistic;
70,410✔
2506
}
70,410✔
2507

2508
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2509
                                                     ObjKey colliding_local_id)
2510
{
12✔
2511
    // Possible optimization: Cache these accessors
6✔
2512
    Allocator& alloc = m_top.get_alloc();
12✔
2513
    Array collision_map{alloc};
12✔
2514
    Array hi{alloc};
12✔
2515
    Array lo{alloc};
12✔
2516
    Array local_id{alloc};
12✔
2517

6✔
2518
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2519
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2520
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2521
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2522

6✔
2523
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2524
    if (collision_map_ref) {
12✔
2525
        collision_map.init_from_parent(); // Throws
×
2526
    }
×
2527
    else {
12✔
2528
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2529
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2530
        collision_map.update_parent();
12✔
2531

6✔
2532
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2533
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2534
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2535
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2536
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2537
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2538
    }
12✔
2539

6✔
2540
    hi.init_from_parent();       // Throws
12✔
2541
    lo.init_from_parent();       // Throws
12✔
2542
    local_id.init_from_parent(); // Throws
12✔
2543

6✔
2544
    size_t num_entries = hi.size();
12✔
2545
    REALM_ASSERT(lo.size() == num_entries);
12✔
2546
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2547

6✔
2548
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2549
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2550
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2551
            ++i;
6✔
2552
        return i;
24✔
2553
    };
24✔
2554

6✔
2555
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
2556
        size_t i = lower_bound_object_id(object_id);
24✔
2557
        if (i != num_entries) {
24✔
2558
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
2559
            if (existing == object_id) {
6✔
2560
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
2561
                return;
×
2562
            }
×
2563
        }
24✔
2564
        hi.insert(i, int64_t(object_id.hi()));
24✔
2565
        lo.insert(i, int64_t(object_id.lo()));
24✔
2566
        local_id.insert(i, new_local_id.value);
24✔
2567
        ++num_entries;
24✔
2568
    };
24✔
2569

6✔
2570
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2571
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2572
    insert_collision(incoming_id, new_local_id);
12✔
2573
    insert_collision(colliding_id, colliding_local_id);
12✔
2574

6✔
2575
    return new_local_id;
12✔
2576
}
12✔
2577

2578
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2579
{
30,831✔
2580
    auto unres_key = key.get_unresolved();
30,831✔
2581

15,336✔
2582
    ensure_graveyard();
30,831✔
2583
    auto tombstone = m_tombstones->try_get_obj(unres_key);
30,831✔
2584
    if (tombstone) {
30,831✔
2585
        if (pk_col) {
2,733✔
2586
            auto existing_pk_value = tombstone.get_any(pk_col);
2,733✔
2587
            // It may just be the same object
1,314✔
2588
            if (existing_pk_value != pk_val) {
2,733✔
2589
                // We have a collision - create new ObjKey
6✔
2590
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
2591
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
2592
            }
12✔
2593
        }
2,721✔
2594
        return tombstone;
2,721✔
2595
    }
2,721✔
2596
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
28,098✔
2597
}
28,098✔
2598

2599
void Table::free_local_id_after_hash_collision(ObjKey key)
2600
{
5,086,767✔
2601
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,086,767✔
2602
        if (key.is_unresolved()) {
30✔
2603
            // Keys will always be inserted as resolved
12✔
2604
            key = key.get_unresolved();
24✔
2605
        }
24✔
2606
        // Possible optimization: Cache these accessors
15✔
2607
        Array collision_map{m_alloc};
30✔
2608
        Array local_id{m_alloc};
30✔
2609

15✔
2610
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2611
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2612
        collision_map.init_from_ref(collision_map_ref);
30✔
2613
        local_id.init_from_parent();
30✔
2614
        auto ndx = local_id.find_first(key.value);
30✔
2615
        if (ndx != realm::npos) {
30✔
2616
            Array hi{m_alloc};
24✔
2617
            Array lo{m_alloc};
24✔
2618

12✔
2619
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2620
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2621
            hi.init_from_parent();
24✔
2622
            lo.init_from_parent();
24✔
2623

12✔
2624
            hi.erase(ndx);
24✔
2625
            lo.erase(ndx);
24✔
2626
            local_id.erase(ndx);
24✔
2627
            if (hi.size() == 0) {
24✔
2628
                free_collision_table();
12✔
2629
            }
12✔
2630
        }
24✔
2631
    }
30✔
2632
}
5,086,767✔
2633

2634
void Table::free_collision_table()
2635
{
4,209✔
2636
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
4,209✔
2637
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2638
        m_top.set(top_position_for_collision_map, 0);
12✔
2639
    }
12✔
2640
}
4,209✔
2641

2642
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2643
{
3,846✔
2644
    while (number--) {
6,321,300✔
2645
        keys.push_back(create_object().get_key());
6,317,454✔
2646
    }
6,317,454✔
2647
}
3,846✔
2648

2649
void Table::create_objects(const std::vector<ObjKey>& keys)
2650
{
630✔
2651
    for (auto k : keys) {
5,616✔
2652
        create_object(k);
5,616✔
2653
    }
5,616✔
2654
}
630✔
2655

2656
void Table::dump_objects()
2657
{
×
2658
    m_clusters.dump_objects();
×
2659
    if (nb_unresolved())
×
2660
        m_tombstones->dump_objects();
×
2661
}
×
2662

2663
void Table::remove_object(ObjKey key)
2664
{
2,549,013✔
2665
    Group* g = get_parent_group();
2,549,013✔
2666

1,274,583✔
2667
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,549,013✔
2668
        CascadeState state(CascadeState::Mode::Strong, g);
2,502✔
2669
        state.m_to_be_deleted.emplace_back(m_key, key);
2,502✔
2670
        m_clusters.nullify_incoming_links(key, state);
2,502✔
2671
        remove_recursive(state);
2,502✔
2672
    }
2,502✔
2673
    else {
2,546,511✔
2674
        CascadeState state(CascadeState::Mode::None, g);
2,546,511✔
2675
        if (g) {
2,546,511✔
2676
            m_clusters.nullify_incoming_links(key, state);
2,464,524✔
2677
        }
2,464,524✔
2678
        m_clusters.erase(key, state);
2,546,511✔
2679
    }
2,546,511✔
2680
}
2,549,013✔
2681

2682
ObjKey Table::invalidate_object(ObjKey key)
2683
{
63,540✔
2684
    if (is_embedded())
63,540✔
2685
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2686
    REALM_ASSERT(!key.is_unresolved());
63,540✔
2687

31,821✔
2688
    Obj tombstone;
63,540✔
2689
    auto obj = get_object(key);
63,540✔
2690
    if (obj.has_backlinks(false)) {
63,540✔
2691
        // If the object has backlinks, we should make a tombstone
273✔
2692
        // and make inward links point to it,
273✔
2693
        if (auto primary_key_col = get_primary_key_column()) {
546✔
2694
            auto pk = obj.get_any(primary_key_col);
390✔
2695
            GlobalKey object_id{pk};
390✔
2696
            auto unres_key = global_to_local_object_id_hashed(object_id);
390✔
2697
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
390✔
2698
        }
390✔
2699
        else {
156✔
2700
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
2701
        }
156✔
2702
        tombstone.assign_pk_and_backlinks(obj);
546✔
2703
    }
546✔
2704

31,821✔
2705
    remove_object(key);
63,540✔
2706

31,821✔
2707
    return tombstone.get_key();
63,540✔
2708
}
63,540✔
2709

2710
void Table::remove_object_recursive(ObjKey key)
2711
{
39✔
2712
    size_t table_ndx = get_index_in_group();
39✔
2713
    if (table_ndx != realm::npos) {
39✔
2714
        CascadeState state(CascadeState::Mode::All, get_parent_group());
39✔
2715
        state.m_to_be_deleted.emplace_back(m_key, key);
39✔
2716
        nullify_links(state);
39✔
2717
        remove_recursive(state);
39✔
2718
    }
39✔
2719
    else {
×
2720
        // No links in freestanding table
2721
        CascadeState state(CascadeState::Mode::None);
×
2722
        m_clusters.erase(key, state);
×
2723
    }
×
2724
}
39✔
2725

2726
Table::Iterator Table::begin() const
2727
{
576,672✔
2728
    return Iterator(m_clusters, 0);
576,672✔
2729
}
576,672✔
2730

2731
Table::Iterator Table::end() const
2732
{
9,442,131✔
2733
    return Iterator(m_clusters, size());
9,442,131✔
2734
}
9,442,131✔
2735

2736
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2737
{
7,074,117✔
2738
    TableRef ret;
7,074,117✔
2739
    if (col_key) {
7,074,438✔
2740
        return table.get_opposite_table(col_key);
7,074,438✔
2741
    }
7,074,438✔
2742
    return ret;
4,294,967,294✔
2743
}
4,294,967,294✔
2744

2745
const uint64_t Table::max_num_columns;
2746

2747
void Table::build_column_mapping()
2748
{
6,767,505✔
2749
    // build column mapping from spec
3,830,454✔
2750
    // TODO: Optimization - Don't rebuild this for every change
3,830,454✔
2751
    m_spec_ndx2leaf_ndx.clear();
6,767,505✔
2752
    m_leaf_ndx2spec_ndx.clear();
6,767,505✔
2753
    m_leaf_ndx2colkey.clear();
6,767,505✔
2754
    size_t num_spec_cols = m_spec.get_column_count();
6,767,505✔
2755
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
6,767,505✔
2756
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
29,033,220✔
2757
        ColKey col_key = m_spec.get_key(spec_ndx);
22,265,715✔
2758
        unsigned leaf_ndx = col_key.get_index().val;
22,265,715✔
2759
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
22,265,715✔
2760
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
21,795,117✔
2761
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
21,795,117✔
2762
        }
21,795,117✔
2763
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
22,265,715✔
2764
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
22,265,715✔
2765
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
22,265,715✔
2766
    }
22,265,715✔
2767
}
6,767,505✔
2768

2769
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2770
{
980,430✔
2771
    REALM_ASSERT(!attr.test(col_attr_Indexed));
980,430✔
2772
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
980,430✔
2773

483,234✔
2774
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
980,430✔
2775
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
980,430✔
2776

483,234✔
2777
    // reuse lowest available leaf ndx:
483,234✔
2778
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
980,430✔
2779
    // look for an unused entry:
483,234✔
2780
    for (unsigned idx = 0; idx < lower; ++idx) {
6,252,876✔
2781
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
5,272,530✔
2782
            lower = idx;
84✔
2783
            break;
84✔
2784
        }
84✔
2785
    }
5,272,530✔
2786
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
980,430✔
2787
}
980,430✔
2788

2789
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2790
                                                  StringData origin_col_name) const noexcept
2791
{
×
2792
    BacklinkOrigin ret;
×
2793
    auto f = [&](ColKey backlink_col_key) {
×
2794
        auto origin_table = get_opposite_table(backlink_col_key);
×
2795
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2796
        if (origin_table->get_name() == origin_table_name &&
×
2797
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
2798
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
2799
            return IteratorControl::Stop;
×
2800
        }
×
2801
        return IteratorControl::AdvanceToNext;
×
2802
    };
×
2803
    this->for_each_backlink_column(f);
×
2804
    return ret;
×
2805
}
×
2806

2807
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2808
{
354✔
2809
    try {
354✔
2810
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
354✔
2811
        ColKey linked_column_key = get_opposite_column(backlink_col);
354✔
2812
        if (linked_table_key == m_key) {
354✔
2813
            return {{m_own_ref, linked_column_key}};
18✔
2814
        }
18✔
2815
        else {
336✔
2816
            Group* current_group = get_parent_group();
336✔
2817
            if (current_group) {
336✔
2818
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2819
                return {{linked_table_ref, linked_column_key}};
336✔
2820
            }
336✔
2821
        }
×
2822
    }
354✔
2823
    catch (...) {
×
2824
        // backlink column not found, returning empty optional
2825
    }
×
2826
    return {};
177✔
2827
}
354✔
2828

2829
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
2830
{
×
2831
    std::vector<std::pair<TableKey, ColKey>> origins;
×
2832
    auto f = [&](ColKey backlink_col_key) {
×
2833
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
2834
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2835
        origins.emplace_back(origin_table_key, origin_link_col);
×
2836
        return IteratorControl::AdvanceToNext;
×
2837
    };
×
2838
    this->for_each_backlink_column(f);
×
2839
    return origins;
×
2840
}
×
2841

2842
ColKey Table::get_primary_key_column() const
2843
{
22,378,545✔
2844
    return m_primary_key_col;
22,378,545✔
2845
}
22,378,545✔
2846

2847
void Table::set_primary_key_column(ColKey col_key)
2848
{
720✔
2849
    if (col_key == m_primary_key_col) {
720✔
2850
        return;
378✔
2851
    }
378✔
2852

171✔
2853
    if (Replication* repl = get_repl()) {
342✔
2854
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
2855
            throw RuntimeError(
×
2856
                ErrorCodes::BrokenInvariant,
×
2857
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
2858
        }
×
2859
    }
342✔
2860

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

171✔
2863
    if (col_key) {
342✔
2864
        check_column(col_key);
222✔
2865
        validate_column_is_unique(col_key);
222✔
2866
        do_set_primary_key_column(col_key);
222✔
2867
    }
222✔
2868
    else {
120✔
2869
        do_set_primary_key_column(col_key);
120✔
2870
    }
120✔
2871
}
342✔
2872

2873

2874
void Table::do_set_primary_key_column(ColKey col_key)
2875
{
137,112✔
2876
    if (col_key) {
137,112✔
2877
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
129,249✔
2878
        auto attr = m_spec.get_column_attr(spec_ndx);
129,249✔
2879
        if (attr.test(col_attr_FullText_Indexed)) {
129,249✔
2880
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
2881
        }
6✔
2882
    }
137,106✔
2883

67,653✔
2884
    if (m_primary_key_col) {
137,106✔
2885
        // If the search index has not been set explicitly on current pk col, we remove it again
3,741✔
2886
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
7,887✔
2887
        auto attr = m_spec.get_column_attr(spec_ndx);
7,887✔
2888
        if (!attr.test(col_attr_Indexed)) {
7,887✔
2889
            remove_search_index(m_primary_key_col);
7,875✔
2890
        }
7,875✔
2891
    }
7,887✔
2892

67,653✔
2893
    if (col_key) {
137,106✔
2894
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
129,243✔
2895
        do_add_search_index(col_key, IndexType::General);
129,243✔
2896
    }
129,243✔
2897
    else {
7,863✔
2898
        m_top.set(top_position_for_pk_col, 0);
7,863✔
2899
    }
7,863✔
2900

67,653✔
2901
    m_primary_key_col = col_key;
137,106✔
2902
}
137,106✔
2903

2904
bool Table::contains_unique_values(ColKey col) const
2905
{
834✔
2906
    if (search_index_type(col) == IndexType::General) {
834✔
2907
        auto search_index = get_search_index(col);
744✔
2908
        return !search_index->has_duplicate_values();
744✔
2909
    }
744✔
2910
    else {
90✔
2911
        TableView tv = where().find_all();
90✔
2912
        tv.distinct(col);
90✔
2913
        return tv.size() == size();
90✔
2914
    }
90✔
2915
}
834✔
2916

2917
void Table::validate_column_is_unique(ColKey col) const
2918
{
834✔
2919
    if (!contains_unique_values(col)) {
834✔
2920
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
2921
                                           get_class_name(), get_column_name(col)));
30✔
2922
    }
30✔
2923
}
834✔
2924

2925
void Table::validate_primary_column()
2926
{
1,812✔
2927
    if (ColKey col = get_primary_key_column()) {
1,812✔
2928
        validate_column_is_unique(col);
612✔
2929
    }
612✔
2930
}
1,812✔
2931

2932
ObjKey Table::get_next_valid_key()
2933
{
504,693✔
2934
    ObjKey key;
504,693✔
2935
    do {
504,699✔
2936
        key = ObjKey(allocate_sequence_number());
504,699✔
2937
    } while (m_clusters.is_valid(key));
504,699✔
2938

241,173✔
2939
    return key;
504,693✔
2940
}
504,693✔
2941

2942
namespace {
2943
template <class T>
2944
typename util::RemoveOptional<T>::type remove_optional(T val)
2945
{
88,128✔
2946
    return val;
88,128✔
2947
}
88,128✔
2948
template <>
2949
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
2950
{
5,451✔
2951
    return *val;
5,451✔
2952
}
5,451✔
2953
template <>
2954
bool remove_optional<Optional<bool>>(Optional<bool> val)
2955
{
11,514✔
2956
    return *val;
11,514✔
2957
}
11,514✔
2958
template <>
2959
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
2960
{
5,475✔
2961
    return *val;
5,475✔
2962
}
5,475✔
2963
template <>
2964
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
2965
{
6,060✔
2966
    return *val;
6,060✔
2967
}
6,060✔
2968
} // namespace
2969

2970
template <class F, class T>
2971
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
2972
{
162✔
2973
    Allocator& allocator = this->get_alloc();
162✔
2974
    bool from_nullability = is_nullable(key_from);
162✔
2975
    auto func = [&](Cluster* cluster) {
162✔
2976
        size_t sz = cluster->node_size();
162✔
2977

81✔
2978
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
2979
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
2980
        cluster->init_leaf(key_from, &from_arr);
162✔
2981
        cluster->init_leaf(key_to, &to_arr);
162✔
2982

81✔
2983
        for (size_t i = 0; i < sz; i++) {
1,512✔
2984
            if (from_nullability && from_arr.is_null(i)) {
1,356!
2985
                if (throw_on_null) {
54!
2986
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
2987
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
2988
                                                    get_column_name(key_from)));
6✔
2989
                }
6✔
2990
                else {
48✔
2991
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
48✔
2992
                }
48✔
2993
            }
54✔
2994
            else {
1,302✔
2995
                auto v = remove_optional(from_arr.get(i));
1,302✔
2996
                to_arr.set(i, v);
1,302✔
2997
            }
1,302✔
2998
        }
1,356✔
2999
    };
162✔
3000

81✔
3001
    m_clusters.update(func);
162✔
3002
}
162✔
3003

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

60✔
3012
        ArrayInteger from_arr(allocator);
120✔
3013
        ArrayInteger to_arr(allocator);
120✔
3014
        cluster->init_leaf(key_from, &from_arr);
120✔
3015
        cluster->init_leaf(key_to, &to_arr);
120✔
3016

60✔
3017
        for (size_t i = 0; i < sz; i++) {
360✔
3018
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3019
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3020
            REALM_ASSERT(!ref_to);
240✔
3021

120✔
3022
            if (ref_from) {
240✔
3023
                BPlusTree<F> from_list(allocator);
120✔
3024
                BPlusTree<T> to_list(allocator);
120✔
3025
                from_list.init_from_ref(ref_from);
120✔
3026
                to_list.create();
120✔
3027
                size_t n = from_list.size();
120✔
3028
                for (size_t j = 0; j < n; j++) {
120,120✔
3029
                    auto v = from_list.get(j);
120,000✔
3030
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
3031
                        to_list.add(remove_optional(v));
115,326✔
3032
                    }
115,326✔
3033
                    else {
4,674✔
3034
                        if (throw_on_null) {
4,674!
3035
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3036
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3037
                                                            get_name(), get_column_name(key_from)));
×
3038
                        }
×
3039
                        else {
4,674✔
3040
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,674✔
3041
                        }
4,674✔
3042
                    }
4,674✔
3043
                }
120,000✔
3044
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3045
            }
120✔
3046
        }
240✔
3047
    };
120✔
3048

60✔
3049
    m_clusters.update(func);
120✔
3050
}
120✔
3051

3052
void Table::convert_column(ColKey from, ColKey to, bool throw_on_null)
3053
{
282✔
3054
    realm::DataType type_id = get_column_type(from);
282✔
3055
    bool _is_list = is_list(from);
282✔
3056
    if (_is_list) {
282✔
3057
        switch (type_id) {
120✔
3058
            case type_Int:
12✔
3059
                if (is_nullable(from)) {
12✔
3060
                    change_nullability_list<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3061
                }
6✔
3062
                else {
6✔
3063
                    change_nullability_list<int64_t, Optional<int64_t>>(from, to, throw_on_null);
6✔
3064
                }
6✔
3065
                break;
12✔
3066
            case type_Float:
12✔
3067
                change_nullability_list<float, float>(from, to, throw_on_null);
12✔
3068
                break;
12✔
3069
            case type_Double:
12✔
3070
                change_nullability_list<double, double>(from, to, throw_on_null);
12✔
3071
                break;
12✔
3072
            case type_Bool:
12✔
3073
                change_nullability_list<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3074
                break;
12✔
3075
            case type_String:
12✔
3076
                change_nullability_list<StringData, StringData>(from, to, throw_on_null);
12✔
3077
                break;
12✔
3078
            case type_Binary:
12✔
3079
                change_nullability_list<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3080
                break;
12✔
3081
            case type_Timestamp:
12✔
3082
                change_nullability_list<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3083
                break;
12✔
3084
            case type_ObjectId:
12✔
3085
                if (is_nullable(from)) {
12✔
3086
                    change_nullability_list<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3087
                }
6✔
3088
                else {
6✔
3089
                    change_nullability_list<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3090
                }
6✔
3091
                break;
12✔
3092
            case type_Decimal:
12✔
3093
                change_nullability_list<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3094
                break;
12✔
3095
            case type_UUID:
12✔
3096
                if (is_nullable(from)) {
12✔
3097
                    change_nullability_list<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3098
                }
6✔
3099
                else {
6✔
3100
                    change_nullability_list<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3101
                }
6✔
3102
                break;
12✔
3103
            case type_Link:
✔
3104
            case type_TypedLink:
✔
3105
            case type_LinkList:
✔
3106
                // Can't have lists of these types
3107
            case type_Mixed:
✔
3108
                // These types are no longer supported at all
3109
                REALM_UNREACHABLE();
×
3110
                break;
×
3111
        }
162✔
3112
    }
162✔
3113
    else {
162✔
3114
        switch (type_id) {
162✔
3115
            case type_Int:
36✔
3116
                if (is_nullable(from)) {
36✔
3117
                    change_nullability<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3118
                }
6✔
3119
                else {
30✔
3120
                    change_nullability<int64_t, Optional<int64_t>>(from, to, throw_on_null);
30✔
3121
                }
30✔
3122
                break;
36✔
3123
            case type_Float:
12✔
3124
                change_nullability<float, float>(from, to, throw_on_null);
12✔
3125
                break;
12✔
3126
            case type_Double:
12✔
3127
                change_nullability<double, double>(from, to, throw_on_null);
12✔
3128
                break;
12✔
3129
            case type_Bool:
12✔
3130
                change_nullability<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3131
                break;
12✔
3132
            case type_String:
30✔
3133
                change_nullability<StringData, StringData>(from, to, throw_on_null);
30✔
3134
                break;
30✔
3135
            case type_Binary:
12✔
3136
                change_nullability<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3137
                break;
12✔
3138
            case type_Timestamp:
12✔
3139
                change_nullability<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3140
                break;
12✔
3141
            case type_ObjectId:
12✔
3142
                if (is_nullable(from)) {
12✔
3143
                    change_nullability<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3144
                }
6✔
3145
                else {
6✔
3146
                    change_nullability<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3147
                }
6✔
3148
                break;
12✔
3149
            case type_Decimal:
12✔
3150
                change_nullability<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3151
                break;
12✔
3152
            case type_UUID:
12✔
3153
                if (is_nullable(from)) {
12✔
3154
                    change_nullability<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3155
                }
6✔
3156
                else {
6✔
3157
                    change_nullability<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3158
                }
6✔
3159
                break;
12✔
3160
            case type_TypedLink:
✔
3161
            case type_Link:
✔
3162
                // Always nullable, so can't convert
3163
            case type_LinkList:
✔
3164
                // Never nullable, so can't convert
3165
            case type_Mixed:
✔
3166
                // These types are no longer supported at all
3167
                REALM_UNREACHABLE();
×
3168
                break;
×
3169
        }
162✔
3170
    }
162✔
3171
}
282✔
3172

3173

3174
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3175
{
522✔
3176
    if (col_key.is_nullable() == nullable)
522✔
3177
        return col_key;
240✔
3178

141✔
3179
    check_column(col_key);
282✔
3180

141✔
3181
    auto index_type = search_index_type(col_key);
282✔
3182
    std::string column_name(get_column_name(col_key));
282✔
3183
    auto type = col_key.get_type();
282✔
3184
    auto attr = col_key.get_attrs();
282✔
3185
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3186
    if (nullable) {
282✔
3187
        attr.set(col_attr_Nullable);
150✔
3188
    }
150✔
3189
    else {
132✔
3190
        attr.reset(col_attr_Nullable);
132✔
3191
    }
132✔
3192

141✔
3193
    ColKey new_col = generate_col_key(type, attr);
282✔
3194
    do_insert_root_column(new_col, type, "__temporary");
282✔
3195

141✔
3196
    try {
282✔
3197
        convert_column(col_key, new_col, throw_on_null);
282✔
3198
    }
282✔
3199
    catch (...) {
144✔
3200
        // remove any partially filled column
3✔
3201
        remove_column(new_col);
6✔
3202
        throw;
6✔
3203
    }
6✔
3204

138✔
3205
    if (is_pk_col) {
276✔
3206
        // If we go from non nullable to nullable, no values change,
6✔
3207
        // so it is safe to preserve the pk column. Otherwise it is not
6✔
3208
        // safe as a null entry might have been converted to default value.
6✔
3209
        do_set_primary_key_column(nullable ? new_col : ColKey{});
9✔
3210
    }
12✔
3211

138✔
3212
    erase_root_column(col_key);
276✔
3213
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3214

138✔
3215
    if (index_type != IndexType::None)
276✔
3216
        do_add_search_index(new_col, index_type);
30✔
3217

138✔
3218
    return new_col;
276✔
3219
}
276✔
3220

3221
bool Table::has_any_embedded_objects()
3222
{
2,554,338✔
3223
    if (!m_has_any_embedded_objects) {
2,554,338✔
3224
        m_has_any_embedded_objects = false;
109,329✔
3225
        for_each_public_column([&](ColKey col_key) {
223,926✔
3226
            auto target_table_key = get_opposite_table_key(col_key);
223,926✔
3227
            if (target_table_key && is_link_type(col_key.get_type())) {
223,926✔
3228
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
9,147✔
3229
                if (target_table->is_embedded()) {
9,147✔
3230
                    m_has_any_embedded_objects = true;
7,017✔
3231
                    return IteratorControl::Stop; // early out
7,017✔
3232
                }
7,017✔
3233
            }
216,909✔
3234
            return IteratorControl::AdvanceToNext;
216,909✔
3235
        });
216,909✔
3236
    }
109,329✔
3237
    return *m_has_any_embedded_objects;
2,554,338✔
3238
}
2,554,338✔
3239

3240
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3241
{
169,650✔
3242
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
169,650✔
3243
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
169,650✔
3244
}
169,650✔
3245

3246
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3247
{
41,766✔
3248
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
145,797✔
3249
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
138,915✔
3250
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
34,884✔
3251
        }
34,884✔
3252
    }
138,915✔
3253

20,766✔
3254
    return {};
24,207✔
3255
}
41,766✔
3256

3257
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3258
{
41,694✔
3259
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
41,694✔
3260

20,730✔
3261
    if (!backlink_col_key) {
41,694✔
3262
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
6,882✔
3263
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
6,882✔
3264

3,441✔
3265
        if (Replication* repl = get_repl())
6,882✔
3266
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
6,594✔
3267
                                    m_key); // Throws
6,594✔
3268
    }
6,882✔
3269

20,730✔
3270
    return backlink_col_key;
41,694✔
3271
}
41,694✔
3272

3273
TableKey Table::get_opposite_table_key(ColKey col_key) const
3274
{
14,560,869✔
3275
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,560,869✔
3276
}
14,560,869✔
3277

3278
bool Table::links_to_self(ColKey col_key) const
3279
{
58,194✔
3280
    return get_opposite_table_key(col_key) == m_key;
58,194✔
3281
}
58,194✔
3282

3283
TableRef Table::get_opposite_table(ColKey col_key) const
3284
{
7,772,295✔
3285
    if (auto k = get_opposite_table_key(col_key)) {
7,772,295✔
3286
        return get_parent_group()->get_table(k);
7,716,345✔
3287
    }
7,716,345✔
3288
    return {};
55,950✔
3289
}
55,950✔
3290

3291
ColKey Table::get_opposite_column(ColKey col_key) const
3292
{
7,230,372✔
3293
    return ColKey(m_opposite_column.get(col_key.get_index().val));
7,230,372✔
3294
}
7,230,372✔
3295

3296
ColKey Table::find_opposite_column(ColKey col_key) const
3297
{
×
3298
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3299
        if (m_opposite_column.get(i) == col_key.value) {
×
3300
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3301
        }
×
3302
    }
×
3303
    return ColKey();
×
3304
}
×
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