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

realm / realm-core / 1735

05 Oct 2023 02:34PM UTC coverage: 91.591%. First build
1735

push

Evergreen

web-flow
Filter TableView using custom function (#7020)

The function TableView::get_num_results_excluded_by_limit() has been removed.
It was supposedly only used in tests. The optimization that takes a following
limit descriptor in account already when running the query makes it
impossible to return that value.
---------
Co-authored-by: Kirill Burtsev <kirill.burtsev@mongodb.com>

94336 of 173524 branches covered (0.0%)

311 of 326 new or added lines in 11 files covered. (95.4%)

230582 of 251752 relevant lines covered (91.59%)

6668714.69 hits per line

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

91.11
/src/realm/object-store/results.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2015 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/object-store/results.hpp>
20

21
#include <realm/object-store/impl/realm_coordinator.hpp>
22
#include <realm/object-store/impl/results_notifier.hpp>
23
#include <realm/object-store/audit.hpp>
24
#include <realm/object-store/object_schema.hpp>
25
#include <realm/object-store/object_store.hpp>
26
#include <realm/object-store/schema.hpp>
27
#include <realm/object-store/class.hpp>
28
#include <realm/object-store/sectioned_results.hpp>
29

30
#include <realm/set.hpp>
31

32
#include <stdexcept>
33

34
namespace realm {
35
[[noreturn]] static void unsupported_operation(ColKey column, Table const& table, const char* operation)
36
{
1,332✔
37
    auto type = ObjectSchema::from_core_type(column);
1,332✔
38
    std::string_view collection_type = column.is_collection() ? collection_type_name(column) : "property";
1,038✔
39
    const char* column_type = string_for_property_type(type & ~PropertyType::Collection);
1,332✔
40
    throw IllegalOperation(util::format("Operation '%1' not supported for %2%3 %4 '%5.%6'", operation, column_type,
1,332✔
41
                                        column.is_nullable() ? "?" : "", collection_type, table.get_class_name(),
1,004✔
42
                                        table.get_column_name(column)));
1,332✔
43
}
1,332✔
44

45
Results::Results() = default;
3,210✔
46
Results::~Results() = default;
64,471✔
47

48
Results::Results(SharedRealm r, Query q, DescriptorOrdering o)
49
    : m_realm(std::move(r))
50
    , m_query(std::move(q))
51
    , m_table(m_query.get_table())
52
    , m_table_view(m_table)
53
    , m_descriptor_ordering(std::move(o))
54
    , m_mode(Mode::Query)
55
    , m_mutex(m_realm && m_realm->is_frozen())
56
{
21,775✔
57
}
21,775✔
58

59
Results::Results(const Class& cls)
60
    : Results(cls.get_realm(), cls.get_table())
61
{
×
62
}
×
63

64

65
Results::Results(SharedRealm r, ConstTableRef table)
66
    : m_realm(std::move(r))
67
    , m_table(table)
68
    , m_table_view(m_table)
69
    , m_mode(Mode::Table)
70
    , m_mutex(m_realm && m_realm->is_frozen())
71
{
7,017✔
72
}
7,017✔
73

74
Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<CollectionBase> coll, util::Optional<Query> q,
75
                 SortDescriptor s)
76
    : m_realm(std::move(r))
77
    , m_table(coll->get_target_table())
78
    , m_collection(std::move(coll))
79
    , m_mode(Mode::Collection)
80
    , m_mutex(m_realm && m_realm->is_frozen())
81
{
13,885✔
82
    if (q) {
13,885✔
83
        m_query = std::move(*q);
17✔
84
        m_mode = Mode::Query;
17✔
85
    }
17✔
86
    m_descriptor_ordering.append_sort(std::move(s));
13,885✔
87
}
13,885✔
88

89
Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<CollectionBase> coll, DescriptorOrdering o)
90
    : m_realm(std::move(r))
91
    , m_table(coll->get_target_table())
92
    , m_descriptor_ordering(std::move(o))
93
    , m_collection(std::move(coll))
94
    , m_mode(Mode::Collection)
95
    , m_mutex(m_realm && m_realm->is_frozen())
96
{
2,614✔
97
}
2,614✔
98

99
Results::Results(std::shared_ptr<Realm> r, TableView tv, DescriptorOrdering o)
100
    : m_realm(std::move(r))
101
    , m_table_view(std::move(tv))
102
    , m_descriptor_ordering(std::move(o))
103
    , m_mode(Mode::TableView)
104
    , m_mutex(m_realm && m_realm->is_frozen())
105
{
106✔
106
    m_table = m_table_view.get_parent();
106✔
107
}
106✔
108

109
Results::Results(const Results&) = default;
2,916✔
110
Results& Results::operator=(const Results&) = default;
×
111
Results::Results(Results&&) = default;
12,948✔
112
Results& Results::operator=(Results&&) = default;
3,510✔
113

114
Results::Mode Results::get_mode() const noexcept
115
{
52✔
116
    util::CheckedUniqueLock lock(m_mutex);
52✔
117
    return m_mode;
52✔
118
}
52✔
119

120
bool Results::is_valid() const
121
{
145,723✔
122
    if (m_realm) {
145,723✔
123
        m_realm->verify_thread();
145,669✔
124
    }
145,669✔
125

72,497✔
126
    // Here we cannot just use if (m_table) as it combines a check if the
72,497✔
127
    // reference contains a value and if that value is valid.
72,497✔
128
    // First we check if a table is referenced ...
72,497✔
129
    if (m_table.unchecked_ptr() != nullptr)
145,723✔
130
        return bool(m_table); // ... and then we check if it is valid
105,651✔
131

20,036✔
132
    if (m_collection)
40,072✔
133
        // Since m_table was not set, this is a collection of primitives
19,989✔
134
        // and the results validity depend directly on the collection
19,989✔
135
        return m_collection->is_attached();
39,978✔
136

47✔
137
    return true;
94✔
138
}
94✔
139

140
void Results::validate_read() const
141
{
139,859✔
142
    // is_valid ensures that we're on the correct thread.
69,565✔
143
    if (!is_valid())
139,859✔
144
        throw StaleAccessor("Access to invalidated Results objects");
16✔
145
}
139,859✔
146

147
void Results::validate_write() const
148
{
548✔
149
    validate_read();
548✔
150
    if (!m_realm || !m_realm->is_in_transaction())
548✔
151
        throw WrongTransactionState("Must be in a write transaction");
4✔
152
}
548✔
153

154
size_t Results::size()
155
{
41,315✔
156
    util::CheckedUniqueLock lock(m_mutex);
41,315✔
157
    return do_size();
41,315✔
158
}
41,315✔
159

