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

realm / realm-core / jorgen.edelbo_402

21 Aug 2024 11:10AM UTC coverage: 91.054% (-0.03%) from 91.085%
jorgen.edelbo_402

Pull #7803

Evergreen

jedelbo
Small fix to Table::typed_write

When writing the realm to a new file from a write transaction,
the Table may be COW so that the top ref is changed. So don't
use the ref that is present in the group when the operation starts.
Pull Request #7803: Feature/string compression

103494 of 181580 branches covered (57.0%)

1929 of 1999 new or added lines in 46 files covered. (96.5%)

695 existing lines in 51 files now uncovered.

220142 of 241772 relevant lines covered (91.05%)

7344461.76 hits per line

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

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

19
#include <realm/table.hpp>
20

21
#include <realm/alloc_slab.hpp>
22
#include <realm/array_binary.hpp>
23
#include <realm/array_bool.hpp>
24
#include <realm/array_decimal128.hpp>
25
#include <realm/array_fixed_bytes.hpp>
26
#include <realm/array_string.hpp>
27
#include <realm/array_timestamp.hpp>
28
#include <realm/db.hpp>
29
#include <realm/dictionary.hpp>
30
#include <realm/exceptions.hpp>
31
#include <realm/impl/destroy_guard.hpp>
32
#include <realm/index_string.hpp>
33
#include <realm/query_conditions_tpl.hpp>
34
#include <realm/replication.hpp>
35
#include <realm/table_view.hpp>
36
#include <realm/util/features.h>
37
#include <realm/util/serializer.hpp>
38
#include <realm/string_interner.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
static inline bool needs_string_interner(ColKey col_key)
268
{
12,443,226✔
269
    return col_key.get_type() == col_type_String || col_key.get_type() == col_type_Mixed || col_key.is_dictionary();
12,443,226✔
270
}
12,443,226✔
271

272
bool TableVersions::operator==(const TableVersions& other) const
273
{
219,900✔
274
    if (size() != other.size())
219,900✔
275
        return false;
15,870✔
276
    size_t sz = size();
204,030✔
277
    for (size_t i = 0; i < sz; i++) {
375,963✔
278
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
205,464✔
279
        if (this->at(i).second != other.at(i).second)
205,464✔
280
            return false;
33,531✔
281
    }
205,464✔
282
    return true;
170,499✔
283
}
204,030✔
284

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

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

342
bool LinkChain::add(ColKey ck)
343
{
806,838✔
344
    // Link column can be a single Link, LinkList, or BackLink.
345
    REALM_ASSERT(m_current_table->valid_column(ck));
806,838✔
346
    ColumnType type = ck.get_type();
806,838✔
347
    if (type == col_type_Link || type == col_type_BackLink) {
806,838✔
348
        m_current_table = m_current_table->get_opposite_table(ck);
88,338✔
349
        m_link_cols.push_back(ck);
88,338✔
350
        return true;
88,338✔
351
    }
88,338✔
352
    return false;
718,500✔
353
}
806,838✔
354

355
// -- Table ---------------------------------------------------------------------------------
356

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

380
Table::Table(Replication* const* repl, Allocator& alloc)
381
    : m_alloc(alloc)
11,241✔
382
    , m_top(m_alloc)
11,241✔
383
    , m_spec(m_alloc)
11,241✔
384
    , m_clusters(this, m_alloc, top_position_for_cluster_tree)
11,241✔
385
    , m_index_refs(m_alloc)
11,241✔
386
    , m_opposite_table(m_alloc)
11,241✔
387
    , m_opposite_column(m_alloc)
11,241✔
388
    , m_interner_data(m_alloc)
11,241✔
389
    , m_repl(repl)
11,241✔
390
    , m_own_ref(this, alloc.get_instance_version())
11,241✔
391
{
21,810✔
392
    m_spec.set_parent(&m_top, top_position_for_spec);
21,810✔
393
    m_index_refs.set_parent(&m_top, top_position_for_search_indexes);
21,810✔
394
    m_opposite_table.set_parent(&m_top, top_position_for_opposite_table);
21,810✔
395
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
21,810✔
396
    m_opposite_column.set_parent(&m_top, top_position_for_opposite_column);
21,810✔
397
    m_interner_data.set_parent(&m_top, top_position_for_interners);
21,810✔
398
    m_cookie = cookie_created;
21,810✔
399
}
21,810✔
400

401
ColKey Table::add_column(DataType type, StringData name, bool nullable, std::optional<CollectionType> collection_type,
402
                         DataType key_type)
403
{
516,744✔
404
    REALM_ASSERT(!is_link_type(ColumnType(type)));
516,744✔
405
    if (type == type_TypedLink) {
516,744✔
406
        throw IllegalOperation("TypedLink properties not yet supported");
×
407
    }
×
408

409
    ColumnAttrMask attr;
516,744✔
410
    if (collection_type) {
516,744✔
411
        switch (*collection_type) {
150,432✔
412
            case CollectionType::List:
61,851✔
413
                attr.set(col_attr_List);
61,851✔
414
                break;
61,851✔
415
            case CollectionType::Set:
43,425✔
416
                attr.set(col_attr_Set);
43,425✔
417
                break;
43,425✔
418
            case CollectionType::Dictionary:
45,156✔
419
                attr.set(col_attr_Dictionary);
45,156✔
420
                break;
45,156✔
421
        }
150,432✔
422
    }
150,432✔
423
    if (nullable || type == type_Mixed)
516,744✔
424
        attr.set(col_attr_Nullable);
185,121✔
425
    ColKey col_key = generate_col_key(ColumnType(type), attr);
516,744✔
426

427
    Table* invalid_link = nullptr;
516,744✔
428
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
516,744✔
429
}
516,744✔
430

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

444
    m_has_any_embedded_objects.reset();
75,291✔
445

446
    DataType data_type = type_Link;
75,291✔
447
    ColumnAttrMask attr;
75,291✔
448
    if (collection_type) {
75,291✔
449
        switch (*collection_type) {
44,169✔
450
            case CollectionType::List:
22,071✔
451
                attr.set(col_attr_List);
22,071✔
452
                break;
22,071✔
453
            case CollectionType::Set:
10,734✔
454
                if (target.is_embedded())
10,734✔
455
                    throw IllegalOperation("Set of embedded objects not supported");
×
456
                attr.set(col_attr_Set);
10,734✔
457
                break;
10,734✔
458
            case CollectionType::Dictionary:
11,364✔
459
                attr.set(col_attr_Dictionary);
11,364✔
460
                attr.set(col_attr_Nullable);
11,364✔
461
                break;
11,364✔
462
        }
44,169✔
463
    }
44,169✔
464
    else {
31,122✔
465
        attr.set(col_attr_Nullable);
31,122✔
466
    }
31,122✔
467
    ColKey col_key = generate_col_key(ColumnType(data_type), attr);
75,291✔
468

469
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
75,291✔
470
}
75,291✔
471

472
void Table::remove_recursive(CascadeState& cascade_state)
473
{
192,663✔
474
    Group* group = get_parent_group();
192,663✔
475
    REALM_ASSERT(group);
192,663✔
476
    cascade_state.m_group = group;
192,663✔
477

478
    do {
240,228✔
479
        cascade_state.send_notifications();
240,228✔
480

481
        for (auto& l : cascade_state.m_to_be_nullified) {
240,228✔
482
            Obj obj = group->get_table_unchecked(l.origin_table)->try_get_object(l.origin_key);
48✔
483
            REALM_ASSERT_DEBUG(obj);
48✔
484
            if (obj) {
48✔
485
                std::move(obj).nullify_link(l.origin_col_key, l.old_target_link);
48✔
486
            }
48✔
487
        }
48✔
488
        cascade_state.m_to_be_nullified.clear();
240,228✔
489

490
        auto to_delete = std::move(cascade_state.m_to_be_deleted);
240,228✔
491
        for (auto obj : to_delete) {
240,228✔
492
            auto table = obj.first == m_key ? this : group->get_table_unchecked(obj.first);
232,569✔
493
            // This might add to the list of objects that should be deleted
494
            REALM_ASSERT(!obj.second.is_unresolved());
232,569✔
495
            table->m_clusters.erase(obj.second, cascade_state);
232,569✔
496
        }
232,569✔
497
        nullify_links(cascade_state);
240,228✔
498
    } while (!cascade_state.m_to_be_deleted.empty() || !cascade_state.m_to_be_nullified.empty());
240,228✔
499
}
192,663✔
500

501
void Table::nullify_links(CascadeState& cascade_state)
502
{
417,846✔
503
    Group* group = get_parent_group();
417,846✔
504
    REALM_ASSERT(group);
417,846✔
505
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
417,846✔
506
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
219,492✔
507
        if (!table->is_asymmetric())
219,492✔
508
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
219,498✔
509
    }
219,492✔
510
}
417,846✔
511

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

524
void Table::remove_columns()
525
{
2,583✔
526
    for (size_t i = get_column_count(); i > 0; --i) {
7,515✔
527
        ColKey col_key = spec_ndx2colkey(i - 1);
4,932✔
528
        remove_column(col_key);
4,932✔
529
    }
4,932✔
530
}
2,583✔
531

532
void Table::remove_column(ColKey col_key)
533
{
5,628✔
534
    check_column(col_key);
5,628✔
535

536
    if (Replication* repl = get_repl())
5,628✔
537
        repl->erase_column(this, col_key); // Throws
4,242✔
538

539
    if (col_key == m_primary_key_col) {
5,628✔
540
        do_set_primary_key_column(ColKey());
1,008✔
541
    }
1,008✔
542
    else {
4,620✔
543
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
4,620✔
544
    }
4,620✔
545

546
    erase_root_column(col_key); // Throws
5,628✔
547
    m_has_any_embedded_objects.reset();
5,628✔
548
}
5,628✔
549

550

551
void Table::rename_column(ColKey col_key, StringData name)
552
{
90✔
553
    check_column(col_key);
90✔
554

555
    auto col_ndx = colkey2spec_ndx(col_key);
90✔
556
    m_spec.rename_column(col_ndx, name); // Throws
90✔
557

558
    bump_content_version();
90✔
559
    bump_storage_version();
90✔
560

561
    if (Replication* repl = get_repl())
90✔
562
        repl->rename_column(this, col_key, name); // Throws
90✔
563
}
90✔
564

565

566
TableKey Table::get_key_direct(Allocator& alloc, ref_type top_ref)
567
{
2,233,788✔
568
    // well, not quite "direct", more like "almost direct":
569
    Array table_top(alloc);
2,233,788✔
570
    table_top.init_from_ref(top_ref);
2,233,788✔
571
    if (table_top.size() > 3) {
2,233,791✔
572
        RefOrTagged rot = table_top.get_as_ref_or_tagged(top_position_for_key);
2,228,859✔
573
        return TableKey(int32_t(rot.get_as_int()));
2,228,859✔
574
    }
2,228,859✔
575
    else {
2,147,488,579✔
576
        return TableKey();
2,147,488,579✔
577
    }
2,147,488,579✔
578
}
2,233,788✔
579

580

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

590
    m_spec.init_from_parent();
2,141,994✔
591

592
    while (m_top.size() <= top_position_for_pk_col) {
2,141,994✔
593
        m_top.add(0);
×
594
    }
×
595

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

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

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

644
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
2,141,994✔
645
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
2,141,994✔
646

647
    if (m_top.size() <= top_position_for_flags) {
2,141,994✔
648
        m_table_type = Type::TopLevel;
30✔
649
    }
30✔
650
    else {
2,141,964✔
651
        uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
2,141,964✔
652
        m_table_type = Type(flags & table_type_mask);
2,141,964✔
653
    }
2,141,964✔
654
    m_has_any_embedded_objects.reset();
2,141,994✔
655

656
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
2,141,994✔
657
        // Tombstones exists
658
        if (!m_tombstones) {
30,852✔
659
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
25,743✔
660
        }
25,743✔
661
        m_tombstones->init_from_parent();
30,852✔
662
    }
30,852✔
663
    else {
2,111,142✔
664
        m_tombstones = nullptr;
2,111,142✔
665
    }
2,111,142✔
666
    if (m_top.size() > top_position_for_interners && m_top.get_as_ref(top_position_for_interners)) {
2,141,994✔
667
        // Interner data exist
668
        m_interner_data.init_from_parent();
1,852,449✔
669
    }
1,852,449✔
670
    else {
289,545✔
671
        REALM_ASSERT_DEBUG(!m_interner_data.is_attached());
289,545✔
672
    }
289,545✔
673
    refresh_string_interners(is_writable);
2,141,994✔
674
    m_cookie = cookie_initialized;
2,141,994✔
675
}
2,141,994✔
676

677

678
ColKey Table::do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table, DataType key_type)
679
{
592,038✔
680
    col_key = do_insert_root_column(col_key, ColumnType(type), name, key_type); // Throws
592,038✔
681

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

685
    if (target_table) {
592,038✔
686
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
75,285✔
687
        target_table->check_column(backlink_col_key);
75,285✔
688

689
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
75,285✔
690
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
75,285✔
691
    }
75,285✔
692

693
    if (Replication* repl = get_repl())
592,038✔
694
        repl->insert_column(this, col_key, type, name, target_table); // Throws
574,506✔
695

696
    return col_key;
592,038✔
697
}
592,038✔
698

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

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

711
    table->traverse_clusters(f);
114,159✔
712
}
114,159✔
713

714

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

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

725
    table->traverse_clusters(f);
24✔
726
}
24✔
727

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

734
    if (type == type_Int) {
114,183✔
735
        if (is_nullable(col_key)) {
50,040✔
736
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
13,791✔
737
        }
13,791✔
738
        else {
36,249✔
739
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
36,249✔
740
        }
36,249✔
741
    }
50,040✔
742
    else if (type == type_Bool) {
64,143✔
743
        if (is_nullable(col_key)) {
51✔
744
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
27✔
745
        }
27✔
746
        else {
24✔
747
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
24✔
748
        }
24✔
749
    }
51✔
750
    else if (type == type_String) {
64,092✔
751
        if (col_key.is_list()) {
18,903✔
752
            do_bulk_insert_index_list(this, index, col_key, get_alloc());
24✔
753
        }
24✔
754
        else {
18,879✔
755
            do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
18,879✔
756
        }
18,879✔
757
    }
18,903✔
758
    else if (type == type_Timestamp) {
45,189✔
759
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
96✔
760
    }
96✔
761
    else if (type == type_ObjectId) {
45,093✔
762
        if (is_nullable(col_key)) {
43,503✔
763
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
996✔
764
        }
996✔
765
        else {
42,507✔
766
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
42,507✔
767
        }
42,507✔
768
    }
