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

realm / realm-core / finn.schiermer-andersen_89

04 Jun 2024 02:04PM UTC coverage: 90.651% (-0.03%) from 90.685%
finn.schiermer-andersen_89

Pull #7654

Evergreen

finnschiermer
optimized string cache gc
Pull Request #7654: Fsa/string interning

102644 of 180648 branches covered (56.82%)

1005 of 1125 new or added lines in 15 files covered. (89.33%)

154 existing lines in 21 files now uncovered.

217953 of 240431 relevant lines covered (90.65%)

7671710.15 hits per line

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

89.94
/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/transaction.hpp>
30
#include <realm/dictionary.hpp>
31
#include <realm/exceptions.hpp>
32
#include <realm/impl/destroy_guard.hpp>
33
#include <realm/index_string.hpp>
34
#include <realm/query_conditions_tpl.hpp>
35
#include <realm/replication.hpp>
36
#include <realm/table_view.hpp>
37
#include <realm/util/features.h>
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
{
19,149✔
269
    if (size() != other.size())
19,149✔
270
        return false;
×
271
    size_t sz = size();
19,149✔
272
    for (size_t i = 0; i < sz; i++) {
30,264✔
273
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
19,281✔
274
        if (this->at(i).second != other.at(i).second)
19,281✔
275
            return false;
8,166✔
276
    }
19,281✔
277
    return true;
10,983✔
278
}
19,149✔
279

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

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

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

350
// -- Table ---------------------------------------------------------------------------------
351

352
Table::Table(Allocator& alloc)
353
    : m_alloc(alloc)
1,779✔
354
    , m_top(m_alloc)
1,779✔
355
    , m_spec(m_alloc)
1,779✔
356
    , m_clusters(this, m_alloc, top_position_for_cluster_tree)
1,779✔
357
    , m_index_refs(m_alloc)
1,779✔
358
    , m_opposite_table(m_alloc)
1,779✔
359
    , m_opposite_column(m_alloc)
1,779✔
360
    , m_interner_data(m_alloc)
1,779✔
361
    , m_repl(&g_dummy_replication)
1,779✔
362
    , m_own_ref(this, alloc.get_instance_version())
1,779✔
363
{
3,558✔
364
    m_spec.set_parent(&m_top, top_position_for_spec);
3,558✔
365
    m_index_refs.set_parent(&m_top, top_position_for_search_indexes);
3,558✔
366
    m_opposite_table.set_parent(&m_top, top_position_for_opposite_table);
3,558✔
367
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
3,558✔
368
    m_interner_data.set_parent(&m_top, top_position_for_interners);
3,558✔
369
    ref_type ref = create_empty_table(m_alloc); // Throws
3,558✔
370
    ArrayParent* parent = nullptr;
3,558✔
371
    size_t ndx_in_parent = 0;
3,558✔
372
    init(ref, parent, ndx_in_parent, true, false);
3,558✔
373
}
3,558✔
374

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

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

404
    ColumnAttrMask attr;
472,314✔
405
    if (collection_type) {
472,314✔
406
        switch (*collection_type) {
150,195✔
407
            case CollectionType::List:
61,734✔
408
                attr.set(col_attr_List);
61,734✔
409
                break;
61,734✔
410
            case CollectionType::Set:
43,332✔
411
                attr.set(col_attr_Set);
43,332✔
412
                break;
43,332✔
413
            case CollectionType::Dictionary:
45,129✔
414
                attr.set(col_attr_Dictionary);
45,129✔
415
                break;
45,129✔
416
        }
150,195✔
417
    }
150,195✔
418
    if (nullable || type == type_Mixed)
472,314✔
419
        attr.set(col_attr_Nullable);
160,248✔
420
    ColKey col_key = generate_col_key(ColumnType(type), attr);
472,314✔
421

422
    Table* invalid_link = nullptr;
472,314✔
423
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
472,314✔
424
}
472,314✔
425

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

439
    m_has_any_embedded_objects.reset();
74,253✔
440

441
    DataType data_type = type_Link;
74,253✔
442
    ColumnAttrMask attr;
74,253✔
443
    if (collection_type) {
74,253✔
444
        switch (*collection_type) {
43,443✔
445
            case CollectionType::List:
21,354✔
446
                attr.set(col_attr_List);
21,354✔
447
                break;
21,354✔
448
            case CollectionType::Set:
10,731✔
449
                if (target.is_embedded())
10,731✔
450
                    throw IllegalOperation("Set of embedded objects not supported");
×
451
                attr.set(col_attr_Set);
10,731✔
452
                break;
10,731✔
453
            case CollectionType::Dictionary:
11,358✔
454
                attr.set(col_attr_Dictionary);
11,358✔
455
                attr.set(col_attr_Nullable);
11,358✔
456
                break;
11,358✔
457
        }
43,443✔
458
    }
43,443✔
459
    else {
30,810✔
460
        attr.set(col_attr_Nullable);
30,810✔
461
    }
30,810✔
462
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
74,253✔
463

464
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
74,253✔
465
}
74,253✔
466

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

473
    do {
23,799✔
474
        cascade_state.send_notifications();
23,799✔
475

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

485
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
23,799✔
486
        for (auto obj : to_delete) {
23,799✔
487
            auto table = obj.first == m_key ? this : group->get_table_unchecked(obj.first);
17,037✔
488
            // This might add to the list of objects that should be deleted
489
            REALM_ASSERT(!obj.second.is_unresolved());
17,037✔
490
            table->m_clusters.erase(obj.second, cascade_state);
17,037✔
491
        }
17,037✔
492
        nullify_links(cascade_state);
23,799✔
493
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
23,799✔
494
}
19,122✔
495

496
void Table::nullify_links(CascadeState& cascade_state)
497
{
29,886✔
498
    Group* group = get_parent_group();
29,886✔
499
    REALM_ASSERT(group);
29,886✔
500
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
29,886✔
501
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
8,469✔
502
        if (!table->is_asymmetric())
8,469✔
503
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
8,469✔
504
    }
8,469✔
505
}
29,886✔
506

507
CollectionType Table::get_collection_type(ColKey col_key) const
508
{
9,156✔
509
    if (col_key.is_list()) {
9,156✔
510
        return CollectionType::List;
648✔
511
    }
648✔
512
    if (col_key.is_set()) {
8,508✔
513
        return CollectionType::Set;
1,056✔
514
    }
1,056✔
515
    REALM_ASSERT(col_key.is_dictionary());
7,452✔
516
    return CollectionType::Dictionary;
7,452✔
517
}
8,508✔
518

519
void Table::remove_columns()
520
{
2,457✔
521
    for (size_t i = get_column_count(); i > 0; --i) {
7,077✔
522
        ColKey col_key = spec_ndx2colkey(i - 1);
4,620✔
523
        remove_column(col_key);
4,620✔
524
    }
4,620✔
525
}
2,457✔
526

527
void Table::remove_column(ColKey col_key)
528
{
5,301✔
529
    check_column(col_key);
5,301✔
530

531
    if (Replication* repl = get_repl())
5,301✔
532
        repl->erase_column(this, col_key); // Throws
4,203✔
533

534
    if (col_key == m_primary_key_col) {
5,301✔
535
        do_set_primary_key_column(ColKey());
897✔
536
    }
897✔
537
    else {
4,404✔
538
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
4,404✔
539
    }
4,404✔
540

541
    erase_root_column(col_key); // Throws
5,301✔
542
    m_has_any_embedded_objects.reset();
5,301✔
543
    auto i = col_key.get_index().val;
5,301✔
544
    if (i < m_string_interners.size() && m_string_interners[i])
5,301✔
NEW
545
        m_string_interners[i].reset();
×
546
}
5,301✔
547

548

549
void Table::rename_column(ColKey col_key, StringData name)
550
{
87✔
551
    check_column(col_key);
87✔
552

553
    auto col_ndx = colkey2spec_ndx(col_key);
87✔
554
    m_spec.rename_column(col_ndx, name); // Throws
87✔
555

556
    bump_content_version();
87✔
557
    bump_storage_version();
87✔
558

559
    if (Replication* repl = get_repl())
87✔
560
        repl->rename_column(this, col_key, name); // Throws
87✔
561
}
87✔
562

563

564
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
565
{
2,284,863✔
566
    // well, not quite "direct", more like "almost direct":
567
    Array table_top(alloc);
2,284,863✔
568
    table_top.init_from_ref(top_ref);
2,284,863✔
569
    if (table_top.size() > 3) {
2,284,863✔
570
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
2,279,028✔
571
        return TableKey(int32_t(rot.get_as_int()));
2,279,028✔
572
    }
2,279,028✔
573
    else {
5,835✔
574
        return TableKey();
5,835✔
575
    }
5,835✔
576
}
2,284,863✔
577

578

579
void Table::init(ref_type top_ref, ArrayParent* parent, size_t ndx_in_parent, bool is_writable, bool is_frzn)
580
{
2,052,807✔
581
    REALM_ASSERT(!(is_writable && is_frzn));
2,052,807✔
582
    m_is_frozen = is_frzn;
2,052,807✔
583
    m_alloc.set_read_only(!is_writable);
2,052,807✔
584
    // Load from allocated memory
585
    m_top.set_parent(parent, ndx_in_parent);
2,052,807✔
586
    m_top.init_from_ref(top_ref);
2,052,807✔
587

588
    m_spec.init_from_parent();
2,052,807✔
589

590
    while (m_top.size() <= top_position_for_pk_col) {
2,052,807✔
591
        m_top.add(0);
×
592
    }
×
593

594
    if (m_top.get_as_ref(top_position_for_cluster_tree) == 0) {
2,052,807✔
595
        // This is an upgrade - create cluster
596
        MemRef mem = Cluster::create_empty_cluster(m_top.get_alloc()); // Throws
×
597
        m_top.set_as_ref(top_position_for_cluster_tree, mem.get_ref());
×
598
    }
×
599
    m_clusters.init_from_parent();
2,052,807✔
600

601
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_key);
2,052,807✔
602
    if (!rot.is_tagged()) {
2,052,807✔
603
        // Create table key
604
        rot = RefOrTagged::make_tagged(ndx_in_parent);
×
605
        m_top.set(top_position_for_key, rot);
×
606
    }
×
607
    m_key = TableKey(int32_t(rot.get_as_int()));
2,052,807✔
608

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

642
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
2,052,807✔
643
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
2,052,807✔
644

645
    if (m_top.size() <= top_position_for_flags) {
2,052,807✔
646
        m_table_type = Type::TopLevel;
30✔
647
    }
30✔
648
    else {
2,052,777✔
649
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
2,052,777✔
650
        m_table_type = Type(flags & table_type_mask);
2,052,777✔
651
    }
2,052,777✔
652
    m_has_any_embedded_objects.reset();
2,052,807✔
653

654
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
2,052,807✔
655
        // Tombstones exists
656
        if (!m_tombstones) {
42,435✔
657
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
33,741✔
658
        }
33,741✔
659
        m_tombstones->init_from_parent();
42,435✔
660
    }
42,435✔
661
    else {
2,010,372✔
662
        m_tombstones = nullptr;
2,010,372✔
663
    }
2,010,372✔
664
    if (m_top.size() > top_position_for_interners && m_top.get_as_ref(top_position_for_interners)) {
2,052,807✔
665
        // Interner data exist
666
        m_interner_data.init_from_parent();
1,777,656✔
667
    }
1,777,656✔
668
    else {
275,151✔
669
        REALM_ASSERT_DEBUG(!m_interner_data.is_attached());
275,151✔
670
    }
275,151✔
671
    refresh_string_interners(is_writable);
2,052,807✔
672
    m_cookie = cookie_initialized;
2,052,807✔
673
}
2,052,807✔
674

675

676
ColKey Table::do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table, DataType key_type)
677
{
546,567✔
678
    col_key = do_insert_root_column(col_key, ColumnType(type), name, key_type); // Throws
546,567✔
679

680
    // When the inserted column is a link-type column, we must also add a
681
    // backlink column to the target table.
682

683
    if (target_table) {
546,567✔
684
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
74,247✔
685
        target_table->check_column(backlink_col_key);
74,247✔
686

687
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
74,247✔
688
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
74,247✔
689
    }
74,247✔
690

691
    if (Replication* repl = get_repl())
546,567✔
692
        repl->insert_column(this, col_key, type, name, target_table); // Throws
528,561✔
693

694
    return col_key;
546,567✔
695
}
546,567✔
696

