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

realm / realm-core / jorgen.edelbo_322

20 Jun 2024 09:43AM UTC coverage: 84.879% (-6.1%) from 90.966%
jorgen.edelbo_322

Pull #7826

Evergreen

jedelbo
remove set_direct methods from integer compressors
Pull Request #7826: Merge Next major

66056 of 81292 branches covered (81.26%)

3131 of 3738 new or added lines in 54 files covered. (83.76%)

566 existing lines in 29 files now uncovered.

84791 of 99896 relevant lines covered (84.88%)

15351931.14 hits per line

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

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

19
#include <realm/table.hpp>
20

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

39
#include <stdexcept>
40

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

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

260

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

264
Replication* Table::g_dummy_replication = nullptr;
265

266
bool TableVersions::operator==(const TableVersions& other) const
267
{
217,146✔
268
    if (size() != other.size())
217,146✔
269
        return false;
12,522✔
270
    size_t sz = size();
204,624✔
271
    for (size_t i = 0; i < sz; i++) {
377,187✔
272
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
206,043✔
273
        if (this->at(i).second != other.at(i).second)
206,043✔
274
            return false;
33,480✔
275
    }
206,043✔
276
    return true;
171,144✔
277
}
204,624✔
278

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

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

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

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

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

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

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

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

399
    ColumnAttrMask attr;
507,774✔
400
    if (collection_type) {
507,774✔
401
        switch (*collection_type) {
150,318✔
402
            case CollectionType::List:
61,845✔
403
                attr.set(col_attr_List);
61,845✔
404
                break;
61,845✔
405
            case CollectionType::Set:
43,341✔
406
                attr.set(col_attr_Set);
43,341✔
407
                break;
43,341✔
408
            case CollectionType::Dictionary:
45,132✔
409
                attr.set(col_attr_Dictionary);
45,132✔
410
                break;
45,132✔
411
        }
150,318✔
412
    }
150,318✔
413
    if (nullable || type == type_Mixed)
507,774✔
414
        attr.set(col_attr_Nullable);
183,780✔
415
    ColKey col_key = generate_col_key(ColumnType(type), attr);
507,774✔
416

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

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

434
    m_has_any_embedded_objects.reset();
74,316✔
435

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

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

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

468
    do {
24,018✔
469
        cascade_state.send_notifications();
24,018✔
470

471
        for (auto& l : cascade_state.m_to_be_nullified) {
24,018✔
472
            Obj obj = group->get_table_unchecked(l.origin_table)->try_get_object(l.origin_key);
48✔
473
            REALM_ASSERT_DEBUG(obj);
48✔
474
            if (obj) {
48✔
475
                std::move(obj).nullify_link(l.origin_col_key, l.old_target_link);
48✔
476
            }
48✔
477
        }
48✔
478
        cascade_state.m_to_be_nullified.clear();
24,018✔
479

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

491
void Table::nullify_links(CascadeState& cascade_state)
492
{
30,171✔
493
    Group* group = get_parent_group();
30,171✔
494
    REALM_ASSERT(group);
30,171✔
495
    for (auto& to_delete : cascade_state.m_to_be_deleted) {
30,171✔
496
        auto table = to_delete.first == m_key ? this : group->get_table_unchecked(to_delete.first);
8,514✔
497
        if (!table->is_asymmetric())
8,514✔
498
            table->m_clusters.nullify_incoming_links(to_delete.second, cascade_state);
8,514✔
499
    }
8,514✔
500
}
30,171✔
501

502
CollectionType Table::get_collection_type(ColKey col_key) const
503
{
9,156✔
504
    if (col_key.is_list()) {
9,156✔
505
        return CollectionType::List;
648✔
506
    }
648✔
507
    if (col_key.is_set()) {
8,508✔
508
        return CollectionType::Set;
1,056✔
509
    }
1,056✔
510
    REALM_ASSERT(col_key.is_dictionary());
7,452✔
511
    return CollectionType::Dictionary;
7,452✔
512
}
8,508✔
513

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

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

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

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

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

540

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

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

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

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

555

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

570

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

580
    m_spec.init_from_parent();
2,219,901✔
581

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

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

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

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

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

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

646
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
2,219,901✔
647
        // Tombstones exists
648
        if (!m_tombstones) {
42,600✔
649
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
33,129✔
650
        }
33,129✔
651
        m_tombstones->init_from_parent();
42,600✔
652
    }