43,503✔
769
    else if (type == type_UUID) {
1,590✔
770
        if (is_nullable(col_key)) {
684✔
771
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
772
        }
516✔
773
        else {
168✔
774
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
168✔
775
        }
168✔
776
    }
684✔
777
    else if (type == type_Mixed) {
906✔
778
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
906✔
779
    }
906✔
780
    else {
×
781
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
782
    }
×
783
}
114,183✔
784

785
void Table::erase_from_search_indexes(ObjKey key)
786
{
5,317,170✔
787
    // Tombstones do not use index - will crash if we try to erase values
788
    if (!key.is_unresolved()) {
5,317,170✔
789
        for (auto&& index : m_index_accessors) {
7,288,203✔
790
            if (index) {
7,288,203✔
791
                index->erase(key);
538,983✔
792
            }
538,983✔
793
        }
7,288,203✔
794
    }
5,277,564✔
795
}
5,317,170✔
796

797
void Table::update_indexes(ObjKey key, const FieldValues& values)
798
{
24,600,552✔
799
    // Tombstones do not use index - will crash if we try to insert values
800
    if (key.is_unresolved()) {
24,600,552✔
801
        return;
40,884✔
802
    }
40,884✔
803

804
    auto sz = m_index_accessors.size();
24,559,668✔
805
    // values are sorted by column index - there may be values missing
806
    auto value = values.begin();
24,559,668✔
807
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
60,667,380✔
808
        // Check if initial value is provided
809
        Mixed init_value;
36,107,634✔
810
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
36,107,634✔
811
            // Value for this column is provided
812
            init_value = value->value;
942,114✔
813
            ++value;
942,114✔
814
        }
942,114✔
815

816
        if (auto&& index = m_index_accessors[column_ndx]) {
36,107,634✔
817
            // There is an index for this column
818
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,363,032✔
819
            if (col_key.is_collection())
1,363,032✔
820
                continue;
102✔
821
            auto type = col_key.get_type();
1,362,930✔
822
            auto attr = col_key.get_attrs();
1,362,930✔
823
            bool nullable = attr.test(col_attr_Nullable);
1,362,930✔
824
            switch (type) {
1,362,930✔
825
                case col_type_Int:
481,530✔
826
                    if (init_value.is_null()) {
481,530✔
827
                        index->insert(key, ArrayIntNull::default_value(nullable));
166,884✔
828
                    }
166,884✔
829
                    else {
314,646✔
830
                        index->insert(key, init_value.get<int64_t>());
314,646✔
831
                    }
314,646✔
832
                    break;
481,530✔
833
                case col_type_Bool:
5,925✔
834
                    if (init_value.is_null()) {
5,925✔
835
                        index->insert(key, ArrayBoolNull::default_value(nullable));
5,925✔
836
                    }
5,925✔
837
                    else {
×
838
                        index->insert(key, init_value.get<bool>());
×
839
                    }
×
840
                    break;
5,925✔
841
                case col_type_String:
715,563✔
842
                    if (init_value.is_null()) {
715,563✔
843
                        index->insert(key, ArrayString::default_value(nullable));
260,886✔
844
                    }
260,886✔
845
                    else {
454,677✔
846
                        index->insert(key, init_value.get<String>());
454,677✔
847
                    }
454,677✔
848
                    break;
715,563✔
849
                case col_type_Timestamp:
7,305✔
850
                    if (init_value.is_null()) {
7,305✔
851
                        index->insert(key, ArrayTimestamp::default_value(nullable));
7,305✔
852
                    }
7,305✔
853
                    else {
×
854
                        index->insert(key, init_value.get<Timestamp>());
×
855
                    }
×
856
                    break;
7,305✔
857
                case col_type_ObjectId:
130,881✔
858
                    if (init_value.is_null()) {
130,881✔
859
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
7,320✔
860
                    }
7,320✔
861
                    else {
123,561✔
862
                        index->insert(key, init_value.get<ObjectId>());
123,561✔
863
                    }
123,561✔
864
                    break;
130,881✔
865
                case col_type_Mixed:
2,286✔
866
                    index->insert(key, init_value);
2,286✔
867
                    break;
2,286✔
868
                case col_type_UUID:
19,542✔
869
                    if (init_value.is_null()) {
19,542✔
870
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
7,338✔
871
                    }
7,338✔
872
                    else {
12,204✔
873
                        index->insert(key, init_value.get<UUID>());
12,204✔
874
                    }
12,204✔
875
                    break;
19,542✔
876
                default:
✔
877
                    REALM_UNREACHABLE();
878
            }
1,362,930✔
879
        }
1,362,930✔
880
    }
36,107,634✔
881
}
24,559,668✔
882

883
void Table::clear_indexes()
884
{
5,568✔
885
    for (auto&& index : m_index_accessors) {
58,371✔
886
        if (index) {
58,371✔
887
            index->clear();
4,890✔
888
        }
4,890✔
889
    }
58,371✔
890
}
5,568✔
891

892
void Table::do_add_search_index(ColKey col_key, IndexType type)
893
{
114,375✔
894
    size_t column_ndx = col_key.get_index().val;
114,375✔
895

896
    // Early-out if already indexed
897
    if (m_index_accessors[column_ndx] != nullptr)
114,375✔
898
        return;
150✔
899

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

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

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

920
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
114,183✔
921

922
    populate_search_index(col_key);
114,183✔
923
}
114,183✔
924

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

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

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

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

961
    do_add_search_index(col_key, type);
3,498✔
962

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

968
void Table::remove_search_index(ColKey col_key)
969
{
1,458✔
970
    check_column(col_key);
1,458✔
971
    auto column_ndx = col_key.get_index();
1,458✔
972

973
    // Early-out if non-indexed
974
    if (m_index_accessors[column_ndx.val] == nullptr)
1,458✔
975
        return;
69✔
976

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

983
    m_index_refs.set(column_ndx.val, 0);
1,389✔
984

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

993
void Table::erase_root_column(ColKey col_key)
994
{
5,904✔
995
    ColumnType col_type = col_key.get_type();
5,904✔
996
    if (is_link_type(col_type)) {
5,904✔
997
        auto target_table = get_opposite_table(col_key);
609✔
998
        auto target_column = get_opposite_column(col_key);
609✔
999
        target_table->do_erase_root_column(target_column);
609✔
1000
    }
609✔
1001
    do_erase_root_column(col_key); // Throws
5,904✔
1002
}
5,904✔
1003

1004

1005
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
1006
{
786,024✔
1007
    // if col_key specifies a key, it must be unused
1008
    REALM_ASSERT(!col_key || !valid_column(col_key));
786,024✔
1009

1010
    // locate insertion point: ordinary columns must come before backlink columns
1011
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
786,024✔
1012

1013
    if (!col_key) {
786,024✔
1014
        col_key = generate_col_key(type, {});
83,133✔
1015
    }
83,133✔
1016

1017
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
786,024✔
1018
    if (col_key.is_dictionary()) {
786,024✔
1019
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
56,520✔
1020
    }
56,520✔
1021
    auto col_ndx = col_key.get_index().val;
786,024✔
1022
    build_column_mapping();
786,024✔
1023
    REALM_ASSERT(col_ndx <= m_index_refs.size());
786,024✔
1024
    if (col_ndx == m_index_refs.size()) {
786,024✔
1025
        m_index_refs.insert(col_ndx, 0);
785,766✔
1026
    }
785,766✔
1027
    else {
258✔
1028
        m_index_refs.set(col_ndx, 0);
258✔
1029
    }
258✔
1030
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
786,024✔
1031
    if (col_ndx == m_opposite_table.size()) {
786,024✔
1032
        // m_opposite_table and m_opposite_column are always resized together!
1033
        m_opposite_table.insert(col_ndx, TableKey().value);
785,754✔
1034
        m_opposite_column.insert(col_ndx, ColKey().value);
785,754✔
1035
    }
785,754✔
1036
    else {
270✔
1037
        m_opposite_table.set(col_ndx, TableKey().value);
270✔
1038
        m_opposite_column.set(col_ndx, ColKey().value);
270✔
1039
    }
270✔
1040
    refresh_index_accessors();
786,024✔
1041
    m_clusters.insert_column(col_key);
786,024✔
1042
    if (m_tombstones) {
786,024✔
1043
        m_tombstones->insert_column(col_key);
210✔
1044
    }
210✔
1045
    if (needs_string_interner(col_key)) {
786,024✔
1046
        // create string interners internal rep as well as data area
1047
        REALM_ASSERT_DEBUG(m_interner_data.is_attached());
189,102✔
1048
        while (col_ndx >= m_string_interners.size()) {
634,182✔
1049
            m_string_interners.push_back({});
445,080✔
1050
        }
445,080✔
1051
        while (col_ndx >= m_interner_data.size()) {
634,179✔
1052
            m_interner_data.add(0);
445,077✔
1053
        }
445,077✔
1054
        REALM_ASSERT(!m_string_interners[col_ndx]);
189,102✔
1055
        m_string_interners[col_ndx] = std::make_unique<StringInterner>(m_alloc, m_interner_data, col_key, true);
189,102✔
1056
    }
189,102✔
1057
    bump_storage_version();
786,024✔
1058

1059
    return col_key;
786,024✔
1060
}
786,024✔
1061

1062

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

1083
    build_column_mapping();
6,513✔
1084
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
12,450✔
1085
        REALM_ASSERT(m_index_accessors.back() == nullptr);
5,937✔
1086
        m_index_accessors.pop_back();
5,937✔
1087
    }
5,937✔
1088
    if (col_ndx < m_string_interners.size() && m_string_interners[col_ndx]) {
6,513✔
1089
        REALM_ASSERT_DEBUG(m_interner_data.is_attached());
1,335✔
1090
        REALM_ASSERT_DEBUG(col_ndx < m_interner_data.size());
1,335✔
1091
        auto data_ref = m_interner_data.get_as_ref(col_ndx);
1,335✔
1092
        if (data_ref)
1,335✔
1093
            Array::destroy_deep(data_ref, m_alloc);
1,335✔
1094
        m_interner_data.set(col_ndx, 0);
1,335✔
1095
        m_string_interners[col_ndx].reset();
1,335✔
1096
    }
1,335✔
1097
    bump_content_version();
6,513✔
1098
    bump_storage_version();
6,513✔
1099
}
6,513✔
1100

1101
Query Table::where(const Dictionary& dict) const
1102
{
12✔
1103
    return Query(m_own_ref, dict.clone_as_obj_list());
12✔
1104
}
12✔
1105

1106
void Table::set_table_type(Type table_type, bool handle_backlinks)
1107
{
312✔
1108
    if (table_type == m_table_type) {
312✔
1109
        return;
×
1110
    }
×
1111

1112
    if (m_table_type == Type::TopLevelAsymmetric || table_type == Type::TopLevelAsymmetric) {
312✔
1113
        throw LogicError(ErrorCodes::MigrationFailed, util::format("Cannot change '%1' from %2 to %3",
×
1114
                                                                   get_class_name(), m_table_type, table_type));
×
1115
    }
×
1116

1117
    REALM_ASSERT_EX(table_type == Type::TopLevel || table_type == Type::Embedded, table_type);
312✔
1118
    set_embedded(table_type == Type::Embedded, handle_backlinks);
312✔
1119
}
312✔
1120

1121
void Table::set_embedded(bool embedded, bool handle_backlinks)
1122
{
312✔
1123
    if (embedded == false) {
312✔
1124
        do_set_table_type(Type::TopLevel);
24✔
1125
        return;
24✔
1126
    }
24✔
1127

1128
    // Embedded objects cannot have a primary key.
1129
    if (get_primary_key_column()) {
288✔
1130
        throw IllegalOperation(
6✔
1131
            util::format("Cannot change '%1' to embedded when using a primary key.", get_class_name()));
6✔
1132
    }
6✔
1133

1134
    if (size() == 0) {
282✔
1135
        do_set_table_type(Type::Embedded);
42✔
1136
        return;
42✔
1137
    }
42✔
1138

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

1155
        for_each_backlink_column([&](ColKey col) {
606✔
1156
            cluster->init_leaf(col, &leaf);
606✔
1157
            // Width zero means all the values are zero and there can't be any backlinks
1158
            if (leaf.get_width() == 0) {
606✔
1159
                return IteratorControl::AdvanceToNext;
36✔
1160
            }
36✔
1161

1162
            for (size_t i = 0, size = leaf.size(); i < size; ++i) {
60,816✔
1163
                auto value = leaf.get_as_ref_or_tagged(i);
60,300✔
1164
                if (value.is_ref() && value.get_as_ref() == 0) {
60,300✔
1165
                    // ref of zero means there's no backlinks
1166
                    continue;
59,340✔
1167
                }
59,340✔
1168

1169
                if (value.is_ref()) {
960✔
1170
                    // Any other ref indicates an array of backlinks, which will
1171
                    // always have more than one entry
1172
                    incoming_link_count[i] = LinkCount::Multiple;
78✔
1173
                }
78✔
1174
                else {
882✔
1175
                    // Otherwise it's a tagged ref to the single linking object
1176
                    if (incoming_link_count[i] == LinkCount::None) {
882✔
1177
                        incoming_link_count[i] = LinkCount::One;
792✔
1178
                    }
792✔
1179
                    else if (incoming_link_count[i] == LinkCount::One) {
90✔
1180
                        incoming_link_count[i] = LinkCount::Multiple;
42✔
1181
                    }
42✔
1182
                }
882✔
1183

1184
                auto source_col = get_opposite_column(col);
960✔
1185
                if (source_col.get_type() == col_type_Mixed) {
960✔
1186
                    auto source_table = get_opposite_table(col);
54✔
1187
                    throw IllegalOperation(util::format(
54✔
1188
                        "Cannot convert '%1' to embedded: there is an incoming link from the Mixed property '%2.%3', "
54✔
1189
                        "which does not support linking to embedded objects.",
54✔
1190
                        get_class_name(), source_table->get_class_name(), source_table->get_column_name(source_col)));
54✔
1191
                }
54✔
1192
            }
960✔
1193
            return IteratorControl::AdvanceToNext;
516✔
1194
        });
570✔
1195

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

1215
        return IteratorControl::AdvanceToNext;
420✔
1216
    });
474✔
1217

1218
    // orphans and multiple_incoming_links will always be empty if `handle_backlinks = false`
1219
    for (auto key : orphans) {
59,406✔
1220
        remove_object(key);
59,406✔
1221
    }
59,406✔
1222
    for (auto key : multiple_incoming_links) {
240✔
1223
        auto obj = get_object(key);
54✔
1224
        obj.handle_multiple_backlinks_during_schema_migration();
54✔
1225
        obj.remove();
54✔
1226
    }
54✔
1227

1228
    do_set_table_type(Type::Embedded);
240✔
1229
}
240✔
1230