160
size_t Results::do_size()
161
{
41,907✔
162
    validate_read();
41,907✔
163
    ensure_up_to_date(EvaluateMode::Count);
41,907✔
164
    switch (m_mode) {
41,907✔
165
        case Mode::Empty:
12✔
166
            return 0;
12✔
167
        case Mode::Table:
4,687✔
168
            return m_table ? m_table->size() : 0;
4,683✔
169
        case Mode::Collection:
6,456✔
170
            return m_list_indices ? m_list_indices->size() : m_collection->size();
5,273✔
171
        case Mode::Query:
26,436✔
172
            return m_query.count(m_descriptor_ordering);
26,436✔
173
        case Mode::TableView:
4,310✔
174
            return m_table_view.size();
4,310✔
175
    }
×
176
    REALM_COMPILER_HINT_UNREACHABLE();
×
177
}
×
178

179
const ObjectSchema& Results::get_object_schema() const
180
{
2,790✔
181
    validate_read();
2,790✔
182

1,395✔
183
    auto object_schema = m_object_schema.load();
2,790✔
184
    if (!object_schema) {
2,790✔
185
        REALM_ASSERT(m_realm);
2,704✔
186
        auto it = m_realm->schema().find(get_object_type());
2,704✔
187
        REALM_ASSERT(it != m_realm->schema().end());
2,704✔
188
        m_object_schema = object_schema = &*it;
2,704✔
189
    }
2,704✔
190

1,395✔
191
    return *object_schema;
2,790✔
192
}
2,790✔
193

194
StringData Results::get_object_type() const noexcept
195
{
2,746✔
196
    if (!m_table) {
2,746✔
197
        return StringData();
42✔
198
    }
42✔
199

1,352✔
200
    return ObjectStore::object_type_for_table_name(m_table->get_name());
2,704✔
201
}
2,704✔
202

203
bool Results::has_changed() REQUIRES(!m_mutex)
204
{
4,818✔
205
    util::CheckedUniqueLock lock(m_mutex);
4,818✔
206
    if (m_collection)
4,818✔
207
        return m_last_collection_content_version != m_collection->get_obj().get_table()->get_content_version();
4,376✔
208

221✔
209
    return m_table_view.has_changed();
442✔
210
}
442✔
211

212
void Results::ensure_up_to_date(EvaluateMode mode)
213
{
115,253✔
214
    if (m_update_policy == UpdatePolicy::Never) {
115,253✔
215
        REALM_ASSERT(m_mode == Mode::TableView);
450✔
216
        return;
450✔
217
    }
450✔
218

57,037✔
219
    switch (m_mode) {
114,803✔
220
        case Mode::Empty:
22✔
221
            return;
22✔
222
        case Mode::Table:
13,685✔
223
            // Tables are always up-to-date
6,807✔
224
            return;
13,685✔
225
        case Mode::Collection: {
29,244✔
226
            // Collections themselves are always up-to-date, but we may need
14,622✔
227
            // to apply sort descriptors
14,622✔
228
            if (m_descriptor_ordering.is_empty())
29,244✔
229
                return;
16,544✔
230

6,350✔
231
            // Collections of objects are sorted/distincted by converting them
6,350✔
232
            // to a TableView
6,350✔
233
            if (do_get_type() == PropertyType::Object) {
12,700✔
234
                m_query = do_get_query();
172✔
235
                m_mode = Mode::Query;
172✔
236
                ensure_up_to_date(mode);
172✔
237
                return;
172✔
238
            }
172✔
239

6,264✔
240
            // Other types we do manually via m_list_indices. Ideally we just
6,264✔
241
            // pull the updated one from the notifier, but we can't if it hasn't
6,264✔
242
            // run yet or if we're currently in a write transaction (as we can't
6,264✔
243
            // know if any relevant changes have happened so far in the write).
6,264✔
244
            if (m_notifier && m_notifier->get_list_indices(m_list_indices) && !m_realm->is_in_transaction())
12,528✔
245
                return;
344✔
246

6,092✔
247
            bool needs_update = m_collection->has_changed();
12,184✔
248
            if (!m_list_indices) {
12,184✔
249
                m_list_indices = std::vector<size_t>{};
1,324✔
250
                needs_update = true;
1,324✔
251
            }
1,324✔
252
            if (!needs_update)
12,184✔
253
                return;
10,630✔
254

777✔
255
            m_last_collection_content_version = m_collection->get_obj().get_table()->get_content_version();
1,554✔
256

777✔
257
            if (m_collection->is_empty()) {
1,554✔
258
                m_list_indices->clear();
86✔
259
                return;
86✔
260
            }
86✔
261

734✔
262
            // Note that for objects this would be wrong as .sort().distinct()
734✔
263
            // and distinct().sort() can pick different objects which have the
734✔
264
            // same value in the column being distincted, but that's not
734✔
265
            // applicable to non-objects. If there's two equal strings, it doesn't
734✔
266
            // matter which we pick.
734✔
267
            util::Optional<bool> sort_order;
1,468✔
268
            bool do_distinct = false;
1,468✔
269
            auto sz = m_descriptor_ordering.size();
1,468✔
270
            for (size_t i = 0; i < sz; i++) {
3,112✔
271
                auto descr = m_descriptor_ordering[i];
1,644✔
272
                if (descr->get_type() == DescriptorType::Sort)
1,644✔
273
                    sort_order = static_cast<const SortDescriptor*>(descr)->is_ascending(0);
1,300✔
274
                if (descr->get_type() == DescriptorType::Distinct)
1,644✔
275
                    do_distinct = true;
344✔
276
            }
1,644✔
277

734✔
278
            if (do_distinct)
1,468✔
279
                m_collection->distinct(*m_list_indices, sort_order);
344✔
280
            else if (sort_order)
1,124✔
281
                m_collection->sort(*m_list_indices, *sort_order);
1,124✔
282
            return;
1,468✔
283
        }
1,468✔
284

734✔
285
        case Mode::Query:
39,372✔
286
            // Everything except for size() requires evaluating the Query and
19,395✔
287
            // getting a TableView, and size() does as well if distinct is involved.
19,395✔
288
            if (mode == EvaluateMode::Count && !m_descriptor_ordering.will_apply_distinct()) {
39,372✔
289
                m_query.sync_view_if_needed();
26,436✔
290
                return;
26,436✔
291
            }
26,436✔
292

6,336✔
293
            // First we check if we ran the Query in the background and can
6,336✔
294
            // just use that
6,336✔
295
            if (m_notifier && m_notifier->get_tableview(m_table_view)) {
12,936✔
296
                m_mode = Mode::TableView;
2,264✔
297
                if (auto audit = m_realm->audit_context())
2,264✔
298
                    audit->record_query(m_realm->read_transaction_version(), m_table_view);
×
299
                return;
2,264✔
300
            }
2,264✔
301

5,204✔
302
            // We have to actually run the Query locally. We have an option
5,204✔
303
            // to disable this for testing purposes as it's otherwise very
5,204✔
304
            // difficult to determine if the async query is actually being
5,204✔
305
            // used.
5,204✔
306
            m_query.sync_view_if_needed();
10,672✔
307
            if (m_update_policy != UpdatePolicy::AsyncOnly)
10,672✔
308
                m_table_view = m_query.find_all(m_descriptor_ordering);
10,650✔
309
            m_mode = Mode::TableView;
10,672✔
310
            if (auto audit = m_realm->audit_context())
10,672✔
311
                audit->record_query(m_realm->read_transaction_version(), m_table_view);
159✔
312

5,204✔
313
            // Unless we're creating a snapshot, create an async notifier that'll
5,204✔
314
            // rerun this query in the background.
5,204✔
315
            if (mode != EvaluateMode::Snapshot && !m_notifier)
10,672✔
316
                prepare_async(ForCallback{false});
10,421✔
317
            return;
10,672✔
318

5,204✔
319
        case Mode::TableView:
32,480✔
320
            // Unless we're creating a snapshot, create an async notifier that'll
16,202✔
321
            // rerun this query in the background.
16,202✔
322
            if (mode != EvaluateMode::Snapshot && !m_notifier)
32,480✔
323
                prepare_async(ForCallback{false});
22,446✔
324
            // First check if we have an up-to-date TableView waiting for us
5,017✔
325
            // which was generated on the background thread
5,017✔
326
            else if (m_notifier)
10,034✔
327
                m_notifier->get_tableview(m_table_view);
10,020✔
328
            // This option is here so that tests can verify that the notifier
16,202✔
329
            // is actually being used.
16,202✔
330
            if (m_update_policy == UpdatePolicy::Auto)
32,480✔
331
                m_table_view.sync_if_needed();
32,438✔
332
            if (auto audit = m_realm->audit_context())
32,480✔
333
                audit->record_query(m_realm->read_transaction_version(), m_table_view);
×
334
            return;
32,480✔
335
    }
114,803✔
336
}
114,803✔
337