42,600✔
653
    else {
2,177,301✔
654
        m_tombstones = nullptr;
2,177,301✔
655
    }
2,177,301✔
656
    m_cookie = cookie_initialized;
2,219,901✔
657
}
2,219,901✔
658

659

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

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

667
    if (target_table) {
582,090✔
668
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
74,310✔
669
        target_table->check_column(backlink_col_key);
74,310✔
670

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

675
    if (Replication* repl = get_repl())
582,090✔
676
        repl->insert_column(this, col_key, type, name, target_table); // Throws
563,985✔
677

678
    return col_key;
582,090✔
679
}
582,090✔
680

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

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

693
    table->traverse_clusters(f);
112,878✔
694
}
112,878✔
695

696

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

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

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

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

716
    if (type == type_Int) {
112,902✔
717
        if (is_nullable(col_key)) {
49,329✔
718
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
13,791✔
719
        }
13,791✔
720
        else {
35,538✔
721
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
35,538✔
722
        }
35,538✔
723
    }
49,329✔
724
    else if (type == type_Bool) {
63,573✔
725
        if (is_nullable(col_key)) {
51✔
726
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
727
        }
24✔
728
        else {
27✔
729
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
27✔
730
        }
27✔
731
    }
51✔
732
    else if (type == type_String) {
63,522✔
733
        if (col_key.is_list()) {
18,717✔
734
            do_bulk_insert_index_list(this, index, col_key, get_alloc());
24✔
735
        }
24✔
736
        else {
18,693✔
737
            do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
18,693✔
738
        }
18,693✔
739
    }
18,717✔
740
    else if (type == type_Timestamp) {
44,805✔
741
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
96✔
742
    }
96✔
743
    else if (type == type_ObjectId) {
44,709✔
744
        if (is_nullable(col_key)) {
43,119✔
745
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
984✔
746
        }
984✔
747
        else {
42,135✔
748
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
42,135✔
749
        }
42,135✔
750
    }
43,119✔
751
    else if (type == type_UUID) {
1,590✔
752
        if (is_nullable(col_key)) {
684✔
753
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
754
        }
516✔
755
        else {
168✔
756
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
168✔
757
        }
168✔
758
    }
684✔
759
    else if (type == type_Mixed) {
906✔
760
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
906✔
761
    }
906✔
UNCOV
762
    else {
×
UNCOV
763
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
×
UNCOV
764
    }
×
765
}
112,902✔
766

767
void Table::erase_from_search_indexes(ObjKey key)
768
{
5,000,898✔
769
    // Tombstones do not use index - will crash if we try to erase values
770
    if (!key.is_unresolved()) {
5,000,898✔
771
        for (auto&& index : m_index_accessors) {
6,670,533✔
772
            if (index) {
6,670,533✔
773
                index->erase(key);
255,639✔
774
            }
255,639✔
775
        }
6,670,533✔
776
    }
4,989,894✔
777
}
5,000,898✔
778

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

786
    auto sz = m_index_accessors.size();
24,655,917✔
787
    // values are sorted by column index - there may be values missing
788
    auto value = values.begin();
24,655,917✔
789
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
61,224,807✔
790
        // Check if initial value is provided
791
        Mixed init_value;
36,568,977✔
792
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
36,568,977✔
793
            // Value for this column is provided
794
            init_value = value->value;
494,073✔
795
            ++value;
494,073✔
796
        }
494,073✔
797

