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

realm / realm-core / github_pull_request_281922

31 Oct 2023 09:13AM UTC coverage: 90.445% (-0.08%) from 90.528%
github_pull_request_281922

Pull #7039

Evergreen

jedelbo
Merge branch 'next-major' into je/global-key
Pull Request #7039: Remove ability to synchronize objects without primary key

95324 of 175822 branches covered (0.0%)

101 of 105 new or added lines in 13 files covered. (96.19%)

238 existing lines in 19 files now uncovered.

232657 of 257235 relevant lines covered (90.45%)

6351359.67 hits per line

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

90.12
/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,734✔
327
    switch (table_type) {
118,734✔
328
        case Table::Type::TopLevel:
99,627✔
329
            return o << "TopLevel";
99,627✔
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,074✔
341
    // Link column can be a single Link, LinkList, or BackLink.
399,534✔
342
    REALM_ASSERT(m_current_table->valid_column(ck));
799,074✔
343
    ColumnType type = ck.get_type();
799,074✔
344
    if (type == col_type_LinkList || type == col_type_Link || type == col_type_BackLink) {
799,074✔
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,063✔
350
}
711,063✔
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,309✔
387
    m_spec.set_parent(&m_top, top_position_for_spec);
30,309✔
388
    m_index_refs.set_parent(&m_top, top_position_for_search_indexes);
30,309✔
389
    m_opposite_table.set_parent(&m_top, top_position_for_opposite_table);
30,309✔
390
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
30,309✔
391
    m_cookie = cookie_created;
30,309✔
392
}
30,309✔
393

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

335,583✔
402
    ColumnAttrMask attr;
681,087✔
403
    if (collection_type) {
681,087✔
404
        switch (*collection_type) {
175,395✔
405
            case CollectionType::List:
80,370✔
406
                attr.set(col_attr_List);
80,370✔
407
                break;
80,370✔
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,087✔
415
    }
681,087✔
416
    if (nullable || type == type_Mixed)
681,087✔
417
        attr.set(col_attr_Nullable);
185,484✔
418
    ColKey col_key = generate_col_key(ColumnType(type), attr);
681,087✔
419

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

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

40,203✔
437
    m_has_any_embedded_objects.reset();
81,372✔
438

40,203✔
439
    DataType data_type = type_Link;
81,372✔
440
    ColumnAttrMask attr;
81,372✔
441
    if (collection_type) {
81,372✔
442
        switch (*collection_type) {
51,843✔
443
            case CollectionType::List:
30,309✔
444
                attr.set(col_attr_List);
30,309✔
445
                data_type = type_LinkList;
30,309✔
446
                break;
30,309✔
447
            case CollectionType::Set:
10,452✔
448
                if (target.is_embedded())
10,452✔
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,529✔
457
    }
29,529✔
458
    else {
29,529✔
459
        attr.set(col_attr_Nullable);
29,529✔
460
    }
29,529✔
461
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
81,372✔
462

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

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

6,405✔
472
    do {
18,105✔
473
        cascade_state.send_notifications();
18,105✔
474

8,418✔
475
        for (auto& l : cascade_state.m_to_be_nullified) {
8,442✔
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,105✔
483

8,418✔
484
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
18,105✔
485
        for (auto obj : to_delete) {
15,780✔
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,712✔
489
            table->m_clusters.erase(obj.second, cascade_state);
14,712✔
490
        }
14,712✔
491
        nullify_links(cascade_state);
18,105✔
492
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
18,105✔
493
}
14,079✔
494

495
void Table::nullify_links(CascadeState& cascade_state)
496
{
22,704✔
497
    Group* group = get_parent_group();
22,704✔
498
    REALM_ASSERT(group);
22,704✔
499
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
14,673✔
500
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
6,963✔
501
        if (!table->is_asymmetric())
7,893✔
502
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
7,893✔
503
    }
7,893✔
504
}
22,704✔
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
{
17,424✔
520
    check_column(col_key);
17,424✔
521

9,015✔
522
    if (Replication* repl = get_repl())
17,424✔
523
        repl->erase_column(this, col_key); // Throws
528✔
524

9,015✔
525
    if (col_key == m_primary_key_col) {
17,424✔
526
        do_set_primary_key_column(ColKey());
7,512✔
527
    }
7,512✔
528
    else {
9,912✔
529
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
9,912✔
530
    }
9,912✔
531

9,015✔
532
    erase_root_column(col_key); // Throws
17,424✔
533
    m_has_any_embedded_objects.reset();
17,424✔
534
}
17,424✔
535

536

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

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

48✔
544
    bump_content_version();
87✔
545
    bump_storage_version();
87✔
546

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

551

552
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
553
{
9,910,332✔
554
    // well, not quite "direct", more like "almost direct":
5,175,168✔
555
    Array table_top(alloc);
9,910,332✔
556
    table_top.init_from_ref(top_ref);
9,910,332✔
557
    if (table_top.size() > 3) {
9,910,332✔
558
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
9,910,152✔
559
        return TableKey(int32_t(rot.get_as_int()));
9,910,152✔
560
    }
9,910,152✔
561
    else {
180✔
562
        return TableKey();
180✔
563
    }
180✔
564
}
9,910,332✔
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,102,934✔
569
    REALM_ASSERT(!(is_writable && is_frzn));
5,102,934✔
570
    m_is_frozen = is_frzn;
5,102,934✔
571
    m_alloc.set_read_only(!is_writable);
5,102,934✔
572
    // Load from allocated memory
2,796,849✔
573
    m_top.set_parent(parent, ndx_in_parent);
5,102,934✔
574
    m_top.init_from_ref(top_ref);
5,102,934✔
575

2,796,849✔
576
    m_spec.init_from_parent();
5,102,934✔
577

2,796,849✔
578
    while (m_top.size() <= top_position_for_pk_col) {
5,102,934✔
579
        m_top.add(0);
×
580
    }
×
581

2,796,849✔
582
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
5,102,934✔
583
        // This is an upgrade - create cluster
584
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
×
585
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
×
586
    }
×
587
    m_clusters.init_from_parent();
5,102,934✔
588

2,796,849✔
589
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
5,102,934✔
590
    if (!rot.is_tagged()) {
5,102,934✔
591
        // Create table key
592
        rot = RefOrTagged::make_tagged(ndx_in_parent);
×
593
        m_top.set(top_position_for_key, rot);
×
594
    }
×
595
    m_key = TableKey(int32_t(rot.get_as_int()));
5,102,934✔
596

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

2,796,849✔
630
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
5,102,934✔
631
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
4,571,175✔
632

2,796,849✔
633
    if (m_top.size() <= top_position_for_flags) {
5,102,934✔
634
        m_table_type = Type::TopLevel;
60✔
635
    }
60✔
636
    else {
5,102,874✔
637
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
5,102,874✔
638
        m_table_type = Type(flags & table_type_mask);
5,102,874✔
639
    }
5,102,874✔
640
    m_has_any_embedded_objects.reset();
5,102,934✔
641

2,796,849✔
642
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
5,102,934✔
643
        // Tombstones exists
438,552✔
644
        if (!m_tombstones) {
888,885✔
645
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
537,492✔
646
        }
537,492✔
647
        m_tombstones->init_from_parent();
888,885✔
648
    }
888,885✔
649
    else {
4,214,049✔
650
        m_tombstones = nullptr;
4,214,049✔
651
    }
4,214,049✔
652
    m_cookie = cookie_initialized;
5,102,934✔
653
}
5,102,934✔
654

655

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

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

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

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

375,786✔
671
    if (Replication* repl = get_repl())
762,459✔
672
        repl->insert_column(this, col_key, type, name, target_table); // Throws
744,936✔
673

375,786✔
674
    return col_key;
762,459✔
675
}
762,459✔
676

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

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

66,171✔
689
    table->traverse_clusters(f);
133,308✔
690
}
133,308✔
691

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

66,168✔
698
    if (type == type_Int) {
133,305✔
699
        if (is_nullable(col_key)) {
74,046✔
700
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
10,785✔
701
        }
10,785✔
702
        else {
63,261✔
703
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
63,261✔
704
        }
63,261✔
705
    }
74,046✔
706
    else if (type == type_Bool) {
59,259✔
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,211✔
715
        do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
20,901✔
716
    }
20,901✔
717
    else if (type == type_Timestamp) {
38,310✔
718
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
90✔
719
    }
90✔
720
    else if (type == type_ObjectId) {
38,220✔
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,569✔
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✔
739
    else {
2,147,483,647✔
740
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
2,147,483,647✔
741
    }
2,147,483,647✔
742
}
133,305✔
743