338
size_t Results::actual_index(size_t ndx) const noexcept
339
{
14,978✔
340
    if (auto& indices = m_list_indices) {
14,978✔
341
        return ndx < indices->size() ? (*indices)[ndx] : npos;
9,188✔
342
    }
9,188✔
343
    return ndx;
5,790✔
344
}
5,790✔
345

346
template <typename T>
347
static T get_unwraped(CollectionBase& collection, size_t ndx)
348
{
7,622✔
349
    using U = typename util::RemoveOptional<T>::type;
7,622✔
350
    Mixed mixed = collection.get_any(ndx);
7,622✔
351
    if (!mixed.is_null())
7,622✔
352
        return mixed.get<U>();
6,828✔
353
    return BPlusTree<T>::default_value(collection.get_col_key().is_nullable());
794✔
354
}
794✔
355

356
template <typename T>
357
util::Optional<T> Results::try_get(size_t ndx)
358
{
8,126✔
359
    validate_read();
8,126✔
360
    ensure_up_to_date();
8,126✔
361
    if (m_mode == Mode::Collection) {
8,126✔
362
        ndx = actual_index(ndx);
8,126✔
363
        if (ndx < m_collection->size()) {
8,126✔
364
            return get_unwraped<T>(*m_collection, ndx);
7,622✔
365
        }
7,622✔
366
    }
504✔
367
    return util::none;
504✔
368
}
504✔
369

370
Results::IteratorWrapper::IteratorWrapper(IteratorWrapper const& rgt)
371
{
2,916✔
372
    *this = rgt;
2,916✔
373
}
2,916✔
374

375
Results::IteratorWrapper& Results::IteratorWrapper::operator=(IteratorWrapper const& rgt)
376
{
2,916✔
377
    if (rgt.m_it)
2,916✔
378
        m_it = std::make_unique<Table::Iterator>(*rgt.m_it);
×
379
    return *this;
2,916✔
380
}
2,916✔
381

382
Obj Results::IteratorWrapper::get(Table const& table, size_t ndx)
383
{
8,846✔
384
    // Using a Table iterator is much faster for repeated access into a table
4,423✔
385
    // than indexing into it as the iterator caches the cluster the last accessed
4,423✔
386
    // object is stored in, but creating the iterator is somewhat expensive.
4,423✔
387
    if (!m_it) {
8,846✔
388
        if (table.size() <= 5)
700✔
389
            return const_cast<Table&>(table).get_object(ndx);
668✔
390
        m_it = std::make_unique<Table::Iterator>(table.begin());
32✔
391
    }
32✔
392
    m_it->go(ndx);
8,512✔
393
    return **m_it;
8,178✔
394
}
8,846✔
395

396
template <>
397
util::Optional<Obj> Results::try_get(size_t row_ndx)
398
{
50,017✔
399
    validate_read();
50,017✔
400
    ensure_up_to_date();
50,017✔
401
    switch (m_mode) {
50,017✔
402
        case Mode::Empty:
8✔
403
            break;
8✔
404
        case Mode::Table:
8,858✔
405
            if (m_table && row_ndx < m_table->size())
8,858✔
406
                return m_table_iterator.get(*m_table, row_ndx);
8,840✔
407
            break;
18✔
408
        case Mode::Collection:
890✔
409
            if (row_ndx < m_collection->size()) {
890✔
410
                auto m = m_collection->get_any(row_ndx);
866✔
411
                if (m.is_null())
866✔
412
                    return Obj();
84✔
413
                if (m.get_type() == type_Link)
782✔
414
                    return m_table->get_object(m.get<ObjKey>());
×
415
                if (m.get_type() == type_TypedLink)
782✔
416
                    return m_table->get_parent_group()->get_object(m.get_link());
782✔
417
            }
24✔
418
            break;
24✔
419
        case Mode::Query:
12✔
420
            REALM_UNREACHABLE();
×
421
        case Mode::TableView:
40,261✔
422
            if (row_ndx >= m_table_view.size())
40,261✔
423
                break;
9,114✔
424
            return m_table_view.get_object(row_ndx);
31,147✔
425
    }
9,164✔
426
    return util::none;
9,164✔
427
}
9,164✔
428