798
        if (auto&& index = m_index_accessors[column_ndx]) {
36,568,977✔
799
            // There is an index for this column
800
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,069,689✔
801
            if (col_key.is_collection())
1,069,689✔
802
                continue;
102✔
803
            auto type = col_key.get_type();
1,069,587✔
804
            auto attr = col_key.get_attrs();
1,069,587✔
805
            bool nullable = attr.test(col_attr_Nullable);
1,069,587✔
806
            switch (type) {
1,069,587✔
807
                case col_type_Int:
480,597✔
808
                    if (init_value.is_null()) {
480,597✔
809
                        index->insert(key, ArrayIntNull::default_value(nullable));
166,920✔
810
                    }
166,920✔
811
                    else {
313,677✔
812
                        index->insert(key, init_value.get<int64_t>());
313,677✔
813
                    }
313,677✔
814
                    break;
480,597✔
815
                case col_type_Bool:
6,504✔
816
                    if (init_value.is_null()) {
6,504✔
817
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,504✔
818
                    }
6,504✔
819
                    else {
×
820
                        index->insert(key, init_value.get<bool>());
×
821
                    }
×
822
                    break;
6,504✔
823
                case col_type_String:
460,167✔
824
                    if (init_value.is_null()) {
460,167✔
825
                        index->insert(key, ArrayString::default_value(nullable));
432,525✔
826
                    }
432,525✔
827
                    else {
27,642✔
828
                        index->insert(key, init_value.get<String>());
27,642✔
829
                    }
27,642✔
830
                    break;
460,167✔
831
                case col_type_Timestamp:
7,434✔
832
                    if (init_value.is_null()) {
7,434✔
833
                        index->insert(key, ArrayTimestamp::default_value(nullable));
7,434✔
834
                    }
7,434✔
835
                    else {
×
836
                        index->insert(key, init_value.get<Timestamp>());
×
837
                    }
×
838
                    break;
7,434✔
839
                case col_type_ObjectId:
93,123✔
840
                    if (init_value.is_null()) {
93,123✔
841
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
7,320✔
842
                    }
7,320✔
843
                    else {
85,803✔
844
                        index->insert(key, init_value.get<ObjectId>());
85,803✔
845
                    }
85,803✔
846
                    break;
93,123✔
847
                case col_type_Mixed:
2,286✔
848
                    index->insert(key, init_value);
2,286✔
849
                    break;
2,286✔
850
                case col_type_UUID:
19,542✔
851
                    if (init_value.is_null()) {
19,542✔
852
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
7,338✔
853
                    }
7,338✔
854
                    else {
12,204✔
855
                        index->insert(key, init_value.get<UUID>());
12,204✔
856
                    }
12,204✔
857
                    break;
19,542✔
858
                default:
✔
859
                    REALM_UNREACHABLE();
860
            }
1,069,587✔
861
        }
1,069,587✔
862
    }
36,568,977✔
863
}
24,655,917✔
864

865
void Table::clear_indexes()
866
{
6,039✔
867
    for (auto&& index : m_index_accessors) {
57,990✔
868
        if (index) {
57,990✔
869
            index->clear();
4,761✔
870
        }
4,761✔
871
    }
57,990✔
872
}
6,039✔
873

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

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

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

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

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

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

904
    populate_search_index(col_key);
112,902✔
905
}
112,902✔
906

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1004

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

1016

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

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

1025
    if (!col_key) {
773,289✔
1026
        col_key = generate_col_key(type, {});
82,068✔
1027
    }
82,068✔
1028

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

1058
    bump_storage_version();
773,289✔
1059

1060
    return col_key;
773,289✔
1061
}
773,289✔
1062

1063

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

1084
    build_column_mapping();
5,925✔
1085
    while (m_index_accessors.size() > m_leaf_ndx2colkey.size()) {
11,277✔
1086
        REALM_ASSERT(m_index_accessors.back() == nullptr);
5,352✔
1087
        m_index_accessors.pop_back();
5,352✔
1088
    }
5,352✔
1089
    bump_content_version();
5,925✔
1090
    bump_storage_version();
5,925✔
1091
}
5,925✔
1092

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1237

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

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

1254

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

1270