697
template <typename Type>
698
static void do_bulk_insert_index(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
699
{
102,753✔
700
    using LeafType = typename ColumnTypeTraits<Type>::cluster_leaf_type;
102,753✔
701
    LeafType leaf(alloc);
102,753✔
702

703
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
107,841✔
704
        cluster->init_leaf(col_key, &leaf);
107,841✔
705
        index->insert_bulk(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
107,841✔
706
        return IteratorControl::AdvanceToNext;
107,841✔
707
    };
107,841✔
708

709
    table->traverse_clusters(f);
102,753✔
710
}
102,753✔
711

712

713
static void do_bulk_insert_index_list(Table* table, SearchIndex* index, ColKey col_key, Allocator& alloc)
714
{
24✔
715
    ArrayInteger leaf(alloc);
24✔
716

717
    auto f = [&col_key, &index, &leaf](const Cluster* cluster) {
258✔
718
        cluster->init_leaf(col_key, &leaf);
258✔
719
        index->insert_bulk_list(cluster->get_key_array(), cluster->get_offset(), cluster->node_size(), leaf);
258✔
720
        return IteratorControl::AdvanceToNext;
258✔
721
    };
258✔
722

723
    table->traverse_clusters(f);
24✔
724
}
24✔
725

726
void Table::populate_search_index(ColKey col_key)
727
{
102,777✔
728
    auto col_ndx = col_key.get_index().val;
102,777✔
729
    SearchIndex* index = m_index_accessors[col_ndx].get();
102,777✔
730
    DataType type = get_column_type(col_key);
102,777✔
731

732
    if (type == type_Int) {
102,777✔
733
        if (is_nullable(col_key)) {
49,305✔
734
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
13,791✔
735
        }
13,791✔
736
        else {
35,514✔
737
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
35,514✔
738
        }
35,514✔
739
    }
49,305✔
740
    else if (type == type_Bool) {
53,472✔
741
        if (is_nullable(col_key)) {
48✔
742
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
743
        }
24✔
744
        else {
24✔
745
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
24✔
746
        }
24✔
747
    }
48✔
748
    else if (type == type_String) {
53,424✔
749
        if (col_key.is_list()) {
8,691✔
750
            do_bulk_insert_index_list(this, index, col_key, get_alloc());
24✔
751
        }
24✔
752
        else {
8,667✔
753
            do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
8,667✔
754
        }
8,667✔
755
    }
8,691✔
756
    else if (type == type_Timestamp) {
44,733✔
757
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
96✔
758
    }
96✔
759
    else if (type == type_ObjectId) {
44,637✔
760
        if (is_nullable(col_key)) {
43,047✔
761
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
984✔
762
        }
984✔
763
        else {
42,063✔
764
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
42,063✔
765
        }
42,063✔
766
    }
43,047✔
767
    else if (type == type_UUID) {
1,590✔
768
        if (is_nullable(col_key)) {
684✔
769
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
770
        }
516✔
771
        else {
168✔
772
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
168✔
773
        }
168✔
774
    }
684✔
775
    else if (type == type_Mixed) {
906✔
776
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
906✔
777
    }
906✔
778
    else {
×
779
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
780
    }
×
781
}
102,777✔
782

783
void Table::erase_from_search_indexes(ObjKey key)
784
{
5,000,574✔
785
    // Tombstones do not use index - will crash if we try to erase values
786
    if (!key.is_unresolved()) {
5,000,574✔
787
        for (auto&& index : m_index_accessors) {
6,669,636✔
788
            if (index) {
6,669,636✔
789
                index->erase(key);
255,294✔
790
            }
255,294✔
791
        }
6,669,636✔
792
    }
4,989,582✔
793
}
5,000,574✔
794

795
void Table::update_indexes(ObjKey key, const FieldValues& values)
796
{
25,321,002✔
797
    // Tombstones do not use index - will crash if we try to insert values
798
    if (key.is_unresolved()) {
25,321,002✔
799
        return;
12,522✔
800
    }
12,522✔
801

802
    auto sz = m_index_accessors.size();
25,308,480✔
803
    // values are sorted by column index - there may be values missing
804
    auto value = values.begin();
25,308,480✔
805
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
62,073,360✔
806
        // Check if initial value is provided
807
        Mixed init_value;
36,764,898✔
808
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
36,764,898✔
809
            // Value for this column is provided
810
            init_value = value->value;
812,142✔
811
            ++value;
812,142✔
812
        }
812,142✔
813

814
        if (auto&& index = m_index_accessors[column_ndx]) {
36,764,898✔
815
            // There is an index for this column
816
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,387,428✔
817
            if (col_key.is_collection())
1,387,428✔
818
                continue;
102✔
819
            auto type = col_key.get_type();
1,387,326✔
820
            auto attr = col_key.get_attrs();
1,387,326✔
821
            bool nullable = attr.test(col_attr_Nullable);
1,387,326✔
822
            switch (type) {
1,387,326✔
823
                case col_type_Int:
479,784✔
824
                    if (init_value.is_null()) {
479,784✔
825
                        index->insert(key, ArrayIntNull::default_value(nullable));
166,872✔
826
                    }
166,872✔
827
                    else {
312,912✔
828
                        index->insert(key, init_value.get<int64_t>());
312,912✔
829
                    }
312,912✔
830
                    break;
479,784✔
831
                case col_type_Bool:
6,051✔
832
                    if (init_value.is_null()) {
6,051✔
833
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,051✔
834
                    }
6,051✔
835
                    else {
×
836
                        index->insert(key, init_value.get<bool>());
×
837
                    }
×
838
                    break;
6,051✔
839
                case col_type_String:
779,271✔
840
                    if (init_value.is_null()) {
779,271✔
841
                        index->insert(key, ArrayString::default_value(nullable));
432,546✔
842
                    }
432,546✔
843
                    else {
346,725✔
844
                        index->insert(key, init_value.get<String>());
346,725✔
845
                    }
346,725✔
846
                    break;
779,271✔
847
                case col_type_Timestamp:
7,371✔
848
                    if (init_value.is_null()) {
7,371✔
849
                        index->insert(key, ArrayTimestamp::default_value(nullable));
7,371✔
850
                    }
7,371✔
851
                    else {
×
852
                        index->insert(key, init_value.get<Timestamp>());
×
853
                    }
×
854
                    break;
7,371✔
855
                case col_type_ObjectId:
93,039✔
856
                    if (init_value.is_null()) {
93,039✔
857
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
7,320✔
858
                    }
7,320✔
859
                    else {
85,719✔
860
                        index->insert(key, init_value.get<ObjectId>());
85,719✔
861
                    }
85,719✔
862
                    break;
93,039✔
863
                case col_type_Mixed:
2,286✔
864
                    index->insert(key, init_value);
2,286✔
865
                    break;
2,286✔
866
                case col_type_UUID:
19,542✔
867
                    if (init_value.is_null()) {
19,542✔
868
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
7,338✔
869
                    }
7,338✔
870
                    else {
12,204✔
871
                        index->insert(key, init_value.get<UUID>());
12,204✔
872
                    }
12,204✔
873
                    break;
19,542✔
874
                default:
✔
875
                    REALM_UNREACHABLE();
876
            }
1,387,326✔
877
        }
1,387,326✔
878
    }
36,764,898✔
879
}
25,308,480✔
880

881
void Table::clear_indexes()
882
{
6,015✔
883
    for (auto&& index : m_index_accessors) {
57,105✔
884
        if (index) {
57,105✔
885
            index->clear();
4,755✔
886
        }
4,755✔
887
    }
57,105✔
888
}
6,015✔
889

890
void Table::do_add_search_index(ColKey col_key, IndexType type)
891
{
102,969✔
892
    size_t column_ndx = col_key.get_index().val;
102,969✔
893

894
    // Early-out if already indexed
895
    if (m_index_accessors[column_ndx] != nullptr)
102,969✔
896
        return;
150✔
897

898
    if (!StringIndex::type_supported(DataType(col_key.get_type())) ||
102,819✔
899
        (col_key.is_collection() && !(col_key.is_list() && col_key.get_type() == col_type_String)) ||
102,819✔
900
        (type == IndexType::Fulltext && col_key.get_type() != col_type_String)) {
102,819✔
901
        // Not ideal, but this is what we used to throw, so keep throwing that for compatibility reasons, even though
902
        // it should probably be a type mismatch exception instead.
903
        throw IllegalOperation(util::format("Index not supported for this property: %1", get_column_name(col_key)));
42✔
904
    }
42✔
905

906
    // m_index_accessors always has the same number of pointers as the number of columns. Columns without search
907
    // index have 0-entries.
908
    REALM_ASSERT(m_index_accessors.size() == m_leaf_ndx2colkey.size());
102,777✔
909
    REALM_ASSERT(m_index_accessors[column_ndx] == nullptr);
102,777✔
910

911
    // Create the index
912
    m_index_accessors[column_ndx] =
102,777✔
913
        std::make_unique<StringIndex>(ClusterColumn(&m_clusters, col_key, type), get_alloc()); // Throws
102,777✔
914
    SearchIndex* index = m_index_accessors[column_ndx].get();
102,777✔
915
    // Insert ref to index
916
    index->set_parent(&m_index_refs, column_ndx);
102,777✔
917

918
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
102,777✔
919

920
    populate_search_index(col_key);
102,777✔
921
}
102,777✔
922

923
void Table::add_search_index(ColKey col_key, IndexType type)
924
{
3,927✔
925
    check_column(col_key);
3,927✔
926

927
    // Check spec
928
    auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
3,927✔
929
    auto attr = m_spec.get_column_attr(spec_ndx);
3,927✔
930

931
    if (col_key == m_primary_key_col && type == IndexType::Fulltext)
3,927✔
932
        throw InvalidColumnKey("primary key cannot have a full text index");
6✔
933

934
    switch (type) {
3,921✔
935
        case IndexType::None:
✔
936
            remove_search_index(col_key);
×
937
            return;
×
938
        case IndexType::Fulltext:
54✔
939
            // Early-out if already indexed
940
            if (attr.test(col_attr_FullText_Indexed)) {
54✔
941
                REALM_ASSERT(search_index_type(col_key) == IndexType::Fulltext);
×
942
                return;
×
943
            }
×
944
            if (attr.test(col_attr_Indexed)) {
54✔
945
                this->remove_search_index(col_key);
×
946
            }
×
947
            break;
54✔
948
        case IndexType::General:
3,867✔
949
            if (attr.test(col_attr_Indexed)) {
3,867✔
950
                REALM_ASSERT(search_index_type(col_key) == IndexType::General);
30✔
951
                return;
30✔
952
            }
30✔
953
            if (attr.test(col_attr_FullText_Indexed)) {
3,837✔
954
                this->remove_search_index(col_key);
×
955
            }
×
956
            break;
3,837✔
957
    }
3,921✔
958

959
    do_add_search_index(col_key, type);
3,891✔
960

961
    // Update spec
962
    attr.set(type == IndexType::Fulltext ? col_attr_FullText_Indexed : col_attr_Indexed);
3,891✔
963
    m_spec.set_column_attr(spec_ndx, attr); // Throws
3,891✔
964
}
3,891✔
965

966
void Table::remove_search_index(ColKey col_key)
967
{
1,311✔
968
    check_column(col_key);
1,311✔
969
    auto column_ndx = col_key.get_index();
1,311✔
970

971
    // Early-out if non-indexed
972
    if (m_index_accessors[column_ndx.val] == nullptr)
1,311✔
973
        return;
66✔
974

975
    // Destroy and remove the index column
976
    auto& index = m_index_accessors[column_ndx.val];
1,245✔
977
    REALM_ASSERT(index != nullptr);
1,245✔
978
    index->destroy();
1,245✔
979
    index.reset();
1,245✔
980

981
    m_index_refs.set(column_ndx.val, 0);
1,245✔
982

983
    // update spec
984
    auto spec_ndx = leaf_ndx2spec_ndx(column_ndx);
1,245✔
985
    auto attr = m_spec.get_column_attr(spec_ndx);
1,245✔
986
    attr.reset(col_attr_Indexed);
1,245✔
987
    attr.reset(col_attr_FullText_Indexed);
1,245✔
988
    m_spec.set_column_attr(spec_ndx, attr); // Throws
1,245✔
989
}
1,245✔
990

991
void Table::enumerate_string_column(ColKey col_key)
992
{
1,299✔
993
    check_column(col_key);
1,299✔
994
    size_t column_ndx = colkey2spec_ndx(col_key);
1,299✔
995
    ColumnType type = col_key.get_type();
1,299✔
996
    if (type == col_type_String && !col_key.is_collection() && !m_spec.is_string_enum_type(column_ndx)) {
1,299✔
997
        m_clusters.enumerate_string_column(col_key);
690✔
998
    }
690✔
999
}
1,299✔
1000

1001
bool Table::is_enumerated(ColKey col_key) const noexcept
1002
{
53,919✔
1003
    size_t col_ndx = colkey2spec_ndx(col_key);
53,919✔
1004
    return m_spec.is_string_enum_type(col_ndx);
53,919✔
1005
}
53,919✔
1006

1007
size_t Table::get_num_unique_values(ColKey col_key) const
1008
{
138✔
1009
    if (!is_enumerated(col_key))
138✔
1010
        return 0;
84✔
1011

1012
    ArrayParent* parent;
54✔
1013
    ref_type ref = const_cast<Spec&>(m_spec).get_enumkeys_ref(colkey2spec_ndx(col_key), parent);
54✔
1014
    BPlusTree<StringData> col(get_alloc());
54✔
1015
    col.init_from_ref(ref);
54✔
1016

1017
    return col.size();
54✔
1018
}
138✔
1019

1020

1021
void Table::erase_root_column(ColKey col_key)
1022
{
5,577✔
1023
    ColumnType col_type = col_key.get_type();
5,577✔
1024
    if (is_link_type(col_type)) {
5,577✔
1025
        auto target_table = get_opposite_table(col_key);
621✔
1026
        auto target_column = get_opposite_column(col_key);
621✔
1027
        target_table->do_erase_root_column(target_column);
621✔
1028
    }
621✔
1029
    do_erase_root_column(col_key); // Throws
5,577✔
1030
}
5,577✔
1031

1032

1033
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
1034
{
727,626✔
1035
    // if col_key specifies a key, it must be unused
1036
    REALM_ASSERT(!col_key || !valid_column(col_key));
727,626✔
1037

1038
    // locate insertion point: ordinary columns must come before backlink columns
1039
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
727,626✔
1040

1041
    if (!col_key) {
727,626✔
1042
        col_key = generate_col_key(type, {});
82,005✔
1043
    }
82,005✔
1044

1045
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
727,626✔
1046
    if (col_key.is_dictionary()) {
727,626✔
1047
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
56,487✔
1048
    }
56,487✔
1049
    auto col_ndx = col_key.get_index().val;
727,626✔
1050
    build_column_mapping();
727,626✔
1051
    REALM_ASSERT(col_ndx <= m_index_refs.size());
727,626✔
1052
    if (col_ndx == m_index_refs.size()) {
727,626✔
1053
        m_index_refs.insert(col_ndx, 0);
727,383✔
1054
    }
727,383✔
1055
    else {
243✔
1056
        m_index_refs.set(col_ndx, 0);
243✔
1057
    }
243✔
1058
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
727,626✔
1059
    if (col_ndx == m_opposite_table.size()) {
727,626✔
1060
        // m_opposite_table and m_opposite_column are always resized together!
1061
        m_opposite_table.insert(col_ndx, TableKey().value);
727,380✔
1062
        m_opposite_column.insert(col_ndx, ColKey().value);
727,380✔
1063
    }
727,380✔
1064
    else {
246✔
1065
        m_opposite_table.set(col_ndx, TableKey().value);
246✔
1066
        m_opposite_column.set(col_ndx, ColKey().value);
246✔
1067
    }
246✔
1068
    refresh_index_accessors();
727,626✔
1069
    m_clusters.insert_column(col_key);
727,626✔
1070
    if (m_tombstones) {
727,626✔
1071
        m_tombstones->insert_column(col_key);
327✔
1072
    }
327✔
1073
    // create string interners internal rep as well as data area
1074
    REALM_ASSERT_DEBUG(m_interner_data.is_attached());
727,626✔
1075
    while (col_ndx >= m_string_interners.size()) {
1,455,003✔
1076
        m_string_interners.push_back({});
727,377✔
1077
    }
727,377✔
1078
    while (col_ndx >= m_interner_data.size()) {
1,455,009✔
1079
        m_interner_data.add(0);
727,383✔
1080
    }
727,383✔
1081
    REALM_ASSERT(!m_string_interners[col_ndx]);
727,626✔
1082
    // FIXME: Limit creation of interners to EXACTLY the columns, where they can be
1083
    // relevant.
1084
    // if (col_key.get_type() == col_type_String)
1085
    m_string_interners[col_ndx] = std::make_unique<StringInterner>(m_alloc, m_interner_data, col_key, true);
727,626✔
1086
    bump_storage_version();
727,626✔
1087

1088
    return col_key;
727,626✔
1089
}
727,626✔
1090

1091

1092
void Table::do_erase_root_column(ColKey col_key)
1093
{
6,198✔
1094
    size_t col_ndx = col_key.get_index().val;
6,198✔
1095
    // If the column had a source index we have to remove and destroy that as well
1096
    ref_type index_ref = m_index_refs.get_as_ref(col_ndx);
6,198✔
1097
    if (index_ref) {
6,198✔
1098
        Array::destroy_deep(index_ref, m_index_refs.get_alloc());
132✔
1099
        m_index_refs.set(col_ndx, 0);
132✔
1100
        m_index_accessors[col_ndx].reset();
132✔
1101
    }
132✔
1102
    m_opposite_table.set(col_ndx, TableKey().value);
6,198✔
1103
    m_opposite_column.set(col_ndx, ColKey().value);
6,198✔
1104
    m_index_accessors[col_ndx] = nullptr;
6,198✔
1105
    m_clusters.remove_column(col_key);
6,198✔
1106
    if (m_tombstones)
6,198✔
1107
        m_tombstones->remove_column(col_key);
495✔
1108
    size_t spec_ndx = colkey2spec_ndx(col_key);
6,198✔
1109
    m_spec.erase_column(spec_ndx);
6,198✔
1110
    m_top.adjust(top_position_for_column_key, 2);
6,198✔
1111

1112
    build_column_mapping();
6,198✔
1113
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
11,823✔
1114
        REALM_ASSERT(m_index_accessors.back() == nullptr);
5,625✔
1115
        m_index_accessors.pop_back();
5,625✔
1116
    }
5,625✔
1117
    REALM_ASSERT_DEBUG(col_ndx < m_string_interners.size());