429
Mixed Results::get_any(size_t ndx)
430
{
7,126✔
431
    util::CheckedUniqueLock lock(m_mutex);
7,126✔
432
    validate_read();
7,126✔
433
    ensure_up_to_date();
7,126✔
434
    switch (m_mode) {
7,126✔
435
        case Mode::Empty:
2✔
436
            break;
2✔
437
        case Mode::Table: {
6✔
438
            // Validity of m_table is checked in validate_read() above, so we
3✔
439
            // can skip all the checks here (which requires not using the
3✔
440
            // Mixed(Obj()) constructor)
3✔
441
            auto table = m_table.unchecked_ptr();
6✔
442
            if (ndx < table->size())
6✔
443
                return ObjLink(table->get_key(), m_table_iterator.get(*table, ndx).get_key());
6✔
444
            break;
×
445
        }
×
446
        case Mode::Collection:
6,528✔
447
            if (auto actual = actual_index(ndx); actual < m_collection->size())
6,528✔
448
                return m_collection->get_any(actual);
6,528✔
449
            break;
×
450
        case Mode::Query:
✔
451
            REALM_UNREACHABLE(); // should always be in TV mode
×
452
        case Mode::TableView: {
586✔
453
            if (ndx >= m_table_view.size())
586✔
454
                break;
4✔
455
            if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_obj_valid(ndx))
582✔
456
                return {};
4✔
457
            auto obj_key = m_table_view.get_key(ndx);
578✔
458
            return Mixed(ObjLink(m_table->get_key(), obj_key));
578✔
459
        }
578✔
460
    }
6✔
461
    throw OutOfBounds{"get_any() on Results", ndx, do_size()};
6✔
462
}
6✔
463

464
std::pair<StringData, Mixed> Results::get_dictionary_element(size_t ndx)
465
{
324✔
466
    util::CheckedUniqueLock lock(m_mutex);
324✔
467
    REALM_ASSERT(m_mode == Mode::Collection);
324✔
468
    auto& dict = static_cast<Dictionary&>(*m_collection);
324✔
469
    REALM_ASSERT(typeid(dict) == typeid(Dictionary));
324✔
470

162✔
471
    ensure_up_to_date();
324✔
472
    if (size_t actual = actual_index(ndx); actual < dict.size()) {
324✔
473
        auto val = dict.get_pair(ndx);
324✔
474
        return {val.first.get_string(), val.second};
324✔
475
    }
324✔
476
    throw OutOfBounds{"get_dictionary_element() on Results", ndx, dict.size()};
×
477
}
×
478

479
template <typename T>
480
T Results::get(size_t row_ndx)
481
{
42,738✔
482
    util::CheckedUniqueLock lock(m_mutex);
42,738✔
483
    if (auto row = try_get<T>(row_ndx)) {
42,738✔
484
        return *row;
42,614✔
485
    }
42,614✔
486
    throw OutOfBounds{"get() on Results", row_ndx, do_size()};
124✔
487
}
124✔
488

489
template <typename T>
490
util::Optional<T> Results::first()
491
{
14,943✔
492
    util::CheckedUniqueLock lock(m_mutex);
14,943✔
493
    return try_get<T>(0);
14,943✔
494
}
14,943✔
495

496
template <typename T>
497
util::Optional<T> Results::last()
498
{
462✔
499
    util::CheckedUniqueLock lock(m_mutex);
462✔
500
    validate_read();
462✔
501
    if (m_mode == Mode::Query)
462✔
502
        ensure_up_to_date(); // avoid running the query twice (for size() and for get())
12✔
503
    return try_get<T>(do_size() - 1);
462✔
504
}
462✔
505

506
void Results::evaluate_query_if_needed(bool wants_notifications)
507
{
26✔
508
    util::CheckedUniqueLock lock(m_mutex);
26✔
509
    validate_read();
26✔
510
    ensure_up_to_date(wants_notifications ? EvaluateMode::Normal : EvaluateMode::Snapshot);
25✔
511
}
26✔
512

513
template <>
514
size_t Results::index_of(Obj const& obj)
515
{
146✔
516
    if (!obj.is_valid()) {
146✔
517
        throw StaleAccessor{"Attempting to access an invalid object"};
12✔
518
    }
12✔
519
    if (m_table && obj.get_table() != m_table) {
134✔
520
        throw InvalidArgument(ErrorCodes::ObjectTypeMismatch,
26✔
521
                              util::format("Object of type '%1' does not match Results type '%2'",
26✔
522
                                           obj.get_table()->get_class_name(), m_table->get_class_name()));
26✔
523
    }
26✔
524
    return index_of(Mixed(obj.get_key()));
108✔
525
}
108✔
526

527
template <>
528
size_t Results::index_of(Mixed const& value)
529
{
2,276✔
530
    util::CheckedUniqueLock lock(m_mutex);
2,276✔
531
    validate_read();
2,276✔
532
    ensure_up_to_date();
2,276✔
533

1,138✔
534
    if (value.is_type(type_TypedLink)) {
2,276✔
535
        if (m_table && m_table->get_key() != value.get_link().get_table_key()) {
2✔
536
            return realm::not_found;
×
537
        }
×
538
    }
2,276✔
539

1,138✔
540
    switch (m_mode) {
2,276✔
541
        case Mode::Empty:
10✔
542
        case Mode::Table:
20✔
543
            if (value.is_type(type_Link, type_TypedLink)) {
20✔
544
                return m_table->get_object_ndx(value.get<ObjKey>());
20✔
545
            }
20✔
546
            break;
×
547
        case Mode::Collection:
2,208✔
548
            if (m_list_indices) {
2,208✔
549
                for (size_t i = 0; i < m_list_indices->size(); ++i) {
1,988✔
550
                    if (value == m_collection->get_any(m_list_indices->at(i)))
1,988✔
551
                        return i;
580✔
552
                }
1,988✔
553
                return not_found;
290✔
554
            }
1,628✔
555
            return m_collection->find_any(value);
1,628✔
556
        case Mode::Query:
814✔
557
        case Mode::TableView:
48✔
558
            if (value.is_type(type_Link, type_TypedLink)) {
48✔
559
                return m_table_view.find_by_source_ndx(value.get<ObjKey>());
44✔
560
            }
44✔
561
            break;
4✔
562
    }
4✔
563
    return realm::not_found;
4✔
564
}
4✔
565

566
size_t Results::index_of(Query&& q)
567
{
×
568
    if (m_descriptor_ordering.will_apply_sort()) {
×
569
        Results filtered(filter(std::move(q)));
×
570
        filtered.assert_unlocked();
×
571
        auto first = filtered.first();
×
572
        return first ? index_of(*first) : not_found;
×
573
    }
×
574

575
    auto query = get_query().and_query(std::move(q));
×
576
    query.sync_view_if_needed();
×
577
    ObjKey row = query.find();
×
578
    return row ? index_of(const_cast<Table&>(*m_table).get_object(row)) : not_found;
×
579
}
×
580