1271
IndexType Table::search_index_type(ColKey col_key) const noexcept
1272
{
4,903,635✔
1273
    if (m_index_accessors[col_key.get_index().val].get()) {
4,903,635✔
1274
        auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_key.get_index().val]);
1,174,128✔
1275
        bool fulltext = attr.test(col_attr_FullText_Indexed);
1,174,128✔
1276
        return fulltext ? IndexType::Fulltext : IndexType::General;
1,174,128✔
1277
    }
1,174,128✔
1278
    return IndexType::None;
3,729,507✔
1279
}
4,903,635✔
1280

1281

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

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

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

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

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

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

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

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

1391

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

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

1404

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

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

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

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

1469
    REALM_ASSERT(top.size() == top_array_size);
282,738✔
1470

1471
    return top.get_ref();
282,738✔
1472
}
282,738✔
1473

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

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

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

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

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

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

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

1542

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

1551
    return static_cast<Group*>(parent);
34,845,756✔
1552
}
61,356,780✔
1553

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

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

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

1577
    return sn;
20,325,861✔
1578
}
20,325,861✔
1579

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

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

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

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

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

1625
    traverse_clusters(f);
18✔
1626

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

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

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

1657
    traverse_clusters(f);
35,154✔
1658
}
35,154✔
1659

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

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

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

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

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

1689

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

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

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

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

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

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

1734
    traverse_clusters(f);
21,531✔
1735

1736
    return key;
21,531✔
1737
}
35,100✔
1738

1739
namespace realm {
1740

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1864

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1965
        m_opposite_table.update_from_parent();
1,087,887✔
1966
        m_opposite_column.update_from_parent();
1,087,887✔
1967
        if (m_top.size() > top_position_for_flags) {
1,087,890✔
1968
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
1,087,764✔
1969
            m_table_type = Type(flags & table_type_mask);
1,087,764✔
1970
        }
1,087,764✔
1971
        else {
2,147,483,773✔
1972
            m_table_type = Type::TopLevel;
2,147,483,773✔
1973
        }
2,147,483,773✔
1974
        if (m_tombstones)
1,087,887✔
1975
            m_tombstones->update_from_parent();
4,506✔
1976

1977
        refresh_content_version();
1,087,887✔
1978
        m_has_any_embedded_objects.reset();
1,087,887✔
1979
    }
1,087,887✔
1980
    m_alloc.bump_storage_version();
1,087,893✔
1981
}
1,087,893✔
1982

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

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

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

2070
    return true;
132✔
2071
}
150✔
2072

2073

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

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

2104

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

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

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

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

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

2167
            if (m_index_accessors[col_ndx]) { // still there, refresh:
1,115,058✔
2168
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
418,101✔
2169
            }
418,101✔
2170
            else { // new index!
696,957✔
2171
                m_index_accessors[col_ndx] =
696,957✔
2172
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
696,957✔
2173
            }
696,957✔
2174
        }
1,115,058✔
2175
    }
14,576,667✔
2176
}
3,389,112✔
2177

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

2190
// LCOV_EXCL_START ignore debug functions
2191

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

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

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

2233
    REALM_ASSERT(key.value >= 0);
24,211,920✔
2234

2235
    Obj obj = m_clusters.insert(key, values); // repl->set()
24,211,920✔
2236

2237
    return obj;
24,211,920✔
2238
}
24,211,920✔
2239

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

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

2248
    if (auto repl = get_repl())
43,002✔
2249
        repl->create_linked_object(this, key);
41,706✔
2250

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

2253
    return obj;
43,002✔
2254
}
43,002✔
2255