6,198✔
1118
    if (m_string_interners[col_ndx]) {
6,198✔
1119
        REALM_ASSERT_DEBUG(m_interner_data.is_attached());
6,198✔
1120
        REALM_ASSERT_DEBUG(col_ndx < m_interner_data.size());
6,198✔
1121
        auto data_ref = m_interner_data.get_as_ref(col_ndx);
6,198✔
1122
        if (data_ref)
6,198✔
1123
            Array::destroy_deep(data_ref, m_alloc);
6,198✔
1124
        m_interner_data.set(col_ndx, 0);
6,198✔
1125
        // m_string_interners[col_ndx]->update_from_parent(true);
1126
        m_string_interners[col_ndx].reset();
6,198✔
1127
    }
6,198✔
1128
    bump_content_version();
6,198✔
1129
    bump_storage_version();
6,198✔
1130
}
6,198✔
1131

1132
Query Table::where(const Dictionary& dict) const
1133
{
12✔
1134
    return Query(m_own_ref, dict.clone_as_obj_list());
12✔
1135
}
12✔
1136

1137
void Table::set_table_type(Type table_type, bool handle_backlinks)
1138
{
312✔
1139
    if (table_type == m_table_type) {
312✔
1140
        return;
×
1141
    }
×
1142

1143
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
1144
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1145
                                                                   get_class_name(), m_table_type, table_type));
×
1146
    }
×
1147

1148
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1149
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1150
}
312✔
1151

1152
void Table::set_embedded(bool embedded, bool handle_backlinks)
1153
{
312✔
1154
    if (embedded == false) {
312✔
1155
        do_set_table_type(Type::TopLevel);
24✔
1156
        return;
24✔
1157
    }
24✔
1158

1159
    // Embedded objects cannot have a primary key.
1160
    if (get_primary_key_column()) {
288✔
1161
        throw IllegalOperation(
6✔
1162
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1163
    }
6✔
1164

1165
    if (size() == 0) {
282✔
1166
        do_set_table_type(Type::Embedded);
42✔
1167
        return;
42✔
1168
    }
42✔
1169

1170
    // Check all of the objects for invalid incoming links. Each embedded object
1171
    // must have exactly one incoming link, and it must be from a non-Mixed property.
1172
    // Objects with no incoming links are either deleted or an error (depending
1173
    // on `handle_backlinks`), and objects with multiple incoming links are either
1174
    // cloned for each of the incoming links or an error (again depending on `handle_backlinks`).
1175
    // Incoming links from a Mixed property are always an error, as those can't
1176
    // link to embedded objects
1177
    ArrayInteger leaf(get_alloc());
240✔
1178
    enum class LinkCount : int8_t { None, One, Multiple };
240✔
1179
    std::vector<LinkCount> incoming_link_count;
240✔
1180
    std::vector<ObjKey> orphans;
240✔
1181
    std::vector<ObjKey> multiple_incoming_links;
240✔
1182
    traverse_clusters([&](const Cluster* cluster) {
474✔
1183
        size_t size = cluster->node_size();
474✔
1184
        incoming_link_count.assign(size, LinkCount::None);
474✔
1185

1186
        for_each_backlink_column([&](ColKey col) {
606✔
1187
            cluster->init_leaf(col, &leaf);
606✔
1188
            // Width zero means all the values are zero and there can't be any backlinks
1189
            if (leaf.get_width() == 0) {
606✔
1190
                return IteratorControl::AdvanceToNext;
36✔
1191
            }
36✔
1192

1193
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1194
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1195
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1196
                    // ref of zero means there's no backlinks
1197
                    continue;
59,340✔
1198
                }
59,340✔
1199

1200
                if (value.is_ref()) {
960✔
1201
                    // Any other ref indicates an array of backlinks, which will
1202
                    // always have more than one entry
1203
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1204
                }
78✔
1205
                else {
882✔
1206
                    // Otherwise it's a tagged ref to the single linking object
1207
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1208
                        incoming_link_count[i] = LinkCount::One;
792✔
1209
                    }
792✔
1210
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1211
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1212
                    }
42✔
1213
                }
882✔
1214

1215
                auto source_col = get_opposite_column(col);
960✔
1216
                if (source_col.get_type() == col_type_Mixed) {
960✔
1217
                    auto source_table = get_opposite_table(col);
54✔
1218
                    throw IllegalOperation(util::format(
54✔
1219
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1220
                        "which does not support linking to embedded objects.",
54✔
1221
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1222
                }
54✔
1223
            }
960✔
1224
            return IteratorControl::AdvanceToNext;
516✔
1225
        });
570✔
1226

1227
        for (size_t i = 0; i < size; ++i) {
60,660✔
1228
            if (incoming_link_count[i] == LinkCount::None) {
60,240✔
1229
                if (!handle_backlinks) {
59,424✔
1230
                    throw IllegalOperation(util::format("Cannot convert '%1' to embedded: at least one object has no "
18✔
1231
                                                        "incoming links and would be deleted.",
18✔
1232
                                                        get_class_name()));
18✔
1233
                }
18✔
1234
                orphans.push_back(cluster->get_real_key(i));
59,406✔
1235
            }
59,406✔
1236
            else if (incoming_link_count[i] == LinkCount::Multiple) {
816✔
1237
                if (!handle_backlinks) {
90✔
1238
                    throw IllegalOperation(util::format(
36✔
1239
                        "Cannot convert '%1' to embedded: at least one object has more than one incoming link.",
36✔
1240
                        get_class_name()));
36✔
1241
                }
36✔
1242
                multiple_incoming_links.push_back(cluster->get_real_key(i));
54✔
1243
            }
54✔
1244
        }
60,240✔
1245

1246
        return IteratorControl::AdvanceToNext;
420✔
1247
    });
474✔
1248

1249
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
1250
    for (auto key : orphans) {
59,406✔
1251
        remove_object(key);
59,406✔
1252
    }
59,406✔
1253
    for (auto key : multiple_incoming_links) {
240✔
1254
        auto obj = get_object(key);
54✔
1255
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1256
        obj.remove();
54✔
1257
    }
54✔
1258

1259
    do_set_table_type(Type::Embedded);
240✔
1260
}
240✔
1261

1262
void Table::do_set_table_type(Type table_type)
1263
{
269,160✔
1264
    while (m_top.size() <= top_position_for_flags)
269,160✔
1265
        m_top.add(0);
×
1266

1267
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
269,160✔
1268
    // reset bits 0-1
1269
    flags &= ~table_type_mask;
269,160✔
1270
    // set table type
1271
    flags |= static_cast<uint8_t>(table_type);
269,160✔
1272
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
269,160✔
1273
    m_table_type = table_type;
269,160✔
1274
}
269,160✔
1275

1276

1277
void Table::detach(LifeCycleCookie cookie) noexcept
1278
{
2,047,410✔
1279
    m_cookie = cookie;
2,047,410✔
1280
    m_alloc.bump_instance_version();
2,047,410✔
1281
    // release string interners
1282
    m_string_interners.clear();
2,047,410✔
1283
    m_interner_data.detach();
2,047,410✔
1284
}
2,047,410✔
1285

1286
void Table::fully_detach() noexcept
1287
{
2,030,994✔
1288
    m_spec.detach();
2,030,994✔
1289
    m_top.detach();
2,030,994✔
1290
    m_index_refs.detach();
2,030,994✔
1291
    m_opposite_table.detach();
2,030,994✔
1292
    m_opposite_column.detach();
2,030,994✔
1293
    m_index_accessors.clear();
2,030,994✔
1294
    m_string_interners.clear();
2,030,994✔
1295
}
2,030,994✔
1296

1297

1298
Table::~Table() noexcept
1299
{
3,555✔
1300
    if (m_top.is_attached()) {
3,558✔
1301
        // If destroyed as a standalone table, destroy all memory allocated
1302
        if (m_top.get_parent() == nullptr) {
3,558✔
1303
            m_top.destroy_deep();
3,558✔
1304
        }
3,558✔
1305
        fully_detach();
3,558✔
1306
    }
3,558✔
1307
    else {
2,147,483,647✔
1308
        REALM_ASSERT(m_index_accessors.size() == 0);
2,147,483,647✔
1309
    }
2,147,483,647✔
1310
    m_cookie = cookie_deleted;
3,555✔
1311
}
3,555✔
1312

1313

1314
IndexType Table::search_index_type(ColKey col_key) const noexcept
1315
{
4,820,646✔
1316
    if (m_index_accessors[col_key.get_index().val].get()) {
4,820,646✔
1317
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
1,176,492✔
1318
        bool fulltext = attr.test(col_attr_FullText_Indexed);
1,176,492✔
1319
        return fulltext ? IndexType::Fulltext : IndexType::General;
1,176,492✔
1320
    }
1,176,492✔
1321
    return IndexType::None;
3,644,154✔
1322
}
4,820,646✔
1323

1324

1325
void Table::migrate_sets_and_dictionaries()
1326
{
180✔
1327
    std::vector<ColKey> to_migrate;
180✔
1328
    for (auto col : get_column_keys()) {
570✔
1329
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
1330
            to_migrate.push_back(col);
12✔
1331
        }
12✔
1332
    }
570✔
1333
    if (to_migrate.size()) {
180✔
1334
        for (auto obj : *this) {
6✔
1335
            for (auto col : to_migrate) {
12✔
1336
                if (col.is_set()) {
12✔
1337
                    auto set = obj.get_set<Mixed>(col);
6✔
1338
                    set.migrate();
6✔
1339
                }
6✔
1340
                else if (col.is_dictionary()) {
6✔
1341
                    auto dict = obj.get_dictionary(col);
6✔
1342
                    dict.migrate();
6✔
1343
                }
6✔
1344
            }
12✔
1345
        }
6✔
1346
    }
6✔
1347
}
180✔
1348

1349
void Table::migrate_set_orderings()
1350
{
354✔
1351
    std::vector<ColKey> to_migrate;
354✔
1352
    for (auto col : get_column_keys()) {
918✔
1353
        if (col.is_set() && (col.get_type() == col_type_Mixed || col.get_type() == col_type_String ||
918✔
1354
                             col.get_type() == col_type_Binary)) {
30✔
1355
            to_migrate.push_back(col);
30✔
1356
        }
30✔
1357
    }
918✔
1358
    if (to_migrate.size()) {
354✔
1359
        for (auto obj : *this) {
90✔
1360
            for (auto col : to_migrate) {
102✔
1361
                if (col.get_type() == col_type_Mixed) {
102✔
1362
                    auto set = obj.get_set<Mixed>(col);
12✔
1363
                    set.migration_resort();
12✔
1364
                }
12✔
1365
                else if (col.get_type() == col_type_Binary) {
90✔
1366
                    auto set = obj.get_set<BinaryData>(col);
6✔
1367
                    set.migration_resort();
6✔
1368
                }
6✔
1369
                else {
84✔
1370
                    REALM_ASSERT_3(col.get_type(), ==, col_type_String);
84✔
1371
                    auto set = obj.get_set<String>(col);
84✔
1372
                    set.migration_resort();
84✔
1373
                }
84✔
1374
            }
102✔
1375
        }
90✔
1376
    }
18✔
1377
}
354✔
1378

1379
void Table::migrate_col_keys()
1380
{
354✔
1381
    if (m_spec.migrate_column_keys()) {
354✔
1382
        build_column_mapping();
24✔
1383
    }
24✔
1384

1385
    // Fix also m_opposite_column col_keys
1386
    ColumnType col_type_LinkList(13);
354✔
1387
    auto sz = m_opposite_column.size();
354✔
1388

1389
    for (size_t n = 0; n < sz; n++) {
1,386✔
1390
        ColKey col_key(m_opposite_column.get(n));
1,032✔
1391
        if (col_key.get_type() == col_type_LinkList) {
1,032✔
1392
            auto attrs = col_key.get_attrs();
24✔
1393
            REALM_ASSERT(attrs.test(col_attr_List));
24✔
1394
            ColKey new_key(col_key.get_index(), col_type_Link, attrs, col_key.get_tag());
24✔
1395
            m_opposite_column.set(n, new_key.value);
24✔
1396
        }
24✔
1397
    }
1,032✔
1398
}
354✔
1399

1400
StringData Table::get_name() const noexcept
1401
{
3,117,468✔
1402
    const Array& real_top = m_top;
3,117,468✔
1403
    ArrayParent* parent = real_top.get_parent();
3,117,468✔
1404
    if (!parent)
3,117,468✔
1405
        return StringData("");
48✔
1406
    REALM_ASSERT(dynamic_cast<Group*>(parent));
3,117,420✔
1407
    return static_cast<Group*>(parent)->get_table_name(get_key());
3,117,420✔
1408
}
3,117,468✔
1409

1410
StringData Table::get_class_name() const noexcept
1411
{
681,891✔
1412
    return Group::table_name_to_class_name(get_name());
681,891✔
1413
}
681,891✔
1414

1415
const char* Table::get_state() const noexcept
1416
{
42✔
1417
    switch (m_cookie) {
42✔
1418
        case cookie_created:
✔
1419
            return "created";
×
1420
        case cookie_transaction_ended:
6✔
1421
            return "transaction_ended";
6✔
1422
        case cookie_initialized:
✔
1423
            return "initialised";
×
1424
        case cookie_removed:
36✔
1425
            return "removed";
36✔
1426
        case cookie_void:
✔
1427
            return "void";
×
1428
        case cookie_deleted:
✔
1429
            return "deleted";
×
1430
    }
42✔
1431
    return "";
×
1432
}
42✔
1433

1434

1435
bool Table::is_nullable(ColKey col_key) const
1436
{
750,531✔
1437
    REALM_ASSERT_DEBUG(valid_column(col_key));
750,531✔
1438
    return col_key.get_attrs().test(col_attr_Nullable);
750,531✔
1439
}
750,531✔
1440

1441
bool Table::is_list(ColKey col_key) const
1442
{
27,897✔
1443
    REALM_ASSERT_DEBUG(valid_column(col_key));
27,897✔
1444
    return col_key.get_attrs().test(col_attr_List);
27,897✔
1445
}
27,897✔
1446

1447

1448
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
1449
{
272,520✔
1450
    Array top(alloc);
272,520✔
1451
    _impl::DeepArrayDestroyGuard dg(&top);
272,520✔
1452
    top.create(Array::type_HasRefs); // Throws
272,520✔
1453
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
272,520✔
1454

1455
    {
272,520✔
1456
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
272,520✔
1457
        dg_2.reset(mem.get_ref());
272,520✔
1458
        int_fast64_t v(from_ref(mem.get_ref()));
272,520✔
1459
        top.add(v); // Throws
272,520✔
1460
        dg_2.release();
272,520✔
1461
    }
272,520✔
1462
    top.add(0); // Old position for columns
272,520✔
1463
    {
272,520✔
1464
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
272,520✔
1465
        dg_2.reset(mem.get_ref());
272,520✔
1466
        int_fast64_t v(from_ref(mem.get_ref()));
272,520✔
1467
        top.add(v); // Throws
272,520✔
1468
        dg_2.release();
272,520✔
1469
    }
272,520✔
1470

1471
    // Table key value
1472
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
272,520✔
1473
    top.add(rot);
272,520✔
1474

1475
    // Search indexes
1476
    {
272,520✔
1477
        bool context_flag = false;
272,520✔
1478
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
272,520✔
1479
        dg_2.reset(mem.get_ref());
272,520✔
1480
        int_fast64_t v(from_ref(mem.get_ref()));
272,520✔
1481
        top.add(v); // Throws
272,520✔
1482
        dg_2.release();
272,520✔
1483
    }
272,520✔
1484
    rot = RefOrTagged::make_tagged(0);
272,520✔
1485
    top.add(rot); // Column key
272,520✔
1486
    top.add(rot); // Version
272,520✔
1487
    dg.release();
272,520✔
1488
    // Opposite keys (table and column)
1489
    {
272,520✔
1490
        bool context_flag = false;
272,520✔
1491
        {
272,520✔
1492
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
272,520✔
1493
            dg_2.reset(mem.get_ref());
272,520✔
1494
            int_fast64_t v(from_ref(mem.get_ref()));
272,520✔
1495
            top.add(v); // Throws
272,520✔
1496
            dg_2.release();
272,520✔
1497
        }
272,520✔
1498
        {
272,520✔
1499
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
272,520✔
1500
            dg_2.reset(mem.get_ref());
272,520✔
1501
            int_fast64_t v(from_ref(mem.get_ref()));
272,520✔
1502
            top.add(v); // Throws
272,520✔
1503
            dg_2.release();
272,520✔
1504
        }
272,520✔
1505
    }
272,520✔
1506
    top.add(0); // Sequence number
272,520✔
1507
    top.add(0); // Collision_map
272,520✔
1508
    top.add(0); // pk col key
272,520✔
1509
    top.add(0); // flags
272,520✔
1510
    top.add(0); // tombstones
272,520✔
1511
    top.add(0); // string interners
272,520✔
1512

1513
    REALM_ASSERT(top.size() == top_array_size);
272,520✔
1514

1515
    return top.get_ref();
272,520✔
1516
}
272,520✔
1517