581
namespace {
582
struct CollectionAggregateAdaptor {
583
    const CollectionBase& list;
584
    util::Optional<Mixed> min(ColKey)
585
    {
472✔
586
        return list.min();
472✔
587
    }
472✔
588
    util::Optional<Mixed> max(ColKey)
589
    {
472✔
590
        return list.max();
472✔
591
    }
472✔
592
    util::Optional<Mixed> sum(ColKey)
593
    {
448✔
594
        return list.sum();
448✔
595
    }
448✔
596
    util::Optional<Mixed> avg(ColKey)
597
    {
448✔
598
        return list.avg();
448✔
599
    }
448✔
600
};
601
} // anonymous namespace
602

603
template <typename AggregateFunction>
604
util::Optional<Mixed> Results::aggregate(ColKey column, const char* name, AggregateFunction&& func)
605
{
3,950✔
606
    util::CheckedUniqueLock lock(m_mutex);
3,950✔
607
    validate_read();
3,950✔
608
    if (!m_table && !m_collection)
3,950✔
609
        return none;
32✔
610

1,959✔
611
    ensure_up_to_date();
3,918✔
612
    std::optional<Mixed> ret;
3,918✔
613
    switch (m_mode) {
3,918✔
614
        case Mode::Table:
112✔
615
            ret = func(*m_table);
112✔
616
            break;
112✔
617
        case Mode::Query:
✔
618
            ret = func(m_query);
×
619
            break;
×
620
        case Mode::Collection:
3,554✔
621
            if (do_get_type() != PropertyType::Object)
3,554✔
622
                ret = func(CollectionAggregateAdaptor{*m_collection});
1,840✔
623
            else
1,714✔
624
                ret = func(do_get_query());
1,714✔
625
            break;
3,554✔
626
        default:
252✔
627
            ret = func(m_table_view);
252✔
628
            break;
252✔
629
    }
3,870✔
630

1,935✔
631
    // `none` indicates that it's an unsupported operation for the column type.
1,935✔
632
    // `some(null)` indicates that there's no rows in the thing being aggregated
1,935✔
633
    // Any other value is just the result to return
1,935✔
634
    if (ret) {
3,870✔
635
        return ret->is_null() ? std::nullopt : std::optional(*ret);
2,022✔
636
    }
2,538✔
637

666✔
638
    // We need to report the column and table actually being aggregated on,
666✔
639
    // which is the collection if it's not a link collection and the target
666✔
640
    // of the links otherwise
666✔
641
    if (m_mode == Mode::Collection && do_get_type() != PropertyType::Object) {
1,332✔
642
        unsupported_operation(m_collection->get_col_key(), *m_collection->get_table(), name);
704✔
643
    }
704✔
644
    else {
628✔
645
        unsupported_operation(column, *m_table, name);
628✔
646
    }
628✔
647
}
1,332✔
648

649
util::Optional<Mixed> Results::max(ColKey column)
650
{
1,010✔
651
    return aggregate(column, "max", [column](auto&& helper) {
1,006✔
652
        return helper.max(column);
1,002✔
653
    });
1,002✔
654
}
1,010✔
655

656
util::Optional<Mixed> Results::min(ColKey column)
657
{
1,010✔
658
    return aggregate(column, "min", [column](auto&& helper) {
1,006✔
659
        return helper.min(column);
1,002✔
660
    });
1,002✔
661
}
1,010✔
662

663
util::Optional<Mixed> Results::sum(ColKey column)
664
{
968✔
665
    return aggregate(column, "sum", [column](auto&& helper) {
964✔
666
        return helper.sum(column);
960✔
667
    });
960✔
668
}
968✔
669

670
util::Optional<Mixed> Results::average(ColKey column)
671
{
962✔
672
    return aggregate(column, "average", [column](auto&& helper) {
958✔
673
        return helper.avg(column);
954✔
674
    });
954✔
675
}
962✔
676

677
void Results::clear()
678
{
496✔
679
    util::CheckedUniqueLock lock(m_mutex);
496✔
680
    validate_write();
496✔
681
    ensure_up_to_date();
496✔
682
    switch (m_mode) {
496✔
683
        case Mode::Empty:
✔
684
            return;
×
685
        case Mode::Table:
2✔
686
            const_cast<Table&>(*m_table).clear();
2✔
687
            break;
2✔
688
        case Mode::Query:
12✔
689
            // Not using Query:remove() because building the tableview and
12✔
690
            // clearing it is actually significantly faster
12✔
691
        case Mode::TableView:
24✔
692
            switch (m_update_policy) {
24✔
693
                case UpdatePolicy::Auto:
20✔
694
                    m_table_view.clear();
20✔
695
                    break;
20✔
696
                case UpdatePolicy::AsyncOnly:
2✔
697
                case UpdatePolicy::Never: {
4✔
698
                    // Copy the TableView because a frozen Results shouldn't let its size() change.
2✔
699
                    TableView copy(m_table_view);
4✔
700
                    copy.clear();
4✔
701
                    break;
4✔
702
                }
24✔
703
            }
24✔
704
            break;
24✔
705
        case Mode::Collection:
466✔
706
            if (auto list = dynamic_cast<LnkLst*>(m_collection.get()))
466✔
707
                list->remove_all_target_rows();
4✔
708
            else if (auto set = dynamic_cast<LnkSet*>(m_collection.get()))
462✔
709
                set->remove_all_target_rows();
84✔
710
            else
378✔
711
                m_collection->clear();
378✔
712
            break;
466✔
713
    }
496✔
714
}
496✔
715

716
PropertyType Results::get_type() const
717
{
10,546✔
718
    util::CheckedUniqueLock lock(m_mutex);
10,546✔
719
    validate_read();
10,546✔
720
    return do_get_type();
10,546✔
721
}
10,546✔
722

723
PropertyType Results::do_get_type() const
724
{
34,045✔
725
    switch (m_mode) {
34,045✔
726
        case Mode::Empty:
3,241✔
727
        case Mode::Query:
4,664✔
728
        case Mode::TableView:
5,349✔
729
        case Mode::Table:
6,637✔
730
            return PropertyType::Object;
6,637✔
731
        case Mode::Collection:
27,408✔
732
            return ObjectSchema::from_core_type(m_collection->get_col_key());
27,408✔
733
    }
×
734
    REALM_COMPILER_HINT_UNREACHABLE();
×
735
}
×
736

737
Query Results::get_query() const
738
{
3,866✔
739
    util::CheckedUniqueLock lock(m_mutex);
3,866✔
740
    return do_get_query();
3,866✔
741
}
3,866✔
742

743
const DescriptorOrdering& Results::get_ordering() const REQUIRES(!m_mutex)
744
{
16✔
745
    return m_descriptor_ordering;
16✔
746
}
16✔
747

