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

realm / realm-core / jorgen.edelbo_334

01 Jul 2024 07:22AM UTC coverage: 90.829% (-0.04%) from 90.865%
jorgen.edelbo_334

Pull #7803

Evergreen

jedelbo
Merge branch 'next-major' into feature/string-compression
Pull Request #7803: Feature/string compression

102912 of 180568 branches covered (56.99%)

1141 of 1267 new or added lines in 33 files covered. (90.06%)

172 existing lines in 24 files now uncovered.

218291 of 240332 relevant lines covered (90.83%)

7818396.4 hits per line

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

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

19
#include <realm/table.hpp>
20

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

41
#include <stdexcept>
42

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

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

262

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

266
Replication* Table::g_dummy_replication = nullptr;
267

268
static inline bool needs_string_interner(ColKey col_key)
269
{
12,548,079✔
270
    return col_key.get_type() == col_type_String || col_key.get_type() == col_type_Mixed || col_key.is_dictionary();
12,548,079✔
271
}
12,548,079✔
272

273
bool TableVersions::operator==(const TableVersions& other) const
274
{
175,884✔
275
    if (size() != other.size())
175,884✔
276
        return false;
10,572✔
277
    size_t sz = size();
165,312✔
278
    for (size_t i = 0; i < sz; i++) {
298,500✔
279
        REALM_ASSERT_DEBUG(this->at(i).first == other.at(i).first);
166,686✔
280
        if (this->at(i).second != other.at(i).second)
166,686✔
281
            return false;
33,498✔
282
    }
166,686✔
283
    return true;
131,814✔
284
}
165,312✔
285

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

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

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

356
// -- Table ---------------------------------------------------------------------------------
357

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

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

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

410
    ColumnAttrMask attr;
507,762✔
411
    if (collection_type) {
507,762✔
412
        switch (*collection_type) {
150,348✔
413
            case CollectionType::List:
61,875✔
414
                attr.set(col_attr_List);
61,875✔
415
                break;
61,875✔
416
            case CollectionType::Set:
43,341✔
417
                attr.set(col_attr_Set);
43,341✔
418
                break;
43,341✔
419
            case CollectionType::Dictionary:
45,132✔
420
                attr.set(col_attr_Dictionary);
45,132✔
421
                break;
45,132✔
422
        }
150,348✔
423
    }
150,348✔
424
    if (nullable || type == type_Mixed)
507,762✔
425
        attr.set(col_attr_Nullable);
183,816✔
426
    ColKey col_key = generate_col_key(ColumnType(type), attr);
507,762✔
427

428
    Table* invalid_link = nullptr;
507,762✔
429
    return do_insert_column(col_key, type, name, invalid_link, key_type); // Throws
507,762✔
430
}
507,762✔
431

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

445
    m_has_any_embedded_objects.reset();
74,328✔
446

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

470
    return do_insert_column(col_key, data_type, name, &target, key_type); // Throws
74,328✔
471
}
74,328✔
472

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

479
    do {
23,946✔
480
        cascade_state.send_notifications();
23,946✔
481

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

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

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

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

525
void Table::remove_columns()
526
{
2,436✔
527
    for (size_t i = get_column_count(); i > 0; --i) {
6,969✔
528
        ColKey col_key = spec_ndx2colkey(i - 1);
4,533✔
529
        remove_column(col_key);
4,533✔
530
    }
4,533✔
531
}
2,436✔
532

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

537
    if (Replication* repl = get_repl())
5,208✔
538
        repl->erase_column(this, col_key); // Throws
4,221✔
539

540
    if (col_key == m_primary_key_col) {
5,208✔
541
        do_set_primary_key_column(ColKey());
888✔
542
    }
888✔
543
    else {
4,320✔
544
        REALM_ASSERT_RELEASE(m_primary_key_col.get_index().val != col_key.get_index().val);
4,320✔
545
    }
4,320✔
546

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

551

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

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

559
    bump_content_version();
87✔
560
    bump_storage_version();
87✔
561

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

566

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

581

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

591
    m_spec.init_from_parent();
2,217,939✔
592

593
    while (m_top.size() <= top_position_for_pk_col) {
2,217,939✔
594
        m_top.add(0);
×
595
    }
×
596

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

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

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

645
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
2,217,939✔
646
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
2,217,939✔
647

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

657
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
2,217,939✔
658
        // Tombstones exists
659
        if (!m_tombstones) {
38,214✔
660
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
32,415✔
661
        }
32,415✔
662
        m_tombstones->init_from_parent();
38,214✔
663
    }
38,214✔
664
    else {
2,179,725✔
665
        m_tombstones = nullptr;
2,179,725✔
666
    }
2,179,725✔
667
    if (m_top.size() > top_position_for_interners && m_top.get_as_ref(top_position_for_interners)) {
2,217,939✔
668
        // Interner data exist
669
        m_interner_data.init_from_parent();
1,931,091✔
670
    }
1,931,091✔
671
    else {
286,848✔
672
        REALM_ASSERT_DEBUG(!m_interner_data.is_attached());
286,848✔
673
    }
286,848✔
674
    refresh_string_interners(is_writable);
2,217,939✔
675
    m_cookie = cookie_initialized;
2,217,939✔
676
}
2,217,939✔
677

678

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

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

686
    if (target_table) {
582,093✔
687
        auto backlink_col_key = target_table->do_insert_root_column(ColKey{}, col_type_BackLink, ""); // Throws
74,322✔
688
        target_table->check_column(backlink_col_key);
74,322✔
689

690
        set_opposite_column(col_key, target_table->get_key(), backlink_col_key);
74,322✔
691
        target_table->set_opposite_column(backlink_col_key, get_key(), col_key);
74,322✔
692
    }
74,322✔
693

694
    if (Replication* repl = get_repl())
582,093✔
695
        repl->insert_column(this, col_key, type, name, target_table); // Throws
564,009✔
696

697
    return col_key;
582,093✔
698
}
582,093✔
699

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

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

712
    table->traverse_clusters(f);
113,013✔
713
}
113,013✔
714

715

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

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

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

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

735
    if (type == type_Int) {
113,037✔
736
        if (is_nullable(col_key)) {
49,461✔
737
            do_bulk_insert_index<Optional<int64_t>>(this, index, col_key, get_alloc());
13,788✔
738
        }
13,788✔
739
        else {
35,673✔
740
            do_bulk_insert_index<int64_t>(this, index, col_key, get_alloc());
35,673✔
741
        }
35,673✔
742
    }
49,461✔
743
    else if (type == type_Bool) {
63,576✔
744
        if (is_nullable(col_key)) {
48✔
745
            do_bulk_insert_index<Optional<bool>>(this, index, col_key, get_alloc());
24✔
746
        }
24✔
747
        else {
24✔
748
            do_bulk_insert_index<bool>(this, index, col_key, get_alloc());
24✔
749
        }
24✔
750
    }
48✔
751
    else if (type == type_String) {
63,528✔
752
        if (col_key.is_list()) {
18,720✔
753
            do_bulk_insert_index_list(this, index, col_key, get_alloc());
24✔
754
        }
24✔
755
        else {
18,696✔
756
            do_bulk_insert_index<StringData>(this, index, col_key, get_alloc());
18,696✔
757
        }
18,696✔
758
    }
18,720✔
759
    else if (type == type_Timestamp) {
44,808✔
760
        do_bulk_insert_index<Timestamp>(this, index, col_key, get_alloc());
96✔
761
    }
96✔
762
    else if (type == type_ObjectId) {
44,712✔
763
        if (is_nullable(col_key)) {
43,119✔
764
            do_bulk_insert_index<Optional<ObjectId>>(this, index, col_key, get_alloc());
984✔
765
        }
984✔
766
        else {
42,135✔
767
            do_bulk_insert_index<ObjectId>(this, index, col_key, get_alloc());
42,135✔
768
        }
42,135✔
769
    }
43,119✔
770
    else if (type == type_UUID) {
1,593✔
771
        if (is_nullable(col_key)) {
684✔
772
            do_bulk_insert_index<Optional<UUID>>(this, index, col_key, get_alloc());
516✔
773
        }
516✔
774
        else {
168✔
775
            do_bulk_insert_index<UUID>(this, index, col_key, get_alloc());
168✔
776
        }
168✔
777
    }
684✔
778
    else if (type == type_Mixed) {
909✔
779
        do_bulk_insert_index<Mixed>(this, index, col_key, get_alloc());
906✔
780
    }
906✔
781
    else {
3✔
782
        REALM_ASSERT_RELEASE(false && "Data type does not support search index");
3✔
783
    }
3✔
784
}
113,037✔
785

786
void Table::erase_from_search_indexes(ObjKey key)
787
{
5,000,460✔
788
    // Tombstones do not use index - will crash if we try to erase values
789
    if (!key.is_unresolved()) {
5,000,460✔
790
        for (auto&& index : m_index_accessors) {
6,669,597✔
791
            if (index) {
6,669,597✔
792
                index->erase(key);
255,225✔
793
            }
255,225✔
794
        }
6,669,597✔
795
    }
4,989,483✔
796
}
5,000,460✔
797