2256
Obj Table::create_object(GlobalKey object_id, const FieldValues& values)
2257
{
30✔
2258
    if (is_embedded())
30✔
2259
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
×
2260
    if (m_primary_key_col)
30✔
2261
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2262
    ObjKey key = object_id.get_local_key(get_sync_file_id());
30✔
2263

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

2267
    try {
30✔
2268
        Obj obj = m_clusters.insert(key, values);
30✔
2269
        // Check if tombstone exists
2270
        if (m_tombstones && m_tombstones->is_valid(key.get_unresolved())) {
30✔
2271
            auto unres_key = key.get_unresolved();
6✔
2272
            // Copy links over
2273
            auto tombstone = m_tombstones->get(unres_key);
6✔
2274
            obj.assign_pk_and_backlinks(tombstone);
6✔
2275
            // If tombstones had no links to it, it may still be alive
2276
            if (m_tombstones->is_valid(unres_key)) {
6✔
2277
                CascadeState state(CascadeState::Mode::None);
6✔
2278
                m_tombstones->erase(unres_key, state);
6✔
2279
            }
6✔
2280
        }
6✔
2281

2282
        return obj;
30✔
2283
    }
30✔
2284
    catch (const KeyAlreadyUsed&) {
30✔
2285
        return m_clusters.get(key);
6✔
2286
    }
6✔
2287
}
30✔
2288

2289
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2290
                                          bool* did_create)
2291
{
486,966✔
2292
    auto primary_key_col = get_primary_key_column();
486,966✔
2293
    if (is_embedded() || !primary_key_col)
486,969✔
2294
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2295
                              util::format("Table has no primary key: %1", get_name()));
6✔
2296

2297
    DataType type = DataType(primary_key_col.get_type());
486,960✔
2298

2299
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
486,960✔
2300
        throw InvalidArgument(
6✔
2301
            ErrorCodes::PropertyNotNullable,
6✔
2302
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2303
    }
6✔
2304

2305
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
486,954✔
2306
        primary_key.get_type() != type) {
486,954✔
2307
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2308
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2309
    }
6✔
2310

2311
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
486,948✔
2312

2313
    if (did_create)
486,948✔
2314
        *did_create = false;
84,870✔
2315

2316
    // Check for existing object
2317
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
486,948✔
2318
        if (mode == UpdateMode::never) {
47,502✔
2319
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2320
        }
6✔
2321
        auto obj = m_clusters.get(key);
47,496✔
2322
        for (auto& val : field_values) {
47,496✔
2323
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2324
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2325
            }
12✔
2326
        }
12✔
2327
        return obj;
47,496✔
2328
    }
47,502✔
2329

2330
    ObjKey unres_key;
439,446✔
2331
    if (m_tombstones) {
439,446✔
2332
        // Check for potential tombstone
2333
        GlobalKey object_id{primary_key};
12,153✔
2334
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
12,153✔
2335

2336
        ObjKey key = object_key.get_unresolved();
12,153✔
2337
        if (auto obj = m_tombstones->try_get_obj(key)) {
12,153✔
2338
            auto existing_pk_value = obj.get_any(primary_key_col);
10,779✔
2339

2340
            // If the primary key is the same, the object should be resurrected below
2341
            if (existing_pk_value == primary_key) {
10,779✔
2342
                unres_key = key;
10,773✔
2343
            }
10,773✔
2344
        }
10,779✔
2345
    }
12,153✔
2346

2347
    ObjKey key = get_next_valid_key();
439,446✔
2348

2349
    auto repl = get_repl();
439,446✔
2350
    if (repl) {
439,446✔
2351
        repl->create_object_with_primary_key(this, key, primary_key);
318,402✔
2352
    }
318,402✔
2353
    if (did_create) {
439,446✔
2354
        *did_create = true;
47,070✔
2355
    }
47,070✔
2356

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

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

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

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

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

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

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

2404
        if (existing_pk_value == primary_key) {
×
2405
            return object_key;
×
2406
        }
×
2407
    }
×
2408
    return {};
1,542✔
2409
}
1,542✔
2410

2411
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2412
{
609,138✔
2413
    // Check if existing
2414
    if (auto key = find_primary_key(primary_key)) {
609,138✔
2415
        return key;
596,907✔
2416
    }
596,907✔
2417

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

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

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

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

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

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

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

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

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

2498
namespace {
2499

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

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

2521
} // namespace
2522

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

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

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

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

2549
    return optimistic;
24,813✔
2550
}
24,837✔
2551

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

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

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

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

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

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

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

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

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

2619
    return new_local_id;