748
ConstTableRef Results::get_table() const
749
{
18✔
750
    util::CheckedUniqueLock lock(m_mutex);
18✔
751
    validate_read();
18✔
752
    switch (m_mode) {
18✔
753
        case Mode::Empty:
7✔
754
        case Mode::Query:
14✔
755
            return const_cast<Query&>(m_query).get_table();
14✔
756
        case Mode::TableView:
7✔
757
            return m_table_view.get_target_table();
×
758
        case Mode::Collection:
7✔
759
            return m_collection->get_target_table();
×
760
        case Mode::Table:
9✔
761
            return m_table;
4✔
762
    }
×
763
    REALM_COMPILER_HINT_UNREACHABLE();
×
764
}
×
765

766
Query Results::do_get_query() const
767
{
8,508✔
768
    validate_read();
8,508✔
769
    switch (m_mode) {
8,508✔
770
        case Mode::Empty:
1,943✔
771
        case Mode::Query:
3,452✔
772
        case Mode::TableView: {
3,886✔
773
            if (const_cast<Query&>(m_query).get_table())
3,886✔
774
                return m_query;
3,880✔
775

3✔
776
            // A TableView has an associated Query if it was produced by Query::find_all
3✔
777
            if (auto& query = m_table_view.get_query()) {
6✔
778
                return *query;
6✔
779
            }
6✔
780

781
            // The TableView has no associated query so create one with no conditions that is restricted
782
            // to the rows in the TableView.
783
            if (m_update_policy == UpdatePolicy::Auto) {
×
784
                m_table_view.sync_if_needed();
×
785
            }
×
786
            return Query(m_table, std::make_unique<TableView>(m_table_view));
×
787
        }
×
788
        case Mode::Collection:
2,050✔
789
            if (auto list = dynamic_cast<ObjList*>(m_collection.get())) {
2,050✔
790
                return m_table->where(*list);
1,420✔
791
            }
1,420✔
792
            if (auto dict = dynamic_cast<Dictionary*>(m_collection.get())) {
630✔
793
                if (dict->get_value_data_type() == type_Link) {
546✔
794
                    return m_table->where(*dict);
504✔
795
                }
504✔
796
            }
126✔
797
            return m_query;
126✔
798
        case Mode::Table:
2,572✔
799
            return m_table->where();
2,572✔
800
    }
×
801
    REALM_COMPILER_HINT_UNREACHABLE();
×
802
}
×
803

804
TableView Results::get_tableview()
805
{
×
806
    util::CheckedUniqueLock lock(m_mutex);
×
807
    validate_read();
×
808
    ensure_up_to_date();
×
809
    switch (m_mode) {
×
810
        case Mode::Empty:
×
811
        case Mode::Collection:
×
812
            return do_get_query().find_all();
×
813
        case Mode::Query:
×
814
        case Mode::TableView:
×
815
            return m_table_view;
×
816
        case Mode::Table:
×
817
            return m_table->where().find_all();
×
818
    }
×
819
    REALM_COMPILER_HINT_UNREACHABLE();
×
820
}
×
821

822
static std::vector<ExtendedColumnKey> parse_keypath(StringData keypath, Schema const& schema,
823
                                                    const ObjectSchema* object_schema)
824
{
2,580✔
825
    auto check = [&](bool condition, const char* fmt, auto... args) {
12,960✔
826
        if (!condition) {
12,960✔
827
            throw InvalidArgument(
36✔
828
                util::format("Cannot sort on key path '%1': %2.", keypath, util::format(fmt, args...)));
36✔
829
        }
36✔
830
    };
12,960✔
831
    auto is_sortable_type = [](PropertyType type) {
2,590✔
832
        return !is_collection(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data;
2,590✔
833
    };
2,590✔
834

1,290✔
835
    const char* begin = keypath.data();
2,580✔
836
    const char* end = keypath.data() + keypath.size();
2,580✔
837
    check(begin != end, "missing property name");
2,580✔
838

1,290✔
839
    std::vector<ExtendedColumnKey> indices;
2,580✔
840
    while (begin != end) {
5,186✔
841
        auto sep = std::find(begin, end, '.');
2,606✔
842
        check(sep != begin && sep + 1 != end, "missing property name");
2,606✔
843
        StringData key(begin, sep - begin);
2,606✔
844
        std::string index;
2,606✔
845
        auto begin_key = std::find(begin, sep, '[');
2,606✔
846
        if (begin_key != sep) {
2,606✔
847
            auto end_key = std::find(begin_key, sep, ']');
4✔
848
            check(end_key != sep, "missing ']'");
4✔
849
            index = std::string(begin_key + 1, end_key);
4✔
850
            key = StringData(begin, begin_key - begin);
4✔
851
        }
4✔
852
        begin = sep + (sep != end);
2,606✔
853

1,303✔
854
        auto prop = object_schema->property_for_public_name(key);
2,606✔
855
        check(prop, "property '%1.%2' does not exist", object_schema->name, key);
2,606✔
856
        if (is_dictionary(prop->type)) {
2,606✔
857
            check(index.length(), "missing dictionary key");
4✔
858
        }
4✔
859
        else {
2,602✔
860
            check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'", object_schema->name,
2,602✔
861
                  key, string_for_property_type(prop->type));
2,602✔
862
        }
2,602✔
863
        if (prop->type == PropertyType::Object)
2,606✔
864
            check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path",
32✔
865
                  object_schema->name, key);
32✔
866
        else
2,574✔
867
            check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path",
2,574✔
868
                  object_schema->name, key, prop->type_string());
2,574✔
869

1,303✔
870
        if (index.length()) {
2,606✔
871
            indices.emplace_back(ColKey(prop->column_key), index);
4✔
872
        }
4✔
873
        else {
2,602✔
874
            indices.emplace_back(ColKey(prop->column_key));
2,602✔
875
        }
2,602✔
876
        if (prop->type == PropertyType::Object)
2,606✔
877
            object_schema = &*schema.find(prop->object_type);
28✔
878
    }
2,606✔
879
    return indices;
2,580✔
880
}
2,580✔
881

882
Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const
883
{
4,434✔
884
    if (keypaths.empty())
4,434✔
885
        return *this;
42✔
886
    auto type = get_type();
4,392✔
887
    if (type != PropertyType::Object) {
4,392✔
888
        if (keypaths.size() != 1)
1,830✔
889
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
42✔
890
                                               string_for_property_type(type & ~PropertyType::Flags)));
42✔
891
        if (keypaths[0].first != "self")
1,788✔
892
            throw InvalidArgument(
42✔
893
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
42✔
894
                             keypaths[0].first, string_for_property_type(type & ~PropertyType::Flags)));