744
void Table::erase_from_search_indexes(ObjKey key)
745
{
5,087,793✔
746
    // Tombstones do not use index - will crash if we try to erase values
2,543,238✔
747
    if (!key.is_unresolved()) {
5,087,793✔
748
        for (auto&& index : m_index_accessors) {
6,821,913✔
749
            if (index) {
6,821,913✔
750
                index->erase(key);
341,850✔
751
            }
341,850✔
752
        }
6,821,913✔
753
    }
5,074,233✔
754
}
5,087,793✔
755

756
void Table::update_indexes(ObjKey key, const FieldValues& values)
757
{
23,187,207✔
758
    // Tombstones do not use index - will crash if we try to insert values
11,523,867✔
759
    if (key.is_unresolved()) {
23,187,207✔
760
        return;
29,406✔
761
    }
29,406✔
762

11,509,170✔
763
    auto sz = m_index_accessors.size();
23,157,801✔
764
    // values are sorted by column index - there may be values missing
11,509,170✔
765
    auto value = values.begin();
23,157,801✔
766
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
57,184,401✔
767
        // Check if initial value is provided
16,889,583✔
768
        Mixed init_value;
34,026,609✔
769
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
34,026,609✔
770
            // Value for this column is provided
268,443✔
771
            init_value = value->value;
560,028✔
772
            ++value;
560,028✔
773
        }
560,028✔
774

16,889,583✔
775
        if (auto&& index = m_index_accessors[column_ndx]) {
34,026,609✔
776
            // There is an index for this column
554,382✔
777
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,132,092✔
778
            auto type = col_key.get_type();
1,132,092✔
779
            auto attr = col_key.get_attrs();
1,132,092✔
780
            bool nullable = attr.test(col_attr_Nullable);
1,132,092✔
781
            switch (type) {
1,132,092✔
782
                case col_type_Int:
537,822✔
783
                    if (init_value.is_null()) {
537,822✔
784
                        index->insert(key, ArrayIntNull::default_value(nullable));
165,519✔
785
                    }
165,519✔
786
                    else {
372,303✔
787
                        index->insert(key, init_value.get<int64_t>());
372,303✔
788
                    }
372,303✔
789
                    break;
537,822✔
790
                case col_type_Bool:
6,045✔
791
                    if (init_value.is_null()) {
6,045✔
792
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,045✔
793
                    }
6,045✔
794
                    else {
×
795
                        index->insert(key, init_value.get<bool>());
×
796
                    }
×
797
                    break;
6,045✔
798
                case col_type_String:
477,180✔
799
                    if (init_value.is_null()) {
477,180✔
800
                        index->insert(key, ArrayString::default_value(nullable));
431,100✔
801
                    }
431,100✔
802
                    else {
46,080✔
803
                        index->insert(key, init_value.get<String>());
46,080✔
804
                    }
46,080✔
805
                    break;
477,180✔
806
                case col_type_Timestamp:
5,973✔
807
                    if (init_value.is_null()) {
5,973✔
808
                        index->insert(key, ArrayTimestamp::default_value(nullable));
5,973✔
809
                    }
5,973✔
810
                    else {
×
811
                        index->insert(key, init_value.get<Timestamp>());
×
812
                    }
×
813
                    break;
5,973✔
814
                case col_type_ObjectId:
85,707✔
815
                    if (init_value.is_null()) {
85,707✔
816
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
6,120✔
817
                    }
6,120✔
818
                    else {
79,587✔
819
                        index->insert(key, init_value.get<ObjectId>());
79,587✔
820
                    }
79,587✔
821
                    break;
85,707✔
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,132,092✔
836
        }
1,132,092✔
837
    }
34,026,609✔
838
}
23,157,801✔
839

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

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

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

66,195✔
857
    if (!StringIndex::type_supported(DataType(col_key.get_type())) || col_key.is_collection() ||
133,356✔
858
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
133,332✔
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

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

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

66,171✔
876
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
133,308✔
877

66,171✔
878
    populate_search_index(col_key);
133,308✔
879
}
133,308✔
880

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

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

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

1,935✔
892
    switch (type) {
3,873✔
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,819✔
907
            if (attr.test(col_attr_Indexed)) {
3,819✔
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,795✔
912
                this->remove_search_index(col_key);
×
913
            }
×
914
            break;
3,795✔
915
    }
3,849✔
916

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

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

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

4,146✔
929
    // Early-out if non-indexed
4,146✔
930
    if (m_index_accessors[column_ndx.val] == nullptr)
7,965✔
931
        return;
72✔
932

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

4,110✔
939
    m_index_refs.set(column_ndx.val, 0);
7,893✔
940

4,110✔
941
    // update spec
4,110✔
942
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
7,893✔
943
    auto attr = m_spec.get_column_attr(spec_ndx);
7,893✔
944
    attr.reset(col_attr_Indexed);
7,893✔
945
    attr.reset(col_attr_FullText_Indexed);
7,893✔
946
    m_spec.set_column_attr(spec_ndx, attr); // Throws
7,893✔
947
}
7,893✔
948

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

959
bool Table::is_enumerated(ColKey col_key) const noexcept
960
{
58,602✔
961
    size_t col_ndx = colkey2spec_ndx(col_key);
58,602✔
962
    return m_spec.is_string_enum_type(col_ndx);
58,602✔
963
}
58,602✔
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
{
17,700✔
981
    ColumnType col_type = col_key.get_type();
17,700✔
982
    if (is_link_type(col_type)) {
17,700✔
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
17,700✔
988
}
17,700✔
989

990

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

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

483,765✔
999
    if (!col_key) {
980,343✔
1000
        col_key = generate_col_key(type, {});
88,248✔
1001
    }
88,248✔
1002

483,765✔
1003
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
980,343✔
1004
    if (col_key.is_dictionary()) {
980,343✔
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,343✔
1008
    build_column_mapping();
980,343✔
1009
    REALM_ASSERT(col_ndx <= m_index_refs.size());
980,343✔
1010
    if (col_ndx == m_index_refs.size()) {
980,343✔
1011
        m_index_refs.insert(col_ndx, 0);
980,061✔
1012
    }
980,061✔
1013
    else {
282✔
1014
        m_index_refs.set(col_ndx, 0);
282✔
1015
    }
282✔
1016
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
980,343✔
1017
    if (col_ndx == m_opposite_table.size()) {
980,343✔
1018
        // m_opposite_table and m_opposite_column are always resized together!
483,612✔
1019
        m_opposite_table.insert(col_ndx, TableKey().value);
980,055✔
1020
        m_opposite_column.insert(col_ndx, ColKey().value);
980,055✔
1021
    }
980,055✔
1022
    else {
288✔
1023
        m_opposite_table.set(col_ndx, TableKey().value);
288✔
1024
        m_opposite_column.set(col_ndx, ColKey().value);
288✔
1025
    }
288✔
1026
    refresh_index_accessors();
980,343✔
1027
    m_clusters.insert_column(col_key);
980,343✔
1028
    if (m_tombstones) {
980,343✔
1029
        m_tombstones->insert_column(col_key);
7,515✔
1030
    }
7,515✔
1031

483,765✔
1032
    bump_storage_version();
980,343✔
1033

483,765✔
1034
    return col_key;
980,343✔
1035
}
980,343✔
1036

1037

1038
void Table::do_erase_root_column(ColKey col_key)
1039
{
17,928✔
1040
    size_t col_ndx = col_key.get_index().val;
17,928✔
1041
    // If the column had a source index we have to remove and destroy that as well
9,276✔
1042
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
17,928✔
1043
    if (index_ref) {
17,928✔
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);
17,928✔
1049
    m_opposite_column.set(col_ndx, ColKey().value);
17,928✔
1050
    m_index_accessors[col_ndx] = nullptr;
17,928✔
1051
    m_clusters.remove_column(col_key);
17,928✔
1052
    if (m_tombstones)
17,928✔
1053
        m_tombstones->remove_column(col_key);
6,066✔
1054
    size_t spec_ndx = colkey2spec_ndx(col_key);
17,928✔
1055
    m_spec.erase_column(spec_ndx);
17,928✔
1056
    m_top.adjust(top_position_for_column_key, 2);
17,928✔
1057

9,276✔
1058
    build_column_mapping();
17,928✔
1059
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
35,253✔
1060
        REALM_ASSERT(m_index_accessors.back() == nullptr);
17,325✔
1061
        m_index_accessors.pop_back();
17,325✔
1062
    }
17,325✔
1063
    bump_content_version();
17,928✔
1064
    bump_storage_version();
17,928✔
1065
}
17,928✔
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
{
335,889✔
1199
    while (m_top.size() <= top_position_for_flags)
335,889✔
1200
        m_top.add(0);
×
1201

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

1211

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

1218
void Table::fully_detach() noexcept
1219
{
5,073,093✔
1220
    m_spec.detach();
5,073,093✔
1221
    m_top.detach();
5,073,093✔
1222
    m_index_refs.detach();
5,073,093✔
1223
    m_opposite_table.detach();
5,073,093✔
1224
    m_opposite_column.detach();
5,073,093✔
1225
    m_index_accessors.clear();
5,073,093✔
1226
}
5,073,093✔
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,867,227✔
1247
    if (m_index_accessors[col_key.get_index().val].get()) {
7,867,227✔
1248
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
1,162,974✔
1249
        bool fulltext = attr.test(col_attr_FullText_Indexed);
1,162,974✔
1250
        return fulltext ? IndexType::Fulltext : IndexType::General;
1,162,764✔
1251
    }
1,162,974✔
1252
    return IndexType::None;
6,704,253✔
1253
}
6,704,253✔
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,795,274✔
1312
    const Array& real_top = m_top;