798
void Table::update_indexes(ObjKey key, const FieldValues& values)
799
{
25,265,946✔
800
    // Tombstones do not use index - will crash if we try to insert values
801
    if (key.is_unresolved()) {
25,265,946✔
802
        return;
12,444✔
803
    }
12,444✔
804

805
    auto sz = m_index_accessors.size();
25,253,502✔
806
    // values are sorted by column index - there may be values missing
807
    auto value = values.begin();
25,253,502✔
808
    for (size_t column_ndx = 0; column_ndx < sz; column_ndx++) {
62,463,195✔
809
        // Check if initial value is provided
810
        Mixed init_value;
37,209,747✔
811
        if (value != values.end() && value->col_key.get_index().val == column_ndx) {
37,209,747✔
812
            // Value for this column is provided
813
            init_value = value->value;
494,247✔
814
            ++value;
494,247✔
815
        }
494,247✔
816

817
        if (auto&& index = m_index_accessors[column_ndx]) {
37,209,747✔
818
            // There is an index for this column
819
            auto col_key = m_leaf_ndx2colkey[column_ndx];
1,069,293✔
820
            if (col_key.is_collection())
1,069,293✔
821
                continue;
102✔
822
            auto type = col_key.get_type();
1,069,191✔
823
            auto attr = col_key.get_attrs();
1,069,191✔
824
            bool nullable = attr.test(col_attr_Nullable);
1,069,191✔
825
            switch (type) {
1,069,191✔
826
                case col_type_Int:
480,492✔
827
                    if (init_value.is_null()) {
480,492✔
828
                        index->insert(key, ArrayIntNull::default_value(nullable));
166,746✔
829
                    }
166,746✔
830
                    else {
313,746✔
831
                        index->insert(key, init_value.get<int64_t>());
313,746✔
832
                    }
313,746✔
833
                    break;
480,492✔
834
                case col_type_Bool:
6,036✔
835
                    if (init_value.is_null()) {
6,036✔
836
                        index->insert(key, ArrayBoolNull::default_value(nullable));
6,036✔
837
                    }
6,036✔
838
                    else {
×
839
                        index->insert(key, init_value.get<bool>());
×
840
                    }
×
841
                    break;
6,036✔
842
                case col_type_String:
460,260✔
843
                    if (init_value.is_null()) {
460,260✔
844
                        index->insert(key, ArrayString::default_value(nullable));
432,489✔
845
                    }
432,489✔
846
                    else {
27,771✔
847
                        index->insert(key, init_value.get<String>());
27,771✔
848
                    }
27,771✔
849
                    break;
460,260✔
850
                case col_type_Timestamp:
7,485✔
851
                    if (init_value.is_null()) {
7,485✔
852
                        index->insert(key, ArrayTimestamp::default_value(nullable));
7,485✔
853
                    }
7,485✔
854
                    else {
×
855
                        index->insert(key, init_value.get<Timestamp>());
×
856
                    }
×
857
                    break;
7,485✔
858
                case col_type_ObjectId:
93,087✔
859
                    if (init_value.is_null()) {
93,087✔
860
                        index->insert(key, ArrayObjectIdNull::default_value(nullable));
7,320✔
861
                    }
7,320✔
862
                    else {
85,767✔
863
                        index->insert(key, init_value.get<ObjectId>());
85,767✔
864
                    }
85,767✔
865
                    break;
93,087✔
866
                case col_type_Mixed:
2,286✔
867
                    index->insert(key, init_value);
2,286✔
868
                    break;
2,286✔
869
                case col_type_UUID:
19,542✔
870
                    if (init_value.is_null()) {
19,542✔
871
                        index->insert(key, ArrayUUIDNull::default_value(nullable));
7,338✔
872
                    }
7,338✔
873
                    else {
12,204✔
874
                        index->insert(key, init_value.get<UUID>());
12,204✔
875
                    }
12,204✔
876
                    break;
19,542✔
877
                default:
✔
878
                    REALM_UNREACHABLE();
879
            }
1,069,191✔
880
        }
1,069,191✔
881
    }
37,209,747✔
882
}
25,253,502✔
883

884
void Table::clear_indexes()
885
{
6,030✔
886
    for (auto&& index : m_index_accessors) {
57,960✔
887
        if (index) {
57,960✔
888
            index->clear();
4,761✔
889
        }
4,761✔
890
    }
57,960✔
891
}
6,030✔
892

893
void Table::do_add_search_index(ColKey col_key, IndexType type)
894
{
113,229✔
895
    size_t column_ndx = col_key.get_index().val;
113,229✔
896

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

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

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

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

921
    m_index_refs.set(column_ndx, index->get_ref()); // Throws
113,037✔
922

923
    populate_search_index(col_key);
113,037✔
924
}
113,037✔
925

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

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

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

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

962
    do_add_search_index(col_key, type);
3,897✔
963

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

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

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

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

984
    m_index_refs.set(column_ndx.val, 0);
1,245✔
985

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

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

1004
bool Table::is_enumerated(ColKey col_key) const noexcept
1005
{
52,152✔
1006
    size_t col_ndx = colkey2spec_ndx(col_key);
52,152✔
1007
    return m_spec.is_string_enum_type(col_ndx);
52,152✔
1008
}
52,152✔
1009

1010
size_t Table::get_num_unique_values(ColKey col_key) const
1011
{
138✔
1012
    if (!is_enumerated(col_key))
138✔
1013
        return 0;
84✔
1014

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

1020
    return col.size();
54✔
1021
}
138✔
1022

1023

1024
void Table::erase_root_column(ColKey col_key)
1025
{
5,484✔
1026
    ColumnType col_type = col_key.get_type();
5,484✔
1027
    if (is_link_type(col_type)) {
5,484✔
1028
        auto target_table = get_opposite_table(col_key);
606✔
1029
        auto target_column = get_opposite_column(col_key);
606✔
1030
        target_table->do_erase_root_column(target_column);
606✔
1031
    }
606✔
1032
    do_erase_root_column(col_key); // Throws
5,484✔
1033
}
5,484✔
1034

1035

1036
ColKey Table::do_insert_root_column(ColKey col_key, ColumnType type, StringData name, DataType key_type)
1037
{
773,481✔
1038
    // if col_key specifies a key, it must be unused
1039
    REALM_ASSERT(!col_key || !valid_column(col_key));
773,481✔
1040

1041
    // locate insertion point: ordinary columns must come before backlink columns
1042
    size_t spec_ndx = (type == col_type_BackLink) ? m_spec.get_column_count() : m_spec.get_public_column_count();
773,481✔
1043

1044
    if (!col_key) {
773,481✔
1045
        col_key = generate_col_key(type, {});
82,080✔
1046
    }
82,080✔
1047

1048
    m_spec.insert_column(spec_ndx, col_key, type, name, col_key.get_attrs().m_value); // Throws
773,481✔
1049
    if (col_key.is_dictionary()) {
773,481✔
1050
        m_spec.set_dictionary_key_type(spec_ndx, key_type);
56,496✔
1051
    }
56,496✔
1052
    auto col_ndx = col_key.get_index().val;
773,481✔
1053
    build_column_mapping();
773,481✔
1054
    REALM_ASSERT(col_ndx <= m_index_refs.size());
773,481✔
1055
    if (col_ndx == m_index_refs.size()) {
773,481✔
1056
        m_index_refs.insert(col_ndx, 0);
773,241✔
1057
    }
773,241✔
1058
    else {
240✔
1059
        m_index_refs.set(col_ndx, 0);
240✔
1060
    }
240✔
1061
    REALM_ASSERT(col_ndx <= m_opposite_table.size());
773,481✔
1062
    if (col_ndx == m_opposite_table.size()) {
773,481✔
1063
        // m_opposite_table and m_opposite_column are always resized together!
1064
        m_opposite_table.insert(col_ndx, TableKey().value);
773,235✔
1065
        m_opposite_column.insert(col_ndx, ColKey().value);
773,235✔
1066
    }
773,235✔
1067
    else {
246✔
1068
        m_opposite_table.set(col_ndx, TableKey().value);
246✔
1069
        m_opposite_column.set(col_ndx, ColKey().value);
246✔
1070
    }
246✔
1071
    refresh_index_accessors();
773,481✔
1072
    m_clusters.insert_column(col_key);
773,481✔
1073
    if (m_tombstones) {
773,481✔
1074
        m_tombstones->insert_column(col_key);
303✔
1075
    }
303✔
1076
    if (needs_string_interner(col_key)) {
773,481✔
1077
        // create string interners internal rep as well as data area
1078
        REALM_ASSERT_DEBUG(m_interner_data.is_attached());
186,000✔
1079
        while (col_ndx >= m_string_interners.size()) {
624,747✔
1080
            m_string_interners.push_back({});
438,747✔
1081
        }
438,747✔
1082
        while (col_ndx >= m_interner_data.size()) {
624,747✔
1083
            m_interner_data.add(0);
438,747✔
1084
        }
438,747✔
1085
        REALM_ASSERT(!m_string_interners[col_ndx]);
186,000✔
1086
        m_string_interners[col_ndx] = std::make_unique<StringInterner>(m_alloc, m_interner_data, col_key, true);
186,000✔
1087
    }
186,000✔
1088
    bump_storage_version();
773,481✔
1089

1090
    return col_key;
773,481✔
1091
}
773,481✔
1092

1093

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1276

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

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

1297

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

1313

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

1324

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

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

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

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

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

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

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

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

1434

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

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

1447

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

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

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

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

1513
    REALM_ASSERT(top.size() == top_array_size);
282,933✔
1514

1515
    return top.get_ref();
282,933✔
1516
}
282,933✔
1517

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

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

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

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

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

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

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

1586

1587
Group* Table::get_parent_group() const noexcept
1588
{
62,688,762✔
1589
    if (!m_top.is_attached())
62,688,762✔
1590
        return 0;                             // Subtable with shared descriptor
×
1591
    ArrayParent* parent = m_top.get_parent(); // ArrayParent guaranteed to be Table::Parent
62,688,762✔
1592
    if (!parent)
62,688,762✔
1593
        return 0; // Free-standing table
26,500,347✔
1594

1595
    return static_cast<Group*>(parent);
36,188,415✔
1596
}
62,688,762✔
1597

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

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

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

1621
    return sn;
20,915,415✔
1622
}
20,915,415✔
1623

1624
void Table::set_col_key_sequence_number(uint64_t seq)
1625
{
24✔
1626
    m_top.set(top_position_for_column_key, RefOrTagged::make_tagged(seq));
24✔
1627
}
24✔
1628

1629
TableRef Table::get_link_target(ColKey col_key) noexcept
1630
{
274,491✔
1631
    return get_opposite_table(col_key);
274,491✔
1632
}
274,491✔
1633

1634
// count ----------------------------------------------
1635

1636
size_t Table::count_int(ColKey col_key, int64_t value) const
1637
{
12,006✔
1638
    if (auto index = this->get_search_index(col_key)) {
12,006✔
1639
        return index->count(value);
12,000✔
1640
    }
12,000✔
1641

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

1669
    traverse_clusters(f);
18✔
1670

1671
    return cnt;
18✔
1672
}
18✔
1673
size_t Table::count_string(ColKey col_key, StringData value) const
1674
{
1,476✔
1675
    if (auto index = this->get_search_index(col_key)) {
1,476✔
1676
        return index->count(value);
732✔
1677
    }
732✔
1678
    return where().equal(col_key, value).count();
744✔
1679
}
1,476✔
1680