12✔
2620
}
12✔
2621

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

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

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

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

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

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

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

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

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

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

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

2718
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,463,837✔
2719
        CascadeState state(CascadeState::Mode::Strong, g);
3,336✔
2720
        state.m_to_be_deleted.emplace_back(m_key, key);
3,336✔
2721
        m_clusters.nullify_incoming_links(key, state);
3,336✔
2722
        remove_recursive(state);
3,336✔
2723
    }
3,336✔
2724
    else {
2,460,501✔
2725
        CascadeState state(CascadeState::Mode::None, g);
2,460,501✔
2726
        if (g) {
2,460,501✔
2727
            m_clusters.nullify_incoming_links(key, state);
2,378,565✔
2728
        }
2,378,565✔
2729
        m_clusters.erase(key, state);
2,460,501✔
2730
    }
2,460,501✔
2731
}
2,463,837✔
2732

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

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

2756
    remove_object(key);
4,344✔
2757

2758
    return tombstone.get_key();
4,344✔
2759
}
4,344✔
2760

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

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

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

2787
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2788
{
7,100,838✔
2789
    TableRef ret;
7,100,838✔
2790
    if (col_key) {
7,100,892✔
2791
        return table.get_opposite_table(col_key);
7,100,886✔
2792
    }
7,100,886✔
2793
    return ret;
2,147,483,653✔
2794
}
7,100,838✔
2795

2796
const uint64_t Table::max_num_columns;
2797

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

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

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

2828
    // reuse lowest available leaf ndx:
2829
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
773,295✔
2830
    // look for an unused entry:
2831
    for (unsigned idx = 0; idx < lower; ++idx) {
5,690,904✔
2832
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
4,917,693✔
2833
            lower = idx;
84✔
2834
            break;
84✔
2835
        }
84✔
2836
    }
4,917,693✔
2837
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
773,295✔
2838
}
773,295✔
2839

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

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

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

2893
ColKey Table::get_primary_key_column() const
2894
{
21,235,380✔
2895
    return m_primary_key_col;
21,235,380✔
2896
}
21,235,380✔
2897

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

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

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

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

2924

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

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

2944
    if (col_key) {
109,932✔
2945
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
109,053✔
2946
        do_add_search_index(col_key, IndexType::General);
109,053✔
2947
    }
109,053✔
2948
    else {
879✔
2949
        m_top.set(top_position_for_pk_col, 0);
879✔
2950
    }
879✔
2951

2952
    m_primary_key_col = col_key;
109,932✔
2953
}
109,932✔
2954

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

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

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

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

2990
    return key;
439,443✔
2991
}
439,443✔
2992

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

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

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

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

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

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

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

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

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

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

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

3221

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

3227
    check_column(col_key);
282✔
3228

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

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

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

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

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

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

3266
    return new_col;
276✔
3267
}
282✔
3268

3269
bool Table::has_any_embedded_objects()
3270
{
2,470,665✔
3271
    if (!m_has_any_embedded_objects) {
2,470,665✔
3272
        m_has_any_embedded_objects = false;
25,233✔
3273
        for_each_public_column([&](ColKey col_key) {
60,792✔
3274
            auto target_table_key = get_opposite_table_key(col_key);
60,792✔
3275
            if (target_table_key && is_link_type(col_key.get_type())) {
60,792✔
3276
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
11,538✔
3277
                if (target_table->is_embedded()) {
11,538✔
3278
                    m_has_any_embedded_objects = true;
9,405✔
3279
                    return IteratorControl::Stop; // early out
9,405✔
3280
                }
9,405✔
3281
            }
11,538✔
3282
            return IteratorControl::AdvanceToNext;
51,387✔
3283
        });
60,792✔
3284
    }
25,233✔
3285
    return *m_has_any_embedded_objects;
2,470,665✔
3286
}
2,470,665✔
3287

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

3294
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3295
{
47,592✔
3296
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
163,347✔
3297
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
155,589✔
3298
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
39,834✔
3299
        }
39,834✔
3300
    }