8,795,274✔
1313
    ArrayParent* parent = real_top.get_parent();
8,795,274✔
1314
    if (!parent)
8,795,274✔
1315
        return StringData("");
60✔
1316
    REALM_ASSERT(dynamic_cast<Group*>(parent));
8,795,214✔
1317
    return static_cast<Group*>(parent)->get_table_name(get_key());
8,795,214✔
1318
}
8,795,214✔
1319

1320
StringData Table::get_class_name() const noexcept
1321
{
6,125,235✔
1322
    return Group::table_name_to_class_name(get_name());
6,125,235✔
1323
}
6,125,235✔
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,832✔
1347
    REALM_ASSERT_DEBUG(valid_column(col_key));
908,832✔
1348
    return col_key.get_attrs().test(col_attr_Nullable);
908,832✔
1349
}
908,832✔
1350

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

1357

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

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

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

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

168,411✔
1422
    REALM_ASSERT(top.size() == top_array_size);
339,237✔
1423

168,411✔
1424
    return top.get_ref();
339,237✔
1425
}
339,237✔
1426

1427
void Table::ensure_graveyard()
1428
{
32,337✔
1429
    if (!m_tombstones) {
32,337✔
1430
        while (m_top.size() < top_position_for_tombstones)
10,467✔
1431
            m_top.add(0);
×
1432
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
10,467✔
1433
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
10,467✔
1434
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
10,467✔
1435
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
10,467✔
1436
        m_tombstones->init_from_parent();
10,467✔
1437
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
25,710✔
1438
            ts->insert_column(col);
25,710✔
1439
            return IteratorControl::AdvanceToNext;
25,710✔
1440
        });
25,710✔
1441
    }
10,467✔
1442
}
32,337✔
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,215✔
1490
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
4,215✔
1491
    m_clusters.clear(state);
4,215✔
1492
}
4,215✔
1493

1494

1495
Group* Table::get_parent_group() const noexcept
1496
{
21,536,799✔
1497
    if (!m_top.is_attached())
21,536,799✔
1498
        return 0;                             // Subtable with shared descriptor
×
1499
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
21,536,799✔
1500
    if (!parent)
21,536,799✔
1501
        return 0; // Free-standing table
128,535✔
1502

10,633,497✔
1503
    return static_cast<Group*>(parent);
21,408,264✔
1504
}
21,408,264✔
1505

1506
inline uint64_t Table::get_sync_file_id() const noexcept
UNCOV
1507
{
×
UNCOV
1508
    Group* g = get_parent_group();
×
UNCOV
1509
    return g ? g->get_sync_file_id() : 0;
×
UNCOV
1510
}
×
1511

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

1522
uint64_t Table::allocate_sequence_number()
1523
{
20,092,482✔
1524
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
20,092,482✔
1525
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
19,950,309✔
1526
    rot = RefOrTagged::make_tagged(sn + 1);
20,092,482✔
1527
    m_top.set(top_position_for_sequence_number, rot);
20,092,482✔
1528

9,997,446✔
1529
    return sn;
20,092,482✔
1530
}
20,092,482✔
1531

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

1537
TableRef Table::get_link_target(ColKey col_key) noexcept
1538
{
309,717✔
1539
    return get_opposite_table(col_key);
309,717✔
1540
}
309,717✔
1541

1542
// count ----------------------------------------------
1543

1544
size_t Table::count_int(ColKey col_key, int64_t value) const
1545
{
12,006✔
1546
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1547
        return index->count(value);
12,000✔
1548
    }
12,000✔
1549

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

9✔
1577
    traverse_clusters(f);
18✔
1578

9✔
1579
    return cnt;
18✔
1580
}
18✔
1581
size_t Table::count_string(ColKey col_key, StringData value) const
1582
{
1,476✔
1583
    if (auto index = this->get_search_index(col_key)) {
1,476✔
1584
        return index->count(value);
732✔
1585
    }
732✔
1586
    return where().equal(col_key, value).count();
744✔
1587
}
744✔
1588

1589
template <typename T>
1590
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1591
{
27,819✔
1592
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
27,819✔
1593
    LeafType leaf(get_alloc());
27,819✔
1594

13,929✔
1595
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
27,837✔
1596
        // direct aggregate on the leaf
13,938✔
1597
        cluster->init_leaf(column_key, &leaf);
27,837✔
1598
        st.m_key_offset = cluster->get_offset();
27,837✔
1599
        st.m_key_values = cluster->get_key_array();
27,837✔
1600

13,938✔
1601
        bool cont = true;
27,837✔
1602
        size_t sz = leaf.size();
27,837✔
1603
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
122,937✔
1604
            auto v = leaf.get(local_index);
95,100✔
1605
            cont = st.match(local_index, v);
95,100✔
1606
        }
95,100✔
1607
        return IteratorControl::AdvanceToNext;
27,837✔
1608
    };
27,837✔
1609

13,929✔
1610
    traverse_clusters(f);
27,819✔
1611
}
27,819✔
1612

1613
// This template is also used by the query engine
1614
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1615
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1616
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1617
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1618
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1619
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1620
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1621

1622
std::optional<Mixed> Table::sum(ColKey col_key) const
1623
{
756✔
1624
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1625
}
756✔
1626

1627
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1628
{
822✔
1629
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1630
}
822✔
1631

1632
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1633
{
1,170✔
1634
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
1635
}
1,170✔
1636

1637
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1638
{
21,543✔
1639
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
21,543✔
1640
}
21,543✔
1641

1642

1643
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1644
{
29,982,750✔
1645
    check_column(col);
29,982,750✔
1646
    return m_index_accessors[col.get_index().val].get();
29,982,750✔
1647
}
29,982,750✔
1648

1649
StringIndex* Table::get_string_index(ColKey col) const noexcept
1650
{
696✔
1651
    check_column(col);
696✔
1652
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
696✔
1653
}
696✔
1654

1655
template <class T>
1656
ObjKey Table::find_first(ColKey col_key, T value) const
1657
{
35,010✔
1658
    check_column(col_key);
35,010✔
1659

17,505✔
1660
    if (!col_key.is_nullable() && value_is_null(value)) {
35,010!
1661
        return {}; // this is a precaution/optimization
6✔
1662
    }
6✔
1663
    // You cannot call GetIndexData on ObjKey
17,502✔
1664
    if constexpr (!std::is_same_v<T, ObjKey>) {
35,004✔
1665
        if (SearchIndex* index = get_search_index(col_key)) {
34,986!
1666
            return index->find_first(value);
27,138✔
1667
        }
27,138✔
1668
        if (col_key == m_primary_key_col) {
7,848!
1669
            return find_primary_key(value);
×
1670
        }
×
1671
    }
7,848✔
1672

3,924✔
1673
    ObjKey key;
7,848✔
1674
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
7,848✔
1675
    LeafType leaf(get_alloc());
7,848✔
1676

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

3,924✔
1687
    traverse_clusters(f);
7,848✔
1688

3,924✔
1689
    return key;
7,848✔
1690
}
7,848✔
1691

1692
namespace realm {
1693

1694
template <>
1695
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1696
{
×
1697
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1698
}
×
1699

1700
template <>
1701
ObjKey Table::find_first(ColKey col_key, util::Optional<double> 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, null) const
1708
{
×
1709
    return find_first_null(col_key);
×
1710
}
×
1711
} // namespace realm
1712

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

1730
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1731
{
7,698✔
1732
    if (is_nullable(col_key))
7,698✔
1733
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1734
    else
7,662✔
1735
        return find_first<int64_t>(col_key, value);
7,662✔
1736
}
7,698✔
1737

1738
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1739
{
78✔
1740
    if (is_nullable(col_key))
78✔
1741
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1742
    else
42✔
1743
        return find_first<bool>(col_key, value);
42✔
1744
}
78✔
1745

1746
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1747
{
108✔
1748
    return find_first(col_key, value);
108✔
1749
}
108✔
1750

1751
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1752
{
6✔
1753
    return find_first(col_key, value);
6✔
1754
}
6✔
1755

1756
ObjKey Table::find_first_float(ColKey col_key, float value) const
1757
{
12✔
1758
    return find_first<Float>(col_key, value);
12✔
1759
}
12✔
1760