42✔
895
        return sort({{{}}, {keypaths[0].second}});
1,746✔
896
    }
1,746✔
897

1,281✔
898
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
2,562✔
899
    std::vector<bool> ascending;
2,562✔
900
    column_keys.reserve(keypaths.size());
2,562✔
901
    ascending.reserve(keypaths.size());
2,562✔
902

1,281✔
903
    for (auto& keypath : keypaths) {
2,570✔
904
        column_keys.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
2,570✔
905
        ascending.push_back(keypath.second);
2,570✔
906
    }
2,570✔
907
    return sort({std::move(column_keys), std::move(ascending)});
2,562✔
908
}
2,562✔
909

910
Results Results::sort(SortDescriptor&& sort) const
911
{
4,320✔
912
    util::CheckedUniqueLock lock(m_mutex);
4,320✔
913
    DescriptorOrdering new_order = m_descriptor_ordering;
4,320✔
914
    new_order.append_sort(std::move(sort));
4,320✔
915
    if (m_mode == Mode::Collection)
4,320✔
916
        return Results(m_realm, m_collection, std::move(new_order));
1,908✔
917
    return Results(m_realm, do_get_query(), std::move(new_order));
2,412✔
918
}
2,412✔
919

920
Results Results::filter(Query&& q) const
921
{
12✔
922
    if (m_descriptor_ordering.will_apply_limit())
12✔
923
        throw IllegalOperation("Filtering a Results with a limit is not yet implemented");
2✔
924
    return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
10✔
925
}
10✔
926

927
Results Results::limit(size_t max_count) const
928
{
80✔
929
    util::CheckedUniqueLock lock(m_mutex);
80✔
930
    auto new_order = m_descriptor_ordering;
80✔
931
    new_order.append_limit(max_count);
80✔
932
    if (m_mode == Mode::Collection)
80✔
933
        return Results(m_realm, m_collection, std::move(new_order));
×
934
    return Results(m_realm, do_get_query(), std::move(new_order));
80✔
935
}
80✔
936

937
Results Results::apply_ordering(DescriptorOrdering&& ordering)
938
{
174✔
939
    util::CheckedUniqueLock lock(m_mutex);
174✔
940
    DescriptorOrdering new_order = m_descriptor_ordering;
174✔
941
    new_order.append(std::move(ordering));
174✔
942
    if (m_mode == Mode::Collection)
174✔
943
        return Results(m_realm, m_collection, std::move(new_order));
168✔
944
    return Results(m_realm, do_get_query(), std::move(new_order));
6✔
945
}
6✔
946

947
Results Results::distinct(DistinctDescriptor&& uniqueness) const
948
{
354✔
949
    DescriptorOrdering new_order = m_descriptor_ordering;
354✔
950
    new_order.append_distinct(std::move(uniqueness));
354✔
951
    util::CheckedUniqueLock lock(m_mutex);
354✔
952
    if (m_mode == Mode::Collection)
354✔
953
        return Results(m_realm, m_collection, std::move(new_order));
308✔
954
    return Results(m_realm, do_get_query(), std::move(new_order));
46✔
955
}
46✔
956

957
Results Results::distinct(std::vector<std::string> const& keypaths) const
958
{
390✔
959
    if (keypaths.empty())
390✔
960
        return *this;
42✔
961
    auto type = get_type();
348✔
962
    if (type != PropertyType::Object) {
348✔
963
        if (keypaths.size() != 1)
338✔
964
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
42✔
965
                                               string_for_property_type(type & ~PropertyType::Flags)));
42✔
966
        if (keypaths[0] != "self")
296✔
967
            throw InvalidArgument(
42✔
968
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", keypaths[0],
42✔
969
                             string_for_property_type(type & ~PropertyType::Flags)));
42✔
970
        return distinct(DistinctDescriptor({{ColKey()}}));
254✔
971
    }
254✔
972

5✔
973
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
10✔
974
    column_keys.reserve(keypaths.size());
10✔
975
    for (auto& keypath : keypaths)
10✔
976
        column_keys.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
10✔
977
    return distinct({std::move(column_keys)});
10✔
978
}
10✔
979

980
Results Results::filter_by_method(std::function<bool(const Obj&)>&& predicate) const
981
{
8✔
982
    DescriptorOrdering new_order = m_descriptor_ordering;
8✔
983
    new_order.append_filter(FilterDescriptor(std::move(predicate)));
8✔
984
    util::CheckedUniqueLock lock(m_mutex);
8✔
985
    if (m_mode == Mode::Collection)
8✔
NEW
986
        return Results(m_realm, m_collection, std::move(new_order));
×
987
    return Results(m_realm, do_get_query(), std::move(new_order));
8✔
988
}
8✔
989

990
SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc&& section_key_func) REQUIRES(m_mutex)
991
{
294✔
992
    return SectionedResults(*this, std::move(section_key_func));
294✔
993
}
294✔
994

995
SectionedResults Results::sectioned_results(SectionedResultsOperator op, util::Optional<StringData> prop_name)
996
    REQUIRES(m_mutex)
997
{
4✔
998
    return SectionedResults(*this, op, prop_name.value_or(StringData()));
4✔
999
}
4✔
1000

1001
Results Results::snapshot() const&
1002
{
214✔
1003
    validate_read();
214✔
1004
    auto clone = *this;
214✔
1005
    clone.assert_unlocked();
214✔
1006
    return static_cast<Results&&>(clone).snapshot();
214✔
1007
}
214✔
1008

1009
Results Results::snapshot() &&
1010
{
385✔
1011
    util::CheckedUniqueLock lock(m_mutex);
385✔
1012
    validate_read();
385✔
1013
    switch (m_mode) {
385✔
1014
        case Mode::Empty:
2✔
1015
            return Results();
2✔
1016

1017
        case Mode::Table:
130✔
1018
        case Mode::Collection:
204✔
1019
            m_query = do_get_query();
204✔
1020
            if (m_query.get_table()) {
204✔
1021
                m_mode = Mode::Query;
78✔
1022
            }
78✔
1023
            REALM_FALLTHROUGH;
204✔
1024
        case Mode::Query:
376✔
1025
        case Mode::TableView:
383✔
1026
            ensure_up_to_date(EvaluateMode::Snapshot);
383✔
1027
            m_notifier.reset();
383✔
1028
            if (do_get_type() == PropertyType::Object) {
383✔
1029
                m_update_policy = UpdatePolicy::Never;
257✔
1030
            }
257✔
1031
            return std::move(*this);
383✔
1032
    }
×
1033
    REALM_COMPILER_HINT_UNREACHABLE();
×
1034
}
×
1035