1681
template <typename T>
1682
void Table::aggregate(QueryStateBase& st, ColKey column_key) const
1683
{
35,112✔
1684
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
35,112✔
1685
    LeafType leaf(get_alloc());
35,112✔
1686

1687
    auto f = [&leaf, column_key, &st](const Cluster* cluster) {
35,130✔
1688
        // direct aggregate on the leaf
1689
        cluster->init_leaf(column_key, &leaf);
35,130✔
1690
        st.m_key_offset = cluster->get_offset();
35,130✔
1691
        st.m_key_values = cluster->get_key_array();
35,130✔
1692
        st.set_payload_column(&leaf);
35,130✔
1693
        bool cont = true;
35,130✔
1694
        size_t sz = leaf.size();
35,130✔
1695
        for (size_t local_index = 0; cont && local_index < sz; local_index++) {
139,536✔
1696
            cont = st.match(local_index);
104,406✔
1697
        }
104,406✔
1698
        return IteratorControl::AdvanceToNext;
35,130✔
1699
    };
35,130✔
1700

1701
    traverse_clusters(f);
35,112✔
1702
}
35,112✔
1703

1704
// This template is also used by the query engine
1705
template void Table::aggregate<int64_t>(QueryStateBase&, ColKey) const;
1706
template void Table::aggregate<std::optional<int64_t>>(QueryStateBase&, ColKey) const;
1707
template void Table::aggregate<float>(QueryStateBase&, ColKey) const;
1708
template void Table::aggregate<double>(QueryStateBase&, ColKey) const;
1709
template void Table::aggregate<Decimal128>(QueryStateBase&, ColKey) const;
1710
template void Table::aggregate<Mixed>(QueryStateBase&, ColKey) const;
1711
template void Table::aggregate<Timestamp>(QueryStateBase&, ColKey) const;
1712

1713
std::optional<Mixed> Table::sum(ColKey col_key) const
1714
{
756✔
1715
    return AggregateHelper<Table>::sum(*this, *this, col_key);
756✔
1716
}
756✔
1717

1718
std::optional<Mixed> Table::avg(ColKey col_key, size_t* value_count) const
1719
{
822✔
1720
    return AggregateHelper<Table>::avg(*this, *this, col_key, value_count);
822✔
1721
}
822✔
1722

1723
std::optional<Mixed> Table::min(ColKey col_key, ObjKey* return_ndx) const
1724
{
1,170✔
1725
    return AggregateHelper<Table>::min(*this, *this, col_key, return_ndx);
1,170✔
1726
}
1,170✔
1727

1728
std::optional<Mixed> Table::max(ColKey col_key, ObjKey* return_ndx) const
1729
{
28,836✔
1730
    return AggregateHelper<Table>::max(*this, *this, col_key, return_ndx);
28,836✔
1731
}
28,836✔
1732

1733

1734
SearchIndex* Table::get_search_index(ColKey col) const noexcept
1735
{
32,605,116✔
1736
    check_column(col);
32,605,116✔
1737
    return m_index_accessors[col.get_index().val].get();
32,605,116✔
1738
}
32,605,116✔
1739

1740
StringIndex* Table::get_string_index(ColKey col) const noexcept
1741
{
690,426✔
1742
    check_column(col);
690,426✔
1743
    return dynamic_cast<StringIndex*>(m_index_accessors[col.get_index().val].get());
690,426✔
1744
}
690,426✔
1745

1746
template <class T>
1747
ObjKey Table::find_first(ColKey col_key, T value) const
1748
{
35,106✔
1749
    check_column(col_key);
35,106✔
1750

1751
    if (!col_key.is_nullable() && value_is_null(value)) {
35,106!
1752
        return {}; // this is a precaution/optimization
6✔
1753
    }
6✔
1754
    // You cannot call GetIndexData on ObjKey
1755
    if constexpr (!std::is_same_v<T, ObjKey>) {
35,100✔
1756
        if (SearchIndex* index = get_search_index(col_key)) {
35,082✔
1757
            return index->find_first(value);
27,234✔
1758
        }
27,234✔
1759
        if (col_key == m_primary_key_col) {
7,848✔
1760
            return find_primary_key(value);
×
1761
        }
×
1762
    }
7,848✔
1763

1764
    ObjKey key;
7,848✔
1765
    using LeafType = typename ColumnTypeTraits<T>::cluster_leaf_type;
21,456✔
1766
    LeafType leaf(get_alloc());
21,456✔
1767

1768
    auto f = [&key, &col_key, &value, &leaf](const Cluster* cluster) {
22,197✔
1769
        cluster->init_leaf(col_key, &leaf);
9,330✔
1770
        size_t row = leaf.find_first(value, 0, cluster->node_size());
9,330✔
1771
        if (row != realm::npos) {
9,330✔
1772
            key = cluster->get_real_key(row);
7,692✔
1773
            return IteratorControl::Stop;
7,692✔
1774
        }
7,692✔
1775
        return IteratorControl::AdvanceToNext;
1,638✔
1776
    };
9,330✔
1777

1778
    traverse_clusters(f);
21,456✔
1779

1780
    return key;
21,456✔
1781
}
35,094✔
1782

1783
namespace realm {
1784

1785
template <>
1786
ObjKey Table::find_first(ColKey col_key, util::Optional<float> value) const
1787
{
×
1788
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1789
}
×
1790

1791
template <>
1792
ObjKey Table::find_first(ColKey col_key, util::Optional<double> value) const
1793
{
×
1794
    return value ? find_first(col_key, *value) : find_first_null(col_key);
×
1795
}
×
1796

1797
template <>
1798
ObjKey Table::find_first(ColKey col_key, null) const
1799
{
×
1800
    return find_first_null(col_key);
×
1801
}
×
1802
} // namespace realm
1803

1804
// Explicitly instantiate the generic case of the template for the types we care about.
1805
template ObjKey Table::find_first(ColKey col_key, bool) const;
1806
template ObjKey Table::find_first(ColKey col_key, int64_t) const;
1807
template ObjKey Table::find_first(ColKey col_key, float) const;
1808
template ObjKey Table::find_first(ColKey col_key, double) const;
1809
template ObjKey Table::find_first(ColKey col_key, Decimal128) const;
1810
template ObjKey Table::find_first(ColKey col_key, ObjectId) const;
1811
template ObjKey Table::find_first(ColKey col_key, ObjKey) const;
1812
template ObjKey Table::find_first(ColKey col_key, util::Optional<bool>) const;
1813
template ObjKey Table::find_first(ColKey col_key, util::Optional<int64_t>) const;
1814
template ObjKey Table::find_first(ColKey col_key, StringData) const;
1815
template ObjKey Table::find_first(ColKey col_key, BinaryData) const;
1816
template ObjKey Table::find_first(ColKey col_key, Mixed) const;
1817
template ObjKey Table::find_first(ColKey col_key, UUID) const;
1818
template ObjKey Table::find_first(ColKey col_key, util::Optional<ObjectId>) const;
1819
template ObjKey Table::find_first(ColKey col_key, util::Optional<UUID>) const;
1820

1821
ObjKey Table::find_first_int(ColKey col_key, int64_t value) const
1822
{
7,698✔
1823
    if (is_nullable(col_key))
7,698✔
1824
        return find_first<util::Optional<int64_t>>(col_key, value);
36✔
1825
    else
7,662✔
1826
        return find_first<int64_t>(col_key, value);
7,662✔
1827
}
7,698✔
1828

1829
ObjKey Table::find_first_bool(ColKey col_key, bool value) const
1830
{
78✔
1831
    if (is_nullable(col_key))
78✔
1832
        return find_first<util::Optional<bool>>(col_key, value);
36✔
1833
    else
42✔
1834
        return find_first<bool>(col_key, value);
42✔
1835
}
78✔
1836

1837
ObjKey Table::find_first_timestamp(ColKey col_key, Timestamp value) const
1838
{
108✔
1839
    return find_first(col_key, value);
108✔
1840
}
108✔
1841

1842
ObjKey Table::find_first_object_id(ColKey col_key, ObjectId value) const
1843
{
6✔
1844
    return find_first(col_key, value);
6✔
1845
}
6✔
1846

1847
ObjKey Table::find_first_float(ColKey col_key, float value) const
1848
{
12✔
1849
    return find_first<Float>(col_key, value);
12✔
1850
}
12✔
1851

1852
ObjKey Table::find_first_double(ColKey col_key, double value) const
1853
{
12✔
1854
    return find_first<Double>(col_key, value);
12✔
1855
}
12✔
1856

1857
ObjKey Table::find_first_decimal(ColKey col_key, Decimal128 value) const
1858
{
×
1859
    return find_first<Decimal128>(col_key, value);
×
1860
}
×
1861

1862
ObjKey Table::find_first_string(ColKey col_key, StringData value) const
1863
{
11,472✔
1864
    return find_first<StringData>(col_key, value);
11,472✔
1865
}
11,472✔
1866

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

1872
ObjKey Table::find_first_null(ColKey col_key) const
1873
{
114✔
1874
    return where().equal(col_key, null{}).find();
114✔
1875
}
114✔
1876

1877
ObjKey Table::find_first_uuid(ColKey col_key, UUID value) const
1878
{
18✔
1879
    return find_first(col_key, value);
18✔
1880
}
18✔
1881

1882
template <class T>
1883
TableView Table::find_all(ColKey col_key, T value)
1884
{
114✔
1885
    return where().equal(col_key, value).find_all();
114✔
1886
}
114✔
1887

1888
TableView Table::find_all_int(ColKey col_key, int64_t value)
1889
{
102✔
1890
    return find_all<int64_t>(col_key, value);
102✔
1891
}
102✔
1892

1893
TableView Table::find_all_int(ColKey col_key, int64_t value) const
1894
{
6✔
1895
    return const_cast<Table*>(this)->find_all<int64_t>(col_key, value);
6✔
1896
}
6✔
1897

1898
TableView Table::find_all_bool(ColKey col_key, bool value)
1899
{
×
1900
    return find_all<bool>(col_key, value);
×
1901
}
×
1902

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

1908

1909
TableView Table::find_all_float(ColKey col_key, float value)
1910
{
×
1911
    return find_all<float>(col_key, value);
×
1912
}
×
1913

1914
TableView Table::find_all_float(ColKey col_key, float value) const
1915
{
×
1916
    return const_cast<Table*>(this)->find_all<float>(col_key, value);
×
1917
}
×
1918

1919
TableView Table::find_all_double(ColKey col_key, double value)
1920
{
6✔
1921
    return find_all<double>(col_key, value);
6✔
1922
}
6✔
1923

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

1929
TableView Table::find_all_string(ColKey col_key, StringData value)
1930
{
282✔
1931
    return where().equal(col_key, value).find_all();
282✔
1932
}
282✔
1933

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

1939
TableView Table::find_all_binary(ColKey, BinaryData)
1940
{
×
1941
    throw Exception(ErrorCodes::IllegalOperation, "Table::find_all_binary not supported");
×
1942
}
×
1943

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