1518
void Table::ensure_graveyard()
1519
{
12,867✔
1520
    if (!m_tombstones) {
12,867✔
1521
        while (m_top.size() < top_position_for_tombstones)
2,232✔
1522
            m_top.add(0);
×
1523
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
2,232✔
1524
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
2,232✔
1525
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
2,232✔
1526
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
2,232✔
1527
        m_tombstones->init_from_parent();
2,232✔
1528
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
8,232✔
1529
            ts->insert_column(col);
8,232✔
1530
            return IteratorControl::AdvanceToNext;
8,232✔
1531
        });
8,232✔
1532
    }
2,232✔
1533
}
12,867✔
1534

1535
void Table::batch_erase_rows(const KeyColumn& keys)
1536
{
558✔
1537
    size_t num_objs = keys.size();
558✔
1538
    std::vector<ObjKey> vec;
558✔
1539
    vec.reserve(num_objs);
558✔
1540
    for (size_t i = 0; i < num_objs; ++i) {
2,898✔
1541
        ObjKey key = keys.get(i);
2,340✔
1542
        if (key != null_key && is_valid(key)) {
2,340✔
1543
            vec.push_back(key);
2,340✔
1544
        }
2,340✔
1545
    }
2,340✔
1546

1547
    sort(vec.begin(), vec.end());
558✔
1548
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
558✔
1549

1550
    batch_erase_objects(vec);
558✔
1551
}
558✔
1552

1553
void Table::batch_erase_objects(std::vector<ObjKey>& keys)
1554
{
6,813✔
1555
    Group* g = get_parent_group();
6,813✔
1556
    bool maybe_has_incoming_links = g && !is_asymmetric();
6,813✔
1557

1558
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
6,813✔
1559
        CascadeState state(CascadeState::Mode::Strong, g);
6,045✔
1560
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
6,045✔
1561
            state.m_to_be_deleted.emplace_back(m_key, k);
1,773✔
1562
        });
1,773✔
1563
        if (maybe_has_incoming_links)
6,045✔
1564
            nullify_links(state);
6,045✔
1565
        remove_recursive(state);
6,045✔
1566
    }
6,045✔
1567
    else {
768✔
1568
        CascadeState state(CascadeState::Mode::None, g);
768✔
1569
        for (auto k : keys) {
2,512,302✔
1570
            if (maybe_has_incoming_links) {
2,512,302✔
1571
                m_clusters.nullify_incoming_links(k, state);
2,512,224✔
1572
            }
2,512,224✔
1573
            m_clusters.erase(k, state);
2,512,302✔
1574
        }
2,512,302✔
1575
    }
768✔
1576
    keys.clear();
6,813✔
1577
}
6,813✔
1578

1579
void Table::clear()
1580
{
6,015✔
1581
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
6,015✔
1582
    m_clusters.clear(state);
6,015✔
1583
    free_collision_table();
6,015✔
1584
}
6,015✔
1585

1586

1587
Group* Table::get_parent_group() const noexcept
1588
{
61,930,194✔
1589
    if (!m_top.is_attached())
61,930,194✔
1590
        return 0;                             // Subtable with shared descriptor
×
1591
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
61,930,194✔
1592
    if (!parent)
61,930,194✔
1593
        return 0; // Free-standing table
25,993,953✔
1594

1595
    return static_cast<Group*>(parent);
35,936,241✔
1596
}
61,930,194✔
1597

1598
inline uint64_t Table::get_sync_file_id() const noexcept
1599
{
40,418,370✔
1600
    Group* g = get_parent_group();
40,418,370✔
1601
    return g ? g->get_sync_file_id() : 0;
40,418,370✔
1602
}
40,418,370✔
1603

1604
size_t Table::get_index_in_group() const noexcept
1605
{
42✔
1606
    if (!m_top.is_attached())
42✔
1607
        return realm::npos;                   // Subtable with shared descriptor
×
1608
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
42✔
1609
    if (!parent)
42✔
1610
        return realm::npos; // Free-standing table
×
1611
    return m_top.get_ndx_in_parent();
42✔
1612
}
42✔
1613

1614
uint64_t Table::allocate_sequence_number()
1615
{
20,969,910✔
1616
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
20,969,910✔
1617
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
20,969,910✔
1618
    rot = RefOrTagged::make_tagged(sn + 1);
20,969,910✔
1619
    m_top.set(top_position_for_sequence_number, rot);
20,969,910✔
1620

1621
    return sn;
20,969,910✔
1622
}
20,969,910✔
1623

1624
void Table::set_sequence_number(uint64_t seq)
1625
{
×
1626
    m_top.set(top_position_for_sequence_number, RefOrTagged::make_tagged(seq));
×
1627
}
×
1628

1629
void Table::set_collision_map(ref_type ref)
1630
{
×
1631
    m_top.set(top_position_for_collision_map, RefOrTagged::make_ref(ref));
×
1632
}
×
1633

1634
void Table::set_col_key_sequence_number(uint64_t seq)
1635
{
24✔
1636
    m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(seq));
24✔
1637
}
24✔
1638

1639
TableRef Table::get_link_target(ColKey col_key) noexcept
1640
{
274,470✔
1641
    return get_opposite_table(col_key);
274,470✔
1642
}
274,470✔
1643

1644
// count ----------------------------------------------
1645

1646
size_t Table::count_int(ColKey col_key, int64_t value) const
1647
{
12,006✔
1648
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1649
        return index->count(value);
12,000✔
1650
    }
12,000✔
1651

1652
    return where().equal(col_key, value).count();
6✔
1653
}
12,006✔
1654
size_t Table::count_float(ColKey col_key, float value) const
1655
{
6✔
1656
    return where().equal(col_key, value).count();
6✔
1657
}
6✔
1658
size_t Table::count_double(ColKey col_key, double value) const
1659
{
6✔
1660
    return where().equal(col_key, value).count();
6✔
1661
}
6✔
1662
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
1663
{
18✔
1664
    ArrayDecimal128 leaf(get_alloc());
18✔
1665
    size_t cnt = 0;
18✔
1666
    bool null_value = value.is_null();
18✔
1667
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
1668
        // direct aggregate on the leaf
1669
        cluster->init_leaf(col_key, &leaf);
18✔
1670
        auto sz = leaf.size();
18✔
1671
        for (size_t i = 0; i < sz; i++) {
1,296✔
1672
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
1673
                cnt++;
24✔
1674
            }
24✔
1675
        }
1,278✔
1676
        return IteratorControl::AdvanceToNext;
18✔
1677
    };
18✔
1678

1679
    traverse_clusters(f);
18✔
1680

1681
    return cnt;
18✔
1682
}
18✔
1683
size_t Table::count_string(ColKey col_key, StringData value) const
1684
{
1,476✔
1685
    if (auto index = this->get_search_index(col_key)) {
1,476✔
1686
        return index->count(value);
732✔
1687
    }
732✔
1688
    return where().equal(col_key, value).count();
744✔
1689
}
1,476✔
1690

1691
template <typename T>
1692
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1693
{
34,740✔
1694
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
34,740✔
1695
    LeafType leaf(get_alloc());
34,740✔
1696

1697
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
34,758✔
1698
        // direct aggregate on the leaf
1699
        cluster->init_leaf(column_key, &leaf);
34,758✔
1700
        st.m_key_offset = cluster->get_offset();
34,758✔
1701
        st.m_key_values = cluster->get_key_array();
34,758✔
1702
        st.set_payload_column(&leaf);
34,758✔
1703
        bool cont = true;
34,758✔
1704
        size_t sz = leaf.size();
34,758✔
1705
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
138,840✔
1706
            cont = st.match(local_index);
104,082✔
1707
        }
104,082✔
1708
        return IteratorControl::AdvanceToNext;
34,758✔
1709
    };
34,758✔
1710

1711
    traverse_clusters(f);
34,740✔
1712
}
34,740✔
1713

1714
// This template is also used by the query engine
1715
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1716
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1717
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1718
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1719
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1720
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1721
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1722

1723
std::optional<Mixed> Table::sum(ColKey col_key) const
1724
{
756✔
1725
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1726
}
756✔
1727

1728
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1729
{
822✔
1730
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1731
}
822✔
1732

1733
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1734
{
1,170✔
1735
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
1736
}
1,170✔
1737

1738
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1739
{
28,464✔
1740
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
28,464✔
1741
}
28,464✔
1742

1743

1744
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1745
{
32,082,216✔
1746
    check_column(col);
32,082,216✔
1747
    return m_index_accessors[col.get_index().val].get();
32,082,216✔
1748
}
32,082,216✔
1749

1750
StringIndex* Table::get_string_index(ColKey col) const noexcept
1751
{
690,261✔
1752
    check_column(col);
690,261✔
1753
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
690,261✔
1754
}
690,261✔
1755

1756
template <class T>
1757
ObjKey Table::find_first(ColKey col_key, T value) const
1758
{
34,950✔
1759
    check_column(col_key);
34,950✔
1760

1761
    if (!col_key.is_nullable() && value_is_null(value)) {
34,950!
1762
        return {}; // this is a precaution/optimization
6✔
1763
    }
6✔
1764
    // You cannot call GetIndexData on ObjKey
1765
    if constexpr (!std::is_same_v<T, ObjKey>) {
34,944✔
1766
        if (SearchIndex* index = get_search_index(col_key)) {
34,926✔
1767
            return index->find_first(value);
27,078✔
1768
        }
27,078✔
1769
        if (col_key == m_primary_key_col) {
7,848✔
1770
            return find_primary_key(value);
×
1771
        }
×
1772
    }
7,848✔
1773

1774
    ObjKey key;
7,848✔
1775
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
21,339✔
1776
    LeafType leaf(get_alloc());
21,339✔
1777

1778
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
22,080✔
1779
        cluster->init_leaf(col_key, &leaf);
9,330✔
1780
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
1781
        if (row != realm::npos) {
9,330✔
1782
            key = cluster->get_real_key(row);
7,692✔
1783
            return IteratorControl::Stop;
7,692✔
1784
        }
7,692✔
1785
        return IteratorControl::AdvanceToNext;
1,638✔
1786
    };
9,330✔
1787

1788
    traverse_clusters(f);
21,339✔
1789

1790
    return key;
21,339✔
1791
}
34,938✔
1792

1793
namespace realm {
1794

1795
template <>
1796
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1797
{
×
1798
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1799
}
×
1800

1801
template <>
1802
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
1803
{
×
1804
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1805
}
×
1806

1807
template <>
1808
ObjKey Table::find_first(ColKey col_key, null) const
1809
{
×
1810
    return find_first_null(col_key);
×
1811
}
×
1812
} // namespace realm
1813

1814
// Explicitly instantiate the generic case of the template for the types we care about.
1815
template ObjKey Table::find_first(ColKey col_key, bool) const;
1816
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
1817
template ObjKey Table::find_first(ColKey col_key, float) const;
1818
template ObjKey Table::find_first(ColKey col_key, double) const;
1819
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
1820
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
1821
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
1822
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
1823
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
1824
template ObjKey Table::find_first(ColKey col_key, StringData) const;
1825
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
1826
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
1827
template ObjKey Table::find_first(ColKey col_key, UUID) const;
1828
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
1829
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
1830

1831
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1832
{
7,698✔
1833
    if (is_nullable(col_key))
7,698✔
1834
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1835
    else
7,662✔
1836
        return find_first<int64_t>(col_key, value);
7,662✔
1837
}
7,698✔
1838

1839
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1840
{
78✔
1841
    if (is_nullable(col_key))
78✔
1842
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1843
    else
42✔
1844
        return find_first<bool>(col_key, value);
42✔
1845
}
78✔
1846

1847
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1848
{
108✔
1849
    return find_first(col_key, value);
108✔
1850
}
108✔
1851

1852
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1853
{
6✔
1854
    return find_first(col_key, value);
6✔
1855
}
6✔
1856

1857
ObjKey Table::find_first_float(ColKey col_key, float value) const
1858
{
12✔
1859
    return find_first<Float>(col_key, value);
12✔
1860
}
12✔
1861

1862
ObjKey Table::find_first_double(ColKey col_key, double value) const
1863
{
12✔
1864
    return find_first<Double>(col_key, value);
12✔
1865
}
12✔
1866

1867
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
1868
{
×
1869
    return find_first<Decimal128>(col_key, value);
×
1870
}
×
1871

1872
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1873
{
11,316✔
1874
    return find_first<StringData>(col_key, value);
11,316✔
1875
}
11,316✔
1876

1877
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
1878
{
×
1879
    return find_first<BinaryData>(col_key, value);
×
1880
}
×
1881

1882
ObjKey Table::find_first_null(ColKey col_key) const
1883
{
114✔
1884
    return where().equal(col_key, null{}).find();
114✔
1885
}
114✔
1886

1887
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1888
{
18✔
1889
    return find_first(col_key, value);
18✔
1890
}
18✔
1891

1892
template <class T>
1893
TableView Table::find_all(ColKey col_key, T value)
1894
{
114✔
1895
    return where().equal(col_key, value).find_all();
114✔
1896
}
114✔
1897

1898
TableView Table::find_all_int(ColKey col_key, int64_t value)
1899
{
102✔
1900
    return find_all<int64_t>(col_key, value);
102✔
1901
}
102✔
1902

1903
TableView Table::find_all_int(ColKey col_key, int64_t value) const
1904
{
6✔
1905
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
6✔
1906
}
6✔
1907

1908
TableView Table::find_all_bool(ColKey col_key, bool value)
1909
{
×
1910
    return find_all<bool>(col_key, value);
×
1911
}
×
1912

1913
TableView Table::find_all_bool(ColKey col_key, bool value) const
1914
{
×
1915
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1916
}
×
1917

1918

1919
TableView Table::find_all_float(ColKey col_key, float value)
1920
{
×
1921
    return find_all<float>(col_key, value);
×
1922
}
×
1923

1924
TableView Table::find_all_float(ColKey col_key, float value) const
1925
{
×
1926
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
1927
}
×
1928

1929
TableView Table::find_all_double(ColKey col_key, double value)
1930
{
6✔
1931
    return find_all<double>(col_key, value);
6✔
1932
}
6✔
1933

1934
TableView Table::find_all_double(ColKey col_key, double value) const
1935
{
×
1936
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
1937
}
×
1938

1939
TableView Table::find_all_string(ColKey col_key, StringData value)
1940
{
282✔
1941
    return where().equal(col_key, value).find_all();
282✔
1942
}
282✔
1943

1944
TableView Table::find_all_string(ColKey col_key, StringData value) const
1945
{
×
1946
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
1947
}
×
1948