1231
void Table::do_set_table_type(Type table_type)
1232
{
282,639✔
1233
    while (m_top.size() <= top_position_for_flags)
282,639✔
1234
        m_top.add(0);
×
1235

1236
    uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
282,639✔
1237
    // reset bits 0-1
1238
    flags &= ~table_type_mask;
282,639✔
1239
    // set table type
1240
    flags |= static_cast<uint8_t>(table_type);
282,639✔
1241
    m_top.set(top_position_for_flags, RefOrTagged::make_tagged(flags));
282,639✔
1242
    m_table_type = table_type;
282,639✔
1243
}
282,639✔
1244

1245

1246
void Table::detach(LifeCycleCookie cookie) noexcept
1247
{
2,137,428✔
1248
    m_cookie = cookie;
2,137,428✔
1249
    m_alloc.bump_instance_version();
2,137,428✔
1250
    // release string interners
1251
    m_string_interners.clear();
2,137,428✔
1252
    m_interner_data.detach();
2,137,428✔
1253
}
2,137,428✔
1254

1255
void Table::fully_detach() noexcept
1256
{
2,121,267✔
1257
    m_spec.detach();
2,121,267✔
1258
    m_top.detach();
2,121,267✔
1259
    m_index_refs.detach();
2,121,267✔
1260
    m_opposite_table.detach();
2,121,267✔
1261
    m_opposite_column.detach();
2,121,267✔
1262
    m_index_accessors.clear();
2,121,267✔
1263
    m_string_interners.clear();
2,121,267✔
1264
}
2,121,267✔
1265

1266

1267
Table::~Table() noexcept
1268
{
3,138✔
1269
    if (m_top.is_attached()) {
3,138✔
1270
        // If destroyed as a standalone table, destroy all memory allocated
1271
        if (m_top.get_parent() == nullptr) {
3,138✔
1272
            m_top.destroy_deep();
3,138✔
1273
        }
3,138✔
1274
        fully_detach();
3,138✔
1275
    }
3,138✔
1276
    else {
×
1277
        REALM_ASSERT(m_index_accessors.size() == 0);
×
1278
    }
×
1279
    m_cookie = cookie_deleted;
3,138✔
1280
}
3,138✔
1281

1282

1283
IndexType Table::search_index_type(ColKey col_key) const noexcept
1284
{
3,897,672✔
1285
    if (m_index_accessors[col_key.get_index().val].get()) {
3,897,672✔
1286
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
914,790✔
1287
        bool fulltext = attr.test(col_attr_FullText_Indexed);
914,790✔
1288
        return fulltext ? IndexType::Fulltext : IndexType::General;
914,790✔
1289
    }
914,790✔
1290
    return IndexType::None;
2,982,882✔
1291
}
3,897,672✔
1292

1293

1294
void Table::migrate_sets_and_dictionaries()
1295
{
180✔
1296
    std::vector<ColKey> to_migrate;
180✔
1297
    for (auto col : get_column_keys()) {
570✔
1298
        if (col.is_dictionary() || (col.is_set() && col.get_type() == col_type_Mixed)) {
570✔
1299
            to_migrate.push_back(col);
12✔
1300
        }
12✔
1301
    }
570✔
1302
    if (to_migrate.size()) {
180✔
1303
        for (auto obj : *this) {
6✔
1304
            for (auto col : to_migrate) {
12✔
1305
                if (col.is_set()) {
12✔
1306
                    auto set = obj.get_set<Mixed>(col);
6✔
1307
                    set.migrate();
6✔
1308
                }
6✔
1309
                else if (col.is_dictionary()) {
6✔
1310
                    auto dict = obj.get_dictionary(col);
6✔
1311
                    dict.migrate();
6✔
1312
                }
6✔
1313
            }
12✔
1314
        }
6✔
1315
    }
6✔
1316
}
180✔
1317

1318
void Table::migrate_set_orderings()
1319
{
354✔
1320
    std::vector<ColKey> to_migrate;
354✔
1321
    for (auto col : get_column_keys()) {
918✔
1322
        if (col.is_set() && (col.get_type() == col_type_Mixed || col.get_type() == col_type_String ||
918✔
1323
                             col.get_type() == col_type_Binary)) {
30✔
1324
            to_migrate.push_back(col);
30✔
1325
        }
30✔
1326
    }
918✔
1327
    if (to_migrate.size()) {
354✔
1328
        for (auto obj : *this) {
90✔
1329
            for (auto col : to_migrate) {
102✔
1330
                if (col.get_type() == col_type_Mixed) {
102✔
1331
                    auto set = obj.get_set<Mixed>(col);
12✔
1332
                    set.migration_resort();
12✔
1333
                }
12✔
1334
                else if (col.get_type() == col_type_Binary) {
90✔
1335
                    auto set = obj.get_set<BinaryData>(col);
6✔
1336
                    set.migration_resort();
6✔
1337
                }
6✔
1338
                else {
84✔
1339
                    REALM_ASSERT_3(col.get_type(), ==, col_type_String);
84✔
1340
                    auto set = obj.get_set<String>(col);
84✔
1341
                    set.migration_resort();
84✔
1342
                }
84✔
1343
            }
102✔
1344
        }
90✔
1345
    }
18✔
1346
}
354✔
1347

1348
void Table::migrate_col_keys()
1349
{
354✔
1350
    if (m_spec.migrate_column_keys()) {
354✔
1351
        build_column_mapping();
24✔
1352
    }
24✔
1353

1354
    // Fix also m_opposite_column col_keys
1355
    ColumnType col_type_LinkList(13);
354✔
1356
    auto sz = m_opposite_column.size();
354✔
1357

1358
    for (size_t n = 0; n < sz; n++) {
1,386✔
1359
        ColKey col_key(m_opposite_column.get(n));
1,032✔
1360
        if (col_key.get_type() == col_type_LinkList) {
1,032✔
1361
            auto attrs = col_key.get_attrs();
24✔
1362
            REALM_ASSERT(attrs.test(col_attr_List));
24✔
1363
            ColKey new_key(col_key.get_index(), col_type_Link, attrs, col_key.get_tag());
24✔
1364
            m_opposite_column.set(n, new_key.value);
24✔
1365
        }
24✔
1366
    }
1,032✔
1367
}
354✔
1368

1369
StringData Table::get_name() const noexcept
1370
{
3,194,826✔
1371
    const Array& real_top = m_top;
3,194,826✔
1372
    ArrayParent* parent = real_top.get_parent();
3,194,826✔
1373
    if (!parent)
3,194,826✔
1374
        return StringData("");
54✔
1375
    REALM_ASSERT(dynamic_cast<Group*>(parent));
3,194,772✔
1376
    return static_cast<Group*>(parent)->get_table_name(get_key());
3,194,772✔
1377
}
3,194,826✔
1378

1379
StringData Table::get_class_name() const noexcept
1380
{
630,939✔
1381
    return Group::table_name_to_class_name(get_name());
630,939✔
1382
}
630,939✔
1383

1384
const char* Table::get_state() const noexcept
1385
{
42✔
1386
    switch (m_cookie) {
42✔
1387
        case cookie_created:
✔
1388
            return "created";
×
1389
        case cookie_transaction_ended:
6✔
1390
            return "transaction_ended";
6✔
1391
        case cookie_initialized:
✔
1392
            return "initialised";
×
1393
        case cookie_removed:
36✔
1394
            return "removed";
36✔
1395
        case cookie_void:
✔
1396
            return "void";
×
1397
        case cookie_deleted:
✔
1398
            return "deleted";
×
1399
    }
42✔
1400
    return "";
×
1401
}
42✔
1402

1403

1404
bool Table::is_nullable(ColKey col_key) const
1405
{
845,775✔
1406
    REALM_ASSERT_DEBUG(valid_column(col_key));
845,775✔
1407
    return col_key.get_attrs().test(col_attr_Nullable);
845,775✔
1408
}
845,775✔
1409

1410
bool Table::is_list(ColKey col_key) const
1411
{
27,843✔
1412
    REALM_ASSERT_DEBUG(valid_column(col_key));
27,843✔
1413
    return col_key.get_attrs().test(col_attr_List);
27,843✔
1414
}
27,843✔
1415

1416

1417
ref_type Table::create_empty_table(Allocator& alloc, TableKey key)
1418
{
285,576✔
1419
    Array top(alloc);
285,576✔
1420
    _impl::DeepArrayDestroyGuard dg(&top);
285,576✔
1421
    top.create(Array::type_HasRefs); // Throws
285,576✔
1422
    _impl::DeepArrayRefDestroyGuard dg_2(alloc);
285,576✔
1423

1424
    {
285,576✔
1425
        MemRef mem = Spec::create_empty_spec(alloc); // Throws
285,576✔
1426
        dg_2.reset(mem.get_ref());
285,576✔
1427
        int_fast64_t v(from_ref(mem.get_ref()));
285,576✔
1428
        top.add(v); // Throws
285,576✔
1429
        dg_2.release();
285,576✔
1430
    }
285,576✔
1431
    top.add(0); // Old position for columns
285,576✔
1432
    {
285,576✔
1433
        MemRef mem = Cluster::create_empty_cluster(alloc); // Throws
285,576✔
1434
        dg_2.reset(mem.get_ref());
285,576✔
1435
        int_fast64_t v(from_ref(mem.get_ref()));
285,576✔
1436
        top.add(v); // Throws
285,576✔
1437
        dg_2.release();
285,576✔
1438
    }
285,576✔
1439

1440
    // Table key value
1441
    RefOrTagged rot = RefOrTagged::make_tagged(key.value);
285,576✔
1442
    top.add(rot);
285,576✔
1443

1444
    // Search indexes
1445
    {
285,576✔
1446
        bool context_flag = false;
285,576✔
1447
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, context_flag, alloc); // Throws
285,576✔
1448
        dg_2.reset(mem.get_ref());
285,576✔
1449
        int_fast64_t v(from_ref(mem.get_ref()));
285,576✔
1450
        top.add(v); // Throws
285,576✔
1451
        dg_2.release();
285,576✔
1452
    }
285,576✔
1453
    rot = RefOrTagged::make_tagged(0);
285,576✔
1454
    top.add(rot); // Column key
285,576✔
1455
    top.add(rot); // Version
285,576✔
1456
    dg.release();
285,576✔
1457
    // Opposite keys (table and column)
1458
    {
285,576✔
1459
        bool context_flag = false;
285,576✔
1460
        {
285,576✔
1461
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
285,576✔
1462
            dg_2.reset(mem.get_ref());
285,576✔
1463
            int_fast64_t v(from_ref(mem.get_ref()));
285,576✔
1464
            top.add(v); // Throws
285,576✔
1465
            dg_2.release();
285,576✔
1466
        }
285,576✔
1467
        {
285,576✔
1468
            MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag, alloc); // Throws
285,576✔
1469
            dg_2.reset(mem.get_ref());
285,576✔
1470
            int_fast64_t v(from_ref(mem.get_ref()));
285,576✔
1471
            top.add(v); // Throws
285,576✔
1472
            dg_2.release();
285,576✔
1473
        }
285,576✔
1474
    }
285,576✔
1475
    top.add(0); // Sequence number
285,576✔
1476
    top.add(0); // Collision_map
285,576✔
1477
    top.add(0); // pk col key
285,576✔
1478
    top.add(0); // flags
285,576✔
1479
    top.add(0); // tombstones
285,576✔
1480
    top.add(0); // string interners
285,576✔
1481

1482
    REALM_ASSERT(top.size() == top_array_size);
285,576✔
1483

1484
    return top.get_ref();
285,576✔
1485
}
285,576✔
1486

1487
void Table::ensure_graveyard()
1488
{
41,286✔
1489
    if (!m_tombstones) {
41,286✔
1490
        while (m_top.size() < top_position_for_tombstones)
2,400✔
1491
            m_top.add(0);
×
1492
        REALM_ASSERT(!m_top.get(top_position_for_tombstones));
2,400✔
1493
        MemRef mem = Cluster::create_empty_cluster(m_alloc);
2,400✔
1494
        m_top.set_as_ref(top_position_for_tombstones, mem.get_ref());
2,400✔
1495
        m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
2,400✔
1496
        m_tombstones->init_from_parent();
2,400✔
1497
        for_each_and_every_column([ts = m_tombstones.get()](ColKey col) {
8,994✔
1498
            ts->insert_column(col);
8,994✔
1499
            return IteratorControl::AdvanceToNext;
8,994✔
1500
        });
8,994✔
1501
    }
2,400✔
1502
}
41,286✔
1503

1504
void Table::batch_erase_rows(const KeyColumn& keys)
1505
{
558✔
1506
    size_t num_objs = keys.size();
558✔
1507
    std::vector<ObjKey> vec;
558✔
1508
    vec.reserve(num_objs);
558✔
1509
    for (size_t i = 0; i < num_objs; ++i) {
2,898✔
1510
        ObjKey key = keys.get(i);
2,340✔
1511
        if (key != null_key && is_valid(key)) {
2,340✔
1512
            vec.push_back(key);
2,340✔
1513
        }
2,340✔
1514
    }
2,340✔
1515

1516
    sort(vec.begin(), vec.end());
558✔
1517
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
558✔
1518

1519
    batch_erase_objects(vec);
558✔
1520
}
558✔
1521

1522
void Table::batch_erase_objects(std::vector<ObjKey>& keys)
1523
{
10,347✔
1524
    Group* g = get_parent_group();
10,347✔
1525
    bool maybe_has_incoming_links = g && !is_asymmetric();
10,347✔
1526

1527
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
10,347✔
1528
        CascadeState state(CascadeState::Mode::Strong, g);
9,579✔
1529
        std::for_each(keys.begin(), keys.end(), [this, &state](ObjKey k) {
9,579✔
1530
            state.m_to_be_deleted.emplace_back(m_key, k);
2,076✔
1531
        });
2,076✔
1532
        if (maybe_has_incoming_links)
9,579✔
1533
            nullify_links(state);
9,579✔
1534
        remove_recursive(state);
9,579✔
1535
    }
9,579✔
1536
    else {
768✔
1537
        CascadeState state(CascadeState::Mode::None, g);
768✔
1538
        for (auto k : keys) {
2,512,302✔
1539
            if (maybe_has_incoming_links) {
2,512,302✔
1540
                m_clusters.nullify_incoming_links(k, state);
2,512,224✔
1541
            }
2,512,224✔
1542
            m_clusters.erase(k, state);
2,512,302✔
1543
        }
2,512,302✔
1544
    }
768✔
1545
    keys.clear();
10,347✔
1546
}
10,347✔
1547