1761
ObjKey Table::find_first_double(ColKey col_key, double value) const
1762
{
12✔
1763
    return find_first<Double>(col_key, value);
12✔
1764
}
12✔
1765

1766
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
1767
{
×
1768
    return find_first<Decimal128>(col_key, value);
×
1769
}
×
1770

1771
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1772
{
11,376✔
1773
    return find_first<StringData>(col_key, value);
11,376✔
1774
}
11,376✔
1775

1776
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
1777
{
×
1778
    return find_first<BinaryData>(col_key, value);
×
1779
}
×
1780

1781
ObjKey Table::find_first_null(ColKey col_key) const
1782
{
114✔
1783
    return where().equal(col_key, null{}).find();
114✔
1784
}
114✔
1785

1786
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1787
{
18✔
1788
    return find_first(col_key, value);
18✔
1789
}
18✔
1790

1791
template <class T>
1792
TableView Table::find_all(ColKey col_key, T value)
1793
{
108✔
1794
    return where().equal(col_key, value).find_all();
108✔
1795
}
108✔
1796

1797
TableView Table::find_all_int(ColKey col_key, int64_t value)
1798
{
102✔
1799
    return find_all<int64_t>(col_key, value);
102✔
1800
}
102✔
1801

1802
TableView Table::find_all_int(ColKey col_key, int64_t value) const
1803
{
×
1804
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1805
}
×
1806

1807
TableView Table::find_all_bool(ColKey col_key, bool value)
1808
{
×
1809
    return find_all<bool>(col_key, value);
×
1810
}
×
1811

1812
TableView Table::find_all_bool(ColKey col_key, bool value) const
1813
{
×
1814
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1815
}
×
1816

1817

1818
TableView Table::find_all_float(ColKey col_key, float value)
1819
{
×
1820
    return find_all<float>(col_key, value);
×
1821
}
×
1822

1823
TableView Table::find_all_float(ColKey col_key, float value) const
1824
{
×
1825
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
1826
}
×
1827

1828
TableView Table::find_all_double(ColKey col_key, double value)
1829
{
6✔
1830
    return find_all<double>(col_key, value);
6✔
1831
}
6✔
1832

1833
TableView Table::find_all_double(ColKey col_key, double value) const
1834
{
×
1835
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
1836
}
×
1837

1838
TableView Table::find_all_string(ColKey col_key, StringData value)
1839
{
282✔
1840
    return where().equal(col_key, value).find_all();
282✔
1841
}
282✔
1842

1843
TableView Table::find_all_string(ColKey col_key, StringData value) const
1844
{
×
1845
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
1846
}
×
1847

1848
TableView Table::find_all_binary(ColKey, BinaryData)
1849
{
×
1850
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
1851
}
×
1852

1853
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
1854
{
×
1855
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
1856
}
×
1857

1858
TableView Table::find_all_null(ColKey col_key)
1859
{
×
1860
    return where().equal(col_key, null{}).find_all();
×
1861
}
×
1862

1863
TableView Table::find_all_null(ColKey col_key) const
1864
{
×
1865
    return const_cast<Table*>(this)->find_all_null(col_key);
×
1866
}
×
1867

1868
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1869
{
6✔
1870
    return where().fulltext(col_key, terms).find_all();
6✔
1871
}
6✔
1872

1873
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1874
{
72✔
1875
    TableView tv = where().find_all();
72✔
1876
    tv.sort(col_key, ascending);
72✔
1877
    return tv;
72✔
1878
}
72✔
1879

1880
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1881
{
6✔
1882
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1883
}
6✔
1884

1885
TableView Table::get_sorted_view(SortDescriptor order)
1886
{
126✔
1887
    TableView tv = where().find_all();
126✔
1888
    tv.sort(std::move(order));
126✔
1889
    return tv;
126✔
1890
}
126✔
1891

1892
TableView Table::get_sorted_view(SortDescriptor order) const
1893
{
×
1894
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
1895
}
×
1896

1897
util::Logger* Table::get_logger() const noexcept
1898
{
1,756,572✔
1899
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
2,148,359,392✔
1900
}
1,756,572✔
1901

1902
// Called after a commit. Table will effectively contain the same as before,
1903
// but now with new refs from the file
1904
void Table::update_from_parent() noexcept
1905
{
809,649✔
1906
    // There is no top for sub-tables sharing spec
402,060✔
1907
    if (m_top.is_attached()) {
809,649✔
1908
        m_top.update_from_parent();
809,649✔
1909
        m_spec.update_from_parent();
809,649✔
1910
        m_clusters.update_from_parent();
809,649✔
1911
        m_index_refs.update_from_parent();
809,649✔
1912
        for (auto&& index : m_index_accessors) {
2,649,456✔
1913
            if (index != nullptr) {
2,649,456✔
1914
                index->update_from_parent();
272,919✔
1915
            }
272,919✔
1916
        }
2,649,456✔
1917

402,060✔
1918
        m_opposite_table.update_from_parent();
809,649✔
1919
        m_opposite_column.update_from_parent();
809,649✔
1920
        if (m_top.size() > top_position_for_flags) {
809,649✔
1921
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
809,637✔
1922
            m_table_type = Type(flags & table_type_mask);
809,637✔
1923
        }
809,637✔
1924
        else {
12✔
1925
            m_table_type = Type::TopLevel;
12✔
1926
        }
12✔
1927
        if (m_tombstones)
809,649✔
1928
            m_tombstones->update_from_parent();
1,278✔
1929

402,060✔
1930
        refresh_content_version();
809,649✔
1931
        m_has_any_embedded_objects.reset();
809,649✔
1932
    }
809,649✔
1933
    m_alloc.bump_storage_version();
809,649✔
1934
}
809,649✔
1935

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

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

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

63✔
2029
    return true;
117✔
2030
}
126✔
2031

2032

2033
void Table::flush_for_commit()
2034
{
3,984,990✔
2035
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
3,984,990✔
2036
        if (!m_top.is_read_only()) {
3,984,966✔
2037
            ++m_in_file_version_at_transaction_boundary;
1,236,453✔
2038
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
1,236,453✔
2039
            m_top.set(top_position_for_version, rot_version);
1,236,453✔
2040
        }
1,236,453✔
2041
    }
3,984,966✔
2042
}
3,984,990✔
2043

2044
void Table::refresh_content_version()
2045
{
1,112,301✔
2046
    REALM_ASSERT(m_top.is_attached());
1,112,301✔
2047
    if (m_top.size() >= top_position_for_version) {
1,112,301✔
2048
        // we have versioning info in the file. Use this to conditionally
560,649✔
2049
        // bump the version counter:
560,649✔
2050
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,112,205✔
2051
        REALM_ASSERT(rot_version.is_tagged());
1,112,205✔
2052
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,112,205✔
2053
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
170,703✔
2054
            bump_content_version();
170,703✔
2055
        }
170,703✔
2056
    }
1,112,205✔
2057
    else {
96✔
2058
        // assume the worst:
78✔
2059
        bump_content_version();
96✔
2060
    }
96✔
2061
}
1,112,301✔
2062

2063

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

2102
void Table::refresh_index_accessors()
2103
{
6,373,197✔
2104
    // Refresh search index accessors
3,428,364✔
2105

3,428,364✔
2106
    // First eliminate any index accessors for eliminated last columns
3,428,364✔
2107
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
6,373,197✔
2108
    m_index_accessors.resize(col_ndx_end);
6,373,197✔
2109

3,428,364✔
2110
    // Then eliminate/refresh/create accessors within column range
3,428,364✔
2111
    // we can not use for_each_column() here, since the columns may have changed
3,428,364✔
2112
    // and the index accessor vector is not updated correspondingly.
3,428,364✔
2113
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
27,878,667✔
2114
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
21,505,470✔
2115

11,137,800✔
2116
        if (ref == 0) {
21,505,470✔
2117
            // accessor drop
9,141,642✔
2118
            m_index_accessors[col_ndx].reset();
17,490,411✔
2119
        }
17,490,411✔
2120
        else {
4,015,059✔
2121
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
4,015,059✔
2122
            bool fulltext = attr.test(col_attr_FullText_Indexed);
4,015,059✔
2123
            auto col_key = m_leaf_ndx2colkey[col_ndx];
4,015,059✔
2124
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
4,015,044✔
2125

1,996,158✔
2126
            if (m_index_accessors[col_ndx]) { // still there, refresh:
4,015,059✔
2127
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
478,029✔
2128
            }
478,029✔
2129
            else { // new index!
3,537,030✔
2130
                m_index_accessors[col_ndx] =
3,537,030✔
2131
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
3,537,030✔
2132
            }
3,537,030✔
2133
        }
4,015,059✔
2134
    }