1949
TableView Table::find_all_binary(ColKey, BinaryData)
1950
{
×
1951
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
1952
}
×
1953

1954
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
1955
{
×
1956
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
1957
}
×
1958

1959
TableView Table::find_all_null(ColKey col_key)
1960
{
×
1961
    return where().equal(col_key, null{}).find_all();
×
1962
}
×
1963

1964
TableView Table::find_all_null(ColKey col_key) const
1965
{
×
1966
    return const_cast<Table*>(this)->find_all_null(col_key);
×
1967
}
×
1968

1969
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1970
{
6✔
1971
    return where().fulltext(col_key, terms).find_all();
6✔
1972
}
6✔
1973

1974
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1975
{
72✔
1976
    TableView tv = where().find_all();
72✔
1977
    tv.sort(col_key, ascending);
72✔
1978
    return tv;
72✔
1979
}
72✔
1980

1981
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1982
{
6✔
1983
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1984
}
6✔
1985

1986
TableView Table::get_sorted_view(SortDescriptor order)
1987
{
126✔
1988
    TableView tv = where().find_all();
126✔
1989
    tv.sort(std::move(order));
126✔
1990
    return tv;
126✔
1991
}
126✔
1992

1993
TableView Table::get_sorted_view(SortDescriptor order) const
1994
{
×
1995
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
1996
}
×
1997

1998
util::Logger* Table::get_logger() const noexcept
1999
{
1,929,489✔
2000
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,929,489✔
2001
}
1,929,489✔
2002

2003
// Called after a commit. Table will effectively contain the same as before,
2004
// but now with new refs from the file
2005
void Table::update_from_parent() noexcept
2006
{
1,015,899✔
2007
    // There is no top for sub-tables sharing spec
2008
    if (m_top.is_attached()) {
1,015,899✔
2009
        m_top.update_from_parent();
1,015,899✔
2010
        m_spec.update_from_parent();
1,015,899✔
2011
        m_clusters.update_from_parent();
1,015,899✔
2012
        m_index_refs.update_from_parent();
1,015,899✔
2013
        for (auto&& index : m_index_accessors) {
2,764,872✔
2014
            if (index != nullptr) {
2,764,872✔
2015
                index->update_from_parent();
402,018✔
2016
            }
402,018✔
2017
        }
2,764,872✔
2018

2019
        m_opposite_table.update_from_parent();
1,015,899✔
2020
        m_opposite_column.update_from_parent();
1,015,899✔
2021
        if (m_top.size() > top_position_for_flags) {
1,015,899✔
2022
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
1,015,884✔
2023
            m_table_type = Type(flags & table_type_mask);
1,015,884✔
2024
        }
1,015,884✔
2025
        else {
15✔
2026
            m_table_type = Type::TopLevel;
15✔
2027
        }
15✔
2028
        if (m_tombstones)
1,015,899✔
2029
            m_tombstones->update_from_parent();
4,032✔
2030

2031
        refresh_content_version();
1,015,899✔
2032
        m_has_any_embedded_objects.reset();
1,015,899✔
2033
        if (m_top.size() > top_position_for_interners) {
1,015,899✔
2034
            if (m_top.get_as_ref(top_position_for_interners))
1,015,899✔
2035
                m_interner_data.update_from_parent();
1,015,896✔
2036
            else
3✔
2037
                m_interner_data.detach();
3✔
2038
        }
1,015,899✔
2039
        refresh_string_interners(false);
1,015,899✔
2040
    }
1,015,899✔
2041
    m_alloc.bump_storage_version();
1,015,899✔
2042
}
1,015,899✔
2043

2044
void Table::schema_to_json(std::ostream& out) const
2045
{
12✔
2046
    out << "{";
12✔
2047
    auto name = get_name();
12✔
2048
    out << "\"name\":\"" << name << "\"";
12✔
2049
    if (this->m_primary_key_col) {
12✔
2050
        out << ",";
×
2051
        out << "\"primaryKey\":\"" << this->get_column_name(m_primary_key_col) << "\"";
×
2052
    }
×
2053
    out << ",\"tableType\":\"" << this->get_table_type() << "\"";
12✔
2054
    out << ",\"properties\":[";
12✔
2055
    auto col_keys = get_column_keys();
12✔
2056
    int sz = int(col_keys.size());
12✔
2057
    for (int i = 0; i < sz; ++i) {
54✔
2058
        auto col_key = col_keys[i];
42✔
2059
        name = get_column_name(col_key);
42✔
2060
        auto type = col_key.get_type();
42✔
2061
        out << "{";
42✔
2062
        out << "\"name\":\"" << name << "\"";
42✔
2063
        if (this->is_link_type(type)) {
42✔
2064
            out << ",\"type\":\"object\"";
6✔
2065
            name = this->get_opposite_table(col_key)->get_name();
6✔
2066
            out << ",\"objectType\":\"" << name << "\"";
6✔
2067
        }
6✔
2068
        else {
36✔
2069
            out << ",\"type\":\"" << get_data_type_name(DataType(type)) << "\"";
36✔
2070
        }
36✔
2071
        if (col_key.is_list()) {
42✔
2072
            out << ",\"isArray\":true";
12✔
2073
        }
12✔
2074
        else if (col_key.is_set()) {
30✔
2075
            out << ",\"isSet\":true";
×
2076
        }
×
2077
        else if (col_key.is_dictionary()) {
30✔
2078
            out << ",\"isMap\":true";
6✔
2079
            auto key_type = get_dictionary_key_type(col_key);
6✔
2080
            out << ",\"keyType\":\"" << get_data_type_name(key_type) << "\"";
6✔
2081
        }
6✔
2082
        if (col_key.is_nullable()) {
42✔
2083
            out << ",\"isOptional\":true";
12✔
2084
        }
12✔
2085
        auto index_type = search_index_type(col_key);
42✔
2086
        if (index_type == IndexType::General) {
42✔
2087
            out << ",\"isIndexed\":true";
×
2088
        }
×
2089
        if (index_type == IndexType::Fulltext) {
42✔
2090
            out << ",\"isFulltextIndexed\":true";
×
2091
        }
×
2092
        out << "}";
42✔
2093
        if (i < sz - 1) {
42✔
2094
            out << ",";
30✔
2095
        }
30✔
2096
    }
42✔
2097
    out << "]}";
12✔
2098
}
12✔
2099

2100
bool Table::operator==(const Table& t) const
2101
{
162✔
2102
    if (size() != t.size()) {
162✔
2103
        return false;
12✔
2104
    }
12✔
2105
    // Check columns
2106
    for (auto ck : this->get_column_keys()) {
558✔
2107
        auto name = get_column_name(ck);
558✔
2108
        auto other_ck = t.get_column_key(name);
558✔
2109
        auto attrs = ck.get_attrs();
558✔
2110
        if (search_index_type(ck) != t.search_index_type(other_ck))
558✔
2111
            return false;
×
2112

2113
        if (!other_ck || other_ck.get_attrs() != attrs) {
558✔
2114
            return false;
×
2115
        }
×
2116
    }
558✔
2117
    auto pk_col = get_primary_key_column();
150✔
2118
    for (auto o : *this) {
2,922✔
2119
        Obj other_o;
2,922✔
2120
        if (pk_col) {
2,922✔
2121
            auto pk = o.get_any(pk_col);
90✔
2122
            other_o = t.get_object_with_primary_key(pk);
90✔
2123
        }
90✔
2124
        else {
2,832✔
2125
            other_o = t.get_object(o.get_key());
2,832✔
2126
        }
2,832✔
2127
        if (!(other_o && o == other_o))
2,922✔
2128
            return false;
18✔
2129
    }
2,922✔
2130

2131
    return true;
132✔
2132
}
150✔
2133

2134

2135
void Table::flush_for_commit()
2136
{
1,125,342✔
2137
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
1,125,345✔
2138
        if (!m_top.is_read_only()) {
1,125,342✔
2139
            ++m_in_file_version_at_transaction_boundary;
770,061✔
2140
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
770,061✔
2141
            m_top.set(top_position_for_version, rot_version);
770,061✔
2142
        }
770,061✔
2143
    }
1,125,342✔
2144
}
1,125,342✔
2145

2146
void Table::refresh_content_version()
2147
{
1,415,892✔
2148
    REALM_ASSERT(m_top.is_attached());
1,415,892✔
2149
    if (m_top.size() >= top_position_for_version) {
1,416,207✔
2150
        // we have versioning info in the file. Use this to conditionally
2151
        // bump the version counter:
2152
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,416,150✔
2153
        REALM_ASSERT(rot_version.is_tagged());
1,416,150✔
2154
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,416,150✔
2155
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
232,431✔
2156
            bump_content_version();
232,431✔
2157
        }
232,431✔
2158
    }
1,416,150✔
2159
    else {
2,147,483,704✔
2160
        // assume the worst:
2161
        bump_content_version();
2,147,483,704✔
2162
    }
2,147,483,704✔
2163
}
1,415,892✔
2164

2165

2166
// Called when Group is moved to another version - either a rollback or an advance.
2167
// The content of the table is potentially different, so make no assumptions.
2168
void Table::refresh_accessor_tree(bool writable)
2169
{
400,506✔
2170
    REALM_ASSERT(m_cookie == cookie_initialized);
400,506✔
2171
    REALM_ASSERT(m_top.is_attached());
400,506✔
2172
    m_top.init_from_parent();
400,506✔
2173
    m_spec.init_from_parent();
400,506✔
2174
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
400,506✔
2175
    m_clusters.init_from_parent();
400,506✔
2176
    m_index_refs.init_from_parent();
400,506✔
2177
    m_opposite_table.init_from_parent();
400,506✔
2178
    m_opposite_column.init_from_parent();
400,506✔
2179
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
400,506✔
2180
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
400,506✔
2181
    if (m_top.size() > top_position_for_flags) {
400,773✔
2182
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
400,770✔
2183
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
400,770✔
2184
    }
400,770✔
2185
    else {
2,147,483,650✔
2186
        m_table_type = Type::TopLevel;
2,147,483,650✔
2187
    }
2,147,483,650✔
2188
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
400,785✔
2189
        // Tombstones exists
2190
        if (!m_tombstones) {
2,100✔
2191
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
318✔
2192
        }
318✔
2193
        m_tombstones->init_from_parent();
2,100✔
2194
    }
2,100✔
2195
    else {
398,406✔
2196
        m_tombstones = nullptr;
398,406✔
2197
    }
398,406✔
2198
    if (writable) {
400,506✔
2199
        while (m_top.size() < top_position_for_interners)
90,537✔
NEW
2200
            m_top.add(0);
×
2201
    }
90,537✔
2202
    if (m_top.size() > top_position_for_interners) {
401,181✔
2203
        if (m_top.get_as_ref(top_position_for_interners))
401,181✔
2204
            m_interner_data.init_from_parent();
400,815✔
2205
        else
366✔
2206
            m_interner_data.detach();
366✔
2207
    }
401,181✔
2208
    refresh_content_version();
400,506✔
2209
    bump_storage_version();
400,506✔
2210
    build_column_mapping();
400,506✔
2211
    refresh_string_interners(writable);
400,506✔
2212
    refresh_index_accessors();
400,506✔
2213
}
400,506✔
2214

2215
void Table::refresh_string_interners(bool writable)
2216
{
3,461,730✔
2217
    if (writable) {
3,461,730✔
2218
        // if we're in a write transaction, make sure interner arrays are created which will allow
2219
        // string interners to expand with their own data when "learning"
2220
        while (m_top.size() <= top_position_for_interners) {
1,082,913✔
2221
            m_top.add(0);
414✔
2222
        }
414✔
2223
    }
1,082,499✔
2224
    if (m_top.size() > top_position_for_interners && m_top.get_as_ref(top_position_for_interners))
3,462,456✔
2225
        m_interner_data.update_from_parent();
3,191,472✔
2226
    else
270,258✔
2227
        m_interner_data.detach();
270,258✔
2228
    if (writable) {
3,461,730✔
2229
        if (!m_interner_data.is_attached()) {
1,082,508✔
2230
            m_interner_data.create(NodeHeader::type_HasRefs);
272,874✔
2231
            m_interner_data.update_parent();
272,874✔
2232
        }
272,874✔
2233
    }
1,082,508✔
2234
    // bring string interners in line with underlying data.
2235
    // Precondition: we rely on the col keys in m_leaf_ndx2colkey[] being up to date.
2236
    for (size_t idx = 0; idx < m_leaf_ndx2colkey.size(); ++idx) {
14,747,643✔
2237
        auto col_key = m_leaf_ndx2colkey[idx];
11,285,913✔
2238
        if (col_key == ColKey()) {
11,285,913✔
2239
            // deleted column, we really don't want a string interner for this
2240
            if (idx < m_string_interners.size() && m_string_interners[idx])
51,285✔
2241
                m_string_interners[idx].reset();
44,274✔
2242
            continue;
51,285✔
2243
        }
51,285✔
2244
        REALM_ASSERT_DEBUG(col_key.get_index().val == idx);
11,234,628✔
2245
        // maintain sufficient size of interner arrays to cover all columns
2246
        while (idx >= m_string_interners.size()) {
17,964,279✔
2247
            m_string_interners.push_back({});
6,729,651✔
2248
        }
6,729,651✔
2249
        while (writable && idx >= m_interner_data.size()) { // m_interner_data.is_attached() per above
11,235,660✔
2250
            m_interner_data.add(0);
1,032✔
2251
        }
1,032✔
2252
        if (m_string_interners[idx]) {
11,234,628✔
2253
            // existing interner
2254
            m_string_interners[idx]->update_from_parent(writable);
4,522,365✔
2255
        }
4,522,365✔
2256
        else {
6,712,263✔
2257
            // new interner. Note: if not in a writable state, the interner will not have a valid
2258
            // underlying data array. The interner will be set in a state, where it cannot "learn",
2259
            // and searches will not find any matching interned strings.
2260
            m_string_interners[idx] = std::make_unique<StringInterner>(m_alloc, m_interner_data, col_key, writable);
6,712,263✔
2261
        }
6,712,263✔
2262
    }
11,234,628✔
2263
    if (m_string_interners.size() > m_leaf_ndx2colkey.size()) {
3,461,730✔
2264
        // remove any string interners which are no longer reachable,
2265
        // e.g. after a rollback
2266
        m_string_interners.resize(m_leaf_ndx2colkey.size());
195✔
2267
    }
195✔
2268
}
3,461,730✔
2269

2270
void Table::refresh_index_accessors()
2271
{
3,175,929✔
2272
    // Refresh search index accessors
2273

2274
    // First eliminate any index accessors for eliminated last columns
2275
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
3,175,929✔
2276
    m_index_accessors.resize(col_ndx_end);
3,175,929✔
2277

2278
    // Then eliminate/refresh/create accessors within column range
2279
    // we can not use for_each_column() here, since the columns may have changed
2280
    // and the index accessor vector is not updated correspondingly.
2281
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
17,237,727✔
2282
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
14,061,798✔
2283

2284
        if (ref == 0) {
14,061,798✔
2285
            // accessor drop
2286
            m_index_accessors[col_ndx].reset();
13,003,683✔
2287
        }
13,003,683✔
2288
        else {
1,058,115✔
2289
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
1,058,115✔
2290
            bool fulltext = attr.test(col_attr_FullText_Indexed);
1,058,115✔
2291
            auto col_key = m_leaf_ndx2colkey[col_ndx];
1,058,115✔
2292
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
1,058,115✔
2293

2294
            if (m_index_accessors[col_ndx]) { // still there, refresh:
1,058,115✔
2295
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
383,112✔
2296
            }
383,112✔
2297
            else { // new index!
675,003✔
2298
                m_index_accessors[col_ndx] =
675,003✔
2299
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
675,003✔
2300
            }
675,003✔
2301
        }
1,058,115✔
2302
    }