1548
void Table::clear()
1549
{
5,568✔
1550
    CascadeState state(CascadeState::Mode::Strong, get_parent_group());
5,568✔
1551
    m_clusters.clear(state);
5,568✔
1552
    free_collision_table();
5,568✔
1553
}
5,568✔
1554

1555

1556
Group* Table::get_parent_group() const noexcept
1557
{
62,015,952✔
1558
    if (!m_top.is_attached())
62,015,952✔
1559
        return 0;                             // Subtable with shared descriptor
×
1560
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
62,015,952✔
1561
    if (!parent)
62,015,952✔
1562
        return 0; // Free-standing table
24,328,995✔
1563

1564
    return static_cast<Group*>(parent);
37,686,957✔
1565
}
62,015,952✔
1566

1567
inline uint64_t Table::get_sync_file_id() const noexcept
1568
{
38,659,026✔
1569
    Group* g = get_parent_group();
38,659,026✔
1570
    return g ? g->get_sync_file_id() : 0;
38,659,026✔
1571
}
38,659,026✔
1572

1573
size_t Table::get_index_in_group() const noexcept
1574
{
16,134,939✔
1575
    if (!m_top.is_attached())
16,134,939✔
1576
        return realm::npos;                   // Subtable with shared descriptor
×
1577
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
16,134,939✔
1578
    if (!parent)
16,134,939✔
1579
        return realm::npos; // Free-standing table
×
1580
    return m_top.get_ndx_in_parent();
16,134,939✔
1581
}
16,134,939✔
1582

1583
uint64_t Table::allocate_sequence_number()
1584
{
20,238,519✔
1585
    RefOrTagged rot = m_top.get_as_ref_or_tagged(top_position_for_sequence_number);
20,238,519✔
1586
    uint64_t sn = rot.is_tagged() ? rot.get_as_int() : 0;
20,238,519✔
1587
    rot = RefOrTagged::make_tagged(sn + 1);
20,238,519✔
1588
    m_top.set(top_position_for_sequence_number, rot);
20,238,519✔
1589

1590
    return sn;
20,238,519✔
1591
}
20,238,519✔
1592

1593
void Table::set_col_key_sequence_number(uint64_t seq)
1594
{
24✔
1595
    m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(seq));
24✔
1596
}
24✔
1597

1598
TableRef Table::get_link_target(ColKey col_key) noexcept
1599
{
251,307✔
1600
    return get_opposite_table(col_key);
251,307✔
1601
}
251,307✔
1602

1603
// count ----------------------------------------------
1604

1605
size_t Table::count_int(ColKey col_key, int64_t value) const
1606
{
12,006✔
1607
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1608
        return index->count(value);
12,000✔
1609
    }
12,000✔
1610

1611
    return where().equal(col_key, value).count();
6✔
1612
}
12,006✔
1613
size_t Table::count_float(ColKey col_key, float value) const
1614
{
6✔
1615
    return where().equal(col_key, value).count();
6✔
1616
}
6✔
1617
size_t Table::count_double(ColKey col_key, double value) const
1618
{
6✔
1619
    return where().equal(col_key, value).count();
6✔
1620
}
6✔
1621
size_t Table::count_decimal(ColKey col_key, Decimal128 value) const
1622
{
18✔
1623
    ArrayDecimal128 leaf(get_alloc());
18✔
1624
    size_t cnt = 0;
18✔
1625
    bool null_value = value.is_null();
18✔
1626
    auto f = [value, &leaf, col_key, null_value, &cnt](const Cluster* cluster) {
18✔
1627
        // direct aggregate on the leaf
1628
        cluster->init_leaf(col_key, &leaf);
18✔
1629
        auto sz = leaf.size();
18✔
1630
        for (size_t i = 0; i < sz; i++) {
1,296✔
1631
            if ((null_value && leaf.is_null(i)) || (leaf.get(i) == value)) {
1,278!
1632
                cnt++;
24✔
1633
            }
24✔
1634
        }
1,278✔
1635
        return IteratorControl::AdvanceToNext;
18✔
1636
    };
18✔
1637

1638
    traverse_clusters(f);
18✔
1639

1640
    return cnt;
18✔
1641
}
18✔
1642
size_t Table::count_string(ColKey col_key, StringData value) const
1643
{
408✔
1644
    if (auto index = this->get_search_index(col_key)) {
408✔
1645
        return index->count(value);
348✔
1646
    }
348✔
1647
    return where().equal(col_key, value).count();
60✔
1648
}
408✔
1649

1650
template <typename T>
1651
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1652
{
36,828✔
1653
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
36,828✔
1654
    LeafType leaf(get_alloc());
36,828✔
1655

1656
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
36,846✔
1657
        // direct aggregate on the leaf
1658
        cluster->init_leaf(column_key, &leaf);
36,846✔
1659
        st.m_key_offset = cluster->get_offset();
36,846✔
1660
        st.m_key_values = cluster->get_key_array();
36,846✔
1661
        st.set_payload_column(&leaf);
36,846✔
1662
        bool cont = true;
36,846✔
1663
        size_t sz = leaf.size();
36,846✔
1664
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
133,608✔
1665
            cont = st.match(local_index);
96,762✔
1666
        }
96,762✔
1667
        return IteratorControl::AdvanceToNext;
36,846✔
1668
    };
36,846✔
1669

1670
    traverse_clusters(f);
36,828✔
1671
}
36,828✔
1672

1673
// This template is also used by the query engine
1674
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1675
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1676
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1677
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1678
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1679
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1680
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1681

1682
std::optional<Mixed> Table::sum(ColKey col_key) const
1683
{
756✔
1684
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1685
}
756✔
1686

1687
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1688
{
822✔
1689
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1690
}
822✔
1691

1692
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1693
{
1,176✔
1694
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,176✔
1695
}
1,176✔
1696

1697
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1698
{
30,546✔
1699
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
30,546✔
1700
}
30,546✔
1701

1702

1703
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1704
{
30,827,274✔
1705
    check_column(col);
30,827,274✔
1706
    return m_index_accessors[col.get_index().val].get();
30,827,274✔
1707
}
30,827,274✔
1708

1709
StringIndex* Table::get_string_index(ColKey col) const noexcept
1710
{
750,858✔
1711
    check_column(col);
750,858✔
1712
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
750,858✔
1713
}
750,858✔
1714

1715
template <class T>
1716
ObjKey Table::find_first(ColKey col_key, T value) const
1717
{
34,503✔
1718
    check_column(col_key);
34,503✔
1719

1720
    if (!col_key.is_nullable() && value_is_null(value)) {
34,503!
1721
        return {}; // this is a precaution/optimization
6✔
1722
    }
6✔
1723
    // You cannot call GetIndexData on ObjKey
1724
    if constexpr (!std::is_same_v<T, ObjKey>) {
34,497✔
1725
        if (SearchIndex* index = get_search_index(col_key)) {
34,479✔
1726
            return index->find_first(value);
26,697✔
1727
        }
26,697✔
1728
        if (col_key == m_primary_key_col) {
7,782✔
1729
            return find_primary_key(value);
×
1730
        }
×
1731
    }
7,782✔
1732

1733
    ObjKey key;
7,782✔
1734
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
21,096✔
1735
    LeafType leaf(get_alloc());
21,096✔
1736

1737
    // In case of a string column we can try to look up the StringID of the search string,
1738
    // and search for that in case the leaf is compressed.
1739
    std::optional<StringID> string_id;
21,096✔
1740
    if constexpr (std::is_same_v<T, StringData>) {
21,105✔
1741
        auto string_interner = get_string_interner(col_key);
9,345✔
1742
        REALM_ASSERT(string_interner != nullptr);
9,345✔
1743
        string_id = string_interner->lookup(value);
9,345✔
1744
    }
9,345✔
1745

1746
    auto f = [&](const Cluster* cluster) {
21,837✔
1747
        cluster->init_leaf(col_key, &leaf);
9,264✔
1748
        size_t row;
9,264✔
1749
        if constexpr (std::is_same_v<T, StringData>) {
9,264✔
1750
            row = leaf.find_first(value, 0, cluster->node_size(), string_id);
4,632✔
1751
        }
4,509✔
1752
        else {
9,018✔
1753
            row = leaf.find_first(value, 0, cluster->node_size());
9,018✔
1754
        }
9,018✔
1755

1756
        if (row != realm::npos) {
9,264✔
1757
            key = cluster->get_real_key(row);
7,626✔
1758
            return IteratorControl::Stop;
7,626✔
1759
        }
7,626✔
1760
        return IteratorControl::AdvanceToNext;
1,638✔
1761
    };
9,264✔
1762

1763
    traverse_clusters(f);
21,096✔
1764

1765
    return key;
21,096✔
1766
}
34,491✔
1767

1768
namespace realm {
1769

1770
template <>
1771
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1772
{
×
1773
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1774
}
×
1775

1776
template <>
1777
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
1778
{
×
1779
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1780
}
×
1781

1782
template <>
1783
ObjKey Table::find_first(ColKey col_key, null) const
1784
{
×
1785
    return find_first_null(col_key);
×
1786
}
×
1787
} // namespace realm
1788

1789
// Explicitly instantiate the generic case of the template for the types we care about.
1790
template ObjKey Table::find_first(ColKey col_key, bool) const;
1791
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
1792
template ObjKey Table::find_first(ColKey col_key, float) const;
1793
template ObjKey Table::find_first(ColKey col_key, double) const;
1794
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
1795
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
1796
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
1797
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
1798
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
1799
template ObjKey Table::find_first(ColKey col_key, StringData) const;
1800
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
1801
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
1802
template ObjKey Table::find_first(ColKey col_key, UUID) const;
1803
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
1804
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
1805

1806
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1807
{
7,698✔
1808
    if (is_nullable(col_key))
7,698✔
1809
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1810
    else
7,662✔
1811
        return find_first<int64_t>(col_key, value);
7,662✔
1812
}
7,698✔
1813

1814
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1815
{
78✔
1816
    if (is_nullable(col_key))
78✔
1817
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1818
    else
42✔
1819
        return find_first<bool>(col_key, value);
42✔
1820
}
78✔
1821

1822
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1823
{
108✔
1824
    return find_first(col_key, value);
108✔
1825
}
108✔
1826

1827
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1828
{
6✔
1829
    return find_first(col_key, value);
6✔
1830
}
6✔
1831

1832
ObjKey Table::find_first_float(ColKey col_key, float value) const
1833
{
12✔
1834
    return find_first<Float>(col_key, value);
12✔
1835
}
12✔
1836

1837
ObjKey Table::find_first_double(ColKey col_key, double value) const
1838
{
12✔
1839
    return find_first<Double>(col_key, value);
12✔
1840
}
12✔
1841

1842
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
1843
{
×
1844
    return find_first<Decimal128>(col_key, value);
×
1845
}
×
1846

1847
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1848
{
11,355✔
1849
    return find_first<StringData>(col_key, value);
11,355✔
1850
}
11,355✔
1851

1852
ObjKey Table::find_first_binary(ColKey col_key, BinaryData value) const
1853
{
×
1854
    return find_first<BinaryData>(col_key, value);
×
1855
}
×
1856

1857
ObjKey Table::find_first_null(ColKey col_key) const
1858
{
114✔
1859
    return where().equal(col_key, null{}).find();
114✔
1860
}
114✔
1861

1862
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1863
{
18✔
1864
    return find_first(col_key, value);
18✔
1865
}
18✔
1866

1867
template <class T>
1868
TableView Table::find_all(ColKey col_key, T value)
1869
{
114✔
1870
    return where().equal(col_key, value).find_all();
114✔
1871
}
114✔
1872

1873
TableView Table::find_all_int(ColKey col_key, int64_t value)
1874
{
102✔
1875
    return find_all<int64_t>(col_key, value);
102✔
1876
}
102✔
1877

1878
TableView Table::find_all_int(ColKey col_key, int64_t value) const
1879
{
6✔
1880
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
6✔
1881
}
6✔
1882

1883
TableView Table::find_all_bool(ColKey col_key, bool value)
1884
{
×
1885
    return find_all<bool>(col_key, value);
×
1886
}
×
1887

1888
TableView Table::find_all_bool(ColKey col_key, bool value) const
1889
{
×
1890
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
×
1891
}
×
1892

1893

1894
TableView Table::find_all_float(ColKey col_key, float value)
1895
{
×
1896
    return find_all<float>(col_key, value);
×
1897
}
×
1898

1899
TableView Table::find_all_float(ColKey col_key, float value) const
1900
{
×
1901
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
1902
}
×
1903

1904
TableView Table::find_all_double(ColKey col_key, double value)
1905
{
6✔
1906
    return find_all<double>(col_key, value);
6✔
1907
}
6✔
1908

1909
TableView Table::find_all_double(ColKey col_key, double value) const
1910
{
×
1911
    return const_cast<Table*>(this)->find_all<double>(col_key, value);
×
1912
}
×
1913

1914
TableView Table::find_all_string(ColKey col_key, StringData value)
1915
{
114✔
1916
    return where().equal(col_key, value).find_all();
114✔
1917
}
114✔
1918

1919
TableView Table::find_all_string(ColKey col_key, StringData value) const
1920
{
×
1921
    return const_cast<Table*>(this)->find_all_string(col_key, value);
×
1922
}
×
1923

1924
TableView Table::find_all_binary(ColKey, BinaryData)
1925
{
×
1926
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
1927
}
×
1928

1929
TableView Table::find_all_binary(ColKey col_key, BinaryData value) const
1930
{
×
1931
    return const_cast<Table*>(this)->find_all_binary(col_key, value);
×
1932
}
×
1933

1934
TableView Table::find_all_null(ColKey col_key)
1935
{
×
1936
    return where().equal(col_key, null{}).find_all();
×
1937
}
×
1938

1939
TableView Table::find_all_null(ColKey col_key) const
1940
{
×
1941
    return const_cast<Table*>(this)->find_all_null(col_key);
×
1942
}
×
1943

1944
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1945
{
6✔
1946
    return where().fulltext(col_key, terms).find_all();
6✔
1947
}
6✔
1948

1949
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1950
{
72✔
1951
    TableView tv = where().find_all();
72✔
1952
    tv.sort(col_key, ascending);
72✔
1953
    return tv;
72✔
1954
}
72✔
1955

1956
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1957
{
6✔
1958
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1959
}
6✔
1960

1961
TableView Table::get_sorted_view(SortDescriptor order)
1962
{
126✔
1963
    TableView tv = where().find_all();
126✔
1964
    tv.sort(std::move(order));
126✔
1965
    return tv;
126✔
1966
}
126✔
1967

1968
TableView Table::get_sorted_view(SortDescriptor order) const
1969
{
×
1970
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
1971
}
×
1972

1973
util::Logger* Table::get_logger() const noexcept
1974
{
1,883,379✔
1975
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,883,379✔
1976
}
1,883,379✔
1977