1949
TableView Table::find_all_null(ColKey col_key)
1950
{
×
1951
    return where().equal(col_key, null{}).find_all();
×
1952
}
×
1953

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

1959
TableView Table::find_all_fulltext(ColKey col_key, StringData terms) const
1960
{
6✔
1961
    return where().fulltext(col_key, terms).find_all();
6✔
1962
}
6✔
1963

1964
TableView Table::get_sorted_view(ColKey col_key, bool ascending)
1965
{
72✔
1966
    TableView tv = where().find_all();
72✔
1967
    tv.sort(col_key, ascending);
72✔
1968
    return tv;
72✔
1969
}
72✔
1970

1971
TableView Table::get_sorted_view(ColKey col_key, bool ascending) const
1972
{
6✔
1973
    return const_cast<Table*>(this)->get_sorted_view(col_key, ascending);
6✔
1974
}
6✔
1975

1976
TableView Table::get_sorted_view(SortDescriptor order)
1977
{
126✔
1978
    TableView tv = where().find_all();
126✔
1979
    tv.sort(std::move(order));
126✔
1980
    return tv;
126✔
1981
}
126✔
1982

1983
TableView Table::get_sorted_view(SortDescriptor order) const
1984
{
×
1985
    return const_cast<Table*>(this)->get_sorted_view(std::move(order));
×
1986
}
×
1987

1988
util::Logger* Table::get_logger() const noexcept
1989
{
1,872,891✔
1990
    return *m_repl ? (*m_repl)->get_logger() : nullptr;
1,872,891✔
1991
}
1,872,891✔
1992

1993
// Called after a commit. Table will effectively contain the same as before,
1994
// but now with new refs from the file
1995
void Table::update_from_parent() noexcept
1996
{
1,088,343✔
1997
    // There is no top for sub-tables sharing spec
1998
    if (m_top.is_attached()) {
1,088,343✔
1999
        m_top.update_from_parent();
1,088,343✔
2000
        m_spec.update_from_parent();
1,088,343✔
2001
        m_clusters.update_from_parent();
1,088,343✔
2002
        m_index_refs.update_from_parent();
1,088,343✔
2003
        for (auto&& index : m_index_accessors) {
2,987,274✔
2004
            if (index != nullptr) {
2,987,274✔
2005
                index->update_from_parent();
462,108✔
2006
            }
462,108✔
2007
        }
2,987,274✔
2008

2009
        m_opposite_table.update_from_parent();
1,088,343✔
2010
        m_opposite_column.update_from_parent();
1,088,343✔
2011
        if (m_top.size() > top_position_for_flags) {
1,088,343✔
2012
            uint64_t flags = m_top.get_as_ref_or_tagged(top_position_for_flags).get_as_int();
1,088,337✔
2013
            m_table_type = Type(flags & table_type_mask);
1,088,337✔
2014
        }
1,088,337✔
2015
        else {
6✔
2016
            m_table_type = Type::TopLevel;
6✔
2017
        }
6✔
2018
        if (m_tombstones)
1,088,343✔
2019
            m_tombstones->update_from_parent();
4,461✔
2020

2021
        refresh_content_version();
1,088,343✔
2022
        m_has_any_embedded_objects.reset();
1,088,343✔
2023
        if (m_top.size() > top_position_for_interners) {
1,088,343✔
2024
            if (m_top.get_as_ref(top_position_for_interners))
1,088,334✔
2025
                m_interner_data.update_from_parent();
1,088,331✔
2026
            else
3✔
2027
                m_interner_data.detach();
3✔
2028
        }
1,088,334✔
2029
        refresh_string_interners(false);
1,088,343✔
2030
    }
1,088,343✔
2031
    m_alloc.bump_storage_version();
1,088,343✔
2032
}
1,088,343✔
2033

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

2090
bool Table::operator==(const Table& t) const
2091
{
162✔
2092
    if (size() != t.size()) {
162✔
2093
        return false;
12✔
2094
    }
12✔
2095
    // Check columns
2096
    for (auto ck : this->get_column_keys()) {
558✔
2097
        auto name = get_column_name(ck);
558✔
2098
        auto other_ck = t.get_column_key(name);
558✔
2099
        auto attrs = ck.get_attrs();
558✔
2100
        if (search_index_type(ck) != t.search_index_type(other_ck))
558✔
2101
            return false;
×
2102

2103
        if (!other_ck || other_ck.get_attrs() != attrs) {
558✔
2104
            return false;
×
2105
        }
×
2106
    }
558✔
2107
    auto pk_col = get_primary_key_column();
150✔
2108
    for (auto o : *this) {
2,922✔
2109
        Obj other_o;
2,922✔
2110
        if (pk_col) {
2,922✔
2111
            auto pk = o.get_any(pk_col);
90✔
2112
            other_o = t.get_object_with_primary_key(pk);
90✔
2113
        }
90✔
2114
        else {
2,832✔
2115
            other_o = t.get_object(o.get_key());
2,832✔
2116
        }
2,832✔
2117
        if (!(other_o && o == other_o))
2,922✔
2118
            return false;
18✔
2119
    }
2,922✔
2120

2121
    return true;
132✔
2122
}
150✔
2123

2124

2125
void Table::flush_for_commit()
2126
{
1,206,972✔
2127
    if (m_top.is_attached() && m_top.size() >= top_position_for_version) {
1,206,975✔
2128
        if (!m_top.is_read_only()) {
1,206,975✔
2129
            ++m_in_file_version_at_transaction_boundary;
792,912✔
2130
            auto rot_version = RefOrTagged::make_tagged(m_in_file_version_at_transaction_boundary);
792,912✔
2131
            m_top.set(top_position_for_version, rot_version);
792,912✔
2132
        }
792,912✔
2133
    }
1,206,975✔
2134
}
1,206,972✔
2135

2136
void Table::refresh_content_version()
2137
{
1,476,534✔
2138
    REALM_ASSERT(m_top.is_attached());
1,476,534✔
2139
    if (m_top.size() >= top_position_for_version) {
1,476,684✔
2140
        // we have versioning info in the file. Use this to conditionally
2141
        // bump the version counter:
2142
        auto rot_version = m_top.get_as_ref_or_tagged(top_position_for_version);
1,476,639✔
2143
        REALM_ASSERT(rot_version.is_tagged());
1,476,639✔
2144
        if (m_in_file_version_at_transaction_boundary != rot_version.get_as_int()) {
1,476,639✔
2145
            m_in_file_version_at_transaction_boundary = rot_version.get_as_int();
221,607✔
2146
            bump_content_version();
221,607✔
2147
        }
221,607✔
2148
    }
1,476,639✔
2149
    else {
2,147,483,692✔
2150
        // assume the worst:
2151
        bump_content_version();
2,147,483,692✔
2152
    }
2,147,483,692✔
2153
}
1,476,534✔
2154

2155

2156
// Called when Group is moved to another version - either a rollback or an advance.
2157
// The content of the table is potentially different, so make no assumptions.
2158
void Table::refresh_accessor_tree(bool writable)
2159
{
388,569✔
2160
    REALM_ASSERT(m_cookie == cookie_initialized);
388,569✔
2161
    REALM_ASSERT(m_top.is_attached());
388,569✔
2162
    m_top.init_from_parent();
388,569✔
2163
    m_spec.init_from_parent();
388,569✔
2164
    REALM_ASSERT(m_top.size() > top_position_for_pk_col);
388,569✔
2165
    m_clusters.init_from_parent();
388,569✔
2166
    m_index_refs.init_from_parent();
388,569✔
2167
    m_opposite_table.init_from_parent();
388,569✔
2168
    m_opposite_column.init_from_parent();
388,569✔
2169
    auto rot_pk_key = m_top.get_as_ref_or_tagged(top_position_for_pk_col);
388,569✔
2170
    m_primary_key_col = rot_pk_key.is_tagged() ? ColKey(rot_pk_key.get_as_int()) : ColKey();
388,569✔
2171
    if (m_top.size() > top_position_for_flags) {
388,713✔
2172
        auto rot_flags = m_top.get_as_ref_or_tagged(top_position_for_flags);
388,686✔
2173
        m_table_type = Type(rot_flags.get_as_int() & table_type_mask);
388,686✔
2174
    }
388,686✔
2175
    else {
2,147,483,674✔
2176
        m_table_type = Type::TopLevel;
2,147,483,674✔
2177
    }
2,147,483,674✔
2178
    if (m_top.size() > top_position_for_tombstones && m_top.get_as_ref(top_position_for_tombstones)) {
388,569✔
2179
        // Tombstones exists
2180
        if (!m_tombstones) {
2,106✔
2181
            m_tombstones = std::make_unique<ClusterTree>(this, m_alloc, size_t(top_position_for_tombstones));
324✔
2182
        }
324✔
2183
        m_tombstones->init_from_parent();
2,106✔
2184
    }
2,106✔
2185
    else {
386,463✔
2186
        m_tombstones = nullptr;
386,463✔
2187
    }
386,463✔
2188
    if (writable) {
388,569✔
2189
        while (m_top.size() < top_position_for_interners)
89,691✔
NEW
2190
            m_top.add(0);
×
2191
    }
89,691✔
2192
    if (m_top.size() > top_position_for_interners) {
388,980✔
2193
        if (m_top.get_as_ref(top_position_for_interners))
388,938✔
2194
            m_interner_data.init_from_parent();
388,578✔
2195
        else
360✔
2196
            m_interner_data.detach();
360✔
2197
    }
388,938✔
2198
    refresh_content_version();
388,569✔
2199
    bump_storage_version();
388,569✔
2200
    build_column_mapping();
388,569✔
2201
    refresh_string_interners(writable);
388,569✔
2202
    refresh_index_accessors();
388,569✔
2203
}
388,569✔
2204