14,061,798✔
2303
}
3,175,929✔
2304

2305
bool Table::is_cross_table_link_target() const noexcept
2306
{
1,536✔
2307
    auto is_cross_link = [this](ColKey col_key) {
1,536✔
2308
        auto t = col_key.get_type();
66✔
2309
        // look for a backlink with a different target than ourselves
2310
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
66✔
2311
                   ? IteratorControl::Stop
66✔
2312
                   : IteratorControl::AdvanceToNext;
66✔
2313
    };
66✔
2314
    return for_each_backlink_column(is_cross_link);
1,536✔
2315
}
1,536✔
2316

2317
// LCOV_EXCL_START ignore debug functions
2318

2319
void Table::verify() const
2320
{
232,101✔
2321
#ifdef REALM_DEBUG
232,101✔
2322
    if (m_top.is_attached())
232,101✔
2323
        m_top.verify();
232,092✔
2324
    m_spec.verify();
232,101✔
2325
    m_clusters.verify();
232,101✔
2326
    if (nb_unresolved())
232,101✔
2327
        m_tombstones->verify();
30,945✔
2328
#endif
232,101✔
2329
}
232,101✔
2330

2331
#ifdef REALM_DEBUG
2332
MemStats Table::stats() const
2333
{
×
2334
    MemStats mem_stats;
×
2335
    m_top.stats(mem_stats);
×
2336
    return mem_stats;
×
2337
}
×
2338
#endif // LCOV_EXCL_STOP ignore debug functions
2339

2340
Obj Table::create_object(ObjKey key, const FieldValues& values)
2341
{
24,541,662✔
2342
    if (is_embedded())
24,541,662✔
2343
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2344
    if (m_primary_key_col)
24,541,614✔
2345
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2346
    if (key == null_key) {
24,541,614✔
2347
        GlobalKey object_id = allocate_object_id_squeezed();
20,192,043✔
2348
        key = object_id.get_local_key(get_sync_file_id());
20,192,043✔
2349
        // Check if this key collides with an already existing object
2350
        // This could happen if objects were at some point created with primary keys,
2351
        // but later primary key property was removed from the schema.
2352
        while (m_clusters.is_valid(key)) {
20,192,043✔
2353
            object_id = allocate_object_id_squeezed();
×
2354
            key = object_id.get_local_key(get_sync_file_id());
×
2355
        }
×
2356
        if (auto repl = get_repl())
20,192,043✔
2357
            repl->create_object(this, object_id);
4,891,659✔
2358
    }
20,192,043✔
2359

2360
    REALM_ASSERT(key.value >= 0);
24,541,614✔
2361

2362
    Obj obj = m_clusters.insert(key, values); // repl->set()
24,541,614✔
2363

2364
    return obj;
24,541,614✔
2365
}
24,541,614✔
2366

2367
Obj Table::create_linked_object()
2368
{
42,864✔
2369
    REALM_ASSERT(is_embedded());
42,864✔
2370

2371
    GlobalKey object_id = allocate_object_id_squeezed();
42,864✔
2372
    ObjKey key = object_id.get_local_key(get_sync_file_id());
42,864✔
2373

2374
    REALM_ASSERT(key.value >= 0);
42,864✔
2375

2376
    Obj obj = m_clusters.insert(key, {});
42,864✔
2377

2378
    return obj;
42,864✔
2379
}
42,864✔
2380

2381
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2382
{
30✔
2383
    if (is_embedded())
30✔
2384
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2385
    if (m_primary_key_col)
30✔
2386
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2387
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2388

2389
    if (auto repl = get_repl())
30✔
2390
        repl->create_object(this, object_id);
30✔
2391

2392
    try {
30✔
2393
        Obj obj = m_clusters.insert(key, values);
30✔
2394
        // Check if tombstone exists
2395
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2396
            auto unres_key = key.get_unresolved();
6✔
2397
            // Copy links over
2398
            auto tombstone = m_tombstones->get(unres_key);
6✔
2399
            obj.assign_pk_and_backlinks(tombstone);
6✔
2400
            // If tombstones had no links to it, it may still be alive
2401
            if (m_tombstones->is_valid(unres_key)) {
6✔
2402
                CascadeState state(CascadeState::Mode::None);
6✔
2403
                m_tombstones->erase(unres_key, state);
6✔
2404
            }
6✔
2405
        }
6✔
2406

2407
        return obj;
30✔
2408
    }
30✔
2409
    catch (const KeyAlreadyUsed&) {
30✔
2410
        return m_clusters.get(key);
6✔
2411
    }
6✔
2412
}
30✔
2413

2414
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2415
                                          bool* did_create)
2416
{
804,945✔
2417
    auto primary_key_col = get_primary_key_column();
804,945✔
2418
    if (is_embedded() || !primary_key_col)
804,945✔
2419
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2420
                              util::format("Table has no primary key: %1", get_name()));
6✔
2421

2422
    DataType type = DataType(primary_key_col.get_type());
804,939✔
2423

2424
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
804,939✔
2425
        throw InvalidArgument(
6✔
2426
            ErrorCodes::PropertyNotNullable,
6✔
2427
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2428
    }
6✔
2429

2430
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
804,933✔
2431
        primary_key.get_type() != type) {
804,933✔
2432
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2433
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2434
    }
6✔
2435

2436
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
804,927✔
2437

2438
    if (did_create)
804,927✔
2439
        *did_create = false;
84,816✔
2440

2441
    // Check for existing object
2442
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
804,927✔
2443
        if (mode == UpdateMode::never) {
47,250✔
2444
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2445
        }
6✔
2446
        auto obj = m_clusters.get(key);
47,244✔
2447
        for (auto& val : field_values) {
47,244✔
2448
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2449
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2450
            }
12✔
2451
        }
12✔
2452
        return obj;
47,244✔
2453
    }
47,250✔
2454

2455
    ObjKey unres_key;
757,677✔
2456
    if (m_tombstones) {
757,677✔
2457
        // Check for potential tombstone
2458
        GlobalKey object_id{primary_key};
12,273✔
2459
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
12,273✔
2460

2461
        ObjKey key = object_key.get_unresolved();
12,273✔
2462
        if (auto obj = m_tombstones->try_get_obj(key)) {
12,273✔
2463
            auto existing_pk_value = obj.get_any(primary_key_col);
10,782✔
2464

2465
            // If the primary key is the same, the object should be resurrected below
2466
            if (existing_pk_value == primary_key) {
10,782✔
2467
                unres_key = key;
10,776✔
2468
            }
10,776✔
2469
        }
10,782✔
2470
    }
12,273✔
2471

2472
    ObjKey key = get_next_valid_key();
757,677✔
2473

2474
    auto repl = get_repl();
757,677✔
2475
    if (repl) {
757,677✔
2476
        repl->create_object_with_primary_key(this, key, primary_key);
306,921✔
2477
    }
306,921✔
2478
    if (did_create) {
757,677✔
2479
        *did_create = true;
47,016✔
2480
    }
47,016✔
2481

2482
    field_values.insert(primary_key_col, primary_key);
757,677✔
2483
    Obj ret = m_clusters.insert(key, field_values);
757,677✔
2484

2485
    // Check if unresolved exists
2486
    if (unres_key) {
757,677✔
2487
        if (Replication* repl = get_repl()) {
10,776✔
2488
            if (auto logger = repl->would_log(util::Logger::Level::debug)) {
10,686✔
2489
                logger->log(LogCategory::object, util::Logger::Level::debug, "Cancel tombstone on '%1': %2",
36✔
2490
                            get_class_name(), unres_key);
36✔
2491
            }
36✔
2492
        }
10,686✔
2493

2494
        auto tombstone = m_tombstones->get(unres_key);
10,776✔
2495
        ret.assign_pk_and_backlinks(tombstone);
10,776✔
2496
        // If tombstones had no links to it, it may still be alive
2497
        if (m_tombstones->is_valid(unres_key)) {
10,776✔
2498
            CascadeState state(CascadeState::Mode::None);
10,716✔
2499
            m_tombstones->erase(unres_key, state);
10,716✔
2500
        }
10,716✔
2501
    }
10,776✔
2502
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
757,677✔
2503
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
2504
    }
1,290✔
2505
    return ret;
757,677✔
2506
}
804,927✔
2507

2508
ObjKey Table::find_primary_key(Mixed primary_key) const
2509
{
724,845✔
2510
    auto primary_key_col = get_primary_key_column();
724,845✔
2511
    REALM_ASSERT(primary_key_col);
724,845✔
2512
    DataType type = DataType(primary_key_col.get_type());
724,845✔
2513
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
724,845✔
2514
                 primary_key.get_type() == type);
724,845✔
2515

2516
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
724,845✔
2517
        return index->find_first(primary_key);
724,785✔
2518
    }
724,785✔
2519

2520
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
2521
    // so try the old algorithm
2522
    GlobalKey object_id{primary_key};
60✔
2523
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
60✔
2524

2525
    // Check if existing
2526
    if (auto obj = m_clusters.try_get_obj(object_key)) {
60✔
2527
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2528

2529
        if (existing_pk_value == primary_key) {
×
2530
            return object_key;
×
2531
        }
×
2532
    }
×
2533
    return {};
60✔
2534
}
60✔
2535

2536
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2537
{
611,370✔
2538
    // Check if existing
2539
    if (auto key = find_primary_key(primary_key)) {
611,370✔
2540
        return key;
599,535✔
2541
    }
599,535✔
2542

2543
    // Object does not exist - create tombstone
2544
    GlobalKey object_id{primary_key};
11,835✔
2545
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
11,835✔
2546
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
11,835✔
2547
}
611,370✔
2548

2549
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2550
{
18✔
2551
    REALM_ASSERT(!m_primary_key_col);
18✔
2552
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2553

2554
    // Check if existing
2555
    if (m_clusters.is_valid(object_key)) {
18✔
2556
        return object_key;
12✔
2557
    }
12✔
2558

2559
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2560
}
18✔
2561

2562
ObjKey Table::get_objkey(GlobalKey global_key) const
2563
{
18✔
2564
    ObjKey key;
18✔
2565
    REALM_ASSERT(!m_primary_key_col);
18✔
2566
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
2567
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
2568
        key = global_key.get_local_key(get_sync_file_id());
6✔
2569
    }
6✔
2570
    if (key && !is_valid(key)) {
18✔
2571
        key = realm::null_key;
6✔
2572
    }
6✔
2573
    return key;
18✔
2574
}
18✔
2575

2576
GlobalKey Table::get_object_id(ObjKey key) const
2577
{
144✔
2578
    auto col = get_primary_key_column();
144✔
2579
    if (col) {
144✔
2580
        const Obj obj = get_object(key);
24✔
2581
        auto val = obj.get_any(col);
24✔
2582
        return {val};
24✔
2583
    }
24✔
2584
    else {
120✔
2585
        return {key, get_sync_file_id()};
120✔
2586
    }
120✔
2587
    return {};
×
2588
}
144✔
2589

2590
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2591
{
79,572✔
2592
    auto primary_key_col = get_primary_key_column();
79,572✔
2593
    REALM_ASSERT(primary_key_col);
79,572✔
2594
    DataType type = DataType(primary_key_col.get_type());
79,572✔
2595
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
79,572✔
2596
                 primary_key.get_type() == type);
79,572✔
2597
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
79,572✔
2598
    return k ? m_clusters.get(k) : Obj{};
79,572✔
2599
}
79,572✔
2600

2601
Mixed Table::get_primary_key(ObjKey key) const
2602
{
777,699✔
2603
    auto primary_key_col = get_primary_key_column();
777,699✔
2604
    REALM_ASSERT(primary_key_col);
777,699✔
2605
    if (key.is_unresolved()) {
777,699✔
2606
        REALM_ASSERT(m_tombstones);
792✔
2607
        return m_tombstones->get(key).get_any(primary_key_col);
792✔
2608
    }
792✔
2609
    else {
776,907✔
2610
        return m_clusters.get(key).get_any(primary_key_col);
776,907✔
2611
    }
776,907✔
2612
}
777,699✔
2613

2614
GlobalKey Table::allocate_object_id_squeezed()
2615
{
20,238,765✔
2616
    // m_client_file_ident will be zero if we haven't been in contact with
2617
    // the server yet.
2618
    auto peer_id = get_sync_file_id();
20,238,765✔
2619
    auto sequence = allocate_sequence_number();
20,238,765✔
2620
    return GlobalKey{peer_id, sequence};
20,238,765✔
2621
}
20,238,765✔
2622

2623
namespace {
2624

2625
/// Calculate optimistic local ID that may collide with others. It is up to
2626
/// the caller to ensure that collisions are detected and that
2627
/// allocate_local_id_after_collision() is called to obtain a non-colliding
2628
/// ID.
2629
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
2630
{
24,966✔
2631
#if REALM_EXERCISE_OBJECT_ID_COLLISION
2632
    const uint64_t optimistic_mask = 0xff;
2633
#else
2634
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
24,966✔
2635
#endif
24,966✔
2636
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
24,966✔
2637
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
24,966✔
2638
}
24,966✔
2639

2640
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2641
{
12✔
2642
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2643
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2644
}
12✔
2645

2646
} // namespace
2647

2648
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2649
{
24,966✔
2650
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
24,966✔
2651

2652
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
24,966✔
2653
        Allocator& alloc = m_top.get_alloc();
24✔
2654
        Array collision_map{alloc};
24✔
2655
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2656

2657
        Array hi{alloc};
24✔
2658
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2659

2660
        // Entries are ordered by hi,lo
2661
        size_t found = hi.find_first(object_id.hi());
24✔
2662
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
2663
            Array lo{alloc};
24✔
2664
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
2665
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
2666
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
2667
                Array local_id{alloc};
24✔
2668
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
2669
                return ObjKey{local_id.get(candidate)};
24✔
2670
            }
24✔
2671
        }
24✔
2672
    }
24✔
2673

2674
    return optimistic;
24,942✔
2675
}
24,966✔
2676

2677
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2678
                                                     ObjKey colliding_local_id)
2679
{
12✔
2680
    // Possible optimization: Cache these accessors
2681
    Allocator& alloc = m_top.get_alloc();
12✔
2682
    Array collision_map{alloc};
12✔
2683
    Array hi{alloc};
12✔
2684
    Array lo{alloc};
12✔
2685
    Array local_id{alloc};
12✔
2686

2687
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2688
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2689
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2690
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2691

2692
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2693
    if (collision_map_ref) {
12✔
2694
        collision_map.init_from_parent(); // Throws
×
2695
    }
×
2696
    else {
12✔
2697
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2698
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2699
        collision_map.update_parent();
12✔
2700

2701
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2702
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2703
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2704
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2705
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2706
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2707
    }
12✔
2708

2709
    hi.init_from_parent();       // Throws
12✔
2710
    lo.init_from_parent();       // Throws
12✔
2711
    local_id.init_from_parent(); // Throws
12✔
2712

2713
    size_t num_entries = hi.size();
12✔
2714
    REALM_ASSERT(lo.size() == num_entries);
12✔
2715
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2716

2717
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2718
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2719
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2720
            ++i;
6✔
2721
        return i;
24✔
2722
    };
24✔
2723

2724
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
2725
        size_t i = lower_bound_object_id(object_id);
24✔
2726
        if (i != num_entries) {
24✔
2727
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
2728
            if (existing == object_id) {
6✔
2729
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
2730
                return;
×
2731
            }
×
2732
        }
6✔
2733
        hi.insert(i, int64_t(object_id.hi()));
24✔
2734
        lo.insert(i, int64_t(object_id.lo()));
24✔
2735
        local_id.insert(i, new_local_id.value);
24✔
2736
        ++num_entries;