1978
// Called after a commit. Table will effectively contain the same as before,
1979
// but now with new refs from the file
1980
void Table::update_from_parent() noexcept
1981
{
1,102,743✔
1982
    // There is no top for sub-tables sharing spec
1983
    if (m_top.is_attached()) {
1,102,743✔
1984
        m_top.update_from_parent();
1,102,740✔
1985
        m_spec.update_from_parent();
1,102,740✔
1986
        m_clusters.update_from_parent();
1,102,740✔
1987
        m_index_refs.update_from_parent();
1,102,740✔
1988
        for (auto&& index : m_index_accessors) {
3,040,266✔
1989
            if (index != nullptr) {
3,040,266✔
1990
                index->update_from_parent();
468,780✔
1991
            }
468,780✔
1992
        }
3,040,266✔
1993

1994
        m_opposite_table.update_from_parent();
1,102,740✔
1995
        m_opposite_column.update_from_parent();
1,102,740✔
1996
        if (m_top.size() > top_position_for_flags) {
1,102,740✔
1997
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
1,102,734✔
1998
            m_table_type = Type(flags & table_type_mask);
1,102,734✔
1999
        }
1,102,734✔
2000
        else {
6✔
2001
            m_table_type = Type::TopLevel;
6✔
2002
        }
6✔
2003
        if (m_tombstones)
1,102,740✔
2004
            m_tombstones->update_from_parent();
5,346✔
2005

2006
        refresh_content_version();
1,102,740✔
2007
        m_has_any_embedded_objects.reset();
1,102,740✔
2008
        if (m_top.size() > top_position_for_interners) {
1,102,740✔
2009
            if (m_top.get_as_ref(top_position_for_interners))
1,102,734✔
2010
                m_interner_data.update_from_parent();
1,102,728✔
2011
            else
6✔
2012
                m_interner_data.detach();
6✔
2013
        }
1,102,734✔
2014
        refresh_string_interners(false);
1,102,740✔
2015
    }
1,102,740✔
2016
    m_alloc.bump_storage_version();
1,102,743✔
2017
}
1,102,743✔
2018

2019
void Table::schema_to_json(std::ostream& out) const
2020
{
12✔
2021
    out << "{";
12✔
2022
    auto name = get_name();
12✔
2023
    out << "\"name\":\"" << name << "\"";
12✔
2024
    if (this->m_primary_key_col) {
12✔
2025
        out << ",";
×
2026
        out << "\"primaryKey\":\"" << this->get_column_name(m_primary_key_col) << "\"";
×
2027
    }
×
2028
    out << ",\"tableType\":\"" << this->get_table_type() << "\"";
12✔
2029
    out << ",\"properties\":[";
12✔
2030
    auto col_keys = get_column_keys();
12✔
2031
    int sz = int(col_keys.size());
12✔
2032
    for (int i = 0; i < sz; ++i) {
54✔
2033
        auto col_key = col_keys[i];
42✔
2034
        name = get_column_name(col_key);
42✔
2035
        auto type = col_key.get_type();
42✔
2036
        out << "{";
42✔
2037
        out << "\"name\":\"" << name << "\"";
42✔
2038
        if (this->is_link_type(type)) {
42✔
2039
            out << ",\"type\":\"object\"";
6✔
2040
            name = this->get_opposite_table(col_key)->get_name();
6✔
2041
            out << ",\"objectType\":\"" << name << "\"";
6✔
2042
        }
6✔
2043
        else {
36✔
2044
            out << ",\"type\":\"" << get_data_type_name(DataType(type)) << "\"";
36✔
2045
        }
36✔
2046
        if (col_key.is_list()) {
42✔
2047
            out << ",\"isArray\":true";
12✔
2048
        }
12✔
2049
        else if (col_key.is_set()) {
30✔
2050
            out << ",\"isSet\":true";
×
2051
        }
×
2052
        else if (col_key.is_dictionary()) {
30✔
2053
            out << ",\"isMap\":true";
6✔
2054
            auto key_type = get_dictionary_key_type(col_key);
6✔
2055
            out << ",\"keyType\":\"" << get_data_type_name(key_type) << "\"";
6✔
2056
        }
6✔
2057
        if (col_key.is_nullable()) {
42✔
2058
            out << ",\"isOptional\":true";
12✔
2059
        }
12✔
2060
        auto index_type = search_index_type(col_key);
42✔
2061
        if (index_type == IndexType::General) {
42✔
2062
            out << ",\"isIndexed\":true";
×
2063
        }
×
2064
        if (index_type == IndexType::Fulltext) {
42✔
2065
            out << ",\"isFulltextIndexed\":true";
×
2066
        }
×
2067
        out << "}";
42✔
2068
        if (i < sz - 1) {
42✔
2069
            out << ",";
30✔
2070
        }
30✔
2071
    }
42✔
2072
    out << "]}";
12✔
2073
}
12✔
2074

2075
bool Table::operator==(const Table& t) const
2076
{
132✔
2077
    if (size() != t.size()) {
132✔
2078
        return false;
12✔
2079
    }
12✔
2080
    // Check columns
2081
    for (auto ck : this->get_column_keys()) {
528✔
2082
        auto name = get_column_name(ck);
528✔
2083
        auto other_ck = t.get_column_key(name);
528✔
2084
        auto attrs = ck.get_attrs();
528✔
2085
        if (search_index_type(ck) != t.search_index_type(other_ck))
528✔
2086
            return false;
×
2087

2088
        if (!other_ck || other_ck.get_attrs() != attrs) {
528✔
2089
            return false;
×
2090
        }
×
2091
    }
528✔
2092
    auto pk_col = get_primary_key_column();
120✔
2093
    for (auto o : *this) {
366✔
2094
        Obj other_o;
366✔
2095
        if (pk_col) {
366✔
2096
            auto pk = o.get_any(pk_col);
90✔
2097
            other_o = t.get_object_with_primary_key(pk);
90✔
2098
        }
90✔
2099
        else {
276✔
2100
            other_o = t.get_object(o.get_key());
276✔
2101
        }
276✔
2102
        if (!(other_o && o == other_o))
366✔
2103
            return false;
6✔
2104
    }
366✔
2105

2106
    return true;
114✔
2107
}
120✔
2108

2109

2110
void Table::flush_for_commit()
2111
{
1,215,102✔
2112
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
1,215,108✔
2113
        if (!m_top.is_read_only()) {
1,215,102✔
2114
            ++m_in_file_version_at_transaction_boundary;
812,886✔
2115
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
812,886✔
2116
            m_top.set(top_position_for_version, rot_version);
812,886✔
2117
        }
812,886✔
2118
    }
1,215,102✔
2119
}
1,215,102✔
2120

2121
void Table::refresh_content_version()
2122
{
1,507,185✔
2123
    REALM_ASSERT(m_top.is_attached());
1,507,185✔
2124
    if (m_top.size() >= top_position_for_version) {
1,507,275✔
2125
        // we have versioning info in the file. Use this to conditionally
2126
        // bump the version counter:
2127
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,507,254✔
2128
        REALM_ASSERT(rot_version.is_tagged());
1,507,254✔
2129
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,507,254✔
2130
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
235,800✔
2131
            bump_content_version();
235,800✔
2132
        }
235,800✔
2133
    }
1,507,254✔
2134
    else {
2,147,483,668✔
2135
        // assume the worst:
2136
        bump_content_version();
2,147,483,668✔
2137
    }
2,147,483,668✔
2138
}
1,507,185✔
2139

2140

2141
// Called when Group is moved to another version - either a rollback or an advance.
2142
// The content of the table is potentially different, so make no assumptions.
2143
void Table::refresh_accessor_tree(bool writable)
2144
{
404,811✔
2145
    REALM_ASSERT(m_cookie == cookie_initialized);
404,811✔
2146
    REALM_ASSERT(m_top.is_attached());
404,811✔
2147
    m_top.init_from_parent();
404,811✔
2148
    m_spec.init_from_parent();
404,811✔
2149
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
404,811✔
2150
    m_clusters.init_from_parent();
404,811✔
2151
    m_index_refs.init_from_parent();
404,811✔
2152
    m_opposite_table.init_from_parent();
404,811✔
2153
    m_opposite_column.init_from_parent();
404,811✔
2154
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
404,811✔
2155
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
404,811✔
2156
    if (m_top.size() > top_position_for_flags) {
404,973✔
2157
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
404,931✔
2158
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
404,931✔
2159
    }
404,931✔
2160
    else {
2,147,483,689✔
2161
        m_table_type = Type::TopLevel;
2,147,483,689✔
2162
    }
2,147,483,689✔
2163
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
404,811✔
2164
        // Tombstones exists
2165
        if (!m_tombstones) {
2,412✔
2166
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
474✔
2167
        }
474✔
2168
        m_tombstones->init_from_parent();
2,412✔
2169
    }
2,412✔
2170
    else {
402,399✔
2171
        m_tombstones = nullptr;
402,399✔
2172
    }
402,399✔
2173
    if (writable) {
404,811✔
2174
        while (m_top.size() < top_position_for_interners)
90,894✔
NEW
2175
            m_top.add(0);
×
2176
    }
90,894✔
2177
    if (m_top.size() > top_position_for_interners) {
405,357✔
2178
        if (m_top.get_as_ref(top_position_for_interners))
405,312✔
2179
            m_interner_data.init_from_parent();
404,967✔
2180
        else
345✔
2181
            m_interner_data.detach();
345✔
2182
    }
405,312✔
2183
    refresh_content_version();
404,811✔
2184
    bump_storage_version();
404,811✔
2185
    build_column_mapping();
404,811✔
2186
    refresh_string_interners(writable);
404,811✔
2187
    refresh_index_accessors();
404,811✔
2188
}
404,811✔
2189

2190
void Table::refresh_string_interners(bool writable)
2191
{
3,641,307✔
2192
    if (writable) {
3,641,307✔
2193
        // if we're in a write transaction, make sure interner arrays are created which will allow
2194
        // string interners to expand with their own data when "learning"
2195
        while (m_top.size() <= top_position_for_interners) {
1,113,027✔
2196
            m_top.add(0);
414✔
2197
        }
414✔
2198
    }
1,112,613✔
2199
    if (m_top.size() > top_position_for_interners && m_top.get_as_ref(top_position_for_interners))
3,644,709✔
2200
        m_interner_data.update_from_parent();
3,355,443✔
2201
    else
285,864✔
2202
        m_interner_data.detach();
285,864✔
2203
    if (writable) {
3,641,307✔
2204
        if (!m_interner_data.is_attached()) {
1,112,610✔
2205
            m_interner_data.create(NodeHeader::type_HasRefs);
285,933✔
2206
            m_interner_data.update_parent();
285,933✔
2207
        }
285,933✔
2208
    }
1,112,610✔
2209
    // bring string interners in line with underlying data.
2210
    // Precondition: we rely on the col keys in m_leaf_ndx2colkey[] being up to date.
2211
    for (size_t idx = 0; idx < m_leaf_ndx2colkey.size(); ++idx) {
15,335,013✔
2212
        auto col_key = m_leaf_ndx2colkey[idx];
11,693,706✔
2213
        if (col_key == ColKey()) {
11,693,706✔
2214
            // deleted column, we really don't want a string interner for this
2215
            if (idx < m_string_interners.size() && m_string_interners[idx])
51,174✔
2216
                m_string_interners[idx].reset();
6✔
2217
            continue;
51,174✔
2218
        }
51,174✔
2219
        if (!needs_string_interner(col_key))
11,642,532✔
2220
            continue;
9,183,987✔
2221

2222
        REALM_ASSERT_DEBUG(col_key.get_index().val == idx);
2,458,545✔
2223
        // maintain sufficient size of interner arrays to cover all columns
2224
        while (idx >= m_string_interners.size()) {
6,298,146✔
2225
            m_string_interners.push_back({});
3,839,601✔
2226
        }
3,839,601✔
2227
        while (writable && idx >= m_interner_data.size()) { // m_interner_data.is_attached() per above
2,459,259✔
2228
            m_interner_data.add(0);
714✔
2229
        }
714✔
2230
        if (m_string_interners[idx]) {
2,458,545✔
2231
            // existing interner
2232
            m_string_interners[idx]->update_from_parent(writable);
1,111,728✔
2233
        }
1,111,728✔
2234
        else {
1,346,817✔
2235
            // new interner. Note: if not in a writable state, the interner will not have a valid
2236
            // underlying data array. The interner will be set in a state, where it cannot "learn",
2237
            // and searches will not find any matching interned strings.
2238
            m_string_interners[idx] = std::make_unique<StringInterner>(m_alloc, m_interner_data, col_key, writable);
1,346,817✔
2239
        }
1,346,817✔
2240
    }
2,458,545✔
2241
    if (m_string_interners.size() > m_leaf_ndx2colkey.size()) {
3,641,307✔
2242
        // remove any string interners which are no longer reachable,
2243
        // e.g. after a rollback
2244
        m_string_interners.resize(m_leaf_ndx2colkey.size());
12✔
2245
    }
12✔
2246
}
3,641,307✔
2247

2248
void Table::refresh_index_accessors()
2249
{
3,327,474✔
2250
    // Refresh search index accessors
2251

2252
    // First eliminate any index accessors for eliminated last columns
2253
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
3,327,474✔
2254
    m_index_accessors.resize(col_ndx_end);
3,327,474✔
2255

2256
    // Then eliminate/refresh/create accessors within column range
2257
    // we can not use for_each_column() here, since the columns may have changed
2258
    // and the index accessor vector is not updated correspondingly.
2259
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
17,731,800✔
2260
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
14,404,326✔
2261

2262
        if (ref == 0) {
14,404,326✔
2263
            // accessor drop
2264
            m_index_accessors[col_ndx].reset();
13,262,034✔
2265
        }
13,262,034✔
2266
        else {
1,142,292✔
2267
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
1,142,292✔
2268
            bool fulltext = attr.test(col_attr_FullText_Indexed);
1,142,292✔
2269
            auto col_key = m_leaf_ndx2colkey[col_ndx];
1,142,292✔
2270
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
1,142,292✔
2271

2272
            if (m_index_accessors[col_ndx]) { // still there, refresh:
1,142,292✔
2273
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
422,040✔
2274
            }
422,040✔
2275
            else { // new index!
720,252✔
2276
                m_index_accessors[col_ndx] =
720,252✔
2277
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
720,252✔
2278
            }
720,252✔
2279
        }
1,142,292✔
2280
    }
14,404,326✔
2281
}
3,327,474✔
2282