21,505,470✔
2135
}
6,373,197✔
2136

2137
bool Table::is_cross_table_link_target() const noexcept
2138
{
7,761✔
2139
    auto is_cross_link = [this](ColKey col_key) {
4,077✔
2140
        auto t = col_key.get_type();
63✔
2141
        // look for a backlink with a different target than ourselves
36✔
2142
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
63✔
2143
                   ? IteratorControl::Stop
45✔
2144
                   : IteratorControl::AdvanceToNext;
54✔
2145
    };
63✔
2146
    return for_each_backlink_column(is_cross_link);
7,761✔
2147
}
7,761✔
2148

2149
// LCOV_EXCL_START ignore debug functions
2150

2151
void Table::verify() const
2152
{
2,970,939✔
2153
#ifdef REALM_DEBUG
2,970,939✔
2154
    if (m_top.is_attached())
2,970,939✔
2155
        m_top.verify();
2,970,936✔
2156
    m_spec.verify();
2,970,939✔
2157
    m_clusters.verify();
2,970,939✔
2158
    if (nb_unresolved())
2,970,939✔
2159
        m_tombstones->verify();
805,830✔
2160
#endif
2,970,939✔
2161
}
2,970,939✔
2162

2163
#ifdef REALM_DEBUG
2164
MemStats Table::stats() const
2165
{
×
2166
    MemStats mem_stats;
×
2167
    m_top.stats(mem_stats);
×
2168
    return mem_stats;
×
2169
}
×
2170
#endif // LCOV_EXCL_STOP ignore debug functions
2171

2172
Obj Table::create_object(ObjKey key, const FieldValues& values)
2173
{
22,688,511✔
2174
    if (is_embedded())
22,688,511✔
2175
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2176
    if (m_primary_key_col)
22,688,463✔
2177
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2178
    if (key == null_key) {
22,688,463✔
2179
        key = get_next_valid_key();
19,530,204✔
2180
        if (auto repl = get_repl())
19,530,204✔
2181
            repl->create_object(this, key);
4,307,856✔
2182
    }
19,530,204✔
2183

11,317,455✔
2184
    REALM_ASSERT(key.value >= 0);
22,688,463✔
2185

11,317,455✔
2186
    Obj obj = m_clusters.insert(key, values); // repl->set()
22,688,463✔
2187

11,317,455✔
2188
    return obj;
22,688,463✔
2189
}
22,688,463✔
2190

2191
Obj Table::create_linked_object()
2192
{
39,717✔
2193
    REALM_ASSERT(is_embedded());
39,717✔
2194

19,764✔
2195
    ObjKey key = get_next_valid_key();
39,717✔
2196
    Obj obj = m_clusters.insert(key, {});
39,717✔
2197

19,764✔
2198
    return obj;
39,717✔
2199
}
39,717✔
2200

2201
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2202
                                          bool* did_create)
2203
{
592,767✔
2204
    auto primary_key_col = get_primary_key_column();
592,767✔
2205
    if (is_embedded() || !primary_key_col)
592,770✔
2206
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2207
                              util::format("Table has no primary key: %1", get_name()));
6✔
2208

284,514✔
2209
    DataType type = DataType(primary_key_col.get_type());
592,761✔
2210

284,514✔
2211
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
592,761✔
2212
        throw InvalidArgument(
6✔
2213
            ErrorCodes::PropertyNotNullable,
6✔
2214
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2215
    }
6✔
2216

284,511✔
2217
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
592,755✔
2218
        primary_key.get_type() != type) {
592,539✔
2219
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2220
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2221
    }
6✔
2222

284,508✔
2223
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
592,749✔
2224

284,508✔
2225
    if (did_create)
592,749✔
2226
        *did_create = false;
74,457✔
2227

284,508✔
2228
    // Check for existing object
284,508✔
2229
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
592,749✔
2230
        if (mode == UpdateMode::never) {
82,464✔
2231
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2232
        }
6✔
2233
        auto obj = m_clusters.get(key);
82,458✔
2234
        for (auto& val : field_values) {
40,941✔
2235
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2236
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2237
            }
12✔
2238
        }
12✔
2239
        return obj;
82,458✔
2240
    }
82,458✔
2241

243,570✔
2242
    ObjKey unres_key;
510,285✔
2243
    if (m_tombstones) {
510,285✔
2244
        if (auto sz = m_tombstones->size()) {
40,920✔
2245
            // Check for potential tombstone
19,086✔
2246
            Iterator end(*m_tombstones, sz);
38,736✔
2247
            for (Iterator it(*m_tombstones, 0); it != end; ++it) {
6,821,706✔
2248
                auto existing_pk_value = it->get_any(primary_key_col);
6,796,440✔
2249
                // If the primary key is the same, the object should be resurrected below
3,398,028✔
2250
                if (existing_pk_value == primary_key) {
6,796,440✔
2251
                    unres_key = it->get_key();
13,470✔
2252
                    break;
13,470✔
2253
                }
13,470✔
2254
            }
6,796,440✔
2255
        }
38,736✔
2256
    }
40,920✔
2257

243,570✔
2258
    ObjKey key = get_next_valid_key();
510,285✔
2259

243,570✔
2260
    auto repl = get_repl();
510,285✔
2261
    if (repl) {
510,285✔
2262
        repl->create_object_with_primary_key(this, key, primary_key);
449,169✔
2263
    }
449,169✔
2264
    if (did_create) {
510,285✔
2265
        *did_create = true;
36,393✔
2266
    }
36,393✔
2267

243,570✔
2268
    field_values.insert(primary_key_col, primary_key);
510,285✔
2269
    Obj ret = m_clusters.insert(key, field_values);
510,285✔
2270

243,570✔
2271
    // Check if unresolved exists
243,570✔
2272
    if (unres_key) {
510,285✔
2273
        auto tombstone = m_tombstones->get(unres_key);
13,470✔
2274
        ret.assign_pk_and_backlinks(tombstone);
13,470✔
2275
        // If tombstones had no links to it, it may still be alive
6,726✔
2276
        if (m_tombstones->is_valid(unres_key)) {
13,470✔
2277
            CascadeState state(CascadeState::Mode::None);
4,302✔
2278
            m_tombstones->erase(unres_key, state);
4,302✔
2279
        }
4,302✔
2280
    }
13,470✔
2281
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
510,285✔
2282
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
2283
    }
1,290✔
2284
    return ret;
510,285✔
2285
}
510,285✔
2286

2287
ObjKey Table::find_primary_key(Mixed primary_key) const
2288
{
907,245✔
2289
    auto primary_key_col = get_primary_key_column();
907,245✔
2290
    REALM_ASSERT(primary_key_col);
907,245✔
2291
    DataType type = DataType(primary_key_col.get_type());
907,245✔
2292
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
907,245✔
2293
                 primary_key.get_type() == type);
907,245✔
2294
    REALM_ASSERT(m_index_accessors[primary_key_col.get_index().val]);
907,245✔
2295

450,843✔
2296
    return m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
907,245✔
2297
}
907,245✔
2298

2299
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2300
{
740,340✔
2301
    // Check if existing
367,545✔
2302
    if (auto key = find_primary_key(primary_key)) {
740,340✔
2303
        return key;
709,104✔
2304
    }
709,104✔
2305

15,507✔
2306
    return get_or_create_tombstone(ObjKey{}, m_primary_key_col, primary_key).get_key();
31,236✔
2307
}
31,236✔
2308

2309
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2310
{
62,826✔
2311
    auto primary_key_col = get_primary_key_column();
62,826✔
2312
    REALM_ASSERT(primary_key_col);
62,826✔
2313
    DataType type = DataType(primary_key_col.get_type());
62,826✔
2314
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
62,826✔
2315
                 primary_key.get_type() == type);
62,826✔
2316
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
62,826✔
2317
    return k ? m_clusters.get(k) : Obj{};
62,820✔
2318
}
62,826✔
2319

2320
Mixed Table::get_primary_key(ObjKey key) const
2321
{
718,431✔
2322
    auto primary_key_col = get_primary_key_column();
718,431✔
2323
    REALM_ASSERT(primary_key_col);
718,431✔
2324
    if (key.is_unresolved()) {
718,431✔
2325
        REALM_ASSERT(m_tombstones);
72✔
2326
        return m_tombstones->get(key).get_any(primary_key_col);
72✔
2327
    }
72✔
2328
    else {
718,359✔
2329
        return m_clusters.get(key).get_any(primary_key_col);
718,359✔
2330
    }
718,359✔
2331
}
718,431✔
2332