24✔
2737
    };
24✔
2738

2739
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2740
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2741
    insert_collision(incoming_id, new_local_id);
12✔
2742
    insert_collision(colliding_id, colliding_local_id);
12✔
2743

2744
    return new_local_id;
12✔
2745
}
12✔
2746

2747
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2748
{
12,867✔
2749
    auto unres_key = key.get_unresolved();
12,867✔
2750

2751
    ensure_graveyard();
12,867✔
2752
    auto tombstone = m_tombstones->try_get_obj(unres_key);
12,867✔
2753
    if (tombstone) {
12,867✔
2754
        if (pk_col) {
345✔
2755
            auto existing_pk_value = tombstone.get_any(pk_col);
345✔
2756
            // It may just be the same object
2757
            if (existing_pk_value != pk_val) {
345✔
2758
                // We have a collision - create new ObjKey
2759
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
2760
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
2761
            }
12✔
2762
        }
345✔
2763
        return tombstone;
333✔
2764
    }
345✔
2765
    if (Replication* repl = get_repl()) {
12,522✔
2766
        if (auto logger = repl->would_log(util::Logger::Level::debug)) {
12,258✔
2767
            logger->log(LogCategory::object, util::Logger::Level::debug,
576✔
2768
                        "Create tombstone for object '%1' with primary key %2 : %3", get_class_name(), pk_val,
576✔
2769
                        unres_key);
576✔
2770
        }
576✔
2771
    }
12,258✔
2772
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
12,522✔
2773
}
12,867✔
2774

2775
void Table::free_local_id_after_hash_collision(ObjKey key)
2776
{
5,000,583✔
2777
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,000,583✔
2778
        if (key.is_unresolved()) {
30✔
2779
            // Keys will always be inserted as resolved
2780
            key = key.get_unresolved();
24✔
2781
        }
24✔
2782
        // Possible optimization: Cache these accessors
2783
        Array collision_map{m_alloc};
30✔
2784
        Array local_id{m_alloc};
30✔
2785

2786
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2787
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2788
        collision_map.init_from_ref(collision_map_ref);
30✔
2789
        local_id.init_from_parent();
30✔
2790
        auto ndx = local_id.find_first(key.value);
30✔
2791
        if (ndx != realm::npos) {
30✔
2792
            Array hi{m_alloc};
24✔
2793
            Array lo{m_alloc};
24✔
2794

2795
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2796
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2797
            hi.init_from_parent();
24✔
2798
            lo.init_from_parent();
24✔
2799

2800
            hi.erase(ndx);
24✔
2801
            lo.erase(ndx);
24✔
2802
            local_id.erase(ndx);
24✔
2803
            if (hi.size() == 0) {
24✔
2804
                free_collision_table();
12✔
2805
            }
12✔
2806
        }
24✔
2807
    }
30✔
2808
}
5,000,583✔
2809

2810
void Table::free_collision_table()
2811
{
6,027✔
2812
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
6,027✔
2813
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2814
        m_top.set(top_position_for_collision_map, 0);
12✔
2815
    }
12✔
2816
}
6,027✔
2817

2818
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2819
{
3,861✔
2820
    while (number--) {
6,323,988✔
2821
        keys.push_back(create_object().get_key());
6,320,127✔
2822
    }
6,320,127✔
2823
}
3,861✔
2824

2825
void Table::create_objects(const std::vector<ObjKey>& keys)
2826
{
630✔
2827
    for (auto k : keys) {
5,616✔
2828
        create_object(k);
5,616✔
2829
    }
5,616✔
2830
}
630✔
2831

2832
void Table::dump_objects()
2833
{
×
2834
    m_clusters.dump_objects();
×
2835
    if (nb_unresolved())
×
2836
        m_tombstones->dump_objects();
×
2837
}
×
2838

2839
void Table::remove_object(ObjKey key)
2840
{
2,463,540✔
2841
    Group* g = get_parent_group();
2,463,540✔
2842

2843
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,463,540✔
2844
        CascadeState state(CascadeState::Mode::Strong, g);
3,294✔
2845
        state.m_to_be_deleted.emplace_back(m_key, key);
3,294✔
2846
        m_clusters.nullify_incoming_links(key, state);
3,294✔
2847
        remove_recursive(state);
3,294✔
2848
    }
3,294✔
2849
    else {
2,460,246✔
2850
        CascadeState state(CascadeState::Mode::None, g);
2,460,246✔
2851
        if (g) {
2,460,246✔
2852
            m_clusters.nullify_incoming_links(key, state);
2,378,247✔
2853
        }
2,378,247✔
2854
        m_clusters.erase(key, state);
2,460,246✔
2855
    }
2,460,246✔
2856
}
2,463,540✔
2857

2858
ObjKey Table::invalidate_object(ObjKey key)
2859
{
4,101✔
2860
    if (is_embedded())
4,101✔
2861
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2862
    REALM_ASSERT(!key.is_unresolved());
4,101✔
2863

2864
    Obj tombstone;
4,101✔
2865
    auto obj = get_object(key);
4,101✔
2866
    if (obj.has_backlinks(false)) {
4,101✔
2867
        // If the object has backlinks, we should make a tombstone
2868
        // and make inward links point to it,
2869
        if (auto primary_key_col = get_primary_key_column()) {
978✔
2870
            auto pk = obj.get_any(primary_key_col);
822✔
2871
            GlobalKey object_id{pk};
822✔
2872
            auto unres_key = global_to_local_object_id_hashed(object_id);
822✔
2873
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
822✔
2874
        }
822✔
2875
        else {
156✔
2876
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
2877
        }
156✔
2878
        tombstone.assign_pk_and_backlinks(obj);
978✔
2879
    }
978✔
2880

2881
    remove_object(key);
4,101✔
2882

2883
    return tombstone.get_key();
4,101✔
2884
}
4,101✔
2885

2886
void Table::remove_object_recursive(ObjKey key)
2887
{
42✔
2888
    size_t table_ndx = get_index_in_group();
42✔
2889
    if (table_ndx != realm::npos) {
42✔
2890
        CascadeState state(CascadeState::Mode::All, get_parent_group());
42✔
2891
        state.m_to_be_deleted.emplace_back(m_key, key);
42✔
2892
        nullify_links(state);
42✔
2893
        remove_recursive(state);
42✔
2894
    }
42✔
2895
    else {
×
2896
        // No links in freestanding table
2897
        CascadeState state(CascadeState::Mode::None);
×
2898
        m_clusters.erase(key, state);
×
2899
    }
×
2900
}
42✔
2901

2902
Table::Iterator Table::begin() const
2903
{
510,900✔
2904
    return Iterator(m_clusters, 0);
510,900✔
2905
}
510,900✔
2906

2907
Table::Iterator Table::end() const
2908
{
9,634,794✔
2909
    return Iterator(m_clusters, size());
9,634,794✔
2910
}
9,634,794✔
2911

2912
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2913
{
7,100,331✔
2914
    TableRef ret;
7,100,331✔
2915
    if (col_key) {
7,100,406✔
2916
        return table.get_opposite_table(col_key);
7,100,337✔
2917
    }
7,100,337✔
2918
    return ret;
2,147,483,716✔
2919
}
7,100,331✔
2920

2921
const uint64_t Table::max_num_columns;
2922

2923
void Table::build_column_mapping()
2924
{
3,185,880✔
2925
    // build column mapping from spec
2926
    // TODO: Optimization - Don't rebuild this for every change
2927
    m_spec_ndx2leaf_ndx.clear();
3,185,880✔
2928
    m_leaf_ndx2spec_ndx.clear();
3,185,880✔
2929
    m_leaf_ndx2colkey.clear();
3,185,880✔
2930
    size_t num_spec_cols = m_spec.get_column_count();
3,185,880✔
2931
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
3,185,880✔
2932
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
17,207,565✔
2933
        ColKey col_key = m_spec.get_key(spec_ndx);
14,021,685✔
2934
        unsigned leaf_ndx = col_key.get_index().val;
14,021,685✔
2935
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
14,021,685✔
2936
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
13,509,297✔
2937
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
13,509,297✔
2938
        }
13,509,297✔
2939
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
14,021,685✔
2940
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
14,021,685✔
2941
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
14,021,685✔
2942
    }
14,021,685✔
2943
}
3,185,880✔
2944

2945
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2946
{
727,620✔
2947
    REALM_ASSERT(!attr.test(col_attr_Indexed));
727,620✔
2948
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
727,620✔
2949

2950
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
727,620✔
2951
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
727,620✔
2952

2953
    // reuse lowest available leaf ndx:
2954
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
727,620✔
2955
    // look for an unused entry:
2956
    for (unsigned idx = 0; idx < lower; ++idx) {
5,527,185✔
2957
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
4,799,652✔
2958
            lower = idx;
87✔
2959
            break;
87✔
2960
        }
87✔
2961
    }
4,799,652✔
2962
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
727,620✔
2963
}
727,620✔
2964

2965
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2966
                                                  StringData origin_col_name) const noexcept
2967
{
×
2968
    BacklinkOrigin ret;
×
2969
    auto f = [&](ColKey backlink_col_key) {
×
2970
        auto origin_table = get_opposite_table(backlink_col_key);
×
2971
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2972
        if (origin_table->get_name() == origin_table_name &&
×
2973
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
2974
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
2975
            return IteratorControl::Stop;
×
2976
        }
×
2977
        return IteratorControl::AdvanceToNext;
×
2978
    };
×
2979
    this->for_each_backlink_column(f);
×
2980
    return ret;
×
2981
}
×
2982

2983
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2984
{
354✔
2985
    try {
354✔
2986
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
354✔
2987
        ColKey linked_column_key = get_opposite_column(backlink_col);
354✔
2988
        if (linked_table_key == m_key) {
354✔
2989
            return {{m_own_ref, linked_column_key}};
18✔
2990
        }
18✔
2991
        else {
336✔
2992
            Group* current_group = get_parent_group();
336✔
2993
            if (current_group) {
336✔
2994
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2995
                return {{linked_table_ref, linked_column_key}};
336✔
2996
            }
336✔
2997
        }
336✔
2998
    }
354✔
2999
    catch (...) {
354✔
3000
        // backlink column not found, returning empty optional
3001
    }
×
3002
    return {};
×
3003
}
354✔
3004

3005
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
3006
{
×
3007
    std::vector<std::pair<TableKey, ColKey>> origins;
×
3008
    auto f = [&](ColKey backlink_col_key) {
×
3009
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
3010
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
3011
        origins.emplace_back(origin_table_key, origin_link_col);
×
3012
        return IteratorControl::AdvanceToNext;
×
3013
    };
×
3014
    this->for_each_backlink_column(f);
×
3015
    return origins;
×
3016
}
×
3017

3018
ColKey Table::get_primary_key_column() const
3019
{
22,247,937✔
3020
    return m_primary_key_col;
22,247,937✔
3021
}
22,247,937✔
3022

3023
void Table::set_primary_key_column(ColKey col_key)
3024
{
720✔
3025
    if (col_key == m_primary_key_col) {
720✔
3026
        return;
378✔
3027
    }
378✔
3028

3029
    if (Replication* repl = get_repl()) {
342✔
3030
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
3031
            throw RuntimeError(
×
3032
                ErrorCodes::BrokenInvariant,
×
3033
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
3034
        }
×
3035
    }
240✔
3036

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

3039
    if (col_key) {
342✔
3040
        check_column(col_key);
222✔
3041
        validate_column_is_unique(col_key);
222✔
3042
        do_set_primary_key_column(col_key);
222✔
3043
    }
222✔
3044
    else {
120✔
3045
        do_set_primary_key_column(col_key);
120✔
3046
    }
120✔
3047
}
342✔
3048

3049

3050
void Table::do_set_primary_key_column(ColKey col_key)
3051
{
99,999✔
3052
    if (col_key) {
99,999✔
3053
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
98,976✔
3054
        auto attr = m_spec.get_column_attr(spec_ndx);
98,976✔
3055
        if (attr.test(col_attr_FullText_Indexed)) {
98,976✔
3056
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
3057
        }
6✔
3058
    }
98,976✔
3059

3060
    if (m_primary_key_col) {
99,993✔
3061
        // If the search index has not been set explicitly on current pk col, we remove it again
3062
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
1,047✔
3063
        auto attr = m_spec.get_column_attr(spec_ndx);
1,047✔
3064
        if (!attr.test(col_attr_Indexed)) {
1,047✔
3065
            remove_search_index(m_primary_key_col);
1,035✔
3066
        }
1,035✔
3067
    }
1,047✔
3068

3069
    if (col_key) {
99,993✔
3070
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
98,970✔
3071
        do_add_search_index(col_key, IndexType::General);
98,970✔
3072
    }
98,970✔
3073
    else {
1,023✔
3074
        m_top.set(top_position_for_pk_col, 0);
1,023✔
3075
    }
1,023✔
3076

3077
    m_primary_key_col = col_key;
99,993✔
3078
}
99,993✔
3079

3080
bool Table::contains_unique_values(ColKey col) const
3081
{
834✔
3082
    if (search_index_type(col) == IndexType::General) {
834✔
3083
        auto search_index = get_search_index(col);
744✔
3084
        return !search_index->has_duplicate_values();
744✔
3085
    }
744✔
3086
    else {
90✔
3087
        TableView tv = where().find_all();
90✔
3088
        tv.distinct(col);
90✔
3089
        return tv.size() == size();
90✔
3090
    }
90✔
3091
}
834✔
3092

3093
void Table::validate_column_is_unique(ColKey col) const
3094
{
834✔
3095
    if (!contains_unique_values(col)) {
834✔
3096
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
3097
                                           get_class_name(), get_column_name(col)));
30✔
3098
    }
30✔
3099
}
834✔
3100

3101
void Table::validate_primary_column()
3102
{
1,812✔
3103
    if (ColKey col = get_primary_key_column()) {
1,812✔
3104
        validate_column_is_unique(col);
612✔
3105
    }
612✔
3106
}
1,812✔
3107

3108
ObjKey Table::get_next_valid_key()
3109
{
757,674✔
3110
    ObjKey key;
757,674✔
3111
    do {
757,680✔
3112
        key = ObjKey(allocate_sequence_number());
757,680✔
3113
    } while (m_clusters.is_valid(key));
757,680✔
3114

3115
    return key;
757,674✔
3116
}
757,674✔
3117

3118
namespace {
3119
template <class T>
3120
typename util::RemoveOptional<T>::type remove_optional(T val)
3121
{
88,068✔
3122
    return val;
88,068✔
3123
}
88,068✔
3124
template <>
3125
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
3126
{
5,412✔
3127
    return *val;
5,412✔
3128
}
5,412✔
3129
template <>
3130
bool remove_optional<Optional<bool>>(Optional<bool> val)
3131
{
11,505✔
3132
    return *val;
11,505✔
3133
}
11,505✔
3134
template <>
3135
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
3136
{
5,472✔
3137
    return *val;
5,472✔
3138
}
5,472✔
3139
template <>
3140
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
3141
{
6,060✔
3142
    return *val;
6,060✔
3143
}
6,060✔
3144
} // namespace
3145

3146
template <class F, class T>
3147
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
3148
{
162✔
3149
    Allocator& allocator = this->get_alloc();
162✔
3150
    bool from_nullability = is_nullable(key_from);
162✔
3151
    auto func = [&](Cluster* cluster) {
162✔
3152
        size_t sz = cluster->node_size();
162✔
3153

3154
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
3155
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
3156
        cluster->init_leaf(key_from, &from_arr);
162✔
3157
        cluster->init_leaf(key_to, &to_arr);
162✔
3158

3159
        for (size_t i = 0; i < sz; i++) {
1,512✔
3160
            if (from_nullability && from_arr.is_null(i)) {
1,356✔
3161
                if (throw_on_null) {
57!
3162
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
3163
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
3164
                                                    get_column_name(key_from)));
6✔
3165
                }
6✔
3166
                else {
51✔
3167
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
51✔
3168
                }
51✔
3169
            }
57✔
3170
            else {
1,299✔
3171
                auto v = remove_optional(from_arr.get(i));
1,299✔
3172
                to_arr.set(i, v);
1,299✔
3173
            }
1,299✔
3174
        }