2205
void Table::refresh_string_interners(bool writable)
2206
{
3,684,078✔
2207
    if (writable) {
3,684,078✔
2208
        // if we're in a write transaction, make sure interner arrays are created which will allow
2209
        // string interners to expand with their own data when "learning"
2210
        while (m_top.size() <= top_position_for_interners) {
1,103,232✔
2211
            m_top.add(0);
414✔
2212
        }
414✔
2213
    }
1,102,818✔
2214
    if (m_top.size() > top_position_for_interners && m_top.get_as_ref(top_position_for_interners))
3,687,981✔
2215
        m_interner_data.update_from_parent();
3,403,020✔
2216
    else
281,058✔
2217
        m_interner_data.detach();
281,058✔
2218
    if (writable) {
3,684,078✔
2219
        if (!m_interner_data.is_attached()) {
1,102,818✔
2220
            m_interner_data.create(NodeHeader::type_HasRefs);
283,290✔
2221
            m_interner_data.update_parent();
283,290✔
2222
        }
283,290✔
2223
    }
1,102,818✔
2224
    // bring string interners in line with underlying data.
2225
    // Precondition: we rely on the col keys in m_leaf_ndx2colkey[] being up to date.
2226
    for (size_t idx = 0; idx < m_leaf_ndx2colkey.size(); ++idx) {
15,494,907✔
2227
        auto col_key = m_leaf_ndx2colkey[idx];
11,810,829✔
2228
        if (col_key == ColKey()) {
11,810,829✔
2229
            // deleted column, we really don't want a string interner for this
2230
            if (idx < m_string_interners.size() && m_string_interners[idx])
51,282✔
2231
                m_string_interners[idx].reset();
6✔
2232
            continue;
51,282✔
2233
        }
51,282✔
2234
        if (!needs_string_interner(col_key))
11,759,547✔
2235
            continue;
9,001,410✔
2236

2237
        REALM_ASSERT_DEBUG(col_key.get_index().val == idx);
2,758,137✔
2238
        // maintain sufficient size of interner arrays to cover all columns
2239
        while (idx >= m_string_interners.size()) {
6,804,909✔
2240
            m_string_interners.push_back({});
4,046,772✔
2241
        }
4,046,772✔
2242
        while (writable && idx >= m_interner_data.size()) { // m_interner_data.is_attached() per above
2,758,851✔
2243
            m_interner_data.add(0);
714✔
2244
        }
714✔
2245
        if (m_string_interners[idx]) {
2,758,137✔
2246
            // existing interner
2247
            m_string_interners[idx]->update_from_parent(writable);
1,115,712✔
2248
        }
1,115,712✔
2249
        else {
1,642,425✔
2250
            // new interner. Note: if not in a writable state, the interner will not have a valid
2251
            // underlying data array. The interner will be set in a state, where it cannot "learn",
2252
            // and searches will not find any matching interned strings.
2253
            m_string_interners[idx] = std::make_unique<StringInterner>(m_alloc, m_interner_data, col_key, writable);
1,642,425✔
2254
        }
1,642,425✔
2255
    }
2,758,137✔
2256
    if (m_string_interners.size() > m_leaf_ndx2colkey.size()) {
3,684,078✔
2257
        // remove any string interners which are no longer reachable,
2258
        // e.g. after a rollback
2259
        m_string_interners.resize(m_leaf_ndx2colkey.size());
12✔
2260
    }
12✔
2261
}
3,684,078✔
2262

2263
void Table::refresh_index_accessors()
2264
{
3,372,492✔
2265
    // Refresh search index accessors
2266

2267
    // First eliminate any index accessors for eliminated last columns
2268
    size_t col_ndx_end = m_leaf_ndx2colkey.size();
3,372,492✔
2269
    m_index_accessors.resize(col_ndx_end);
3,372,492✔
2270

2271
    // Then eliminate/refresh/create accessors within column range
2272
    // we can not use for_each_column() here, since the columns may have changed
2273
    // and the index accessor vector is not updated correspondingly.
2274
    for (size_t col_ndx = 0; col_ndx < col_ndx_end; col_ndx++) {
17,906,976✔
2275
        ref_type ref = m_index_refs.get_as_ref(col_ndx);
14,534,484✔
2276

2277
        if (ref == 0) {
14,534,484✔
2278
            // accessor drop
2279
            m_index_accessors[col_ndx].reset();
13,422,543✔
2280
        }
13,422,543✔
2281
        else {
1,111,941✔
2282
            auto attr = m_spec.get_column_attr(m_leaf_ndx2spec_ndx[col_ndx]);
1,111,941✔
2283
            bool fulltext = attr.test(col_attr_FullText_Indexed);
1,111,941✔
2284
            auto col_key = m_leaf_ndx2colkey[col_ndx];
1,111,941✔
2285
            ClusterColumn virtual_col(&m_clusters, col_key, fulltext ? IndexType::Fulltext : IndexType::General);
1,111,941✔
2286

2287
            if (m_index_accessors[col_ndx]) { // still there, refresh:
1,111,941✔
2288
                m_index_accessors[col_ndx]->refresh_accessor_tree(virtual_col);
417,969✔
2289
            }
417,969✔
2290
            else { // new index!
693,972✔
2291
                m_index_accessors[col_ndx] =
693,972✔
2292
                    std::make_unique<StringIndex>(ref, &m_index_refs, col_ndx, virtual_col, get_alloc());
693,972✔
2293
            }
693,972✔
2294
        }
1,111,941✔
2295
    }
14,534,484✔
2296
}
3,372,492✔
2297

2298
bool Table::is_cross_table_link_target() const noexcept
2299
{
1,512✔
2300
    auto is_cross_link = [this](ColKey col_key) {
1,512✔
2301
        auto t = col_key.get_type();
57✔
2302
        // look for a backlink with a different target than ourselves
2303
        return (t == col_type_BackLink && get_opposite_table_key(col_key) != get_key())
57✔
2304
                   ? IteratorControl::Stop
57✔
2305
                   : IteratorControl::AdvanceToNext;
57✔
2306
    };
57✔
2307
    return for_each_backlink_column(is_cross_link);
1,512✔
2308
}
1,512✔
2309

2310
// LCOV_EXCL_START ignore debug functions
2311

2312
void Table::verify() const
2313
{
241,530✔
2314
#ifdef REALM_DEBUG
241,530✔
2315
    if (m_top.is_attached())
241,530✔
2316
        m_top.verify();
241,530✔
2317
    m_spec.verify();
241,530✔
2318
    m_clusters.verify();
241,530✔
2319
    if (nb_unresolved())
241,530✔
2320
        m_tombstones->verify();
29,490✔
2321
#endif
241,530✔
2322
}
241,530✔
2323

2324
#ifdef REALM_DEBUG
2325
MemStats Table::stats() const
2326
{
×
2327
    MemStats mem_stats;
×
2328
    m_top.stats(mem_stats);
×
2329
    return mem_stats;
×
2330
}
×
2331
#endif // LCOV_EXCL_STOP ignore debug functions
2332

2333
Obj Table::create_object(ObjKey key, const FieldValues& values)
2334
{
24,804,081✔
2335
    if (is_embedded())
24,804,081✔
2336
        throw IllegalOperation(util::format("Explicit creation of embedded object not allowed in: %1", get_name()));
48✔
2337
    if (m_primary_key_col)
24,804,033✔
2338
        throw IllegalOperation(util::format("Table has primary key: %1", get_name()));
×
2339
    if (key == null_key) {
24,804,033✔
2340
        GlobalKey object_id = allocate_object_id_squeezed();
20,454,951✔
2341
        key = object_id.get_local_key(get_sync_file_id());
20,454,951✔
2342
        // Check if this key collides with an already existing object
2343
        // This could happen if objects were at some point created with primary keys,
2344
        // but later primary key property was removed from the schema.
2345
        while (m_clusters.is_valid(key)) {
20,454,951✔
2346
            object_id = allocate_object_id_squeezed();
×
2347
            key = object_id.get_local_key(get_sync_file_id());
×
2348
        }
×
2349
        if (auto repl = get_repl())
20,454,951✔
2350
            repl->create_object(this, object_id);
4,890,384✔
2351
    }
20,454,951✔
2352

2353
    REALM_ASSERT(key.value >= 0);
24,804,033✔
2354

2355
    Obj obj = m_clusters.insert(key, values); // repl->set()
24,804,033✔
2356

2357
    return obj;
24,804,033✔
2358
}
24,804,033✔
2359

2360
Obj Table::create_linked_object()
2361
{
42,966✔
2362
    REALM_ASSERT(is_embedded());
42,966✔
2363

2364
    GlobalKey object_id = allocate_object_id_squeezed();
42,966✔
2365
    ObjKey key = object_id.get_local_key(get_sync_file_id());
42,966✔
2366
    REALM_ASSERT(key.value >= 0);
42,966✔
2367

2368
    if (auto repl = get_repl())
42,966✔
2369
        repl->create_linked_object(this, key);
41,670✔
2370

2371
    Obj obj = m_clusters.insert(key, {});
42,966✔
2372

2373
    return obj;
42,966✔
2374
}
42,966✔
2375

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

2384
    if (auto repl = get_repl())
30✔
2385
        repl->create_object(this, object_id);
30✔
2386

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

2402
        return obj;
30✔
2403
    }
30✔
2404
    catch (const KeyAlreadyUsed&) {
30✔
2405
        return m_clusters.get(key);
6✔
2406
    }
6✔
2407
}
30✔
2408

2409
Obj Table::create_object_with_primary_key(const Mixed& primary_key, FieldValues&& field_values, UpdateMode mode,
2410
                                          bool* did_create)
2411
{
487,542✔
2412
    auto primary_key_col = get_primary_key_column();
487,542✔
2413
    if (is_embedded() || !primary_key_col)
487,542✔
2414
        throw InvalidArgument(ErrorCodes::UnexpectedPrimaryKey,
6✔
2415
                              util::format("Table has no primary key: %1", get_name()));
6✔
2416

2417
    DataType type = DataType(primary_key_col.get_type());
487,536✔
2418

2419
    if (primary_key.is_null() && !primary_key_col.is_nullable()) {
487,536✔
2420
        throw InvalidArgument(
6✔
2421
            ErrorCodes::PropertyNotNullable,
6✔
2422
            util::format("Primary key for class %1 cannot be NULL", Group::table_name_to_class_name(get_name())));
6✔
2423
    }
6✔
2424

2425
    if (!(primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) &&
487,530✔
2426
        primary_key.get_type() != type) {
487,530✔
2427
        throw InvalidArgument(ErrorCodes::TypeMismatch, util::format("Wrong primary key type for class %1",
6✔
2428
                                                                     Group::table_name_to_class_name(get_name())));
6✔
2429
    }
6✔
2430

2431
    REALM_ASSERT(type == type_String || type == type_ObjectId || type == type_Int || type == type_UUID);
487,524✔
2432

2433
    if (did_create)
487,524✔
2434
        *did_create = false;
84,840✔
2435

2436
    // Check for existing object
2437
    if (ObjKey key = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key)) {
487,524✔
2438
        if (mode == UpdateMode::never) {
47,910✔
2439
            throw ObjectAlreadyExists(this->get_class_name(), primary_key);
6✔
2440
        }
6✔
2441
        auto obj = m_clusters.get(key);
47,904✔
2442
        for (auto& val : field_values) {
47,904✔
2443
            if (mode == UpdateMode::all || obj.get_any(val.col_key) != val.value) {
12✔
2444
                obj.set_any(val.col_key, val.value, val.is_default);
12✔
2445
            }
12✔
2446
        }
12✔
2447
        return obj;
47,904✔
2448
    }