2333
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2334
{
32,337✔
2335
    ensure_graveyard();
32,337✔
2336
    if (auto sz = m_tombstones->size()) {
32,337✔
2337
        // Check for existing tombstone
10,545✔
2338
        Iterator end(*m_tombstones, sz);
20,418✔
2339
        for (Iterator it(*m_tombstones, 0); it != end; ++it) {
6,780,318✔
2340
            auto existing_pk_value = it->get_any(m_primary_key_col);
6,762,831✔
2341
            // If the primary key is the same, the object should be resurrected below
3,381,864✔
2342
            if (existing_pk_value == pk_val) {
6,762,831✔
2343
                return *it;
2,931✔
2344
            }
2,931✔
2345
        }
6,762,831✔
2346
    }
20,418✔
2347

16,350✔
2348
    // Create new tombstone
16,350✔
2349
    if (!key) {
31,059✔
2350
        key = get_next_valid_key();
28,860✔
2351
    }
28,860✔
2352
    auto unres_key = key.get_unresolved();
29,406✔
2353
    REALM_ASSERT(!m_tombstones->is_valid(unres_key));
29,406✔
2354
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
29,406✔
2355
}
32,337✔
2356

2357
void Table::free_collision_table()
2358
{
354✔
2359
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
354✔
UNCOV
2360
        Array::destroy_deep(collision_map_ref, m_alloc);
×
UNCOV
2361
        m_top.set(top_position_for_collision_map, 0);
×
UNCOV
2362
    }
×
2363
}
354✔
2364

2365
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2366
{
3,855✔
2367
    while (number--) {
6,323,787✔
2368
        keys.push_back(create_object().get_key());
6,319,932✔
2369
    }
6,319,932✔
2370
}
3,855✔
2371

2372
void Table::create_objects(const std::vector<ObjKey>& keys)
2373
{
630✔
2374
    for (auto k : keys) {
5,616✔
2375
        create_object(k);
5,616✔
2376
    }
5,616✔
2377
}
630✔
2378

2379
void Table::dump_objects()
2380
{
×
2381
    m_clusters.dump_objects();
×
2382
    if (nb_unresolved())
×
2383
        m_tombstones->dump_objects();
×
2384
}
×
2385

2386
void Table::remove_object(ObjKey key)
2387
{
2,549,781✔
2388
    Group* g = get_parent_group();
2,549,781✔
2389

1,274,199✔
2390
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,549,781✔
2391
        CascadeState state(CascadeState::Mode::Strong, g);
2,502✔
2392
        state.m_to_be_deleted.emplace_back(m_key, key);
2,502✔
2393
        m_clusters.nullify_incoming_links(key, state);
2,502✔
2394
        remove_recursive(state);
2,502✔
2395
    }
2,502✔
2396
    else {
2,547,279✔
2397
        CascadeState state(CascadeState::Mode::None, g);
2,547,279✔
2398
        if (g) {
2,547,279✔
2399
            m_clusters.nullify_incoming_links(key, state);
2,465,448✔
2400
        }
2,465,448✔
2401
        m_clusters.erase(key, state);
2,547,279✔
2402
    }
2,547,279✔
2403
}
2,549,781✔
2404

2405
ObjKey Table::invalidate_object(ObjKey key)
2406
{
64,278✔
2407
    if (is_embedded())
64,278✔
2408
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2409
    REALM_ASSERT(!key.is_unresolved());
64,278✔
2410

31,443✔
2411
    Obj tombstone;
64,278✔
2412
    auto obj = get_object(key);
64,278✔
2413
    if (obj.has_backlinks(false)) {
64,278✔
2414
        // If the object has backlinks, we should make a tombstone
273✔
2415
        // and make inward links point to it,
273✔
2416
        auto primary_key_col = get_primary_key_column();
546✔
2417
        REALM_ASSERT(primary_key_col);
546✔
2418
        auto pk = obj.get_any(primary_key_col);
546✔
2419
        tombstone = get_or_create_tombstone(key, primary_key_col, pk);
546✔
2420
        tombstone.assign_pk_and_backlinks(obj);
546✔
2421
    }
546✔
2422

31,443✔
2423
    remove_object(key);
64,278✔
2424

31,443✔
2425
    return tombstone.get_key();
64,278✔
2426
}
64,278✔
2427

2428
void Table::remove_object_recursive(ObjKey key)
2429
{
42✔
2430
    size_t table_ndx = get_index_in_group();
42✔
2431
    if (table_ndx != realm::npos) {
42✔
2432
        CascadeState state(CascadeState::Mode::All, get_parent_group());
42✔
2433
        state.m_to_be_deleted.emplace_back(m_key, key);
42✔
2434
        nullify_links(state);
42✔
2435
        remove_recursive(state);
42✔
2436
    }
42✔
2437
    else {
×
2438
        // No links in freestanding table
2439
        CascadeState state(CascadeState::Mode::None);
×
2440
        m_clusters.erase(key, state);
×
2441
    }
×
2442
}
42✔
2443

2444
Table::Iterator Table::begin() const
2445
{
506,748✔
2446
    return Iterator(m_clusters, 0);
506,748✔
2447
}
506,748✔
2448

2449
Table::Iterator Table::end() const
2450
{
9,590,649✔
2451
    return Iterator(m_clusters, size());
9,590,649✔
2452
}
9,590,649✔
2453

2454
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2455
{
7,072,776✔
2456
    TableRef ret;
7,072,776✔
2457
    if (col_key) {
7,072,956✔
2458
        return table.get_opposite_table(col_key);
7,072,956✔
2459
    }
7,072,956✔
2460
    return ret;
4,294,967,294✔
2461
}
4,294,967,294✔
2462

2463
const uint64_t Table::max_num_columns;
2464

2465
void Table::build_column_mapping()
2466
{
6,396,585✔
2467
    // build column mapping from spec
3,441,336✔
2468
    // TODO: Optimization - Don't rebuild this for every change
3,441,336✔
2469
    m_spec_ndx2leaf_ndx.clear();
6,396,585✔
2470
    m_leaf_ndx2spec_ndx.clear();
6,396,585✔
2471
    m_leaf_ndx2colkey.clear();
6,396,585✔
2472
    size_t num_spec_cols = m_spec.get_column_count();
6,396,585✔
2473
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
6,396,585✔
2474
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
27,868,710✔
2475
        ColKey col_key = m_spec.get_key(spec_ndx);
21,472,125✔
2476
        unsigned leaf_ndx = col_key.get_index().val;
21,472,125✔
2477
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
21,472,125✔
2478
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
21,003,633✔
2479
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
21,003,633✔
2480
        }
21,003,633✔
2481
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
21,472,125✔
2482
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
21,472,125✔
2483
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
21,472,125✔
2484
    }
21,472,125✔
2485
}
6,396,585✔
2486

2487
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2488
{
980,343✔
2489
    REALM_ASSERT(!attr.test(col_attr_Indexed));
980,343✔
2490
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
980,343✔
2491

483,765✔
2492
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
980,343✔
2493
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
980,343✔
2494

483,765✔
2495
    // reuse lowest available leaf ndx:
483,765✔
2496
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
980,343✔
2497
    // look for an unused entry:
483,765✔
2498
    for (unsigned idx = 0; idx < lower; ++idx) {
6,253,908✔
2499
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
5,273,670✔
2500
            lower = idx;
105✔
2501
            break;
105✔
2502
        }
105✔
2503
    }
5,273,670✔
2504
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
980,343✔
2505
}
980,343✔
2506

2507
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2508
                                                  StringData origin_col_name) const noexcept
2509
{
×
2510
    BacklinkOrigin ret;
×
2511
    auto f = [&](ColKey backlink_col_key) {
×
2512
        auto origin_table = get_opposite_table(backlink_col_key);
×
2513
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2514
        if (origin_table->get_name() == origin_table_name &&
×
2515
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
2516
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
2517
            return IteratorControl::Stop;
×
2518
        }
×
2519
        return IteratorControl::AdvanceToNext;
×
2520
    };
×
2521
    this->for_each_backlink_column(f);
×
2522
    return ret;
×
2523
}
×
2524

2525
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2526
{
354✔
2527
    try {
354✔
2528
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
354✔
2529
        ColKey linked_column_key = get_opposite_column(backlink_col);
354✔
2530
        if (linked_table_key == m_key) {
354✔
2531
            return {{m_own_ref, linked_column_key}};
18✔
2532
        }
18✔
2533
        else {
336✔
2534
            Group* current_group = get_parent_group();
336✔
2535
            if (current_group) {
336✔
2536
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2537
                return {{linked_table_ref, linked_column_key}};
336✔
2538
            }
336✔
2539
        }
×
2540
    }
354✔
2541
    catch (...) {
×
2542
        // backlink column not found, returning empty optional
2543
    }
×
2544
    return {};
177✔
2545
}
354✔
2546

2547
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
2548
{
×
2549
    std::vector<std::pair<TableKey, ColKey>> origins;
×
2550
    auto f = [&](ColKey backlink_col_key) {
×
2551
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
2552
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2553
        origins.emplace_back(origin_table_key, origin_link_col);
×
2554
        return IteratorControl::AdvanceToNext;
×
2555
    };
×
2556
    this->for_each_backlink_column(f);
×
2557
    return origins;
×
2558
}
×
2559