2283
bool Table::is_cross_table_link_target() const noexcept
2284
{
1,659✔
2285
    auto is_cross_link = [this](ColKey col_key) {
1,659✔
2286
        auto t = col_key.get_type();
48✔
2287
        // look for a backlink with a different target than ourselves
2288
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
48✔
2289
                   ? IteratorControl::Stop
48✔
2290
                   : IteratorControl::AdvanceToNext;
48✔
2291
    };
48✔
2292
    return for_each_backlink_column(is_cross_link);
1,659✔
2293
}
1,659✔
2294

2295
// LCOV_EXCL_START ignore debug functions
2296

2297
void Table::verify() const
2298
{
230,814✔
2299
#ifdef REALM_DEBUG
230,814✔
2300
    if (m_top.is_attached())
230,814✔
2301
        m_top.verify();
230,814✔
2302
    m_spec.verify();
230,814✔
2303
    m_clusters.verify();
230,814✔
2304
    if (nb_unresolved())
230,814✔
2305
        m_tombstones->verify();
21,741✔
2306
#endif
230,814✔
2307
}
230,814✔
2308

2309
#ifdef REALM_DEBUG
2310
MemStats Table::stats() const
2311
{
×
2312
    MemStats mem_stats;
×
2313
    m_top.stats(mem_stats);
×
2314
    return mem_stats;
×
2315
}
×
2316
#endif // LCOV_EXCL_STOP ignore debug functions
2317

2318
Obj Table::create_object(ObjKey key, const FieldValues& values)
2319
{
23,647,224✔
2320
    if (is_embedded())
23,647,224✔
2321
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2322
    if (m_primary_key_col)
23,647,176✔
2323
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2324
    if (key == null_key) {
23,647,176✔
2325
        GlobalKey object_id = allocate_object_id_squeezed();
19,306,734✔
2326
        key = object_id.get_local_key(get_sync_file_id());
19,306,734✔
2327
        // Check if this key collides with an already existing object
2328
        // This could happen if objects were at some point created with primary keys,
2329
        // but later primary key property was removed from the schema.
2330
        while (m_clusters.is_valid(key)) {
19,306,734✔
2331
            object_id = allocate_object_id_squeezed();
×
2332
            key = object_id.get_local_key(get_sync_file_id());
×
2333
        }
×
2334
        if (auto repl = get_repl())
19,306,734✔
2335
            repl->create_object(this, object_id);
4,888,389✔
2336
    }
19,306,734✔
2337

2338
    REALM_ASSERT(key.value >= 0);
23,647,176✔
2339

2340
    Obj obj = m_clusters.insert(key, values); // repl->set()
23,647,176✔
2341

2342
    return obj;
23,647,176✔
2343
}
23,647,176✔
2344

2345
Obj Table::create_linked_object()
2346
{
47,847✔
2347
    REALM_ASSERT(is_embedded());
47,847✔
2348

2349
    GlobalKey object_id = allocate_object_id_squeezed();
47,847✔
2350
    ObjKey key = object_id.get_local_key(get_sync_file_id());
47,847✔
2351
    REALM_ASSERT(key.value >= 0);
47,847✔
2352

2353
    if (auto repl = get_repl())
47,847✔
2354
        repl->create_linked_object(this, key);
46,533✔
2355

2356
    Obj obj = m_clusters.insert(key, {});
47,847✔
2357

2358
    return obj;
47,847✔
2359
}
47,847✔
2360

2361
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2362
{
30✔
2363
    if (is_embedded())
30✔
2364
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2365
    if (m_primary_key_col)
30✔
2366
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2367
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2368

2369
    if (auto repl = get_repl())
30✔
2370
        repl->create_object(this, object_id);
30✔
2371

2372
    try {
30✔
2373
        Obj obj = m_clusters.insert(key, values);
30✔
2374
        // Check if tombstone exists
2375
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2376
            auto unres_key = key.get_unresolved();
6✔
2377
            // Copy links over
2378
            auto tombstone = m_tombstones->get(unres_key);
6✔
2379
            obj.assign_pk_and_backlinks(tombstone);
6✔
2380
            // If tombstones had no links to it, it may still be alive
2381
            if (m_tombstones->is_valid(unres_key)) {
6✔
2382
                CascadeState state(CascadeState::Mode::None);
6✔
2383
                m_tombstones->erase(unres_key, state);
6✔
2384
            }
6✔
2385
        }
6✔
2386

2387
        return obj;
30✔
2388
    }
30✔
2389
    catch (const KeyAlreadyUsed&) {
30✔
2390
        return m_clusters.get(key);
6✔
2391
    }
6✔
2392
}
30✔
2393

2394
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2395
                                          bool* did_create)
2396
{
1,082,178✔
2397
    auto primary_key_col = get_primary_key_column();
1,082,178✔
2398
    if (is_embedded() || !primary_key_col)
1,082,178✔
2399
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2400
                              util::format("Table has no primary key: %1", get_name()));
6✔
2401

2402
    DataType type = DataType(primary_key_col.get_type());
1,082,172✔
2403

2404
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
1,082,172✔
2405
        throw InvalidArgument(
6✔
2406
            ErrorCodes::PropertyNotNullable,
6✔
2407
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2408
    }
6✔
2409

2410
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
1,082,166✔
2411
        primary_key.get_type() != type) {
1,082,166✔
2412
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2413
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2414
    }
6✔
2415

2416
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
1,082,160✔
2417

2418
    if (did_create)
1,082,160✔
2419
        *did_create = false;
97,353✔
2420

2421
    // Check for existing object
2422
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
1,082,160✔
2423
        if (mode == UpdateMode::never) {
176,811✔
2424
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2425
        }
6✔
2426
        auto obj = m_clusters.get(key);
176,805✔
2427
        for (auto& val : field_values) {
176,805✔
2428
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2429
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2430
            }
12✔
2431
        }
12✔
2432
        return obj;
176,805✔
2433
    }
176,811✔
2434

2435
    ObjKey unres_key;
905,349✔
2436
    if (m_tombstones) {
905,349✔
2437
        // Check for potential tombstone
2438
        GlobalKey object_id{primary_key};
46,176✔
2439
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
46,176✔
2440

2441
        ObjKey key = object_key.get_unresolved();
46,176✔
2442
        if (auto obj = m_tombstones->try_get_obj(key)) {
46,176✔
2443
            auto existing_pk_value = obj.get_any(primary_key_col);
39,378✔
2444

2445
            // If the primary key is the same, the object should be resurrected below
2446
            if (existing_pk_value == primary_key) {
39,378✔
2447
                unres_key = key;
39,372✔
2448
            }
39,372✔
2449
        }
39,378✔
2450
    }
46,176✔
2451

2452
    ObjKey key = get_next_valid_key();
905,349✔
2453

2454
    auto repl = get_repl();
905,349✔
2455
    if (repl) {
905,349✔
2456
        repl->create_object_with_primary_key(this, key, primary_key);
358,299✔
2457
    }
358,299✔
2458
    if (did_create) {
905,349✔
2459
        *did_create = true;
56,622✔
2460
    }
56,622✔
2461

2462
    field_values.insert(primary_key_col, primary_key);
905,349✔
2463
    Obj ret = m_clusters.insert(key, field_values);
905,349✔
2464

2465
    // Check if unresolved exists
2466
    if (unres_key) {
905,349✔
2467
        if (Replication* repl = get_repl()) {
39,372✔
2468
            if (auto logger = repl->would_log(util::Logger::Level::debug)) {
39,282✔
2469
                logger->log(LogCategory::object, util::Logger::Level::debug, "Cancel tombstone on '%1': %2",
36✔
2470
                            get_class_name(), unres_key);
36✔
2471
            }
36✔
2472
        }
39,282✔
2473

2474
        auto tombstone = m_tombstones->get(unres_key);
39,372✔
2475
        ret.assign_pk_and_backlinks(tombstone);
39,372✔
2476
        // If tombstones had no links to it, it may still be alive
2477
        if (m_tombstones->is_valid(unres_key)) {
39,372✔
2478
            CascadeState state(CascadeState::Mode::None);
39,312✔
2479
            m_tombstones->erase(unres_key, state);
39,312✔
2480
        }
39,312✔
2481
    }
39,372✔
2482
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
905,349✔
2483
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
2484
    }
1,290✔
2485
    return ret;
905,349✔
2486
}
1,082,160✔
2487

2488
ObjKey Table::find_primary_key(Mixed primary_key) const
2489
{
763,875✔
2490
    auto primary_key_col = get_primary_key_column();
763,875✔
2491
    REALM_ASSERT(primary_key_col);
763,875✔
2492
    DataType type = DataType(primary_key_col.get_type());
763,875✔
2493
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
763,875✔
2494
                 primary_key.get_type() == type);
763,875✔
2495

2496
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
763,881✔
2497
        return index->find_first(primary_key);
763,860✔
2498
    }
763,860✔
2499

2500
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
2501
    // so try the old algorithm
2502
    GlobalKey object_id{primary_key};
2,147,483,668✔
2503
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
2,147,483,668✔
2504

2505
    // Check if existing
2506
    if (auto obj = m_clusters.try_get_obj(object_key)) {
2,147,483,668✔
2507
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2508

2509
        if (existing_pk_value == primary_key) {
×
2510
            return object_key;
×
2511
        }
×
2512
    }
×
2513
    return {};
2,147,483,668✔
2514
}
2,147,483,668✔
2515

2516
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2517
{
642,669✔
2518
    // Check if existing
2519
    if (auto key = find_primary_key(primary_key)) {
642,669✔
2520
        return key;
602,325✔
2521
    }
602,325✔
2522

2523
    // Object does not exist - create tombstone
2524
    GlobalKey object_id{primary_key};
40,344✔
2525
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
40,344✔
2526
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
40,344✔
2527
}
642,669✔
2528

2529
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2530
{
18✔
2531
    REALM_ASSERT(!m_primary_key_col);
18✔
2532
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2533

2534
    // Check if existing
2535
    if (m_clusters.is_valid(object_key)) {
18✔
2536
        return object_key;
12✔
2537
    }
12✔
2538

2539
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2540
}
18✔
2541

2542
ObjKey Table::get_objkey(GlobalKey global_key) const
2543
{
18✔
2544
    ObjKey key;
18✔
2545
    REALM_ASSERT(!m_primary_key_col);
18✔
2546
    uint32_t max = std::numeric_limits<uint32_t>::max();
18✔
2547
    if (global_key.hi() <= max && global_key.lo() <= max) {
18✔
2548
        key = global_key.get_local_key(get_sync_file_id());
6✔
2549
    }
6✔
2550
    if (key && !is_valid(key)) {
18✔
2551
        key = realm::null_key;
6✔
2552
    }
6✔
2553
    return key;
18✔
2554
}
18✔
2555

2556
GlobalKey Table::get_object_id(ObjKey key) const
2557
{
144✔
2558
    auto col = get_primary_key_column();
144✔
2559
    if (col) {
144✔
2560
        const Obj obj = get_object(key);
24✔
2561
        auto val = obj.get_any(col);
24✔
2562
        return {val};
24✔
2563
    }
24✔
2564
    else {
120✔
2565
        return {key, get_sync_file_id()};
120✔
2566
    }
120✔
2567
    return {};
×
2568
}
144✔
2569

2570
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2571
{
115,746✔
2572
    auto primary_key_col = get_primary_key_column();
115,746✔
2573
    REALM_ASSERT(primary_key_col);
115,746✔
2574
    DataType type = DataType(primary_key_col.get_type());
115,746✔
2575
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
115,746✔
2576
                 primary_key.get_type() == type);
115,746✔
2577
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
115,746✔
2578
    return k ? m_clusters.get(k) : Obj{};
115,746✔
2579
}
115,746✔
2580

2581
Mixed Table::get_primary_key(ObjKey key) const
2582
{
703,131✔
2583
    auto primary_key_col = get_primary_key_column();
703,131✔
2584
    REALM_ASSERT(primary_key_col);
703,131✔
2585
    if (key.is_unresolved()) {
703,131✔
2586
        REALM_ASSERT(m_tombstones);
792✔
2587
        return m_tombstones->get(key).get_any(primary_key_col);
792✔
2588
    }
792✔
2589
    else {
702,339✔
2590
        return m_clusters.get(key).get_any(primary_key_col);
702,339✔
2591
    }
702,339✔
2592
}
703,131✔
2593

2594
GlobalKey Table::allocate_object_id_squeezed()
2595
{
19,352,706✔
2596
    // m_client_file_ident will be zero if we haven't been in contact with
2597
    // the server yet.
2598
    auto peer_id = get_sync_file_id();
19,352,706✔
2599
    auto sequence = allocate_sequence_number();
19,352,706✔
2600
    return GlobalKey{peer_id, sequence};
19,352,706✔
2601
}
19,352,706✔
2602

2603
namespace {
2604

2605
/// Calculate optimistic local ID that may collide with others. It is up to
2606
/// the caller to ensure that collisions are detected and that
2607
/// allocate_local_id_after_collision() is called to obtain a non-colliding
2608
/// ID.
2609
inline ObjKey get_optimistic_local_id_hashed(GlobalKey global_id)
2610
{
87,288✔
2611
#if REALM_EXERCISE_OBJECT_ID_COLLISION
2612
    const uint64_t optimistic_mask = 0xff;
2613
#else
2614
    const uint64_t optimistic_mask = 0x3fffffffffffffff;
87,288✔
2615
#endif
87,288✔
2616
    static_assert(!(optimistic_mask >> 62), "optimistic Object ID mask must leave the 63rd and 64th bit zero");
87,288✔
2617
    return ObjKey{int64_t(global_id.lo() & optimistic_mask)};
87,288✔
2618
}
87,288✔
2619

2620
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2621
{
12✔
2622
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2623
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2624
}
12✔
2625

2626
} // namespace
2627

2628
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2629
{
87,288✔
2630
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
87,288✔
2631

2632
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
87,288✔
2633
        Allocator& alloc = m_top.get_alloc();
24✔
2634
        Array collision_map{alloc};
24✔
2635
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2636

2637
        Array hi{alloc};
24✔
2638
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2639

2640
        // Entries are ordered by hi,lo
2641
        size_t found = hi.find_first(object_id.hi());
24✔
2642
        if (found != npos && uint64_t(hi.get(found)) == object_id.hi()) {
24✔
2643
            Array lo{alloc};
24✔
2644
            lo.init_from_ref(to_ref(collision_map.get(s_collision_map_lo))); // Throws
24✔
2645
            size_t candidate = lo.find_first(object_id.lo(), found);
24✔
2646
            if (candidate != npos && uint64_t(hi.get(candidate)) == object_id.hi()) {
24✔
2647
                Array local_id{alloc};
24✔
2648
                local_id.init_from_ref(to_ref(collision_map.get(s_collision_map_local_id))); // Throws
24✔
2649
                return ObjKey{local_id.get(candidate)};
24✔
2650
            }
24✔
2651
        }
24✔
2652
    }