47,910✔
2449

2450
    ObjKey unres_key;
439,614✔
2451
    if (m_tombstones) {
439,614✔
2452
        // Check for potential tombstone
2453
        GlobalKey object_id{primary_key};
12,042✔
2454
        ObjKey object_key = global_to_local_object_id_hashed(object_id);
12,042✔
2455

2456
        ObjKey key = object_key.get_unresolved();
12,042✔
2457
        if (auto obj = m_tombstones->try_get_obj(key)) {
12,042✔
2458
            auto existing_pk_value = obj.get_any(primary_key_col);
10,767✔
2459

2460
            // If the primary key is the same, the object should be resurrected below
2461
            if (existing_pk_value == primary_key) {
10,767✔
2462
                unres_key = key;
10,761✔
2463
            }
10,761✔
2464
        }
10,767✔
2465
    }
12,042✔
2466

2467
    ObjKey key = get_next_valid_key();
439,614✔
2468

2469
    auto repl = get_repl();
439,614✔
2470
    if (repl) {
439,614✔
2471
        repl->create_object_with_primary_key(this, key, primary_key);
318,567✔
2472
    }
318,567✔
2473
    if (did_create) {
439,614✔
2474
        *did_create = true;
47,070✔
2475
    }
47,070✔
2476

2477
    field_values.insert(primary_key_col, primary_key);
439,614✔
2478
    Obj ret = m_clusters.insert(key, field_values);
439,614✔
2479

2480
    // Check if unresolved exists
2481
    if (unres_key) {
439,614✔
2482
        if (Replication* repl = get_repl()) {
10,761✔
2483
            if (auto logger = repl->would_log(util::Logger::Level::debug)) {
10,671✔
2484
                logger->log(LogCategory::object, util::Logger::Level::debug, "Cancel tombstone on '%1': %2",
36✔
2485
                            get_class_name(), unres_key);
36✔
2486
            }
36✔
2487
        }
10,671✔
2488

2489
        auto tombstone = m_tombstones->get(unres_key);
10,761✔
2490
        ret.assign_pk_and_backlinks(tombstone);
10,761✔
2491
        // If tombstones had no links to it, it may still be alive
2492
        if (m_tombstones->is_valid(unres_key)) {
10,761✔
2493
            CascadeState state(CascadeState::Mode::None);
10,701✔
2494
            m_tombstones->erase(unres_key, state);
10,701✔
2495
        }
10,701✔
2496
    }
10,761✔
2497
    if (is_asymmetric() && repl && repl->get_history_type() == Replication::HistoryType::hist_SyncClient) {
439,614✔
2498
        get_parent_group()->m_tables_to_clear.insert(this->m_key);
1,290✔
2499
    }
1,290✔
2500
    return ret;
439,614✔
2501
}
487,524✔
2502

2503
ObjKey Table::find_primary_key(Mixed primary_key) const
2504
{
728,682✔
2505
    auto primary_key_col = get_primary_key_column();
728,682✔
2506
    REALM_ASSERT(primary_key_col);
728,682✔
2507
    DataType type = DataType(primary_key_col.get_type());
728,682✔
2508
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
728,682✔
2509
                 primary_key.get_type() == type);
728,682✔
2510

2511
    if (auto&& index = m_index_accessors[primary_key_col.get_index().val]) {
728,682✔
2512
        return index->find_first(primary_key);
728,628✔
2513
    }
728,628✔
2514

2515
    // This must be file format 11, 20 or 21 as those are the ones we can open in read-only mode
2516
    // so try the old algorithm
2517
    GlobalKey object_id{primary_key};
54✔
2518
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
54✔
2519

2520
    // Check if existing
2521
    if (auto obj = m_clusters.try_get_obj(object_key)) {
54✔
2522
        auto existing_pk_value = obj.get_any(primary_key_col);
×
2523

2524
        if (existing_pk_value == primary_key) {
×
2525
            return object_key;
×
2526
        }
×
2527
    }
×
2528
    return {};
54✔
2529
}
54✔
2530

2531
ObjKey Table::get_objkey_from_primary_key(const Mixed& primary_key)
2532
{
611,352✔
2533
    // Check if existing
2534
    if (auto key = find_primary_key(primary_key)) {
611,352✔
2535
        return key;
599,520✔
2536
    }
599,520✔
2537

2538
    // Object does not exist - create tombstone
2539
    GlobalKey object_id{primary_key};
11,832✔
2540
    ObjKey object_key = global_to_local_object_id_hashed(object_id);
11,832✔
2541
    return get_or_create_tombstone(object_key, m_primary_key_col, primary_key).get_key();
11,832✔
2542
}
611,352✔
2543

2544
ObjKey Table::get_objkey_from_global_key(GlobalKey global_key)
2545
{
18✔
2546
    REALM_ASSERT(!m_primary_key_col);
18✔
2547
    auto object_key = global_key.get_local_key(get_sync_file_id());
18✔
2548

2549
    // Check if existing
2550
    if (m_clusters.is_valid(object_key)) {
18✔
2551
        return object_key;
12✔
2552
    }
12✔
2553

2554
    return get_or_create_tombstone(object_key, {}, {}).get_key();
6✔
2555
}
18✔
2556

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

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

2585
Obj Table::get_object_with_primary_key(Mixed primary_key) const
2586
{
80,004✔
2587
    auto primary_key_col = get_primary_key_column();
80,004✔
2588
    REALM_ASSERT(primary_key_col);
80,004✔
2589
    DataType type = DataType(primary_key_col.get_type());
80,004✔
2590
    REALM_ASSERT((primary_key.is_null() && primary_key_col.get_attrs().test(col_attr_Nullable)) ||
80,004✔
2591
                 primary_key.get_type() == type);
80,004✔
2592
    ObjKey k = m_index_accessors[primary_key_col.get_index().val]->find_first(primary_key);
80,004✔
2593
    return k ? m_clusters.get(k) : Obj{};
80,004✔
2594
}
80,004✔
2595

2596
Mixed Table::get_primary_key(ObjKey key) const
2597
{
699,507✔
2598
    auto primary_key_col = get_primary_key_column();
699,507✔
2599
    REALM_ASSERT(primary_key_col);
699,507✔
2600
    if (key.is_unresolved()) {
699,507✔
2601
        REALM_ASSERT(m_tombstones);
792✔
2602
        return m_tombstones->get(key).get_any(primary_key_col);
792✔
2603
    }
792✔
2604
    else {
698,715✔
2605
        return m_clusters.get(key).get_any(primary_key_col);
698,715✔
2606
    }
698,715✔
2607
}
699,507✔
2608

2609
GlobalKey Table::allocate_object_id_squeezed()
2610
{
20,499,048✔
2611
    // m_client_file_ident will be zero if we haven't been in contact with
2612
    // the server yet.
2613
    auto peer_id = get_sync_file_id();
20,499,048✔
2614
    auto sequence = allocate_sequence_number();
20,499,048✔
2615
    return GlobalKey{peer_id, sequence};
20,499,048✔
2616
}
20,499,048✔
2617

2618
namespace {
2619

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

2635
inline ObjKey make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
2636
{
12✔
2637
    REALM_ASSERT(!(sequence_number >> 62));
12✔
2638
    return ObjKey{int64_t(0x4000000000000000 | sequence_number)};
12✔
2639
}
12✔
2640

2641
} // namespace
2642

2643
ObjKey Table::global_to_local_object_id_hashed(GlobalKey object_id) const
2644
{
24,669✔
2645
    ObjKey optimistic = get_optimistic_local_id_hashed(object_id);
24,669✔
2646

2647
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
24,669✔
2648
        Allocator& alloc = m_top.get_alloc();
24✔
2649
        Array collision_map{alloc};
24✔
2650
        collision_map.init_from_ref(collision_map_ref); // Throws
24✔
2651

2652
        Array hi{alloc};
24✔
2653
        hi.init_from_ref(to_ref(collision_map.get(s_collision_map_hi))); // Throws
24✔
2654

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

2669
    return optimistic;
24,645✔
2670
}
24,669✔
2671

2672
ObjKey Table::allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
2673
                                                     ObjKey colliding_local_id)
2674
{
12✔
2675
    // Possible optimization: Cache these accessors
2676
    Allocator& alloc = m_top.get_alloc();
12✔
2677
    Array collision_map{alloc};
12✔
2678
    Array hi{alloc};
12✔
2679
    Array lo{alloc};
12✔
2680
    Array local_id{alloc};
12✔
2681

2682
    collision_map.set_parent(&m_top, top_position_for_collision_map);
12✔
2683
    hi.set_parent(&collision_map, s_collision_map_hi);
12✔
2684
    lo.set_parent(&collision_map, s_collision_map_lo);
12✔
2685
    local_id.set_parent(&collision_map, s_collision_map_local_id);
12✔
2686

2687
    ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map));
12✔
2688
    if (collision_map_ref) {
12✔
2689
        collision_map.init_from_parent(); // Throws
×
2690
    }
×
2691
    else {
12✔
2692
        MemRef mem = Array::create_empty_array(Array::type_HasRefs, false, alloc); // Throws
12✔
2693
        collision_map.init_from_mem(mem);                                          // Throws
12✔
2694
        collision_map.update_parent();
12✔
2695

2696
        ref_type lo_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2697
        ref_type hi_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref();       // Throws
12✔
2698
        ref_type local_id_ref = Array::create_array(Array::type_Normal, false, 0, 0, alloc).get_ref(); // Throws
12✔
2699
        collision_map.add(lo_ref);                                                                     // Throws
12✔
2700
        collision_map.add(hi_ref);                                                                     // Throws
12✔
2701
        collision_map.add(local_id_ref);                                                               // Throws
12✔
2702
    }
12✔
2703

2704
    hi.init_from_parent();       // Throws
12✔
2705
    lo.init_from_parent();       // Throws
12✔
2706
    local_id.init_from_parent(); // Throws
12✔
2707

2708
    size_t num_entries = hi.size();
12✔
2709
    REALM_ASSERT(lo.size() == num_entries);
12✔
2710
    REALM_ASSERT(local_id.size() == num_entries);
12✔
2711