2560
ColKey Table::get_primary_key_column() const
2561
{
21,739,575✔
2562
    return m_primary_key_col;
21,739,575✔
2563
}
21,739,575✔
2564

2565
void Table::set_primary_key_column(ColKey col_key)
2566
{
720✔
2567
    if (col_key == m_primary_key_col) {
720✔
2568
        return;
378✔
2569
    }
378✔
2570

171✔
2571
    if (Replication* repl = get_repl()) {
342✔
2572
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
2573
            throw RuntimeError(
×
2574
                ErrorCodes::BrokenInvariant,
×
2575
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
2576
        }
×
2577
    }
342✔
2578

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

171✔
2581
    if (col_key) {
342✔
2582
        check_column(col_key);
222✔
2583
        validate_column_is_unique(col_key);
222✔
2584
        do_set_primary_key_column(col_key);
222✔
2585
    }
222✔
2586
    else {
120✔
2587
        do_set_primary_key_column(col_key);
120✔
2588
    }
120✔
2589
}
342✔
2590

2591

2592
void Table::do_set_primary_key_column(ColKey col_key)
2593
{
137,193✔
2594
    if (col_key) {
137,193✔
2595
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
129,555✔
2596
        auto attr = m_spec.get_column_attr(spec_ndx);
129,555✔
2597
        if (attr.test(col_attr_FullText_Indexed)) {
129,555✔
2598
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
2599
        }
6✔
2600
    }
137,187✔
2601

68,277✔
2602
    if (m_primary_key_col) {
137,187✔
2603
        // If the search index has not been set explicitly on current pk col, we remove it again
3,996✔
2604
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
7,662✔
2605
        auto attr = m_spec.get_column_attr(spec_ndx);
7,662✔
2606
        if (!attr.test(col_attr_Indexed)) {
7,662✔
2607
            remove_search_index(m_primary_key_col);
7,650✔
2608
        }
7,650✔
2609
    }
7,662✔
2610

68,277✔
2611
    if (col_key) {
137,187✔
2612
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
129,549✔
2613
        do_add_search_index(col_key, IndexType::General);
129,549✔
2614
    }
129,549✔
2615
    else {
7,638✔
2616
        m_top.set(top_position_for_pk_col, 0);
7,638✔
2617
    }
7,638✔
2618

68,277✔
2619
    m_primary_key_col = col_key;
137,187✔
2620
}
137,187✔
2621

2622
bool Table::contains_unique_values(ColKey col) const
2623
{
834✔
2624
    if (search_index_type(col) == IndexType::General) {
834✔
2625
        auto search_index = get_search_index(col);
744✔
2626
        return !search_index->has_duplicate_values();
744✔
2627
    }
744✔
2628
    else {
90✔
2629
        TableView tv = where().find_all();
90✔
2630
        tv.distinct(col);
90✔
2631
        return tv.size() == size();
90✔
2632
    }
90✔
2633
}
834✔
2634

2635
void Table::validate_column_is_unique(ColKey col) const
2636
{
834✔
2637
    if (!contains_unique_values(col)) {
834✔
2638
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
2639
                                           get_class_name(), get_column_name(col)));
30✔
2640
    }
30✔
2641
}
834✔
2642

2643
void Table::validate_primary_column()
2644
{
1,812✔
2645
    if (ColKey col = get_primary_key_column()) {
1,812✔
2646
        validate_column_is_unique(col);
612✔
2647
    }
612✔
2648
}
1,812✔
2649

2650
ObjKey Table::get_next_valid_key()
2651
{
20,108,964✔
2652
    ObjKey key;
20,108,964✔
2653
    do {
20,108,970✔
2654
        key = ObjKey(allocate_sequence_number());
20,108,970✔
2655
    } while (key.value < m_clusters.get_last_key_value() && m_clusters.is_valid(key));
20,108,970✔
2656

10,011,000✔
2657
    return key;
20,108,964✔
2658
}
20,108,964✔
2659

2660
namespace {
2661
template <class T>
2662
typename util::RemoveOptional<T>::type remove_optional(T val)
2663
{
88,023✔
2664
    return val;
88,023✔
2665
}
88,023✔
2666
template <>
2667
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
2668
{
5,475✔
2669
    return *val;
5,475✔
2670
}
5,475✔
2671
template <>
2672
bool remove_optional<Optional<bool>>(Optional<bool> val)
2673
{
11,553✔
2674
    return *val;
11,553✔
2675
}
11,553✔
2676
template <>
2677
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
2678
{
5,433✔
2679
    return *val;
5,433✔
2680
}
5,433✔
2681
template <>
2682
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
2683
{
6,060✔
2684
    return *val;
6,060✔
2685
}
6,060✔
2686
} // namespace
2687

2688
template <class F, class T>
2689
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
2690
{
162✔
2691
    Allocator& allocator = this->get_alloc();
162✔
2692
    bool from_nullability = is_nullable(key_from);
162✔
2693
    auto func = [&](Cluster* cluster) {
162✔
2694
        size_t sz = cluster->node_size();
162✔
2695

81✔
2696
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
2697
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
2698
        cluster->init_leaf(key_from, &from_arr);
162✔
2699
        cluster->init_leaf(key_to, &to_arr);
162✔
2700

81✔
2701
        for (size_t i = 0; i < sz; i++) {
1,512✔
2702
            if (from_nullability && from_arr.is_null(i)) {
1,356!
2703
                if (throw_on_null) {
45!
2704
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
2705
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
2706
                                                    get_column_name(key_from)));
6✔
2707
                }
6✔
2708
                else {
39✔
2709
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
39✔
2710
                }
39✔
2711
            }
45✔
2712
            else {
1,311✔
2713
                auto v = remove_optional(from_arr.get(i));
1,311✔
2714
                to_arr.set(i, v);
1,311✔
2715
            }
1,311✔
2716
        }
1,356✔
2717
    };
162✔
2718

81✔
2719
    m_clusters.update(func);
162✔
2720
}
162✔
2721

2722
template <class F, class T>
2723
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
2724
{
120✔
2725
    Allocator& allocator = this->get_alloc();
120✔
2726
    bool from_nullability = is_nullable(key_from);
120✔
2727
    auto func = [&](Cluster* cluster) {
120✔
2728
        size_t sz = cluster->node_size();
120✔
2729

60✔
2730
        ArrayInteger from_arr(allocator);
120✔
2731
        ArrayInteger to_arr(allocator);
120✔
2732
        cluster->init_leaf(key_from, &from_arr);
120✔
2733
        cluster->init_leaf(key_to, &to_arr);
120✔
2734

60✔
2735
        for (size_t i = 0; i < sz; i++) {
360✔
2736
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
2737
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
2738
            REALM_ASSERT(!ref_to);
240✔
2739

120✔
2740
            if (ref_from) {
240✔
2741
                BPlusTree<F> from_list(allocator);
120✔
2742
                BPlusTree<T> to_list(allocator);
120✔
2743
                from_list.init_from_ref(ref_from);
120✔
2744
                to_list.create();
120✔
2745
                size_t n = from_list.size();
120✔
2746
                for (size_t j = 0; j < n; j++) {
120,120✔
2747
                    auto v = from_list.get(j);
120,000✔
2748
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
2749
                        to_list.add(remove_optional(v));
115,233✔
2750
                    }
115,233✔
2751
                    else {
4,767✔
2752
                        if (throw_on_null) {
4,767!
2753
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
2754
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
2755
                                                            get_name(), get_column_name(key_from)));
×
2756
                        }
×
2757
                        else {
4,767✔
2758
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,767✔
2759
                        }
4,767✔
2760
                    }
4,767✔
2761
                }
120,000✔
2762
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
2763
            }
120✔
2764
        }
240✔
2765
    };
120✔
2766

60✔
2767
    m_clusters.update(func);
120✔
2768
}
120✔
2769