24✔
2653

2654
    return optimistic;
87,264✔
2655
}
87,288✔
2656

2657
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2658
                                                     ObjKey colliding_local_id)
2659
{
12✔
2660
    // Possible optimization: Cache these accessors
2661
    Allocator& alloc = m_top.get_alloc();
12✔
2662
    Array collision_map{alloc};
12✔
2663
    Array hi{alloc};
12✔
2664
    Array lo{alloc};
12✔
2665
    Array local_id{alloc};
12✔
2666

2667
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2668
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2669
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2670
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2671

2672
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2673
    if (collision_map_ref) {
12✔
2674
        collision_map.init_from_parent(); // Throws
×
2675
    }
×
2676
    else {
12✔
2677
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2678
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2679
        collision_map.update_parent();
12✔
2680

2681
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2682
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2683
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2684
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2685
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2686
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2687
    }
12✔
2688

2689
    hi.init_from_parent();       // Throws
12✔
2690
    lo.init_from_parent();       // Throws
12✔
2691
    local_id.init_from_parent(); // Throws
12✔
2692

2693
    size_t num_entries = hi.size();
12✔
2694
    REALM_ASSERT(lo.size() == num_entries);
12✔
2695
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2696

2697
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2698
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2699
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2700
            ++i;
6✔
2701
        return i;
24✔
2702
    };
24✔
2703

2704
    auto insert_collision = [&](GlobalKey object_id, ObjKey new_local_id) {
24✔
2705
        size_t i = lower_bound_object_id(object_id);
24✔
2706
        if (i != num_entries) {
24✔
2707
            GlobalKey existing{uint64_t(hi.get(i)), uint64_t(lo.get(i))};
6✔
2708
            if (existing == object_id) {
6✔
2709
                REALM_ASSERT(new_local_id.value == local_id.get(i));
×
2710
                return;
×
2711
            }
×
2712
        }
6✔
2713
        hi.insert(i, int64_t(object_id.hi()));
24✔
2714
        lo.insert(i, int64_t(object_id.lo()));
24✔
2715
        local_id.insert(i, new_local_id.value);
24✔
2716
        ++num_entries;
24✔
2717
    };
24✔
2718

2719
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2720
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2721
    insert_collision(incoming_id, new_local_id);
12✔
2722
    insert_collision(colliding_id, colliding_local_id);
12✔
2723

2724
    return new_local_id;
12✔
2725
}
12✔
2726

2727
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2728
{
41,286✔
2729
    auto unres_key = key.get_unresolved();
41,286✔
2730

2731
    ensure_graveyard();
41,286✔
2732
    auto tombstone = m_tombstones->try_get_obj(unres_key);
41,286✔
2733
    if (tombstone) {
41,286✔
2734
        if (pk_col) {
402✔
2735
            auto existing_pk_value = tombstone.get_any(pk_col);
402✔
2736
            // It may just be the same object
2737
            if (existing_pk_value != pk_val) {
402✔
2738
                // We have a collision - create new ObjKey
2739
                key = allocate_local_id_after_hash_collision({pk_val}, {existing_pk_value}, key);
12✔
2740
                return get_or_create_tombstone(key, pk_col, pk_val);
12✔
2741
            }
12✔
2742
        }
402✔
2743
        return tombstone;
390✔
2744
    }
402✔
2745
    if (Replication* repl = get_repl()) {
40,884✔
2746
        if (auto logger = repl->would_log(util::Logger::Level::debug)) {
40,614✔
2747
            logger->log(LogCategory::object, util::Logger::Level::debug,
576✔
2748
                        "Create tombstone for object '%1' with primary key %2 : %3", get_class_name(), pk_val,
576✔
2749
                        unres_key);
576✔
2750
        }
576✔
2751
    }
40,614✔
2752
    return m_tombstones->insert(unres_key, {{pk_col, pk_val}});
40,884✔
2753
}
41,286✔
2754

2755
void Table::free_local_id_after_hash_collision(ObjKey key)
2756
{
5,317,338✔
2757
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,317,338✔
2758
        if (key.is_unresolved()) {
30✔
2759
            // Keys will always be inserted as resolved
2760
            key = key.get_unresolved();
24✔
2761
        }
24✔
2762
        // Possible optimization: Cache these accessors
2763
        Array collision_map{m_alloc};
30✔
2764
        Array local_id{m_alloc};
30✔
2765

2766
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2767
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2768
        collision_map.init_from_ref(collision_map_ref);
30✔
2769
        local_id.init_from_parent();
30✔
2770
        auto ndx = local_id.find_first(key.value);
30✔
2771
        if (ndx != realm::npos) {
30✔
2772
            Array hi{m_alloc};
24✔
2773
            Array lo{m_alloc};
24✔
2774

2775
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2776
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2777
            hi.init_from_parent();
24✔
2778
            lo.init_from_parent();
24✔
2779

2780
            hi.erase(ndx);
24✔
2781
            lo.erase(ndx);
24✔
2782
            local_id.erase(ndx);
24✔
2783
            if (hi.size() == 0) {
24✔
2784
                free_collision_table();
12✔
2785
            }
12✔
2786
        }
24✔
2787
    }
30✔
2788
}
5,317,338✔
2789

2790
void Table::free_collision_table()
2791
{
5,580✔
2792
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
5,580✔
2793
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2794
        m_top.set(top_position_for_collision_map, 0);
12✔
2795
    }
12✔
2796
}
5,580✔
2797

2798
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2799
{
3,819✔
2800
    while (number--) {
6,323,577✔
2801
        keys.push_back(create_object().get_key());
6,319,758✔
2802
    }
6,319,758✔
2803
}
3,819✔
2804

2805
void Table::create_objects(const std::vector<ObjKey>& keys)
2806
{
630✔
2807
    for (auto k : keys) {
5,616✔
2808
        create_object(k);
5,616✔
2809
    }
5,616✔
2810
}
630✔
2811

2812
void Table::dump_objects()
2813
{
×
2814
    m_clusters.dump_objects();
×
2815
    if (nb_unresolved())
×
2816
        m_tombstones->dump_objects();
×
2817
}
×
2818

2819
void Table::remove_object(ObjKey key)
2820
{
2,536,884✔
2821
    Group* g = get_parent_group();
2,536,884✔
2822

2823
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,536,884✔
2824
        CascadeState state(CascadeState::Mode::Strong, g);
4,002✔
2825
        state.m_to_be_deleted.emplace_back(m_key, key);
4,002✔
2826
        m_clusters.nullify_incoming_links(key, state);
4,002✔
2827
        remove_recursive(state);
4,002✔
2828
    }
4,002✔
2829
    else {
2,532,882✔
2830
        CascadeState state(CascadeState::Mode::None, g);
2,532,882✔
2831
        if (g) {
2,532,882✔
2832
            m_clusters.nullify_incoming_links(key, state);
2,456,544✔
2833
        }
2,456,544✔
2834
        m_clusters.erase(key, state);
2,532,882✔
2835
    }
2,532,882✔
2836
}
2,536,884✔
2837

2838
ObjKey Table::invalidate_object(ObjKey key)
2839
{
8,562✔
2840
    if (is_embedded())
8,562✔
2841
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2842
    REALM_ASSERT(!key.is_unresolved());
8,562✔
2843

2844
    Obj tombstone;
8,562✔
2845
    auto obj = get_object(key);
8,562✔
2846
    if (obj.has_backlinks(false)) {
8,562✔
2847
        // If the object has backlinks, we should make a tombstone
2848
        // and make inward links point to it,
2849
        if (auto primary_key_col = get_primary_key_column()) {
984✔
2850
            auto pk = obj.get_any(primary_key_col);
828✔
2851
            GlobalKey object_id{pk};
828✔
2852
            auto unres_key = global_to_local_object_id_hashed(object_id);
828✔
2853
            tombstone = get_or_create_tombstone(unres_key, primary_key_col, pk);
828✔
2854
        }
828✔
2855
        else {
156✔
2856
            tombstone = get_or_create_tombstone(key, {}, {});
156✔
2857
        }
156✔
2858
        tombstone.assign_pk_and_backlinks(obj);
984✔
2859
    }
984✔
2860

2861
    remove_object(key);
8,562✔
2862

2863
    return tombstone.get_key();
8,562✔
2864
}
8,562✔
2865

2866
void Table::remove_object_recursive(ObjKey key)
2867
{
168,018✔
2868
    size_t table_ndx = get_index_in_group();
168,018✔
2869
    if (table_ndx != realm::npos) {
168,018✔
2870
        CascadeState state(CascadeState::Mode::All, get_parent_group());
168,006✔
2871
        state.m_to_be_deleted.emplace_back(m_key, key);
168,006✔
2872
        nullify_links(state);
168,006✔
2873
        remove_recursive(state);
168,006✔
2874
    }
168,006✔
2875
    else {
12✔
2876
        // No links in freestanding table
2877
        CascadeState state(CascadeState::Mode::None);
12✔
2878
        m_clusters.erase(key, state);
12✔
2879
    }
12✔
2880
}
168,018✔
2881

2882
Table::Iterator Table::begin() const
2883
{
514,806✔
2884
    return Iterator(m_clusters, 0);
514,806✔
2885
}
514,806✔
2886

2887
Table::Iterator Table::end() const
2888
{
9,582,795✔
2889
    return Iterator(m_clusters, size());
9,582,795✔
2890
}
9,582,795✔
2891

2892
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2893
{
7,164,951✔
2894
    TableRef ret;
7,164,951✔
2895
    if (col_key) {
7,165,398✔
2896
        return table.get_opposite_table(col_key);
7,165,398✔
2897
    }
7,165,398✔
2898
    return ret;
4,294,967,294✔
2899
}
7,164,951✔
2900

2901
const uint64_t Table::max_num_columns;
2902

2903
void Table::build_column_mapping()
2904
{
3,336,753✔
2905
    // build column mapping from spec
2906
    // TODO: Optimization - Don't rebuild this for every change
2907
    m_spec_ndx2leaf_ndx.clear();
3,336,753✔
2908
    m_leaf_ndx2spec_ndx.clear();
3,336,753✔
2909
    m_leaf_ndx2colkey.clear();
3,336,753✔
2910
    size_t num_spec_cols = m_spec.get_column_count();
3,336,753✔
2911
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
3,336,753✔
2912
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
17,702,271✔
2913
        ColKey col_key = m_spec.get_key(spec_ndx);
14,365,518✔
2914
        unsigned leaf_ndx = col_key.get_index().val;
14,365,518✔
2915
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
14,365,518✔
2916
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
13,833,438✔
2917
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
13,833,438✔
2918
        }
13,833,438✔
2919
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
14,365,518✔
2920
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
14,365,518✔
2921
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
14,365,518✔
2922
    }
14,365,518✔
2923
}
3,336,753✔
2924

2925
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2926
{
786,018✔
2927
    REALM_ASSERT(!attr.test(col_attr_Indexed));
786,018✔
2928
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
786,018✔
2929

2930
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
786,018✔
2931
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
786,018✔
2932

2933
    // reuse lowest available leaf ndx:
2934
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
786,018✔
2935
    // look for an unused entry:
2936
    for (unsigned idx = 0; idx < lower; ++idx) {
5,732,544✔
2937
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
4,946,613✔
2938
            lower = idx;
87✔
2939
            break;
87✔
2940
        }
87✔
2941
    }
4,946,613✔
2942
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
786,018✔
2943
}
786,018✔
2944

2945
Table::BacklinkOrigin Table::find_backlink_origin(StringData origin_table_name,
2946
                                                  StringData origin_col_name) const noexcept
2947
{
×
2948
    BacklinkOrigin ret;
×
2949
    auto f = [&](ColKey backlink_col_key) {
×
2950
        auto origin_table = get_opposite_table(backlink_col_key);
×
2951
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2952
        if (origin_table->get_name() == origin_table_name &&
×
2953
            origin_table->get_column_name(origin_link_col) == origin_col_name) {
×
2954
            ret = BacklinkOrigin{{origin_table, origin_link_col}};
×
2955
            return IteratorControl::Stop;
×
2956
        }
×
2957
        return IteratorControl::AdvanceToNext;
×
2958
    };
×
2959
    this->for_each_backlink_column(f);
×
2960
    return ret;
×
2961
}
×
2962

2963
Table::BacklinkOrigin Table::find_backlink_origin(ColKey backlink_col) const noexcept
2964
{
354✔
2965
    try {
354✔
2966
        TableKey linked_table_key = get_opposite_table_key(backlink_col);
354✔
2967
        ColKey linked_column_key = get_opposite_column(backlink_col);
354✔
2968
        if (linked_table_key == m_key) {
354✔
2969
            return {{m_own_ref, linked_column_key}};
18✔
2970
        }
18✔
2971
        else {
336✔
2972
            Group* current_group = get_parent_group();
336✔
2973
            if (current_group) {
336✔
2974
                ConstTableRef linked_table_ref = current_group->get_table(linked_table_key);
336✔
2975
                return {{linked_table_ref, linked_column_key}};
336✔
2976
            }
336✔
2977
        }
336✔
2978
    }
354✔
2979
    catch (...) {
354✔
2980
        // backlink column not found, returning empty optional
2981
    }
×
2982
    return {};
×
2983
}
354✔
2984

2985
std::vector<std::pair<TableKey, ColKey>> Table::get_incoming_link_columns() const noexcept
2986
{
×
2987
    std::vector<std::pair<TableKey, ColKey>> origins;
×
2988
    auto f = [&](ColKey backlink_col_key) {
×
2989
        auto origin_table_key = get_opposite_table_key(backlink_col_key);
×
2990
        auto origin_link_col = get_opposite_column(backlink_col_key);
×
2991
        origins.emplace_back(origin_table_key, origin_link_col);
×
2992
        return IteratorControl::AdvanceToNext;
×
2993
    };
×
2994
    this->for_each_backlink_column(f);
×
2995
    return origins;
×
2996
}
×
2997

2998
ColKey Table::get_primary_key_column() const
2999
{
21,567,741✔
3000
    return m_primary_key_col;
21,567,741✔
3001
}
21,567,741✔
3002

3003
void Table::set_primary_key_column(ColKey col_key)
3004
{
720✔
3005
    if (col_key == m_primary_key_col) {
720✔
3006
        return;
378✔
3007
    }
378✔
3008

3009
    if (Replication* repl = get_repl()) {
342✔
3010
        if (repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
240✔
3011
            throw RuntimeError(
×
3012
                ErrorCodes::BrokenInvariant,
×
3013
                util::format("Cannot change primary key property in '%1' when realm is synchronized", get_name()));
×
3014
        }
×
3015
    }
240✔
3016

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

3019
    if (col_key) {
342✔
3020
        check_column(col_key);
222✔
3021
        validate_column_is_unique(col_key);
222✔
3022
        do_set_primary_key_column(col_key);
222✔
3023
    }
222✔
3024
    else {
120✔
3025
        do_set_primary_key_column(col_key);
120✔
3026
    }
120✔
3027
}
342✔
3028