155,589✔
3301

3302
    return {};
7,758✔
3303
}
47,592✔
3304

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

3309
    if (!backlink_col_key) {
47,520✔
3310
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
7,758✔
3311
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
7,758✔
3312

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

3318
    return backlink_col_key;
47,520✔
3319
}
47,520✔
3320

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

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

3331
TableRef Table::get_opposite_table(ColKey col_key) const
3332
{
7,821,345✔
3333
    if (auto k = get_opposite_table_key(col_key)) {
7,821,345✔
3334
        return get_parent_group()->get_table(k);
7,759,503✔
3335
    }
7,759,503✔
3336
    return {};
61,842✔
3337
}
7,821,345✔
3338

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

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

3354
ref_type Table::typed_write(ref_type ref, _impl::ArrayWriterBase& out) const
3355
{
1,365,447✔
3356
    REALM_ASSERT(ref == m_top.get_mem().get_ref());
1,365,447✔
3357
    if (out.only_modified && m_alloc.is_read_only(ref))
1,365,447✔
3358
        return ref;
572,190✔
3359
    out.table = this;
793,257✔
3360
    // ignore ref from here, just use Tables own accessors
3361
    TempArray dest(m_top.size());
793,257✔
3362
    for (unsigned j = 0; j < m_top.size(); ++j) {
11,893,848✔
3363
        RefOrTagged rot = m_top.get_as_ref_or_tagged(j);
11,100,591✔
3364
        if (rot.is_tagged() || (rot.is_ref() && rot.get_as_ref() == 0)) {
11,100,591✔
3365
            dest.set(j, rot);
7,129,920✔
3366
        }
7,129,920✔
3367
        else {
3,970,671✔
3368
            ref_type new_ref;
3,970,671✔
3369
            if (j == 2) {
3,970,671✔
3370
                // only do type driven write for clustertree
3371
                new_ref = m_clusters.typed_write(rot.get_as_ref(), out);
793,266✔
3372
            }
793,266✔
3373
            else {
3,177,405✔
3374
                // rest is handled using untyped approach
3375
                Array a(m_alloc);
3,177,405✔
3376
                a.init_from_ref(rot.get_as_ref());
3,177,405✔
3377
                new_ref = a.write(out, true, out.only_modified, false);
3,177,405✔
3378
            }
3,177,405✔
3379
            dest.set_as_ref(j, new_ref);
3,970,671✔
3380
        }
3,970,671✔
3381
    }
11,100,591✔
3382
    return dest.write(out);
793,257✔
3383
}
1,365,447✔
3384

3385
void Table::typed_print(std::string prefix, ref_type ref) const
NEW
3386
{
×
NEW
3387
    REALM_ASSERT(ref == m_top.get_mem().get_ref());
×
NEW
3388
    std::cout << prefix << "Table with key = " << m_key << " " << NodeHeader::header_to_string(m_top.get_header())
×
NEW
3389
              << " {" << std::endl;
×
NEW
3390
    for (unsigned j = 0; j < m_top.size(); ++j) {
×
NEW
3391
        auto pref = prefix + "  " + to_string(j) + ":\t";
×
NEW
3392
        auto rot = m_top.get_as_ref_or_tagged(j);
×
NEW
3393
        if (rot.is_ref() && rot.get_as_ref()) {
×
NEW
3394
            if (j == 0) {
×
NEW
3395
                m_spec.typed_print(pref);
×
NEW
3396
            }
×
NEW
3397
            else if (j == 2) {
×
NEW
3398
                m_clusters.typed_print(pref);
×
NEW
3399
            }
×
NEW
3400
            else {
×
NEW
3401
                Array a(m_alloc);
×
NEW
3402
                a.init_from_ref(rot.get_as_ref());
×
NEW
3403
                std::cout << pref;
×
NEW
3404
                a.typed_print(pref);
×
NEW
3405
            }
×
NEW
3406
        }
×
NEW
3407
    }
×
NEW
3408
    std::cout << prefix << "}" << std::endl;
×
NEW
3409
}
×
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