2770
void Table::convert_column(ColKey from, ColKey to, bool throw_on_null)
2771
{
282✔
2772
    realm::DataType type_id = get_column_type(from);
282✔
2773
    bool _is_list = is_list(from);
282✔
2774
    if (_is_list) {
282✔
2775
        switch (type_id) {
120✔
2776
            case type_Int:
12✔
2777
                if (is_nullable(from)) {
12✔
2778
                    change_nullability_list<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
2779
                }
6✔
2780
                else {
6✔
2781
                    change_nullability_list<int64_t, Optional<int64_t>>(from, to, throw_on_null);
6✔
2782
                }
6✔
2783
                break;
12✔
2784
            case type_Float:
12✔
2785
                change_nullability_list<float, float>(from, to, throw_on_null);
12✔
2786
                break;
12✔
2787
            case type_Double:
12✔
2788
                change_nullability_list<double, double>(from, to, throw_on_null);
12✔
2789
                break;
12✔
2790
            case type_Bool:
12✔
2791
                change_nullability_list<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
2792
                break;
12✔
2793
            case type_String:
12✔
2794
                change_nullability_list<StringData, StringData>(from, to, throw_on_null);
12✔
2795
                break;
12✔
2796
            case type_Binary:
12✔
2797
                change_nullability_list<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
2798
                break;
12✔
2799
            case type_Timestamp:
12✔
2800
                change_nullability_list<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
2801
                break;
12✔
2802
            case type_ObjectId:
12✔
2803
                if (is_nullable(from)) {
12✔
2804
                    change_nullability_list<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
2805
                }
6✔
2806
                else {
6✔
2807
                    change_nullability_list<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
2808
                }
6✔
2809
                break;
12✔
2810
            case type_Decimal:
12✔
2811
                change_nullability_list<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
2812
                break;
12✔
2813
            case type_UUID:
12✔
2814
                if (is_nullable(from)) {
12✔
2815
                    change_nullability_list<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
2816
                }
6✔
2817
                else {
6✔
2818
                    change_nullability_list<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
2819
                }
6✔
2820
                break;
12✔
2821
            case type_Link:
✔
2822
            case type_TypedLink:
✔
2823
            case type_LinkList:
✔
2824
                // Can't have lists of these types
2825
            case type_Mixed:
✔
2826
                // These types are no longer supported at all
2827
                REALM_UNREACHABLE();
×
2828
                break;
×
2829
        }
162✔
2830
    }
162✔
2831
    else {
162✔
2832
        switch (type_id) {
162✔
2833
            case type_Int:
36✔
2834
                if (is_nullable(from)) {
36✔
2835
                    change_nullability<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
2836
                }
6✔
2837
                else {
30✔
2838
                    change_nullability<int64_t, Optional<int64_t>>(from, to, throw_on_null);
30✔
2839
                }
30✔
2840
                break;
36✔
2841
            case type_Float:
12✔
2842
                change_nullability<float, float>(from, to, throw_on_null);
12✔
2843
                break;
12✔
2844
            case type_Double:
12✔
2845
                change_nullability<double, double>(from, to, throw_on_null);
12✔
2846
                break;
12✔
2847
            case type_Bool:
12✔
2848
                change_nullability<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
2849
                break;
12✔
2850
            case type_String:
30✔
2851
                change_nullability<StringData, StringData>(from, to, throw_on_null);
30✔
2852
                break;
30✔
2853
            case type_Binary:
12✔
2854
                change_nullability<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
2855
                break;
12✔
2856
            case type_Timestamp:
12✔
2857
                change_nullability<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
2858
                break;
12✔
2859
            case type_ObjectId:
12✔
2860
                if (is_nullable(from)) {
12✔
2861
                    change_nullability<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
2862
                }
6✔
2863
                else {
6✔
2864
                    change_nullability<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
2865
                }
6✔
2866
                break;
12✔
2867
            case type_Decimal:
12✔
2868
                change_nullability<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
2869
                break;
12✔
2870
            case type_UUID:
12✔
2871
                if (is_nullable(from)) {
12✔
2872
                    change_nullability<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
2873
                }
6✔
2874
                else {
6✔
2875
                    change_nullability<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
2876
                }
6✔
2877
                break;
12✔
2878
            case type_TypedLink:
✔
2879
            case type_Link:
✔
2880
                // Always nullable, so can't convert
2881
            case type_LinkList:
✔
2882
                // Never nullable, so can't convert
2883
            case type_Mixed:
✔
2884
                // These types are no longer supported at all
2885
                REALM_UNREACHABLE();
×
2886
                break;
×
2887
        }
162✔
2888
    }
162✔
2889
}
282✔
2890

2891

2892
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
2893
{
522✔
2894
    if (col_key.is_nullable() == nullable)
522✔
2895
        return col_key;
240✔
2896

141✔
2897
    check_column(col_key);
282✔
2898

141✔
2899
    auto index_type = search_index_type(col_key);
282✔
2900
    std::string column_name(get_column_name(col_key));
282✔
2901
    auto type = col_key.get_type();
282✔
2902
    auto attr = col_key.get_attrs();
282✔
2903
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
2904
    if (nullable) {
282✔
2905
        attr.set(col_attr_Nullable);
150✔
2906
    }
150✔
2907
    else {
132✔
2908
        attr.reset(col_attr_Nullable);
132✔
2909
    }
132✔
2910

141✔
2911
    ColKey new_col = generate_col_key(type, attr);
282✔
2912
    do_insert_root_column(new_col, type, "__temporary");
282✔
2913

141✔
2914
    try {
282✔
2915
        convert_column(col_key, new_col, throw_on_null);
282✔
2916
    }
282✔
2917
    catch (...) {
144✔
2918
        // remove any partially filled column
3✔
2919
        remove_column(new_col);
6✔
2920
        throw;
6✔
2921
    }
6✔
2922

138✔
2923
    if (is_pk_col) {
276✔
2924
        // If we go from non nullable to nullable, no values change,
6✔
2925
        // so it is safe to preserve the pk column. Otherwise it is not
6✔
2926
        // safe as a null entry might have been converted to default value.
6✔
2927
        do_set_primary_key_column(nullable ? new_col : ColKey{});
9✔
2928
    }
12✔
2929

138✔
2930
    erase_root_column(col_key);
276✔
2931
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
2932

138✔
2933
    if (index_type != IndexType::None)
276✔
2934
        do_add_search_index(new_col, index_type);
30✔
2935

138✔
2936
    return new_col;
276✔
2937
}
276✔
2938

2939
bool Table::has_any_embedded_objects()
2940
{
2,555,073✔
2941
    if (!m_has_any_embedded_objects) {
2,555,073✔
2942
        m_has_any_embedded_objects = false;
110,622✔
2943
        for_each_public_column([&](ColKey col_key) {
222,711✔
2944
            auto target_table_key = get_opposite_table_key(col_key);
222,711✔
2945
            if (target_table_key && is_link_type(col_key.get_type())) {
222,711✔
2946
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
9,147✔
2947
                if (target_table->is_embedded()) {
9,147✔
2948
                    m_has_any_embedded_objects = true;
7,017✔
2949
                    return IteratorControl::Stop; // early out
7,017✔
2950
                }
7,017✔
2951
            }
215,694✔
2952
            return IteratorControl::AdvanceToNext;
215,694✔
2953
        });
215,694✔
2954
    }
110,622✔
2955
    return *m_has_any_embedded_objects;
2,555,073✔
2956
}
2,555,073✔
2957

2958
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
2959
{
169,614✔
2960
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
169,614✔
2961
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
169,614✔
2962
}
169,614✔
2963

2964
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
2965
{
41,766✔
2966
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
146,031✔
2967
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
139,149✔
2968
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
34,884✔
2969
        }
34,884✔
2970
    }
139,149✔
2971

20,766✔
2972
    return {};
24,207✔
2973
}
41,766✔
2974

2975
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
2976
{
41,694✔
2977
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
41,694✔
2978

20,730✔
2979
    if (!backlink_col_key) {
41,694✔
2980
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
6,882✔
2981
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
6,882✔
2982

3,441✔
2983
        if (Replication* repl = get_repl())
6,882✔
2984
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
6,594✔
2985
                                    m_key); // Throws
6,594✔
2986
    }
6,882✔
2987

20,730✔
2988
    return backlink_col_key;
41,694✔
2989
}
41,694✔
2990

2991
TableKey Table::get_opposite_table_key(ColKey col_key) const
2992
{
14,557,575✔
2993
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,557,575✔
2994
}
14,557,575✔
2995

2996
bool Table::links_to_self(ColKey col_key) const
2997
{
58,194✔
2998
    return get_opposite_table_key(col_key) == m_key;
58,194✔
2999
}
58,194✔
3000

3001
TableRef Table::get_opposite_table(ColKey col_key) const
3002
{
7,771,725✔
3003
    if (auto k = get_opposite_table_key(col_key)) {
7,771,725✔
3004
        return get_parent_group()->get_table(k);
7,714,599✔
3005
    }
7,714,599✔
3006
    return {};
57,126✔
3007
}
57,126✔
3008

3009
ColKey Table::get_opposite_column(ColKey col_key) const
3010
{
7,230,261✔
3011
    return ColKey(m_opposite_column.get(col_key.get_index().val));
7,230,261✔
3012
}
7,230,261✔
3013

3014
ColKey Table::find_opposite_column(ColKey col_key) const
3015
{
×
3016
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3017
        if (m_opposite_column.get(i) == col_key.value) {
×
3018
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3019
        }
×
3020
    }
×
3021
    return ColKey();
×
3022
}
×
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