2712
    auto lower_bound_object_id = [&](GlobalKey object_id) -> size_t {
24✔
2713
        size_t i = hi.lower_bound_int(int64_t(object_id.hi()));
24✔
2714
        while (i < num_entries && uint64_t(hi.get(i)) == object_id.hi() && uint64_t(lo.get(i)) < object_id.lo())
30✔
2715
            ++i;
6✔
2716
        return i;
24✔
2717
    };
24✔
2718

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

2734
    auto sequence_number_for_local_id = allocate_sequence_number();
12✔
2735
    ObjKey new_local_id = make_tagged_local_id_after_hash_collision(sequence_number_for_local_id);
12✔
2736
    insert_collision(incoming_id, new_local_id);
12✔
2737
    insert_collision(colliding_id, colliding_local_id);
12✔
2738

2739
    return new_local_id;
12✔
2740
}
12✔
2741

2742
Obj Table::get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val)
2743
{
12,801✔
2744
    auto unres_key = key.get_unresolved();
12,801✔
2745

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

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

2781
        collision_map.set_parent(&m_top, top_position_for_collision_map);
30✔
2782
        local_id.set_parent(&collision_map, s_collision_map_local_id);
30✔
2783
        collision_map.init_from_ref(collision_map_ref);
30✔
2784
        local_id.init_from_parent();
30✔
2785
        auto ndx = local_id.find_first(key.value);
30✔
2786
        if (ndx != realm::npos) {
30✔
2787
            Array hi{m_alloc};
24✔
2788
            Array lo{m_alloc};
24✔
2789

2790
            hi.set_parent(&collision_map, s_collision_map_hi);
24✔
2791
            lo.set_parent(&collision_map, s_collision_map_lo);
24✔
2792
            hi.init_from_parent();
24✔
2793
            lo.init_from_parent();
24✔
2794

2795
            hi.erase(ndx);
24✔
2796
            lo.erase(ndx);
24✔
2797
            local_id.erase(ndx);
24✔
2798
            if (hi.size() == 0) {
24✔
2799
                free_collision_table();
12✔
2800
            }
12✔
2801
        }
24✔
2802
    }
30✔
2803
}
5,000,517✔
2804

2805
void Table::free_collision_table()
2806
{
6,042✔
2807
    if (ref_type collision_map_ref = to_ref(m_top.get(top_position_for_collision_map))) {
6,042✔
2808
        Array::destroy_deep(collision_map_ref, m_alloc);
12✔
2809
        m_top.set(top_position_for_collision_map, 0);
12✔
2810
    }
12✔
2811
}
6,042✔
2812

2813
void Table::create_objects(size_t number, std::vector<ObjKey>& keys)
2814
{
3,849✔
2815
    while (number--) {
6,322,266✔
2816
        keys.push_back(create_object().get_key());
6,318,417✔
2817
    }
6,318,417✔
2818
}
3,849✔
2819

2820
void Table::create_objects(const std::vector<ObjKey>& keys)
2821
{
630✔
2822
    for (auto k : keys) {
5,616✔
2823
        create_object(k);
5,616✔
2824
    }
5,616✔
2825
}
630✔
2826

2827
void Table::dump_objects()
2828
{
×
2829
    m_clusters.dump_objects();
×
2830
    if (nb_unresolved())
×
2831
        m_tombstones->dump_objects();
×
2832
}
×
2833

2834
void Table::remove_object(ObjKey key)
2835
{
2,463,387✔
2836
    Group* g = get_parent_group();
2,463,387✔
2837

2838
    if (has_any_embedded_objects() || (g && g->has_cascade_notification_handler())) {
2,463,387✔
2839
        CascadeState state(CascadeState::Mode::Strong, g);
3,330✔
2840
        state.m_to_be_deleted.emplace_back(m_key, key);
3,330✔
2841
        m_clusters.nullify_incoming_links(key, state);
3,330✔
2842
        remove_recursive(state);
3,330✔
2843
    }
3,330✔
2844
    else {
2,460,057✔
2845
        CascadeState state(CascadeState::Mode::None, g);
2,460,057✔
2846
        if (g) {
2,460,057✔
2847
            m_clusters.nullify_incoming_links(key, state);
2,378,157✔
2848
        }
2,378,157✔
2849
        m_clusters.erase(key, state);
2,460,057✔
2850
    }
2,460,057✔
2851
}
2,463,387✔
2852

2853
ObjKey Table::invalidate_object(ObjKey key)
2854
{
4,143✔
2855
    if (is_embedded())
4,143✔
2856
        throw IllegalOperation("Deletion of embedded object not allowed");
×
2857
    REALM_ASSERT(!key.is_unresolved());
4,143✔
2858

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

2876
    remove_object(key);
4,143✔
2877

2878
    return tombstone.get_key();
4,143✔
2879
}
4,143✔
2880

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

2897
Table::Iterator Table::begin() const
2898
{
606,255✔
2899
    return Iterator(m_clusters, 0);
606,255✔
2900
}
606,255✔
2901

2902
Table::Iterator Table::end() const
2903
{
9,634,914✔
2904
    return Iterator(m_clusters, size());
9,634,914✔
2905
}
9,634,914✔
2906

2907
TableRef _impl::TableFriend::get_opposite_link_table(const Table& table, ColKey col_key)
2908
{
7,101,633✔
2909
    TableRef ret;
7,101,633✔
2910
    if (col_key) {
7,101,753✔
2911
        return table.get_opposite_table(col_key);
7,101,747✔
2912
    }
7,101,747✔
2913
    return ret;
2,147,483,653✔
2914
}
7,101,633✔
2915

2916
const uint64_t Table::max_num_columns;
2917

2918
void Table::build_column_mapping()
2919
{
3,381,000✔
2920
    // build column mapping from spec
2921
    // TODO: Optimization - Don't rebuild this for every change
2922
    m_spec_ndx2leaf_ndx.clear();
3,381,000✔
2923
    m_leaf_ndx2spec_ndx.clear();
3,381,000✔
2924
    m_leaf_ndx2colkey.clear();
3,381,000✔
2925
    size_t num_spec_cols = m_spec.get_column_count();
3,381,000✔
2926
    m_spec_ndx2leaf_ndx.resize(num_spec_cols);
3,381,000✔
2927
    for (size_t spec_ndx = 0; spec_ndx < num_spec_cols; ++spec_ndx) {
17,874,645✔
2928
        ColKey col_key = m_spec.get_key(spec_ndx);
14,493,645✔
2929
        unsigned leaf_ndx = col_key.get_index().val;
14,493,645✔
2930
        if (leaf_ndx >= m_leaf_ndx2colkey.size()) {
14,493,645✔
2931
            m_leaf_ndx2colkey.resize(leaf_ndx + 1);
13,978,134✔
2932
            m_leaf_ndx2spec_ndx.resize(leaf_ndx + 1, -1);
13,978,134✔
2933
        }
13,978,134✔
2934
        m_spec_ndx2leaf_ndx[spec_ndx] = ColKey::Idx{leaf_ndx};
14,493,645✔
2935
        m_leaf_ndx2spec_ndx[leaf_ndx] = spec_ndx;
14,493,645✔
2936
        m_leaf_ndx2colkey[leaf_ndx] = col_key;
14,493,645✔
2937
    }
14,493,645✔
2938
}
3,381,000✔
2939

2940
ColKey Table::generate_col_key(ColumnType tp, ColumnAttrMask attr)
2941
{
773,481✔
2942
    REALM_ASSERT(!attr.test(col_attr_Indexed));
773,481✔
2943
    REALM_ASSERT(!attr.test(col_attr_Unique)); // Must not be encoded into col_key
773,481✔
2944

2945
    int64_t col_seq_number = m_top.get_as_ref_or_tagged(top_position_for_column_key).get_as_int();
773,481✔
2946
    unsigned upper = unsigned(col_seq_number ^ get_key().value);
773,481✔
2947

2948
    // reuse lowest available leaf ndx:
2949
    unsigned lower = unsigned(m_leaf_ndx2colkey.size());
773,481✔
2950
    // look for an unused entry:
2951
    for (unsigned idx = 0; idx < lower; ++idx) {
5,690,805✔
2952
        if (m_leaf_ndx2colkey[idx] == ColKey()) {
4,917,408✔
2953
            lower = idx;
84✔
2954
            break;
84✔
2955
        }
84✔
2956
    }
4,917,408✔
2957
    return ColKey(ColKey::Idx{lower}, tp, attr, upper);
773,481✔
2958
}
773,481✔
2959

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

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

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

3013
ColKey Table::get_primary_key_column() const
3014
{
21,842,751✔
3015
    return m_primary_key_col;
21,842,751✔
3016
}
21,842,751✔
3017

3018
void Table::set_primary_key_column(ColKey col_key)
3019
{
720✔
3020
    if (col_key == m_primary_key_col) {
720✔
3021
        return;
378✔
3022
    }
378✔
3023

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

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

3034
    if (col_key) {
342✔
3035
        check_column(col_key);
222✔
3036
        validate_column_is_unique(col_key);
222✔
3037
        do_set_primary_key_column(col_key);
222✔
3038
    }
222✔
3039
    else {
120✔
3040
        do_set_primary_key_column(col_key);
120✔
3041
    }
120✔
3042
}
342✔
3043

3044

3045
void Table::do_set_primary_key_column(ColKey col_key)
3046
{
110,244✔
3047
    if (col_key) {
110,244✔
3048
        auto spec_ndx = leaf_ndx2spec_ndx(col_key.get_index());
109,230✔
3049
        auto attr = m_spec.get_column_attr(spec_ndx);
109,230✔
3050
        if (attr.test(col_attr_FullText_Indexed)) {
109,230✔
3051
            throw InvalidColumnKey("primary key cannot have a full text index");
6✔
3052
        }
6✔
3053
    }
109,230✔
3054

3055
    if (m_primary_key_col) {
110,238✔
3056
        // If the search index has not been set explicitly on current pk col, we remove it again
3057
        auto spec_ndx = leaf_ndx2spec_ndx(m_primary_key_col.get_index());
1,038✔
3058
        auto attr = m_spec.get_column_attr(spec_ndx);
1,038✔
3059
        if (!attr.test(col_attr_Indexed)) {
1,038✔
3060
            remove_search_index(m_primary_key_col);
1,026✔
3061
        }
1,026✔
3062
    }
1,038✔
3063

3064
    if (col_key) {
110,238✔
3065
        m_top.set(top_position_for_pk_col, RefOrTagged::make_tagged(col_key.value));
109,224✔
3066
        do_add_search_index(col_key, IndexType::General);
109,224✔
3067
    }
109,224✔
3068
    else {
1,014✔
3069
        m_top.set(top_position_for_pk_col, 0);
1,014✔
3070
    }
1,014✔
3071

3072
    m_primary_key_col = col_key;
110,238✔
3073
}
110,238✔
3074

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

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