3029

3030
void Table::do_set_primary_key_column(ColKey col_key)
3031
{
111,909✔
3032
    if (col_key) {
111,909✔
3033
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
110,775✔
3034
        auto attr = m_spec.get_column_attr(spec_ndx);
110,775✔
3035
        if (attr.test(col_attr_FullText_Indexed)) {
110,775✔
3036
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
3037
        }
6✔
3038
    }
110,775✔
3039

3040
    if (m_primary_key_col) {
111,903✔
3041
        // If the search index has not been set explicitly on current pk col, we remove it again
3042
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
1,158✔
3043
        auto attr = m_spec.get_column_attr(spec_ndx);
1,158✔
3044
        if (!attr.test(col_attr_Indexed)) {
1,158✔
3045
            remove_search_index(m_primary_key_col);
1,146✔
3046
        }
1,146✔
3047
    }
1,158✔
3048

3049
    if (col_key) {
111,903✔
3050
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
110,766✔
3051
        do_add_search_index(col_key, IndexType::General);
110,766✔
3052
    }
110,766✔
3053
    else {
1,137✔
3054
        m_top.set(top_position_for_pk_col, 0);
1,137✔
3055
    }
1,137✔
3056

3057
    m_primary_key_col = col_key;
111,903✔
3058
}
111,903✔
3059

3060
bool Table::contains_unique_values(ColKey col) const
3061
{
834✔
3062
    if (search_index_type(col) == IndexType::General) {
834✔
3063
        auto search_index = get_search_index(col);
744✔
3064
        return !search_index->has_duplicate_values();
744✔
3065
    }
744✔
3066
    else {
90✔
3067
        TableView tv = where().find_all();
90✔
3068
        tv.distinct(col);
90✔
3069
        return tv.size() == size();
90✔
3070
    }
90✔
3071
}
834✔
3072

3073
void Table::validate_column_is_unique(ColKey col) const
3074
{
834✔
3075
    if (!contains_unique_values(col)) {
834✔
3076
        throw MigrationFailed(util::format("Primary key property '%1.%2' has duplicate values after migration.",
30✔
3077
                                           get_class_name(), get_column_name(col)));
30✔
3078
    }
30✔
3079
}
834✔
3080

3081
void Table::validate_primary_column()
3082
{
1,812✔
3083
    if (ColKey col = get_primary_key_column()) {
1,812✔
3084
        validate_column_is_unique(col);
612✔
3085
    }
612✔
3086
}
1,812✔
3087

3088
ObjKey Table::get_next_valid_key()
3089
{
905,370✔
3090
    ObjKey key;
905,370✔
3091
    do {
905,376✔
3092
        key = ObjKey(allocate_sequence_number());
905,376✔
3093
    } while (m_clusters.is_valid(key));
905,376✔
3094

3095
    return key;
905,370✔
3096
}
905,370✔
3097

3098
namespace {
3099
template <class T>
3100
typename util::RemoveOptional<T>::type remove_optional(T val)
3101
{
88,035✔
3102
    return val;
88,035✔
3103
}
88,035✔
3104
template <>
3105
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
3106
{
5,469✔
3107
    return *val;
5,469✔
3108
}
5,469✔
3109
template <>
3110
bool remove_optional<Optional<bool>>(Optional<bool> val)
3111
{
11,493✔
3112
    return *val;
11,493✔
3113
}
11,493✔
3114
template <>
3115
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
3116
{
5,409✔
3117
    return *val;
5,409✔
3118
}
5,409✔
3119
template <>
3120
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
3121
{
6,060✔
3122
    return *val;
6,060✔
3123
}
6,060✔
3124
} // namespace
3125

3126
template <class F, class T>
3127
void Table::change_nullability(ColKey key_from, ColKey key_to, bool throw_on_null)
3128
{
162✔
3129
    Allocator& allocator = this->get_alloc();
162✔
3130
    bool from_nullability = is_nullable(key_from);
162✔
3131
    auto func = [&](Cluster* cluster) {
162✔
3132
        size_t sz = cluster->node_size();
162✔
3133

3134
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
3135
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
3136
        cluster->init_leaf(key_from, &from_arr);
162✔
3137
        cluster->init_leaf(key_to, &to_arr);
162✔
3138

3139
        for (size_t i = 0; i < sz; i++) {
1,512✔
3140
            if (from_nullability && from_arr.is_null(i)) {
1,356!
3141
                if (throw_on_null) {
63!
3142
                    throw RuntimeError(ErrorCodes::BrokenInvariant,
6✔
3143
                                       util::format("Objects in '%1' has null value(s) in property '%2'", get_name(),
6✔
3144
                                                    get_column_name(key_from)));
6✔
3145
                }
6✔
3146
                else {
57✔
3147
                    to_arr.set(i, ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
57✔
3148
                }
57✔
3149
            }
63✔
3150
            else {
1,293✔
3151
                auto v = remove_optional(from_arr.get(i));
1,293✔
3152
                to_arr.set(i, v);
1,293✔
3153
            }
1,293✔
3154
        }
1,356✔
3155
    };
162✔
3156

3157
    m_clusters.update(func);
162✔
3158
}
162✔
3159

3160
template <class F, class T>
3161
void Table::change_nullability_list(ColKey key_from, ColKey key_to, bool throw_on_null)
3162
{
120✔
3163
    Allocator& allocator = this->get_alloc();
120✔
3164
    bool from_nullability = is_nullable(key_from);
120✔
3165
    auto func = [&](Cluster* cluster) {
120✔
3166
        size_t sz = cluster->node_size();
120✔
3167

3168
        ArrayInteger from_arr(allocator);
120✔
3169
        ArrayInteger to_arr(allocator);
120✔
3170
        cluster->init_leaf(key_from, &from_arr);
120✔
3171
        cluster->init_leaf(key_to, &to_arr);
120✔
3172

3173
        for (size_t i = 0; i < sz; i++) {
360✔
3174
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3175
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3176
            REALM_ASSERT(!ref_to);
240✔
3177

3178
            if (ref_from) {
240✔
3179
                BPlusTree<F> from_list(allocator);
120✔
3180
                BPlusTree<T> to_list(allocator);
120✔
3181
                from_list.init_from_ref(ref_from);
120✔
3182
                to_list.create();
120✔
3183
                size_t n = from_list.size();
120✔
3184
                for (size_t j = 0; j < n; j++) {
120,120✔
3185
                    auto v = from_list.get(j);
120,000✔
3186
                    if (!from_nullability || aggregate_operations::valid_for_agg(v)) {
120,000!
3187
                        to_list.add(remove_optional(v));
115,173✔
3188
                    }
115,173✔
3189
                    else {
4,827✔
3190
                        if (throw_on_null) {
4,827!
3191
                            throw RuntimeError(ErrorCodes::BrokenInvariant,
×
3192
                                               util::format("Objects in '%1' has null value(s) in list property '%2'",
×
3193
                                                            get_name(), get_column_name(key_from)));
×
3194
                        }
×
3195
                        else {
4,827✔
3196
                            to_list.add(ColumnTypeTraits<T>::cluster_leaf_type::default_value(false));
4,827✔
3197
                        }
4,827✔
3198
                    }
4,827✔
3199
                }
120,000✔
3200
                to_arr.set(i, from_ref(to_list.get_ref()));
120✔
3201
            }
120✔
3202
        }
240✔
3203
    };
120✔
3204

3205
    m_clusters.update(func);
120✔
3206
}
120✔
3207

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

3326

3327
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3328
{
522✔
3329
    if (col_key.is_nullable() == nullable)
522✔
3330
        return col_key;
240✔
3331

3332
    check_column(col_key);
282✔
3333

3334
    auto index_type = search_index_type(col_key);
282✔
3335
    std::string column_name(get_column_name(col_key));
282✔
3336
    auto type = col_key.get_type();
282✔
3337
    auto attr = col_key.get_attrs();
282✔
3338
    bool is_pk_col = (col_key == m_primary_key_col);
282✔
3339
    if (nullable) {
282✔
3340
        attr.set(col_attr_Nullable);
150✔
3341
    }
150✔
3342
    else {
132✔
3343
        attr.reset(col_attr_Nullable);
132✔
3344
    }
132✔
3345

3346
    ColKey new_col = generate_col_key(type, attr);
282✔
3347
    do_insert_root_column(new_col, type, "__temporary");
282✔
3348

3349
    try {
282✔
3350
        convert_column(col_key, new_col, throw_on_null);
282✔
3351
    }
282✔
3352
    catch (...) {
282✔
3353
        // remove any partially filled column
3354
        remove_column(new_col);
6✔
3355
        throw;
6✔
3356
    }
6✔
3357

3358
    if (is_pk_col) {
276✔
3359
        // If we go from non nullable to nullable, no values change,
3360
        // so it is safe to preserve the pk column. Otherwise it is not
3361
        // safe as a null entry might have been converted to default value.
3362
        do_set_primary_key_column(nullable ? new_col : ColKey{});
12✔
3363
    }
12✔
3364

3365
    erase_root_column(col_key);
276✔
3366
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3367

3368
    if (index_type != IndexType::None)
276✔
3369
        do_add_search_index(new_col, index_type);
30✔
3370

3371
    return new_col;
276✔
3372
}
282✔
3373

3374
bool Table::has_any_embedded_objects()
3375
{
2,547,210✔
3376
    if (!m_has_any_embedded_objects) {
2,547,210✔
3377
        m_has_any_embedded_objects = false;
28,830✔
3378
        for_each_public_column([&](ColKey col_key) {
70,875✔
3379
            auto target_table_key = get_opposite_table_key(col_key);
70,875✔
3380
            if (target_table_key && is_link_type(col_key.get_type())) {
70,875✔
3381
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
15,663✔
3382
                if (target_table->is_embedded()) {
15,663✔
3383
                    m_has_any_embedded_objects = true;
13,533✔
3384
                    return IteratorControl::Stop; // early out
13,533✔
3385
                }
13,533✔
3386
            }
15,663✔
3387
            return IteratorControl::AdvanceToNext;
57,342✔
3388
        });
70,875✔
3389
    }
28,830✔
3390
    return *m_has_any_embedded_objects;
2,547,210✔
3391
}
2,547,210✔
3392

3393
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3394
{
158,418✔
3395
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
158,418✔
3396
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
158,418✔
3397
}
158,418✔
3398

3399
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3400
{
623,667✔
3401
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
1,315,377✔
3402
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
1,307,352✔
3403
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
615,642✔
3404
        }
615,642✔
3405
    }
1,307,352✔
3406

3407
    return {};
8,025✔
3408
}
623,667✔
3409

3410
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3411
{
623,610✔
3412
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
623,610✔
3413

3414
    if (!backlink_col_key) {
623,610✔
3415
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
7,848✔
3416
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
7,848✔
3417

3418
        if (Replication* repl = get_repl())
7,848✔
3419
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
7,452✔
3420
                                    m_key); // Throws
7,452✔
3421
    }
7,848✔
3422

3423
    return backlink_col_key;
623,610✔
3424
}
623,610✔
3425

3426
TableKey Table::get_opposite_table_key(ColKey col_key) const
3427
{
14,605,593✔
3428
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,605,593✔
3429
}
14,605,593✔
3430

3431
bool Table::links_to_self(ColKey col_key) const
3432
{
69,843✔
3433
    return get_opposite_table_key(col_key) == m_key;
69,843✔
3434
}
69,843✔
3435

3436
TableRef Table::get_opposite_table(ColKey col_key) const
3437
{
7,888,161✔
3438
    if (auto k = get_opposite_table_key(col_key)) {
7,888,161✔
3439
        return get_parent_group()->get_table(k);
7,825,497✔
3440
    }
7,825,497✔
3441
    return {};
62,664✔
3442
}
7,888,161✔
3443

3444
ColKey Table::get_opposite_column(ColKey col_key) const
3445
{
16,357,215✔
3446
    return ColKey(m_opposite_column.get(col_key.get_index().val));
16,357,215✔
3447
}
16,357,215✔
3448

3449
ColKey Table::find_opposite_column(ColKey col_key) const
3450
{
×
3451
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3452
        if (m_opposite_column.get(i) == col_key.value) {
×
3453
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3454
        }
×
3455
    }
×
3456
    return ColKey();
×
3457
}
×
3458

3459
ref_type Table::typed_write(_impl::ArrayWriterBase& out) const
3460
{
1,390,233✔
3461
    auto ref = m_top.get_ref();
1,390,233✔
3462
    if (out.only_modified && m_alloc.is_read_only(ref))
1,390,233✔
3463
        return ref;
576,333✔
3464
    out.table = this;
813,900✔
3465
    // ignore ref from here, just use Tables own accessors
3466
    TempArray dest(m_top.size());
813,900✔
3467
    for (unsigned j = 0; j < m_top.size(); ++j) {
13,019,925✔
3468
        RefOrTagged rot = m_top.get_as_ref_or_tagged(j);
12,206,025✔
3469
        if (rot.is_tagged() || (rot.is_ref() && rot.get_as_ref() == 0)) {
12,206,025✔
3470
            dest.set(j, rot);
7,317,126✔
3471
        }
7,317,126✔
3472
        else {
4,888,899✔
3473
            ref_type new_ref;
4,888,899✔
3474
            if (j == 2) {
4,888,899✔
3475
                // only do type driven write for clustertree
3476
                new_ref = m_clusters.typed_write(rot.get_as_ref(), out);
813,888✔
3477
            }
813,888✔
3478
            else {
4,075,011✔
3479
                // rest is handled using untyped approach
3480
                Array a(m_alloc);
4,075,011✔
3481
                a.init_from_ref(rot.get_as_ref());
4,075,011✔
3482
                new_ref = a.write(out, true, out.only_modified, false);
4,075,011✔
3483
            }
4,075,011✔
3484
            dest.set_as_ref(j, new_ref);
4,888,899✔
3485
        }
4,888,899✔
3486
    }
12,206,025✔
3487
    return dest.write(out);
813,900✔
3488
}
1,390,233✔
3489

3490
StringInterner* Table::get_string_interner(ColKey::Idx idx) const
3491
{
134,329,398✔
3492
    REALM_ASSERT(idx.val < m_string_interners.size());
134,329,398✔
3493
    auto interner = m_string_interners[idx.val].get();
134,329,398✔
3494
    REALM_ASSERT(interner);
134,329,398✔
3495
    return interner;
134,329,398✔
3496
}
134,329,398✔
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