1036
// This function cannot be called on frozen results and so does not require locking
1037
void Results::prepare_async(ForCallback force) NO_THREAD_SAFETY_ANALYSIS
1038
{
38,575✔
1039
    REALM_ASSERT(m_realm);
38,575✔
1040
    if (m_notifier)
38,575✔
1041
        return;
166✔
1042
    if (!m_realm->verify_notifications_available(force))
38,409✔
1043
        return;
20,660✔
1044
    if (m_update_policy == UpdatePolicy::Never) {
17,749✔
1045
        if (force)
2✔
1046
            throw LogicError(ErrorCodes::IllegalOperation,
2✔
1047
                             "Cannot create asynchronous query for snapshotted Results.");
2✔
1048
        return;
×
1049
    }
×
1050

8,821✔
1051
    REALM_ASSERT(!force || !m_realm->is_frozen());
17,747✔
1052
    if (!force) {
17,747✔
1053
        // Don't do implicit background updates if we can't actually deliver them
6,051✔
1054
        if (!m_realm->can_deliver_notifications())
12,207✔
1055
            return;
12,151✔
1056
        // Don't do implicit background updates if there isn't actually anything
28✔
1057
        // that needs to be run.
28✔
1058
        if (!m_query.get_table() && m_descriptor_ordering.is_empty())
56✔
1059
            return;
2✔
1060
    }
5,594✔
1061

2,797✔
1062
    if (do_get_type() != PropertyType::Object)
5,594✔
1063
        m_notifier = std::make_shared<_impl::ListResultsNotifier>(*this);
1,810✔
1064
    else
3,784✔
1065
        m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
3,784✔
1066
    _impl::RealmCoordinator::register_notifier(m_notifier);
5,594✔
1067
}
5,594✔
1068

1069
NotificationToken Results::add_notification_callback(CollectionChangeCallback callback,
1070
                                                     std::optional<KeyPathArray> key_path_array) &
1071
{
5,708✔
1072
    prepare_async(ForCallback{true});
5,708✔
1073
    return {m_notifier, m_notifier->add_callback(std::move(callback), std::move(key_path_array))};
5,708✔
1074
}
5,708✔
1075

1076
// This function cannot be called on frozen results and so does not require locking
1077
bool Results::is_in_table_order() const NO_THREAD_SAFETY_ANALYSIS
1078
{
3,796✔
1079
    REALM_ASSERT(!m_realm || !m_realm->is_frozen());
3,796✔
1080
    switch (m_mode) {
3,796✔
1081
        case Mode::Empty:
62✔
1082
        case Mode::Table:
124✔
1083
            return true;
124✔
1084
        case Mode::Collection:
69✔
1085
            return false;
14✔
1086
        case Mode::Query:
2,808✔
1087
            return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
2,808✔
1088
        case Mode::TableView:
850✔
1089
            return m_table_view.is_in_table_order();
850✔
1090
    }
×
1091
    REALM_COMPILER_HINT_UNREACHABLE();
×
1092
}
×
1093

1094
ColKey Results::key(StringData name) const
1095
{
2✔
1096
    return m_table->get_column_key(name);
2✔
1097
}
2✔
1098
#define REALM_RESULTS_TYPE(T)                                                                                        \
1099
    template T Results::get<T>(size_t);                                                                              \
1100
    template util::Optional<T> Results::first<T>();                                                                  \
1101
    template util::Optional<T> Results::last<T>();
1102

1103
REALM_RESULTS_TYPE(bool)
1104
REALM_RESULTS_TYPE(int64_t)
1105
REALM_RESULTS_TYPE(float)
1106
REALM_RESULTS_TYPE(double)
1107
REALM_RESULTS_TYPE(StringData)
1108
REALM_RESULTS_TYPE(BinaryData)
1109
REALM_RESULTS_TYPE(Timestamp)
1110
REALM_RESULTS_TYPE(ObjectId)
1111
REALM_RESULTS_TYPE(Decimal)
1112
REALM_RESULTS_TYPE(UUID)
1113
REALM_RESULTS_TYPE(Mixed)
1114
REALM_RESULTS_TYPE(Obj)
1115
REALM_RESULTS_TYPE(util::Optional<bool>)
1116
REALM_RESULTS_TYPE(util::Optional<int64_t>)
1117
REALM_RESULTS_TYPE(util::Optional<float>)
1118
REALM_RESULTS_TYPE(util::Optional<double>)
1119
REALM_RESULTS_TYPE(util::Optional<ObjectId>)
1120
REALM_RESULTS_TYPE(util::Optional<UUID>)
1121

1122
#undef REALM_RESULTS_TYPE
1123

1124
Results Results::import_copy_into_realm(std::shared_ptr<Realm> const& realm)
1125
{
124✔
1126
    util::CheckedUniqueLock lock(m_mutex);
124✔
1127
    if (m_mode == Mode::Empty)
124✔
1128
        return *this;
4✔
1129

60✔
1130
    validate_read();
120✔
1131

60✔
1132
    switch (m_mode) {
120✔
1133
        case Mode::Table:
14✔
1134
            return Results(realm, realm->import_copy_of(m_table));
14✔
1135
        case Mode::Collection:
94✔
1136
            if (std::shared_ptr<CollectionBase> collection = realm->import_copy_of(*m_collection)) {
94✔
1137
                return Results(realm, collection, m_descriptor_ordering);
92✔
1138
            }
92✔
1139
            // If collection is gone, fallback to empty selection on table.
1✔
1140
            return Results(realm, TableView(realm->import_copy_of(m_table)));
2✔
1141
            break;
1✔
1142
        case Mode::Query:
4✔
1143
            return Results(realm, *realm->import_copy_of(m_query, PayloadPolicy::Copy), m_descriptor_ordering);
4✔
1144
        case Mode::TableView: {
2✔
1145
            Results results(realm, *realm->import_copy_of(m_table_view, PayloadPolicy::Copy), m_descriptor_ordering);
2✔
1146
            results.assert_unlocked();
2✔
1147
            results.evaluate_query_if_needed(false);
2✔
1148
            return results;
2✔
1149
        }
2✔
1150
        default:
1✔
1151
            REALM_COMPILER_HINT_UNREACHABLE();
×
1152
    }
120✔
1153
}
120✔
1154

1155
Results Results::freeze(std::shared_ptr<Realm> const& frozen_realm)
1156
{
124✔
1157
    return import_copy_into_realm(frozen_realm);
124✔
1158
}
124✔
1159

1160
bool Results::is_frozen() const
1161
{
8,750✔
1162
    return !m_realm || m_realm->is_frozen();
8,750✔
1163
}
8,750✔
1164

1165
} // namespace realm
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