3096
void Table::validate_primary_column()
3097
{
1,812✔
3098
    if (ColKey col = get_primary_key_column()) {
1,812✔
3099
        validate_column_is_unique(col);
612✔
3100
    }
612✔
3101
}
1,812✔
3102

3103
ObjKey Table::get_next_valid_key()
3104
{
439,608✔
3105
    ObjKey key;
439,608✔
3106
    do {
439,614✔
3107
        key = ObjKey(allocate_sequence_number());
439,614✔
3108
    } while (m_clusters.is_valid(key));
439,614✔
3109

3110
    return key;
439,608✔
3111
}
439,608✔
3112

3113
namespace {
3114
template <class T>
3115
typename util::RemoveOptional<T>::type remove_optional(T val)
3116
{
88,014✔
3117
    return val;
88,014✔
3118
}
88,014✔
3119
template <>
3120
int64_t remove_optional<Optional<int64_t>>(Optional<int64_t> val)
3121
{
5,388✔
3122
    return *val;
5,388✔
3123
}
5,388✔
3124
template <>
3125
bool remove_optional<Optional<bool>>(Optional<bool> val)
3126
{
11,493✔
3127
    return *val;
11,493✔
3128
}
11,493✔
3129
template <>
3130
ObjectId remove_optional<Optional<ObjectId>>(Optional<ObjectId> val)
3131
{
5,409✔
3132
    return *val;
5,409✔
3133
}
5,409✔
3134
template <>
3135
UUID remove_optional<Optional<UUID>>(Optional<UUID> val)
3136
{
6,060✔
3137
    return *val;
6,060✔
3138
}
6,060✔
3139
} // namespace
3140

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

3149
        typename ColumnTypeTraits<F>::cluster_leaf_type from_arr(allocator);
162✔
3150
        typename ColumnTypeTraits<T>::cluster_leaf_type to_arr(allocator);
162✔
3151
        cluster->init_leaf(key_from, &from_arr);
162✔
3152
        cluster->init_leaf(key_to, &to_arr);
162✔
3153

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

3172
    m_clusters.update(func);
162✔
3173
}
162✔
3174

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

3183
        ArrayInteger from_arr(allocator);
120✔
3184
        ArrayInteger to_arr(allocator);
120✔
3185
        cluster->init_leaf(key_from, &from_arr);
120✔
3186
        cluster->init_leaf(key_to, &to_arr);
120✔
3187

3188
        for (size_t i = 0; i < sz; i++) {
360✔
3189
            ref_type ref_from = to_ref(from_arr.get(i));
240✔
3190
            ref_type ref_to = to_ref(to_arr.get(i));
240✔
3191
            REALM_ASSERT(!ref_to);
240✔
3192

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

3220
    m_clusters.update(func);
120✔
3221
}
120✔
3222

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

3341

3342
ColKey Table::set_nullability(ColKey col_key, bool nullable, bool throw_on_null)
3343
{
522✔
3344
    if (col_key.is_nullable() == nullable)
522✔
3345
        return col_key;
240✔
3346

3347
    check_column(col_key);
282✔
3348

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

3361
    ColKey new_col = generate_col_key(type, attr);
282✔
3362
    do_insert_root_column(new_col, type, "__temporary");
282✔
3363

3364
    try {
282✔
3365
        convert_column(col_key, new_col, throw_on_null);
282✔
3366
    }
282✔
3367
    catch (...) {
282✔
3368
        // remove any partially filled column
3369
        remove_column(new_col);
6✔
3370
        throw;
6✔
3371
    }
6✔
3372

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

3380
    erase_root_column(col_key);
276✔
3381
    m_spec.rename_column(colkey2spec_ndx(new_col), column_name);
276✔
3382

3383
    if (index_type != IndexType::None)
276✔
3384
        do_add_search_index(new_col, index_type);
30✔
3385

3386
    return new_col;
276✔
3387
}
282✔
3388

3389
bool Table::has_any_embedded_objects()
3390
{
2,470,257✔
3391
    if (!m_has_any_embedded_objects) {
2,470,257✔
3392
        m_has_any_embedded_objects = false;
25,041✔
3393
        for_each_public_column([&](ColKey col_key) {
60,291✔
3394
            auto target_table_key = get_opposite_table_key(col_key);
60,291✔
3395
            if (target_table_key && is_link_type(col_key.get_type())) {
60,291✔
3396
                auto target_table = get_parent_group()->get_table_unchecked(target_table_key);
11,511✔
3397
                if (target_table->is_embedded()) {
11,511✔
3398
                    m_has_any_embedded_objects = true;
9,378✔
3399
                    return IteratorControl::Stop; // early out
9,378✔
3400
                }
9,378✔
3401
            }
11,511✔
3402
            return IteratorControl::AdvanceToNext;
50,913✔
3403
        });
60,291✔
3404
    }
25,041✔
3405
    return *m_has_any_embedded_objects;
2,470,257✔
3406
}
2,470,257✔
3407

3408
void Table::set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column)
3409
{
156,402✔
3410
    m_opposite_table.set(col_key.get_index().val, opposite_table.value);
156,402✔
3411
    m_opposite_column.set(col_key.get_index().val, opposite_column.value);
156,402✔
3412
}
156,402✔
3413

3414
ColKey Table::find_backlink_column(ColKey origin_col_key, TableKey origin_table) const
3415
{
47,592✔
3416
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
163,347✔
3417
        if (m_opposite_column.get(i) == origin_col_key.value && m_opposite_table.get(i) == origin_table.value) {
155,589✔
3418
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
39,834✔
3419
        }
39,834✔
3420
    }
155,589✔
3421

3422
    return {};
7,758✔
3423
}
47,592✔
3424

3425
ColKey Table::find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table)
3426
{
47,520✔
3427
    ColKey backlink_col_key = find_backlink_column(origin_col_key, origin_table);
47,520✔
3428

3429
    if (!backlink_col_key) {
47,520✔
3430
        backlink_col_key = do_insert_root_column(ColKey{}, col_type_BackLink, "");
7,758✔
3431
        set_opposite_column(backlink_col_key, origin_table, origin_col_key);
7,758✔
3432

3433
        if (Replication* repl = get_repl())
7,758✔
3434
            repl->typed_link_change(get_parent_group()->get_table_unchecked(origin_table), origin_col_key,
7,452✔
3435
                                    m_key); // Throws
7,452✔
3436
    }
7,758✔
3437

3438
    return backlink_col_key;
47,520✔
3439
}
47,520✔
3440

3441
TableKey Table::get_opposite_table_key(ColKey col_key) const
3442
{
14,473,977✔
3443
    return TableKey(int32_t(m_opposite_table.get(col_key.get_index().val)));
14,473,977✔
3444
}
14,473,977✔
3445

3446
bool Table::links_to_self(ColKey col_key) const
3447
{
67,989✔
3448
    return get_opposite_table_key(col_key) == m_key;
67,989✔
3449
}
67,989✔
3450

3451
TableRef Table::get_opposite_table(ColKey col_key) const
3452
{
7,822,899✔
3453
    if (auto k = get_opposite_table_key(col_key)) {
7,822,899✔
3454
        return get_parent_group()->get_table(k);
7,760,622✔
3455
    }
7,760,622✔
3456
    return {};
62,277✔
3457
}
7,822,899✔
3458

3459
ColKey Table::get_opposite_column(ColKey col_key) const
3460
{
16,272,639✔
3461
    return ColKey(m_opposite_column.get(col_key.get_index().val));
16,272,639✔
3462
}
16,272,639✔
3463

3464
ColKey Table::find_opposite_column(ColKey col_key) const
3465
{
×
3466
    for (size_t i = 0; i < m_opposite_column.size(); i++) {
×
3467
        if (m_opposite_column.get(i) == col_key.value) {
×
3468
            return m_spec.get_key(m_leaf_ndx2spec_ndx[i]);
×
3469
        }
×
3470
    }
×
3471
    return ColKey();
×
3472
}
×
3473

3474
ref_type Table::typed_write(ref_type ref, _impl::ArrayWriterBase& out) const
3475
{
1,364,970✔
3476
    REALM_ASSERT(ref == m_top.get_mem().get_ref());
1,364,970✔
3477
    if (out.only_modified && m_alloc.is_read_only(ref))
1,364,970✔
3478
        return ref;
571,044✔
3479
    out.table = this;
793,926✔
3480
    // ignore ref from here, just use Tables own accessors
3481
    TempArray dest(m_top.size());
793,926✔
3482
    for (unsigned j = 0; j < m_top.size(); ++j) {
12,701,034✔
3483
        RefOrTagged rot = m_top.get_as_ref_or_tagged(j);
11,907,108✔
3484
        if (rot.is_tagged() || (rot.is_ref() && rot.get_as_ref() == 0)) {
11,907,108✔
3485
            dest.set(j, rot);
7,137,798✔
3486
        }
7,137,798✔
3487
        else {
4,769,310✔
3488
            ref_type new_ref;
4,769,310✔
3489
            if (j == 2) {
4,769,310✔
3490
                // only do type driven write for clustertree
3491
                new_ref = m_clusters.typed_write(rot.get_as_ref(), out);
793,926✔
3492
            }
793,926✔
3493
            else {
3,975,384✔
3494
                // rest is handled using untyped approach
3495
                Array a(m_alloc);
3,975,384✔
3496
                a.init_from_ref(rot.get_as_ref());
3,975,384✔
3497
                new_ref = a.write(out, true, out.only_modified, false);
3,975,384✔
3498
            }
3,975,384✔
3499
            dest.set_as_ref(j, new_ref);
4,769,310✔
3500
        }
4,769,310✔
3501
    }
11,907,108✔
3502
    return dest.write(out);
793,926✔
3503
}
1,364,970✔
3504

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

3531
StringInterner* Table::get_string_interner(ColKey::Idx idx) const
3532
{
72,307,605✔
3533
    REALM_ASSERT(idx.val < m_string_interners.size());
72,307,605✔
3534
    auto interner = m_string_interners[idx.val].get();
72,307,605✔
3535
    REALM_ASSERT(interner);
72,307,605✔
3536
    return interner;
72,307,605✔
3537
}
72,307,605✔
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