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

realm / realm-core / 2424

17 Jun 2024 06:44PM UTC coverage: 90.98%. Remained the same
2424

push

Evergreen

web-flow
Prepare for release 14.10.1 (#7816)

Co-authored-by: ironage <2826060+ironage@users.noreply.github.com>

102174 of 180444 branches covered (56.62%)

214802 of 236097 relevant lines covered (90.98%)

5951448.33 hits per line

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

91.84
/src/realm/table.hpp
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
#ifndef REALM_TABLE_HPP
20
#define REALM_TABLE_HPP
21

22
#include "external/mpark/variant.hpp"
23
#include <algorithm>
24
#include <map>
25
#include <utility>
26
#include <typeinfo>
27
#include <memory>
28
#include <mutex>
29

30
#include <realm/util/features.h>
31
#include <realm/util/function_ref.hpp>
32
#include <realm/util/thread.hpp>
33
#include <realm/table_ref.hpp>
34
#include <realm/spec.hpp>
35
#include <realm/query.hpp>
36
#include <realm/cluster_tree.hpp>
37
#include <realm/keys.hpp>
38
#include <realm/global_key.hpp>
39

40
// Only set this to one when testing the code paths that exercise object ID
41
// hash collisions. It artificially limits the "optimistic" local ID to use
42
// only the lower 15 bits of the ID rather than the lower 63 bits, making it
43
// feasible to generate collisions within reasonable time.
44
#define REALM_EXERCISE_OBJECT_ID_COLLISION 0
45

46
namespace realm {
47

48
class BacklinkColumn;
49
template <class>
50
class BacklinkCount;
51
class ColKeys;
52
template <class>
53
class Columns;
54
class DictionaryLinkValues;
55
struct GlobalKey;
56
class Group;
57
class LinkChain;
58
class SearchIndex;
59
class SortDescriptor;
60
class StringIndex;
61
class Subexpr;
62
template <class>
63
class SubQuery;
64
class TableView;
65

66
struct Link {};
67
typedef Link BackLink;
68

69

70
namespace _impl {
71
class TableFriend;
72
}
73
namespace util {
74
class Logger;
75
}
76
namespace query_parser {
77
class Arguments;
78
class KeyPathMapping;
79
class ParserDriver;
80
} // namespace query_parser
81

82
enum class ExpressionComparisonType : unsigned char {
83
    Any,
84
    All,
85
    None,
86
};
87

88
class Table {
89
public:
90
    /// The type of tables supported by a realm.
91
    /// Note: Any change to this enum is a file-format breaking change.
92
    /// Note: Enumeration value assignments must be kept in sync with
93
    /// <realm/object-store/object_schema.hpp>.
94
    enum class Type : uint8_t { TopLevel = 0, Embedded = 0x1, TopLevelAsymmetric = 0x2 };
95
    constexpr static uint8_t table_type_mask = 0x3;
96

97
    /// Construct a new freestanding top-level table with static
98
    /// lifetime. For debugging only.
99
    Table(Allocator& = Allocator::get_default());
100

101
    /// Construct a copy of the specified table as a new freestanding
102
    /// top-level table with static lifetime. For debugging only.
103
    Table(const Table&, Allocator& = Allocator::get_default());
104

105
    ~Table() noexcept;
106

107
    Allocator& get_alloc() const;
108

109
    /// Get the name of this table, if it has one. Only group-level tables have
110
    /// names. For a table of any other kind, this function returns the empty
111
    /// string.
112
    StringData get_name() const noexcept;
113

114
    // Get table name with class prefix removed
115
    StringData get_class_name() const noexcept;
116

117
    const char* get_state() const noexcept;
118

119
    /// If this table is a group-level table, the parent group is returned,
120
    /// otherwise null is returned.
121
    Group* get_parent_group() const noexcept;
122

123

124
    Replication* get_repl() const noexcept
125
    {
87,713,673✔
126
        return *m_repl;
87,713,673✔
127
    }
87,713,673✔
128

129
    // Whether or not elements can be null.
130
    bool is_nullable(ColKey col_key) const;
131

132
    // Whether or not the column is a list.
133
    bool is_list(ColKey col_key) const;
134

135
    //@{
136
    /// Conventience functions for inspecting the dynamic table type.
137
    ///
138
    bool is_embedded() const noexcept;   // true if table holds embedded objects
139
    bool is_asymmetric() const noexcept; // true if table is asymmetric
140
    Type get_table_type() const noexcept;
141
    size_t get_column_count() const noexcept;
142
    DataType get_column_type(ColKey column_key) const;
143
    StringData get_column_name(ColKey column_key) const;
144
    StringData get_column_name(StableIndex) const;
145
    ColumnAttrMask get_column_attr(ColKey column_key) const noexcept;
146
    DataType get_dictionary_key_type(ColKey column_key) const noexcept;
147
    ColKey get_column_key(StringData name) const noexcept;
148
    ColKey get_column_key(StableIndex) const noexcept;
149
    ColKeys get_column_keys() const;
150
    typedef util::Optional<std::pair<ConstTableRef, ColKey>> BacklinkOrigin;
151
    BacklinkOrigin find_backlink_origin(StringData origin_table_name, StringData origin_col_name) const noexcept;
152
    BacklinkOrigin find_backlink_origin(ColKey backlink_col) const noexcept;
153
    std::vector<std::pair<TableKey, ColKey>> get_incoming_link_columns() const noexcept;
154
    //@}
155

156
    // Primary key columns
157
    ColKey get_primary_key_column() const;
158
    void set_primary_key_column(ColKey col);
159
    void validate_primary_column();
160

161
    //@{
162
    /// Convenience functions for manipulating the dynamic table type.
163
    ///
164
    static const size_t max_column_name_length = 63;
165
    static const uint64_t max_num_columns = 0xFFFFUL; // <-- must be power of two -1
166

167
    // Add column holding primitive values. The optional CollectionType specifies if the
168
    // property is a collection. If collection type is not specified, the property is a single value.
169
    // If the vector contains a value - eg. CollectionType::Dictionary, the property is a dictionary
170
    // of the type specified.
171
    ColKey add_column(DataType type, StringData name, bool nullable = false, std::optional<CollectionType> = {},
172
                      DataType key_type = type_String);
173
    // As above, but the values are links to objects in the target table.
174
    ColKey add_column(Table& target, StringData name, std::optional<CollectionType> = {},
175
                      DataType key_type = type_String);
176

177
    // Map old functions to the more general interface above
178
    ColKey add_column_list(DataType type, StringData name, bool nullable = false)
179
    {
3,189✔
180
        return add_column(type, name, nullable, {CollectionType::List});
3,189✔
181
    }
3,189✔
182
    ColKey add_column_list(Table& target, StringData name)
183
    {
5,547✔
184
        return add_column(target, name, {CollectionType::List});
5,547✔
185
    }
5,547✔
186
    ColKey add_column_set(DataType type, StringData name, bool nullable = false)
187
    {
948✔
188
        return add_column(type, name, nullable, {CollectionType::Set});
948✔
189
    }
948✔
190
    ColKey add_column_set(Table& target, StringData name)
191
    {
126✔
192
        return add_column(target, name, {CollectionType::Set});
126✔
193
    }
126✔
194
    ColKey add_column_dictionary(DataType type, StringData name, bool nullable = false,
195
                                 DataType key_type = type_String)
196
    {
714✔
197
        return add_column(type, name, nullable, {CollectionType::Dictionary}, key_type);
714✔
198
    }
714✔
199
    ColKey add_column_dictionary(Table& target, StringData name, DataType key_type = type_String)
200
    {
270✔
201
        return add_column(target, name, {CollectionType::Dictionary}, key_type);
270✔
202
    }
270✔
203

204
    CollectionType get_collection_type(ColKey col_key) const;
205

206
    void remove_columns();
207
    void remove_column(ColKey col_key);
208
    void rename_column(ColKey col_key, StringData new_name);
209
    bool valid_column(ColKey col_key) const noexcept;
210
    void check_column(ColKey col_key) const
211
    {
298,360,521✔
212
        if (REALM_UNLIKELY(!valid_column(col_key)))
298,360,521✔
213
            throw InvalidColumnKey();
324✔
214
    }
298,360,521✔
215
    // Change the type of a table. Only allowed to switch to/from TopLevel from/to Embedded.
216
    void set_table_type(Type new_type, bool handle_backlinks = false);
217
    //@}
218

219
    /// True for `col_type_Link` and `col_type_LinkList`.
220
    static bool is_link_type(ColumnType) noexcept;
221

222
    //@{
223

224
    /// has_search_index() returns true if, and only if a search index has been
225
    /// added to the specified column. Rather than throwing, it returns false if
226
    /// the table accessor is detached or the specified index is out of range.
227
    ///
228
    /// add_search_index() adds a search index to the specified column of the
229
    /// table. It has no effect if a search index has already been added to the
230
    /// specified column (idempotency).
231
    ///
232
    /// remove_search_index() removes the search index from the specified column
233
    /// of the table. It has no effect if the specified column has no search
234
    /// index. The search index cannot be removed from the primary key of a
235
    /// table.
236
    ///
237
    /// \param col_key The key of a column of the table.
238

239
    IndexType search_index_type(ColKey col_key) const noexcept;
240
    bool has_search_index(ColKey col_key) const noexcept
241
    {
4,572✔
242
        return search_index_type(col_key) == IndexType::General;
4,572✔
243
    }
4,572✔
244
    void add_search_index(ColKey col_key, IndexType type = IndexType::General);
245
    void add_fulltext_index(ColKey col_key)
246
    {
32✔
247
        add_search_index(col_key, IndexType::Fulltext);
32✔
248
    }
32✔
249
    void remove_search_index(ColKey col_key);
250

251
    void enumerate_string_column(ColKey col_key);
252
    bool is_enumerated(ColKey col_key) const noexcept;
253
    bool contains_unique_values(ColKey col_key) const;
254

255
    //@}
256

257
    /// If the specified column is optimized to store only unique values, then
258
    /// this function returns the number of unique values currently
259
    /// stored. Otherwise it returns zero. This function is mainly intended for
260
    /// debugging purposes.
261
    size_t get_num_unique_values(ColKey col_key) const;
262

263
    template <class T>
264
    Columns<T> column(ColKey col_key, util::Optional<ExpressionComparisonType> = util::none) const;
265
    template <class T>
266
    Columns<T> column(const Table& origin, ColKey origin_col_key) const;
267

268
    // BacklinkCount is a total count per row and therefore not attached to a specific column
269
    template <class T>
270
    BacklinkCount<T> get_backlink_count() const;
271

272
    template <class T>
273
    SubQuery<T> column(ColKey col_key, Query subquery) const;
274
    template <class T>
275
    SubQuery<T> column(const Table& origin, ColKey origin_col_key, Query subquery) const;
276

277
    // Table size and deletion
278
    bool is_empty() const noexcept;
279
    size_t size() const noexcept
280
    {
11,398,755✔
281
        return m_clusters.size();
11,398,755✔
282
    }
11,398,755✔
283
    size_t nb_unresolved() const noexcept
284
    {
241,308✔
285
        return m_tombstones ? m_tombstones->size() : 0;
241,308✔
286
    }
241,308✔
287

288
    //@{
289

290
    /// Object handling.
291

292
    enum class UpdateMode { never, changed, all };
293

294
    // Create an object with key. If the key is omitted, a key will be generated by the system
295
    Obj create_object(ObjKey key = {}, const FieldValues& = {});
296
    // Create an object with specific GlobalKey - or return already existing object
297
    // Potential tombstone will be resurrected
298
    Obj create_object(GlobalKey object_id, const FieldValues& = {});
299
    // Create an object with primary key. If an object with the given primary key already exists, it
300
    // will be returned and did_create (if supplied) will be set to false.
301
    // Potential tombstone will be resurrected
302
    Obj create_object_with_primary_key(const Mixed& primary_key, FieldValues&&, UpdateMode mode = UpdateMode::all,
303
                                       bool* did_create = nullptr);
304
    Obj create_object_with_primary_key(const Mixed& primary_key, bool* did_create = nullptr)
305
    {
453,918✔
306
        return create_object_with_primary_key(primary_key, {{}}, UpdateMode::all, did_create);
453,918✔
307
    }
453,918✔
308
    // Return key for existing object or return null key.
309
    ObjKey find_primary_key(Mixed value) const;
310
    // Return ObjKey for object identified by id. If objects does not exist, return null key
311
    // Important: This function must not be called for tables with primary keys.
312
    ObjKey get_objkey(GlobalKey id) const;
313
    // Return key for existing object or return unresolved key.
314
    // Important: This is to be used ONLY by the Sync client. SDKs should NEVER
315
    // observe an unresolved key. Ever.
316
    ObjKey get_objkey_from_primary_key(const Mixed& primary_key);
317
    // Return key for existing object or return unresolved key.
318
    // Important: This is to be used ONLY by the Sync client. SDKs should NEVER
319
    // observe an unresolved key. Ever.
320
    // Important (2): This function must not be called for tables with primary keys.
321
    ObjKey get_objkey_from_global_key(GlobalKey key);
322
    /// Create a number of objects and add corresponding keys to a vector
323
    void create_objects(size_t number, std::vector<ObjKey>& keys);
324
    /// Create a number of objects with keys supplied
325
    void create_objects(const std::vector<ObjKey>& keys);
326
    /// Does the key refer to an object within the table?
327
    bool is_valid(ObjKey key) const noexcept
328
    {
4,329,954✔
329
        return key && m_clusters.is_valid(key);
4,329,954✔
330
    }
4,329,954✔
331
    GlobalKey get_object_id(ObjKey key) const;
332
    Obj get_object(ObjKey key) const
333
    {
91,518,453✔
334
        REALM_ASSERT(!key.is_unresolved());
91,518,453✔
335
        return m_clusters.get(key);
91,518,453✔
336
    }
91,518,453✔
337
    Obj try_get_object(ObjKey key) const noexcept
338
    {
30,896,583✔
339
        return m_clusters.try_get_obj(key);
30,896,583✔
340
    }
30,896,583✔
341
    Obj get_object(size_t ndx) const
342
    {
11,347,671✔
343
        return m_clusters.get(ndx);
11,347,671✔
344
    }
11,347,671✔
345
    // Get object based on primary key
346
    Obj get_object_with_primary_key(Mixed pk) const;
347
    // Get primary key based on ObjKey
348
    Mixed get_primary_key(ObjKey key) const;
349
    // Get logical index for object. This function is not very efficient
350
    size_t get_object_ndx(ObjKey key) const noexcept
351
    {
12,108✔
352
        return m_clusters.get_ndx(key);
12,108✔
353
    }
12,108✔
354

355
    void dump_objects();
356

357
    bool traverse_clusters(ClusterTree::TraverseFunction func) const
358
    {
1,938,582✔
359
        return m_clusters.traverse(func);
1,938,582✔
360
    }
1,938,582✔
361

362
    /// remove_object() removes the specified object from the table.
363
    /// Any links from the specified object into objects residing in an embedded
364
    /// table will cause those objects to be deleted as well, and so on recursively.
365
    void remove_object(ObjKey key);
366
    /// remove_object_recursive() will delete linked rows if the removed link was the
367
    /// last one holding on to the row in question. This will be done recursively.
368
    void remove_object_recursive(ObjKey key);
369
    // Invalidate object. To be used by the Sync client.
370
    // - turns the object into a tombstone if links exist
371
    // - otherwise works just as remove_object()
372
    ObjKey invalidate_object(ObjKey key);
373
    // Remove several objects
374
    void batch_erase_objects(std::vector<ObjKey>& keys);
375
    Obj try_get_tombstone(ObjKey key) const
376
    {
34,008✔
377
        REALM_ASSERT(key.is_unresolved());
34,008✔
378
        REALM_ASSERT(m_tombstones);
34,008✔
379
        return m_tombstones->try_get_obj(key);
34,008✔
380
    }
34,008✔
381

382
    void clear();
383
    using Iterator = ClusterTree::Iterator;
384
    Iterator begin() const;
385
    Iterator end() const;
386
    void remove_object(const Iterator& it)
387
    {
620✔
388
        remove_object(it->get_key());
620✔
389
    }
620✔
390
    //@}
391

392

393
    TableRef get_link_target(ColKey column_key) noexcept;
394
    ConstTableRef get_link_target(ColKey column_key) const noexcept;
395

396
    static const size_t max_string_size = 0xFFFFF8 - Array::header_size - 1;
397
    static const size_t max_binary_size = 0xFFFFF8 - Array::header_size;
398

399
    static constexpr int_fast64_t max_integer = std::numeric_limits<int64_t>::max();
400
    static constexpr int_fast64_t min_integer = std::numeric_limits<int64_t>::min();
401

402
    /// Only group-level unordered tables can be used as origins or targets of
403
    /// links.
404
    bool is_group_level() const noexcept;
405

406
    /// A Table accessor obtained from a frozen transaction is also frozen.
407
    bool is_frozen() const noexcept
408
    {
409✔
409
        return m_is_frozen;
409✔
410
    }
409✔
411

412
    /// If this table is a group-level table, then this function returns the
413
    /// index of this table within the group. Otherwise it returns realm::npos.
414
    size_t get_index_in_group() const noexcept;
415
    TableKey get_key() const noexcept;
416

417
    uint64_t allocate_sequence_number();
418
    // Used for testing purposes.
419
    void set_col_key_sequence_number(uint64_t seq);
420

421
    // Get the key of this table directly, without needing a Table accessor.
422
    static TableKey get_key_direct(Allocator& alloc, ref_type top_ref);
423

424
    // Aggregate functions
425
    size_t count_int(ColKey col_key, int64_t value) const;
426
    size_t count_string(ColKey col_key, StringData value) const;
427
    size_t count_float(ColKey col_key, float value) const;
428
    size_t count_double(ColKey col_key, double value) const;
429
    size_t count_decimal(ColKey col_key, Decimal128 value) const;
430

431
    // Aggregates return nullopt if the operation is not supported on the given column
432
    // Everything but `sum` returns `some(null)` if there are no non-null values
433
    // Sum returns `some(0)` if there are no non-null values.
434
    std::optional<Mixed> sum(ColKey col_key) const;
435
    std::optional<Mixed> min(ColKey col_key, ObjKey* = nullptr) const;
436
    std::optional<Mixed> max(ColKey col_key, ObjKey* = nullptr) const;
437
    std::optional<Mixed> avg(ColKey col_key, size_t* value_count = nullptr) const;
438

439
    // Will return pointer to search index accessor. Will return nullptr if no index
440
    SearchIndex* get_search_index(ColKey col) const noexcept;
441
    StringIndex* get_string_index(ColKey col) const noexcept;
442

443
    template <class T>
444
    ObjKey find_first(ColKey col_key, T value) const;
445

446
    ObjKey find_first_int(ColKey col_key, int64_t value) const;
447
    ObjKey find_first_bool(ColKey col_key, bool value) const;
448
    ObjKey find_first_timestamp(ColKey col_key, Timestamp value) const;
449
    ObjKey find_first_object_id(ColKey col_key, ObjectId value) const;
450
    ObjKey find_first_float(ColKey col_key, float value) const;
451
    ObjKey find_first_double(ColKey col_key, double value) const;
452
    ObjKey find_first_decimal(ColKey col_key, Decimal128 value) const;
453
    ObjKey find_first_string(ColKey col_key, StringData value) const;
454
    ObjKey find_first_binary(ColKey col_key, BinaryData value) const;
455
    ObjKey find_first_null(ColKey col_key) const;
456
    ObjKey find_first_uuid(ColKey col_key, UUID value) const;
457

458
    //    TableView find_all_link(Key target_key);
459
    TableView find_all_int(ColKey col_key, int64_t value);
460
    TableView find_all_int(ColKey col_key, int64_t value) const;
461
    TableView find_all_bool(ColKey col_key, bool value);
462
    TableView find_all_bool(ColKey col_key, bool value) const;
463
    TableView find_all_float(ColKey col_key, float value);
464
    TableView find_all_float(ColKey col_key, float value) const;
465
    TableView find_all_double(ColKey col_key, double value);
466
    TableView find_all_double(ColKey col_key, double value) const;
467
    TableView find_all_string(ColKey col_key, StringData value);
468
    TableView find_all_string(ColKey col_key, StringData value) const;
469
    TableView find_all_binary(ColKey col_key, BinaryData value);
470
    TableView find_all_binary(ColKey col_key, BinaryData value) const;
471
    TableView find_all_null(ColKey col_key);
472
    TableView find_all_null(ColKey col_key) const;
473

474
    TableView find_all_fulltext(ColKey col_key, StringData value) const;
475

476
    TableView get_sorted_view(ColKey col_key, bool ascending = true);
477
    TableView get_sorted_view(ColKey col_key, bool ascending = true) const;
478

479
    TableView get_sorted_view(SortDescriptor order);
480
    TableView get_sorted_view(SortDescriptor order) const;
481

482
    // Report the current content version. This is a 64-bit value which is bumped whenever
483
    // the content in the table changes.
484
    uint_fast64_t get_content_version() const noexcept;
485

486
    // Report the current instance version. This is a 64-bit value which is bumped
487
    // whenever the table accessor is recycled.
488
    uint_fast64_t get_instance_version() const noexcept;
489

490
    // Report the current storage version. This is a 64-bit value which is bumped
491
    // whenever the location in memory of any part of the table changes.
492
    uint_fast64_t get_storage_version(uint64_t instance_version) const;
493
    uint_fast64_t get_storage_version() const;
494
    void bump_storage_version() const noexcept;
495
    void bump_content_version() const noexcept;
496

497
    // Change the nullability of the column identified by col_key.
498
    // This might result in the creation of a new column and deletion of the old.
499
    // The column key to use going forward is returned.
500
    // If the conversion is from nullable to non-nullable, throw_on_null determines
501
    // the reaction to encountering a null value: If clear, null values will be
502
    // converted to default values. If set, a 'column_not_nullable' is thrown and the
503
    // table is unchanged.
504
    ColKey set_nullability(ColKey col_key, bool nullable, bool throw_on_null);
505

506
    // Iterate through (subset of) columns. The supplied function may abort iteration
507
    // by returning 'IteratorControl::Stop' (early out).
508
    template <typename Func>
509
    bool for_each_and_every_column(Func func) const
510
    {
29,034,042✔
511
        for (auto col_key : m_leaf_ndx2colkey) {
42,582,348✔
512
            if (!col_key)
42,582,348✔
513
                continue;
96✔
514
            if (func(col_key) == IteratorControl::Stop)
42,582,252✔
515
                return true;
×
516
        }
42,582,252✔
517
        return false;
29,034,042✔
518
    }
29,034,042✔
519
    template <typename Func>
520
    bool for_each_public_column(Func func) const
521
    {
25,290✔
522
        for (auto col_key : m_leaf_ndx2colkey) {
69,519✔
523
            if (!col_key)
69,519✔
524
                continue;
24✔
525
            if (col_key.get_type() == col_type_BackLink)
69,495✔
526
                continue;
9,147✔
527
            if (func(col_key) == IteratorControl::Stop)
60,348✔
528
                return true;
9,420✔
529
        }
60,348✔
530
        return false;
15,870✔
531
    }
25,290✔
532
    template <typename Func>
533
    bool for_each_backlink_column(Func func) const
534
    {
5,328,409✔
535
        // Could be optimized - to not iterate through all non-backlink columns:
536
        for (auto col_key : m_leaf_ndx2colkey) {
8,966,478✔
537
            if (!col_key)
8,966,478✔
538
                continue;
42✔
539
            if (col_key.get_type() != col_type_BackLink)
8,966,436✔
540
                continue;
7,962,709✔
541
            if (func(col_key) == IteratorControl::Stop)
1,003,727✔
542
                return true;
178,164✔
543
        }
1,003,727✔
544
        return false;
5,150,245✔
545
    }
5,328,409✔
546

547
private:
548
    template <class T>
549
    TableView find_all(ColKey col_key, T value);
550
    void build_column_mapping();
551
    ColKey generate_col_key(ColumnType ct, ColumnAttrMask attrs);
552
    void convert_column(ColKey from, ColKey to, bool throw_on_null);
553
    template <class F, class T>
554
    void change_nullability(ColKey from, ColKey to, bool throw_on_null);
555
    template <class F, class T>
556
    void change_nullability_list(ColKey from, ColKey to, bool throw_on_null);
557
    Obj create_linked_object();
558
    // Change the embedded property of a table. If switching to being embedded, the table must
559
    // not have a primary key and all objects must have exactly 1 backlink.
560
    void set_embedded(bool embedded, bool handle_backlinks);
561
    /// Changes type unconditionally. Called only from Group::do_get_or_add_table()
562
    void do_set_table_type(Type table_type);
563

564
public:
565
    // mapping between index used in leaf nodes (leaf_ndx) and index used in spec (spec_ndx)
566
    // as well as the full column key. A leaf_ndx can be obtained directly from the column key
567
    size_t colkey2spec_ndx(ColKey key) const;
568
    size_t leaf_ndx2spec_ndx(ColKey::Idx idx) const;
569
    ColKey::Idx spec_ndx2leaf_ndx(size_t idx) const;
570
    ColKey leaf_ndx2colkey(ColKey::Idx idx) const;
571
    ColKey spec_ndx2colkey(size_t ndx) const;
572

573
    // Queries
574
    // Using where(tv) is the new method to perform queries on TableView. The 'tv' can have any order; it does not
575
    // need to be sorted, and, resulting view retains its order.
576
    Query where(TableView* tv = nullptr)
577
    {
2,256,240✔
578
        return Query(m_own_ref, tv);
2,256,240✔
579
    }
2,256,240✔
580

581
    Query where(TableView* tv = nullptr) const
582
    {
30,984✔
583
        return Query(m_own_ref, tv);
30,984✔
584
    }
30,984✔
585

586
    // Perform queries on a Link Collection. The returned Query holds a reference to collection.
587
    Query where(const ObjList& list) const
588
    {
266✔
589
        return Query(m_own_ref, list);
266✔
590
    }
266✔
591
    Query where(const Dictionary& dict) const;
592
    Query where(LinkCollectionPtr&& list) const
593
    {
1,924✔
594
        return Query(m_own_ref, std::move(list));
1,924✔
595
    }
1,924✔
596

597
    Query query(const std::string& query_string,
598
                const std::vector<mpark::variant<Mixed, std::vector<Mixed>>>& arguments = {}) const;
599
    Query query(const std::string& query_string, const std::vector<Mixed>& arguments) const;
600
    Query query(const std::string& query_string, const std::vector<Mixed>& arguments,
601
                const query_parser::KeyPathMapping& mapping) const;
602
    Query query(const std::string& query_string,
603
                const std::vector<mpark::variant<Mixed, std::vector<Mixed>>>& arguments,
604
                const query_parser::KeyPathMapping& mapping) const;
605
    Query query(const std::string& query_string, query_parser::Arguments& arguments,
606
                const query_parser::KeyPathMapping&) const;
607

608
    //@{
609
    /// WARNING: The link() and backlink() methods will alter a state on the Table object and return a reference
610
    /// to itself. Be aware if assigning the return value of link() to a variable; this might be an error!
611

612
    /// This is an error:
613

614
    /// Table& cats = owners->link(1);
615
    /// auto& dogs = owners->link(2);
616

617
    /// Query q = person_table->where()
618
    /// .and_query(cats.column<String>(5).equal("Fido"))
619
    /// .Or()
620
    /// .and_query(dogs.column<String>(6).equal("Meowth"));
621

622
    /// Instead, do this:
623

624
    /// Query q = owners->where()
625
    /// .and_query(person_table->link(1).column<String>(5).equal("Fido"))
626
    /// .Or()
627
    /// .and_query(person_table->link(2).column<String>(6).equal("Meowth"));
628

629
    /// The two calls to link() in the erroneous example will append the two values 0 and 1 to an internal vector in
630
    /// the owners table, and we end up with three references to that same table: owners, cats and dogs. They are all
631
    /// the same table, its vector has the values {0, 1}, so a query would not make any sense.
632
    LinkChain link(ColKey link_column) const;
633
    LinkChain backlink(const Table& origin, ColKey origin_col_key) const;
634

635
    // Conversion
636
    void schema_to_json(std::ostream& out) const;
637
    void to_json(std::ostream& out, JSONOutputMode output_mode = output_mode_json) const;
638

639
    /// \brief Compare two tables for equality.
640
    ///
641
    /// Two tables are equal if they have equal descriptors
642
    /// (`Descriptor::operator==()`) and equal contents. Equal descriptors imply
643
    /// that the two tables have the same columns in the same order. Equal
644
    /// contents means that the two tables must have the same number of rows,
645
    /// and that for each row index, the two rows must have the same values in
646
    /// each column.
647
    ///
648
    /// In mixed columns, both the value types and the values are required to be
649
    /// equal.
650
    ///
651
    /// For a particular row and column, if the two values are themselves tables
652
    /// (subtable and mixed columns) value equality implies a recursive
653
    /// invocation of `Table::operator==()`.
654
    bool operator==(const Table&) const;
655

656
    /// \brief Compare two tables for inequality.
657
    ///
658
    /// See operator==().
659
    bool operator!=(const Table& t) const;
660

661
    // Debug
662
    void verify() const;
663

664
#ifdef REALM_DEBUG
665
    MemStats stats() const;
666
#endif
667
    TableRef get_opposite_table(ColKey col_key) const;
668
    TableKey get_opposite_table_key(ColKey col_key) const;
669
    bool links_to_self(ColKey col_key) const;
670
    ColKey get_opposite_column(ColKey col_key) const;
671
    ColKey find_opposite_column(ColKey col_key) const;
672

673
    class DisableReplication {
674
    public:
675
        DisableReplication(Table& table) noexcept
676
            : m_table(table)
759✔
677
            , m_repl(table.m_repl)
759✔
678
        {
2,811✔
679
            m_table.m_repl = &g_dummy_replication;
2,811✔
680
        }
2,811✔
681
        ~DisableReplication()
682
        {
2,811✔
683
            m_table.m_repl = m_repl;
2,811✔
684
        }
2,811✔
685

686
    private:
687
        Table& m_table;
688
        Replication* const* m_repl;
689
    };
690

691
private:
692
    enum LifeCycleCookie {
693
        cookie_created = 0x1234,
694
        cookie_transaction_ended = 0xcafe,
695
        cookie_initialized = 0xbeef,
696
        cookie_removed = 0xbabe,
697
        cookie_void = 0x5678,
698
        cookie_deleted = 0xdead,
699
    };
700

701
    // This is only used for debugging checks, so relaxed operations are fine.
702
    class AtomicLifeCycleCookie {
703
    public:
704
        void operator=(LifeCycleCookie cookie)
705
        {
4,425,801✔
706
            m_storage.store(cookie, std::memory_order_relaxed);
4,425,801✔
707
        }
4,425,801✔
708
        operator LifeCycleCookie() const
709
        {
348,561✔
710
            return m_storage.load(std::memory_order_relaxed);
348,561✔
711
        }
348,561✔
712

713
    private:
714
        std::atomic<LifeCycleCookie> m_storage = {};
715
    };
716

717
    mutable WrappedAllocator m_alloc;
718
    Array m_top;
719
    void update_allocator_wrapper(bool writable)
720
    {
2,610,393✔
721
        m_alloc.update_from_underlying_allocator(writable);
2,610,393✔
722
    }
2,610,393✔
723
    void refresh_allocator_wrapper() const noexcept
724
    {
×
725
        m_alloc.refresh_ref_translation();
×
726
    }
×
727
    Spec m_spec;                               // 1st slot in m_top
728
    ClusterTree m_clusters;                    // 3rd slot in m_top
729
    std::unique_ptr<ClusterTree> m_tombstones; // 13th slot in m_top
730
    TableKey m_key;                            // 4th slot in m_top
731
    Array m_index_refs;                        // 5th slot in m_top
732
    Array m_opposite_table;                    // 7th slot in m_top
733
    Array m_opposite_column;                   // 8th slot in m_top
734
    std::vector<std::unique_ptr<SearchIndex>> m_index_accessors;
735
    ColKey m_primary_key_col;
736
    Replication* const* m_repl;
737
    static Replication* g_dummy_replication;
738
    bool m_is_frozen = false;
739
    util::Optional<bool> m_has_any_embedded_objects;
740
    TableRef m_own_ref;
741

742
    void batch_erase_rows(const KeyColumn& keys);
743
    size_t do_set_link(ColKey col_key, size_t row_ndx, size_t target_row_ndx);
744

745
    void populate_search_index(ColKey col_key);
746
    void erase_from_search_indexes(ObjKey key);
747
    void update_indexes(ObjKey key, const FieldValues& values);
748
    void clear_indexes();
749
    template <typename T>
750
    void do_populate_index(StringIndex* index, ColKey::Idx col_ndx);
751

752
    // Migration support
753
    void migrate_sets_and_dictionaries();
754
    void migrate_set_orderings();
755
    void migrate_col_keys();
756

757
    /// Disable copying assignment.
758
    ///
759
    /// It could easily be implemented by calling assign(), but the
760
    /// non-checking nature of the low-level dynamically typed API
761
    /// makes it too risky to offer this feature as an
762
    /// operator.
763
    Table& operator=(const Table&) = delete;
764

765
    /// Create an uninitialized accessor whose lifetime is managed by Group
766
    Table(Replication* const* repl, Allocator&);
767
    void revive(Replication* const* repl, Allocator& new_allocator, bool writable);
768

769
    void init(ref_type top_ref, ArrayParent*, size_t ndx_in_parent, bool is_writable, bool is_frozen);
770
    void ensure_graveyard();
771

772
    void set_key(TableKey key);
773

774
    ColKey do_insert_column(ColKey col_key, DataType type, StringData name, Table* target_table,
775
                            DataType key_type = DataType(0));
776

777
    struct InsertSubtableColumns;
778
    struct EraseSubtableColumns;
779
    struct RenameSubtableColumns;
780

781
    void erase_root_column(ColKey col_key);
782
    ColKey do_insert_root_column(ColKey col_key, ColumnType, StringData name, DataType key_type = DataType(0));
783
    void do_erase_root_column(ColKey col_key);
784
    void do_add_search_index(ColKey col_key, IndexType type);
785

786
    bool has_any_embedded_objects();
787
    void set_opposite_column(ColKey col_key, TableKey opposite_table, ColKey opposite_column);
788
    ColKey find_backlink_column(ColKey origin_col_key, TableKey origin_table) const;
789
    ColKey find_or_add_backlink_column(ColKey origin_col_key, TableKey origin_table);
790
    void do_set_primary_key_column(ColKey col_key);
791
    void validate_column_is_unique(ColKey col_key) const;
792

793
    ObjKey get_next_valid_key();
794
    /// Some Object IDs are generated as a tuple of the client_file_ident and a
795
    /// local sequence number. This function takes the next number in the
796
    /// sequence for the given table and returns an appropriate globally unique
797
    /// GlobalKey.
798
    GlobalKey allocate_object_id_squeezed();
799

800
    /// Find the local 64-bit object ID for the provided global 128-bit ID.
801
    ObjKey global_to_local_object_id_hashed(GlobalKey global_id) const;
802

803
    /// After a local ObjKey collision has been detected, this function may be
804
    /// called to obtain a non-colliding local ObjKey in such a way that subsequent
805
    /// calls to global_to_local_object_id() will return the correct local ObjKey
806
    /// for both \a incoming_id and \a colliding_id.
807
    ObjKey allocate_local_id_after_hash_collision(GlobalKey incoming_id, GlobalKey colliding_id,
808
                                                  ObjKey colliding_local_id);
809
    /// Create a placeholder for a not yet existing object and return key to it
810
    Obj get_or_create_tombstone(ObjKey key, ColKey pk_col, Mixed pk_val);
811
    /// Should be called when an object is deleted
812
    void free_local_id_after_hash_collision(ObjKey key);
813
    /// Should be called when last entry is removed - or when table is cleared
814
    void free_collision_table();
815

816
    /// Called in the context of Group::commit() to ensure that
817
    /// attached table accessors stay valid across a commit. Please
818
    /// note that this works only for non-transactional commits. Table
819
    /// accessors obtained during a transaction are always detached
820
    /// when the transaction ends.
821
    void update_from_parent() noexcept;
822

823
    // Detach accessor. This recycles the Table accessor and all subordinate
824
    // accessors become invalid.
825
    void detach(LifeCycleCookie) noexcept;
826
    void fully_detach() noexcept;
827

828
    ColumnType get_real_column_type(ColKey col_key) const noexcept;
829

830
    uint64_t get_sync_file_id() const noexcept;
831

832
    /// Create an empty table with independent spec and return just
833
    /// the reference to the underlying memory.
834
    static ref_type create_empty_table(Allocator&, TableKey = TableKey());
835

836
    void nullify_links(CascadeState&);
837
    void remove_recursive(CascadeState&);
838

839
    util::Logger* get_logger() const noexcept;
840

841
    void set_ndx_in_parent(size_t ndx_in_parent) noexcept;
842

843
    /// Refresh the part of the accessor tree that is rooted at this
844
    /// table.
845
    void refresh_accessor_tree();
846
    void refresh_index_accessors();
847
    void refresh_content_version();
848
    void flush_for_commit();
849

850
    bool is_cross_table_link_target() const noexcept;
851

852
    template <typename T>
853
    void aggregate(QueryStateBase& st, ColKey col_key) const;
854

855
    std::vector<ColKey> m_leaf_ndx2colkey;
856
    std::vector<ColKey::Idx> m_spec_ndx2leaf_ndx;
857
    std::vector<size_t> m_leaf_ndx2spec_ndx;
858
    Type m_table_type = Type::TopLevel;
859
    uint64_t m_in_file_version_at_transaction_boundary = 0;
860
    AtomicLifeCycleCookie m_cookie;
861

862
    static constexpr int top_position_for_spec = 0;
863
    static constexpr int top_position_for_columns = 1;
864
    static constexpr int top_position_for_cluster_tree = 2;
865
    static constexpr int top_position_for_key = 3;
866
    static constexpr int top_position_for_search_indexes = 4;
867
    static constexpr int top_position_for_column_key = 5;
868
    static constexpr int top_position_for_version = 6;
869
    static constexpr int top_position_for_opposite_table = 7;
870
    static constexpr int top_position_for_opposite_column = 8;
871
    static constexpr int top_position_for_sequence_number = 9;
872
    static constexpr int top_position_for_collision_map = 10;
873
    static constexpr int top_position_for_pk_col = 11;
874
    static constexpr int top_position_for_flags = 12;
875
    // flags contents: bit 0-1 - table type
876
    static constexpr int top_position_for_tombstones = 13;
877
    static constexpr int top_array_size = 14;
878

879
    enum { s_collision_map_lo = 0, s_collision_map_hi = 1, s_collision_map_local_id = 2, s_collision_map_num_slots };
880

881
    friend class _impl::TableFriend;
882
    friend class Query;
883
    template <class>
884
    friend class SimpleQuerySupport;
885
    friend class TableView;
886
    template <class T>
887
    friend class Columns;
888
    friend class Columns<StringData>;
889
    friend class ParentNode;
890
    friend struct util::serializer::SerialisationState;
891
    friend class LinkMap;
892
    friend class LinkView;
893
    friend class Group;
894
    friend class Transaction;
895
    friend class Cluster;
896
    friend class ClusterTree;
897
    friend class ColKeyIterator;
898
    friend class Obj;
899
    friend class CollectionParent;
900
    friend class CollectionList;
901
    friend class LnkLst;
902
    friend class Dictionary;
903
    friend class IncludeDescriptor;
904
    template <class T>
905
    friend class AggregateHelper;
906
};
907

908
std::ostream& operator<<(std::ostream& o, Table::Type table_type);
909

910
class ColKeyIterator {
911
public:
912
    bool operator!=(const ColKeyIterator& other)
913
    {
4,697,028✔
914
        return m_pos != other.m_pos;
4,697,028✔
915
    }
4,697,028✔
916
    ColKeyIterator& operator++()
917
    {
3,341,451✔
918
        ++m_pos;
3,341,451✔
919
        return *this;
3,341,451✔
920
    }
3,341,451✔
921
    ColKeyIterator operator++(int)
922
    {
174✔
923
        ColKeyIterator tmp(m_table, m_pos);
174✔
924
        ++m_pos;
174✔
925
        return tmp;
174✔
926
    }
174✔
927
    ColKey operator*()
928
    {
27,295,131✔
929
        if (m_pos < m_table->get_column_count()) {
27,295,131✔
930
            REALM_ASSERT(m_table->m_spec.get_key(m_pos) == m_table->spec_ndx2colkey(m_pos));
27,237,339✔
931
            return m_table->m_spec.get_key(m_pos);
27,237,339✔
932
        }
27,237,339✔
933
        return {};
57,792✔
934
    }
27,295,131✔
935

936
private:
937
    friend class ColKeys;
938
    ConstTableRef m_table;
939
    size_t m_pos;
940

941
    ColKeyIterator(const ConstTableRef& t, size_t p)
942
        : m_table(t)
8,946,207✔
943
        , m_pos(p)
8,946,207✔
944
    {
26,597,757✔
945
    }
26,597,757✔
946
};
947

948
class ColKeys {
949
public:
950
    ColKeys(ConstTableRef&& t)
951
        : m_table(std::move(t))
1,954,839✔
952
    {
3,149,472✔
953
    }
3,149,472✔
954

955
    ColKeys()
956
        : m_table(nullptr)
957
    {
×
958
    }
×
959

960
    size_t size() const
961
    {
1,358,094✔
962
        return m_table->get_column_count();
1,358,094✔
963
    }
1,358,094✔
964
    bool empty() const
965
    {
82✔
966
        return size() == 0;
82✔
967
    }
82✔
968
    ColKey operator[](size_t p) const
969
    {
23,866,323✔
970
        return ColKeyIterator(m_table, p).operator*();
23,866,323✔
971
    }
23,866,323✔
972
    ColKeyIterator begin() const
973
    {
1,355,592✔
974
        return ColKeyIterator(m_table, 0);
1,355,592✔
975
    }
1,355,592✔
976
    ColKeyIterator end() const
977
    {
1,355,580✔
978
        return ColKeyIterator(m_table, size());
1,355,580✔
979
    }
1,355,580✔
980

981
private:
982
    ConstTableRef m_table;
983
};
984

985
// Class used to collect a chain of links when building up a Query following links.
986
// It has member functions corresponding to the ones defined on Table.
987
class LinkChain {
988
public:
989
    LinkChain(ConstTableRef t = {}, util::Optional<ExpressionComparisonType> type = util::none)
990
        : m_current_table(t)
530,216✔
991
        , m_base_table(t)
530,216✔
992
        , m_comparison_type(type)
530,216✔
993
    {
1,060,492✔
994
    }
1,060,492✔
995
    ConstTableRef get_base_table()
996
    {
×
997
        return m_base_table;
×
998
    }
×
999

1000
    ConstTableRef get_current_table() const
1001
    {
1,455,750✔
1002
        return m_current_table;
1,455,750✔
1003
    }
1,455,750✔
1004

1005
    ColKey get_current_col() const
1006
    {
316✔
1007
        return m_link_cols.back();
316✔
1008
    }
316✔
1009

1010
    LinkChain& link(ColKey link_column)
1011
    {
1,656✔
1012
        add(link_column);
1,656✔
1013
        return *this;
1,656✔
1014
    }
1,656✔
1015

1016
    void pop_back()
1017
    {
4✔
1018
        m_link_cols.pop_back();
4✔
1019
        // Recalculate m_current_table
1020
        m_current_table = m_base_table;
4✔
1021
        for (auto col : m_link_cols) {
4✔
1022
            m_current_table = m_current_table->get_opposite_table(col);
×
1023
        }
×
1024
    }
4✔
1025

1026
    bool link(std::string col_name)
1027
    {
494,956✔
1028
        if (auto ck = m_current_table->get_column_key(col_name)) {
494,956✔
1029
            return add(ck);
494,776✔
1030
        }
494,776✔
1031
        return false;
180✔
1032
    }
494,956✔
1033

1034
    bool index(PathElement index)
1035
    {
268✔
1036
        if (!m_link_cols.empty() && !m_link_cols.back().has_index()) {
268✔
1037
            if (index.is_all())
156✔
1038
                return true;
4✔
1039
            ColKey last_col = m_link_cols.back();
152✔
1040
            if (index.is_ndx() && last_col.is_list()) {
152✔
1041
                m_link_cols.back().set_index(index);
76✔
1042
                return true;
76✔
1043
            }
76✔
1044
            if (index.is_key() && last_col.is_dictionary()) {
76✔
1045
                m_link_cols.back().set_index(index);
56✔
1046
                return true;
56✔
1047
            }
56✔
1048
        }
76✔
1049
        return false;
132✔
1050
    }
268✔
1051

1052
    LinkChain& backlink(const Table& origin, ColKey origin_col_key)
1053
    {
528✔
1054
        auto backlink_col_key = origin.get_opposite_column(origin_col_key);
528✔
1055
        return link(backlink_col_key);
528✔
1056
    }
528✔
1057

1058
    std::unique_ptr<Subexpr> column(const std::string&, bool has_path);
1059
    std::unique_ptr<Subexpr> subquery(Query subquery);
1060

1061
    template <class T>
1062
    inline Columns<T> column(ColKey col_key)
1063
    {
37,314✔
1064
        m_current_table->check_column(col_key);
37,314✔
1065

1066
        // Check if user-given template type equals Realm type.
1067
        if constexpr (std::is_same_v<T, Dictionary>) {
33,390✔
1068
            if (!col_key.is_dictionary())
18,657✔
1069
                throw LogicError(ErrorCodes::TypeMismatch, "Not a dictionary");
3,924✔
1070
        }
4,043✔
1071
        else {
37,076✔
1072
            if (col_key.get_type() != ColumnTypeTraits<T>::column_id)
37,076✔
1073
                throw LogicError(ErrorCodes::TypeMismatch,
4✔
1074
                                 util::format("Expected %1 to be a %2", m_current_table->get_column_name(col_key),
4✔
1075
                                              ColumnTypeTraits<T>::column_id));
4✔
1076
        }
37,076✔
1077

1078
        if (std::is_same<T, Link>::value || std::is_same<T, LnkLst>::value || std::is_same<T, BackLink>::value) {
37,312✔
1079
            m_link_cols.push_back(col_key);
3,900✔
1080
        }
3,900✔
1081

1082
        return Columns<T>(col_key, m_base_table, m_link_cols, m_comparison_type);
37,312✔
1083
    }
37,314✔
1084
    template <class T>
1085
    Columns<T> column(const Table& origin, ColKey origin_col_key)
1086
    {
42✔
1087
        static_assert(std::is_same<T, BackLink>::value, "");
42✔
1088

1089
        auto backlink_col_key = origin.get_opposite_column(origin_col_key);
42✔
1090
        m_link_cols.push_back(backlink_col_key);
42✔
1091

1092
        return Columns<T>(backlink_col_key, m_base_table, m_link_cols);
42✔
1093
    }
42✔
1094
    template <class T>
1095
    SubQuery<T> column(ColKey col_key, Query subquery)
1096
    {
62✔
1097
        static_assert(std::is_same<T, Link>::value, "A subquery must involve a link list or backlink column");
62✔
1098
        return SubQuery<T>(column<T>(col_key), std::move(subquery));
62✔
1099
    }
62✔
1100

1101
    template <class T>
1102
    SubQuery<T> column(const Table& origin, ColKey origin_col_key, Query subquery)
1103
    {
4✔
1104
        static_assert(std::is_same<T, BackLink>::value, "A subquery must involve a link list or backlink column");
4✔
1105
        return SubQuery<T>(column<T>(origin, origin_col_key), std::move(subquery));
4✔
1106
    }
4✔
1107

1108
    template <class T>
1109
    BacklinkCount<T> get_backlink_count()
1110
    {
304✔
1111
        return BacklinkCount<T>(m_base_table, std::move(m_link_cols));
304✔
1112
    }
304✔
1113

1114
private:
1115
    friend class Table;
1116
    friend class query_parser::ParserDriver;
1117

1118
    std::vector<ExtendedColumnKey> m_link_cols;
1119
    ConstTableRef m_current_table;
1120
    ConstTableRef m_base_table;
1121
    util::Optional<ExpressionComparisonType> m_comparison_type;
1122

1123
    bool add(ColKey ck);
1124

1125
    template <class T>
1126
    std::unique_ptr<Subexpr> create_subexpr(ColKey col_key)
1127
    {
480,442✔
1128
        return std::make_unique<Columns<T>>(col_key, m_base_table, m_link_cols, m_comparison_type);
480,442✔
1129
    }
480,442✔
1130
};
1131

1132
// Implementation:
1133

1134
inline ColKeys Table::get_column_keys() const
1135
{
3,146,646✔
1136
    return ColKeys(ConstTableRef(this, m_alloc.get_instance_version()));
3,146,646✔
1137
}
3,146,646✔
1138

1139
inline uint_fast64_t Table::get_content_version() const noexcept
1140
{
2,023,122✔
1141
    return m_alloc.get_content_version();
2,023,122✔
1142
}
2,023,122✔
1143

1144
inline uint_fast64_t Table::get_instance_version() const noexcept
1145
{
992,695,782✔
1146
    return m_alloc.get_instance_version();
992,695,782✔
1147
}
992,695,782✔
1148

1149

1150
inline uint_fast64_t Table::get_storage_version(uint64_t instance_version) const
1151
{
×
1152
    return m_alloc.get_storage_version(instance_version);
×
1153
}
×
1154

1155
inline uint_fast64_t Table::get_storage_version() const
1156
{
1,457,196✔
1157
    return m_alloc.get_storage_version();
1,457,196✔
1158
}
1,457,196✔
1159

1160

1161
inline TableKey Table::get_key() const noexcept
1162
{
45,244,842✔
1163
    return m_key;
45,244,842✔
1164
}
45,244,842✔
1165

1166
inline void Table::bump_storage_version() const noexcept
1167
{
3,295,656✔
1168
    return m_alloc.bump_storage_version();
3,295,656✔
1169
}
3,295,656✔
1170

1171
inline void Table::bump_content_version() const noexcept
1172
{
2,415,324✔
1173
    m_alloc.bump_content_version();
2,415,324✔
1174
}
2,415,324✔
1175

1176

1177
inline size_t Table::get_column_count() const noexcept
1178
{
35,433,939✔
1179
    return m_spec.get_public_column_count();
35,433,939✔
1180
}
35,433,939✔
1181

1182
inline bool Table::is_embedded() const noexcept
1183
{
32,329,878✔
1184
    return m_table_type == Type::Embedded;
32,329,878✔
1185
}
32,329,878✔
1186

1187
inline bool Table::is_asymmetric() const noexcept
1188
{
3,167,574✔
1189
    return m_table_type == Type::TopLevelAsymmetric;
3,167,574✔
1190
}
3,167,574✔
1191

1192
inline Table::Type Table::get_table_type() const noexcept
1193
{
349,161✔
1194
    return m_table_type;
349,161✔
1195
}
349,161✔
1196

1197
inline StringData Table::get_column_name(ColKey column_key) const
1198
{
5,959,071✔
1199
    auto spec_ndx = colkey2spec_ndx(column_key);
5,959,071✔
1200
    REALM_ASSERT_3(spec_ndx, <, get_column_count());
5,959,071✔
1201
    return m_spec.get_column_name(spec_ndx);
5,959,071✔
1202
}
5,959,071✔
1203

1204
inline StringData Table::get_column_name(StableIndex index) const
1205
{
3,246✔
1206
    return m_spec.get_column_name(m_leaf_ndx2spec_ndx[index.get_index().val]);
3,246✔
1207
}
3,246✔
1208

1209
inline ColKey Table::get_column_key(StringData name) const noexcept
1210
{
8,092,302✔
1211
    size_t spec_ndx = m_spec.get_column_index(name);
8,092,302✔
1212
    if (spec_ndx == npos)
8,092,302✔
1213
        return ColKey();
15,738✔
1214
    return spec_ndx2colkey(spec_ndx);
8,076,564✔
1215
}
8,092,302✔
1216

1217
inline ColKey Table::get_column_key(StableIndex index) const noexcept
1218
{
489,684✔
1219
    return m_leaf_ndx2colkey[index.get_index().val];
489,684✔
1220
}
489,684✔
1221

1222
inline ColumnType Table::get_real_column_type(ColKey col_key) const noexcept
1223
{
58,899✔
1224
    return col_key.get_type();
58,899✔
1225
}
58,899✔
1226

1227
inline DataType Table::get_column_type(ColKey column_key) const
1228
{
959,853✔
1229
    return DataType(column_key.get_type());
959,853✔
1230
}
959,853✔
1231

1232
inline ColumnAttrMask Table::get_column_attr(ColKey column_key) const noexcept
1233
{
16✔
1234
    return column_key.get_attrs();
16✔
1235
}
16✔
1236

1237
inline DataType Table::get_dictionary_key_type(ColKey column_key) const noexcept
1238
{
204,231✔
1239
    if (column_key.is_dictionary()) {
204,231✔
1240
        auto spec_ndx = colkey2spec_ndx(column_key);
194,553✔
1241
        REALM_ASSERT_3(spec_ndx, <, get_column_count());
194,553✔
1242
        return m_spec.get_dictionary_key_type(spec_ndx);
194,553✔
1243
    }
194,553✔
1244
    return type_String;
9,678✔
1245
}
204,231✔
1246

1247

1248
inline void Table::revive(Replication* const* repl, Allocator& alloc, bool writable)
1249
{
2,167,317✔
1250
    m_alloc.switch_underlying_allocator(alloc);
2,167,317✔
1251
    m_alloc.update_from_underlying_allocator(writable);
2,167,317✔
1252
    m_repl = repl;
2,167,317✔
1253
    m_own_ref = TableRef(this, m_alloc.get_instance_version());
2,167,317✔
1254

1255
    // since we're rebinding to a new table, we'll bump version counters
1256
    // Possible optimization: save version counters along with the table data
1257
    // and restore them from there. Should decrease amount of non-necessary
1258
    // recomputations of any queries relying on this table.
1259
    bump_content_version();
2,167,317✔
1260
    bump_storage_version();
2,167,317✔
1261
    // we assume all other accessors are detached, so we're done.
1262
}
2,167,317✔
1263

1264
inline Allocator& Table::get_alloc() const
1265
{
17,163,345✔
1266
    return m_alloc;
17,163,345✔
1267
}
17,163,345✔
1268

1269
// For use by queries
1270
template <class T>
1271
inline Columns<T> Table::column(ColKey col_key, util::Optional<ExpressionComparisonType> cmp_type) const
1272
{
35,668✔
1273
    LinkChain lc(m_own_ref, cmp_type);
35,668✔
1274
    return lc.column<T>(col_key);
35,668✔
1275
}
35,668✔
1276

1277
template <class T>
1278
inline Columns<T> Table::column(const Table& origin, ColKey origin_col_key) const
1279
{
36✔
1280
    LinkChain lc(m_own_ref);
36✔
1281
    return lc.column<T>(origin, origin_col_key);
36✔
1282
}
36✔
1283

1284
template <class T>
1285
inline BacklinkCount<T> Table::get_backlink_count() const
1286
{
1287
    return BacklinkCount<T>(this, {});
1288
}
1289

1290
template <class T>
1291
SubQuery<T> Table::column(ColKey col_key, Query subquery) const
1292
{
62✔
1293
    LinkChain lc(m_own_ref);
62✔
1294
    return lc.column<T>(col_key, subquery);
62✔
1295
}
62✔
1296

1297
template <class T>
1298
SubQuery<T> Table::column(const Table& origin, ColKey origin_col_key, Query subquery) const
1299
{
4✔
1300
    LinkChain lc(m_own_ref);
4✔
1301
    return lc.column<T>(origin, origin_col_key, subquery);
4✔
1302
}
4✔
1303

1304
inline LinkChain Table::link(ColKey link_column) const
1305
{
20,552✔
1306
    LinkChain lc(m_own_ref);
20,552✔
1307
    lc.add(link_column);
20,552✔
1308

1309
    return lc;
20,552✔
1310
}
20,552✔
1311

1312
inline LinkChain Table::backlink(const Table& origin, ColKey origin_col_key) const
1313
{
2,984✔
1314
    auto backlink_col_key = origin.get_opposite_column(origin_col_key);
2,984✔
1315
    return link(backlink_col_key);
2,984✔
1316
}
2,984✔
1317

1318
inline bool Table::is_empty() const noexcept
1319
{
79,491✔
1320
    return size() == 0;
79,491✔
1321
}
79,491✔
1322

1323
inline ConstTableRef Table::get_link_target(ColKey col_key) const noexcept
1324
{
236,535✔
1325
    return const_cast<Table*>(this)->get_link_target(col_key);
236,535✔
1326
}
236,535✔
1327

1328
inline bool Table::is_group_level() const noexcept
1329
{
×
1330
    return bool(get_parent_group());
×
1331
}
×
1332

1333
inline bool Table::operator!=(const Table& t) const
1334
{
78✔
1335
    return !(*this == t); // Throws
78✔
1336
}
78✔
1337

1338
inline bool Table::is_link_type(ColumnType col_type) noexcept
1339
{
1,278,783✔
1340
    return col_type == col_type_Link;
1,278,783✔
1341
}
1,278,783✔
1342

1343
inline void Table::set_ndx_in_parent(size_t ndx_in_parent) noexcept
1344
{
×
1345
    REALM_ASSERT(m_top.is_attached());
×
1346
    m_top.set_ndx_in_parent(ndx_in_parent);
×
1347
}
×
1348

1349
inline size_t Table::colkey2spec_ndx(ColKey key) const
1350
{
8,656,356✔
1351
    auto leaf_idx = key.get_index();
8,656,356✔
1352
    REALM_ASSERT(leaf_idx.val < m_leaf_ndx2spec_ndx.size());
8,656,356✔
1353
    return m_leaf_ndx2spec_ndx[leaf_idx.val];
8,656,356✔
1354
}
8,656,356✔
1355

1356
inline ColKey Table::spec_ndx2colkey(size_t spec_ndx) const
1357
{
43,772,346✔
1358
    REALM_ASSERT(spec_ndx < m_spec_ndx2leaf_ndx.size());
43,772,346✔
1359
    return m_leaf_ndx2colkey[m_spec_ndx2leaf_ndx[spec_ndx].val];
43,772,346✔
1360
}
43,772,346✔
1361

1362
inline size_t Table::leaf_ndx2spec_ndx(ColKey::Idx leaf_ndx) const
1363
{
8,775,657✔
1364
    REALM_ASSERT(leaf_ndx.val < m_leaf_ndx2colkey.size());
8,775,657✔
1365
    return m_leaf_ndx2spec_ndx[leaf_ndx.val];
8,775,657✔
1366
}
8,775,657✔
1367

1368
inline ColKey::Idx Table::spec_ndx2leaf_ndx(size_t spec_ndx) const
1369
{
×
1370
    REALM_ASSERT(spec_ndx < m_spec_ndx2leaf_ndx.size());
×
1371
    return m_spec_ndx2leaf_ndx[spec_ndx];
×
1372
}
×
1373

1374
inline ColKey Table::leaf_ndx2colkey(ColKey::Idx leaf_ndx) const
1375
{
18,267✔
1376
    // this may be called with leaf indicies outside of the table. This can happen
1377
    // when a column is removed from the mapping, but space for it is still reserved
1378
    // at leaf level. Operations on Cluster and ClusterTree which walks the columns
1379
    // based on leaf indicies may ask for colkeys which are no longer valid.
1380
    if (leaf_ndx.val < m_leaf_ndx2spec_ndx.size())
18,267✔
1381
        return m_leaf_ndx2colkey[leaf_ndx.val];
18,267✔
1382
    else
×
1383
        return ColKey();
×
1384
}
18,267✔
1385

1386
bool inline Table::valid_column(ColKey col_key) const noexcept
1387
{
301,196,148✔
1388
    if (col_key == ColKey())
301,196,148✔
1389
        return false;
6✔
1390
    ColKey::Idx leaf_idx = col_key.get_index();
301,196,142✔
1391
    if (leaf_idx.val >= m_leaf_ndx2colkey.size())
301,196,142✔
1392
        return false;
691,278✔
1393
    return col_key == m_leaf_ndx2colkey[leaf_idx.val];
300,504,864✔
1394
}
301,196,142✔
1395

1396
// The purpose of this class is to give internal access to some, but
1397
// not all of the non-public parts of the Table class.
1398
class _impl::TableFriend {
1399
public:
1400
    static Spec& get_spec(Table& table) noexcept
1401
    {
×
1402
        return table.m_spec;
×
1403
    }
×
1404

1405
    static const Spec& get_spec(const Table& table) noexcept
1406
    {
×
1407
        return table.m_spec;
×
1408
    }
×
1409

1410
    static TableRef get_opposite_link_table(const Table& table, ColKey col_key);
1411

1412
    static Group* get_parent_group(const Table& table) noexcept
1413
    {
5,089✔
1414
        return table.get_parent_group();
5,089✔
1415
    }
5,089✔
1416

1417
    static void remove_recursive(Table& table, CascadeState& rows)
1418
    {
4,614✔
1419
        table.remove_recursive(rows); // Throws
4,614✔
1420
    }
4,614✔
1421

1422
    static void batch_erase_objects(Table& table, std::vector<ObjKey>& keys)
1423
    {
6,342✔
1424
        table.batch_erase_objects(keys); // Throws
6,342✔
1425
    }
6,342✔
1426
    static void batch_erase_rows(Table& table, const KeyColumn& keys)
1427
    {
558✔
1428
        table.batch_erase_rows(keys); // Throws
558✔
1429
    }
558✔
1430
    static ObjKey global_to_local_object_id_hashed(const Table& table, GlobalKey global_id)
1431
    {
×
1432
        return table.global_to_local_object_id_hashed(global_id);
×
1433
    }
×
1434
};
1435

1436
} // namespace realm
1437

1438
#endif // REALM_TABLE_HPP
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

© 2026 Coveralls, Inc