1,356✔
3175
    };
162✔
3176

3177
    m_clusters.update(func);
162✔
3178
}
162✔
3179

3180
template <class F, class T>
3181
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
3182
{
120✔
3183
    Allocator& allocator = this->get_alloc();
120✔
3184
    bool from_nullability = is_nullable(key_from);
120✔
3185
    auto func = [&](Cluster* cluster) {
120✔
3186
        size_t sz = cluster->node_size();
120✔
3187

3188
        ArrayInteger from_arr(allocator);
120✔
3189
        ArrayInteger to_arr(allocator);
120✔
3190
        cluster->init_leaf(key_from, &from_arr);
120✔
3191
        cluster->init_leaf(key_to, &to_arr);
120✔
3192

3193
        for (size_t i = 0; i < sz; i++) {
360✔
3194
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3195
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3196
            REALM_ASSERT(!ref_to);
240✔
3197

3198
            if (ref_from) {
240✔
3199
                BPlusTree<F> from_list(allocator);
120✔
3200
                BPlusTree<T> to_list(allocator);
120✔
3201
                from_list.init_from_ref(ref_from);
120✔
3202
                to_list.create();
120✔
3203
                size_t n = from_list.size();
120✔
3204
                for (size_t j = 0; j < n; j++) {
120,120✔
3205
                    auto v = from_list.get(j);
120,000✔
3206
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
3207
                        to_list.add(remove_optional(v));
115,218✔
3208
                    }
115,218✔
3209
                    else {
4,782✔
3210
                        if (throw_on_null) {
4,782!
3211
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3212
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3213
                                                            get_name(), get_column_name(key_from)));
×
3214
                        }
×
3215
                        else {
4,782✔
3216
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,782✔
3217
                        }
4,782✔
3218
                    }
4,782✔
3219
                }
120,000✔
3220
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3221
            }
120✔
3222
        }
240✔
3223
    };
120✔
3224

3225
    m_clusters.update(func);
120✔
3226
}
120✔
3227

3228
void Table::convert_column(ColKey from, ColKey to, bool throw_on_null)
3229
{
282✔
3230
    realm::DataType type_id = get_column_type(from);
282✔
3231
    bool _is_list = is_list(from);
282✔
3232
    if (_is_list) {
282✔
3233
        switch (type_id) {
120✔
3234
            case type_Int:
12✔
3235
                if (is_nullable(from)) {
12✔
3236
                    change_nullability_list<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3237
                }
6✔
3238
                else {
6✔
3239
                    change_nullability_list<int64_t, Optional<int64_t>>(from, to, throw_on_null);
6✔
3240
                }
6✔
3241
                break;
12✔
3242
            case type_Float:
12✔
3243
                change_nullability_list<float, float>(from, to, throw_on_null);
12✔
3244
                break;
12✔
3245
            case type_Double:
12✔
3246
                change_nullability_list<double, double>(from, to, throw_on_null);
12✔
3247
                break;
12✔
3248
            case type_Bool:
12✔
3249
                change_nullability_list<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3250
                break;
12✔
3251
            case type_String:
12✔
3252
                change_nullability_list<StringData, StringData>(from, to, throw_on_null);
12✔
3253
                break;
12✔
3254
            case type_Binary:
12✔
3255
                change_nullability_list<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3256
                break;
12✔
3257
            case type_Timestamp:
12✔
3258
                change_nullability_list<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3259
                break;
12✔
3260
            case type_ObjectId:
12✔
3261
                if (is_nullable(from)) {
12✔
3262
                    change_nullability_list<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3263
                }
6✔
3264
                else {
6✔
3265
                    change_nullability_list<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3266
                }
6✔
3267
                break;
12✔
3268
            case type_Decimal:
12✔
3269
                change_nullability_list<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3270
                break;
12✔
3271
            case type_UUID:
12✔
3272
                if (is_nullable(from)) {
12✔
3273
                    change_nullability_list<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3274
                }
6✔
3275
                else {
6✔
3276
                    change_nullability_list<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3277
                }
6✔
3278
                break;
12✔
3279
            case type_Link:
✔
3280
            case type_TypedLink:
✔
3281
                // Can't have lists of these types
3282
            case type_Mixed:
✔
3283
                // These types are no longer supported at all
3284
                REALM_UNREACHABLE();
3285
                break;
×
3286
        }
120✔
3287
    }
120✔
3288
    else {
162✔
3289
        switch (type_id) {
162✔
3290
            case type_Int:
36✔
3291
                if (is_nullable(from)) {
36✔
3292
                    change_nullability<Optional<int64_t>, int64_t>(from, to, throw_on_null);
6✔
3293
                }
6✔
3294
                else {
30✔
3295
                    change_nullability<int64_t, Optional<int64_t>>(from, to, throw_on_null);
30✔
3296
                }
30✔
3297
                break;
36✔
3298
            case type_Float:
12✔
3299
                change_nullability<float, float>(from, to, throw_on_null);
12✔
3300
                break;
12✔
3301
            case type_Double:
12✔
3302
                change_nullability<double, double>(from, to, throw_on_null);
12✔
3303
                break;
12✔
3304
            case type_Bool:
12✔
3305
                change_nullability<Optional<bool>, Optional<bool>>(from, to, throw_on_null);
12✔
3306
                break;
12✔
3307
            case type_String:
30✔
3308
                change_nullability<StringData, StringData>(from, to, throw_on_null);
30✔
3309
                break;
30✔
3310
            case type_Binary:
12✔
3311
                change_nullability<BinaryData, BinaryData>(from, to, throw_on_null);
12✔
3312
                break;
12✔
3313
            case type_Timestamp:
12✔
3314
                change_nullability<Timestamp, Timestamp>(from, to, throw_on_null);
12✔
3315
                break;
12✔
3316
            case type_ObjectId:
12✔
3317
                if (is_nullable(from)) {
12✔
3318
                    change_nullability<Optional<ObjectId>, ObjectId>(from, to, throw_on_null);
6✔
3319
                }
6✔
3320
                else {
6✔
3321
                    change_nullability<ObjectId, Optional<ObjectId>>(from, to, throw_on_null);
6✔
3322
                }
6✔
3323
                break;
12✔
3324
            case type_Decimal:
12✔
3325
                change_nullability<Decimal128, Decimal128>(from, to, throw_on_null);
12✔
3326
                break;
12✔
3327
            case type_UUID:
12✔
3328
                if (is_nullable(from)) {
12✔
3329
                    change_nullability<Optional<UUID>, UUID>(from, to, throw_on_null);
6✔
3330
                }
6✔
3331
                else {
6✔
3332
                    change_nullability<UUID, Optional<UUID>>(from, to, throw_on_null);
6✔
3333
                }
6✔
3334
                break;
12✔
3335
            case type_TypedLink:
✔
3336
            case type_Link:
✔
3337
                // Always nullable, so can't convert
3338
            case type_Mixed:
✔
3339
                // These types are no longer supported at all
3340
                REALM_UNREACHABLE();
3341
                break;
×
3342
        }
162✔
3343
    }
162✔
3344
}
282✔
3345

3346

3347
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3348
{
522✔
3349
    if (col_key.is_nullable() == nullable)
522✔
3350
        return col_key;
240✔
3351

3352
    check_column(col_key);
282✔
3353

3354
    auto index_type = search_index_type(col_key);
282✔
3355
    std::string column_name(get_column_name(col_key));
282✔
3356
    auto type = col_key.get_type();
282✔
3357
    auto attr = col_key.get_attrs();
282✔
3358
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3359
    if (nullable) {
282✔
3360
        attr.set(col_attr_Nullable);
150✔
3361
    }
150✔
3362
    else {
132✔
3363
        attr.reset(col_attr_Nullable);
132✔
3364
    }
132✔
3365

3366
    ColKey new_col = generate_col_key(type, attr);
282✔
3367
    do_insert_root_column(new_col, type, "__temporary");
282✔
3368

3369
    try {
282✔
3370
        convert_column(col_key, new_col, throw_on_null);
282✔
3371
    }
282✔
3372
    catch (...) {
282✔
3373
        // remove any partially filled column
3374
        remove_column(new_col);
6✔
3375
        throw;
6✔
3376
    }
6✔
3377

3378
    if (is_pk_col) {
276✔
3379
        // If we go from non nullable to nullable, no values change,
3380
        // so it is safe to preserve the pk column. Otherwise it is not
3381
        // safe as a null entry might have been converted to default value.
3382
        do_set_primary_key_column(nullable ? new_col : ColKey{});
12✔
3383
    }
12✔
3384

3385
    erase_root_column(col_key);
276✔
3386
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3387

3388
    if (index_type != IndexType::None)
276✔
3389
        do_add_search_index(new_col, index_type);
30✔
3390

3391
    return new_col;
276✔
3392
}
282✔
3393

3394
bool Table::has_any_embedded_objects()
3395
{
2,470,347✔
3396
    if (!m_has_any_embedded_objects) {
2,470,347✔
3397
        m_has_any_embedded_objects = false;
24,882✔
3398
        for_each_public_column([&](ColKey col_key) {
60,318✔
3399
            auto target_table_key = get_opposite_table_key(col_key);
60,318✔
3400
            if (target_table_key && is_link_type(col_key.get_type())) {
60,318✔
3401
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
11,424✔
3402
                if (target_table->is_embedded()) {
11,424✔
3403
                    m_has_any_embedded_objects = true;
9,291✔
3404
                    return IteratorControl::Stop; // early out
9,291✔
3405
                }
9,291✔
3406
            }
11,424✔
3407
            return IteratorControl::AdvanceToNext;
51,027✔
3408
        });
60,318✔
3409
    }
24,882✔
3410
    return *m_has_any_embedded_objects;
2,470,347✔
3411
}
2,470,347✔
3412

3413
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3414
{
156,252✔
3415
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
156,252✔
3416
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
156,252✔
3417
}
156,252✔
3418

3419
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3420
{
47,580✔
3421
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
163,305✔
3422
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
155,547✔
3423
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
39,822✔
3424
        }
39,822✔
3425
    }
155,547✔
3426

3427
    return {};
7,758✔
3428
}
47,580✔
3429

3430
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3431
{
47,508✔
3432
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
47,508✔
3433

3434
    if (!backlink_col_key) {
47,508✔
3435
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
7,758✔
3436
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
7,758✔
3437

3438
        if (Replication* repl = get_repl())
7,758✔
3439
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
7,452✔
3440
                                    m_key); // Throws
7,452✔
3441
    }
7,758✔
3442

3443
    return backlink_col_key;
47,508✔
3444
}
47,508✔
3445

3446
TableKey Table::get_opposite_table_key(ColKey col_key) const
3447
{
14,465,493✔
3448
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,465,493✔
3449
}
14,465,493✔
3450

3451
bool Table::links_to_self(ColKey col_key) const
3452
{
67,119✔
3453
    return get_opposite_table_key(col_key) == m_key;
67,119✔
3454
}
67,119✔
3455

3456
TableRef Table::get_opposite_table(ColKey col_key) const
3457
{
7,813,152✔
3458
    if (auto k = get_opposite_table_key(col_key)) {
7,813,152✔
3459
        return get_parent_group()->get_table(k);
7,751,445✔
3460
    }
7,751,445✔
3461
    return {};
61,707✔
3462
}
7,813,152✔
3463

3464
ColKey Table::get_opposite_column(ColKey col_key) const
3465
{
16,300,080✔
3466
    return ColKey(m_opposite_column.get(col_key.get_index().val));
16,300,080✔
3467
}
16,300,080✔
3468

3469
ColKey Table::find_opposite_column(ColKey col_key) const
3470
{
×
3471
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3472
        if (m_opposite_column.get(i) == col_key.value) {
×
3473
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3474
        }
×
3475
    }
×
3476
    return ColKey();
×
3477
}
×
3478

3479
ref_type Table::typed_write(ref_type ref, _impl::ArrayWriterBase& out) const
3480
{
1,295,229✔
3481
    REALM_ASSERT(ref == m_top.get_mem().get_ref());
1,295,229✔
3482
    if (out.only_modified && m_alloc.is_read_only(ref))
1,295,229✔
3483
        return ref;
523,563✔
3484
    out.table = this;
771,666✔
3485
    // ignore ref from here, just use Tables own accessors
3486
    TempArray dest(m_top.size());
771,666✔
3487
    for (unsigned j = 0; j < m_top.size(); ++j) {
12,344,688✔
3488
        RefOrTagged rot = m_top.get_as_ref_or_tagged(j);
11,573,022✔
3489
        if (rot.is_tagged() || (rot.is_ref() && rot.get_as_ref() == 0)) {
11,573,022✔
3490
            dest.set(j, rot);
6,936,168✔
3491
        }
6,936,168✔
3492
        else {
4,636,854✔
3493
            ref_type new_ref;
4,636,854✔
3494
            if (j == 2) {
4,636,854✔
3495
                // only do type driven write for clustertree
3496
                new_ref = m_clusters.typed_write(rot.get_as_ref(), out);
771,654✔
3497
            }
771,654✔
3498
            else {
3,865,200✔
3499
                // rest is handled using untyped approach
3500
                Array a(m_alloc);
3,865,200✔
3501
                a.init_from_ref(rot.get_as_ref());
3,865,200✔
3502
                new_ref = a.write(out, true, out.only_modified, false);
3,865,200✔
3503
            }
3,865,200✔
3504
            dest.set_as_ref(j, new_ref);
4,636,854✔
3505
        }
4,636,854✔
3506
    }
11,573,022✔
3507
    return dest.write(out);
771,666✔
3508
}
1,295,229✔
3509

3510
void Table::typed_print(std::string prefix, ref_type ref) const
3511
{
×
3512
    REALM_ASSERT(ref == m_top.get_mem().get_ref());
×
3513
    std::cout << prefix << "Table with key = " << m_key << " " << NodeHeader::header_to_string(m_top.get_header())
×
3514
              << " {" << std::endl;
×
3515
    for (unsigned j = 0; j < m_top.size(); ++j) {
×
3516
        auto pref = prefix + "  " + to_string(j) + ":\t";
×
3517
        auto rot = m_top.get_as_ref_or_tagged(j);
×
3518
        if (rot.is_ref() && rot.get_as_ref()) {
×
3519
            if (j == 0) {
×
3520
                m_spec.typed_print(pref);
×
3521
            }
×
3522
            else if (j == 2) {
×
3523
                m_clusters.typed_print(pref);
×
3524
            }
×
3525
            else {
×
3526
                Array a(m_alloc);
×
3527
                a.init_from_ref(rot.get_as_ref());
×
3528
                std::cout << pref;
×
3529
                a.typed_print(pref);
×
3530
            }
×
3531
        }
×
3532
    }
×
3533
    std::cout << prefix << "}" << std::endl;
×
3534
}
×
3535

3536
StringInterner* Table::get_string_interner(ColKey col_key) const
3537
{
71,682,309✔
3538
    auto idx = col_key.get_index().val;
71,682,309✔
3539
    REALM_ASSERT(idx < m_string_interners.size());
71,682,309✔
3540
    auto interner = m_string_interners[idx].get();
71,682,309✔
3541
    REALM_ASSERT(interner);
71,682,309✔
3542
    return interner;
71,682,309✔
3543
}
71,682,309✔
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