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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

90.97
/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/sectioned_results.hpp>
28

29
#include <realm/set.hpp>
30

31
#include <stdexcept>
32

33
namespace realm {
34
[[noreturn]] static void unsupported_operation(ColKey column, Table const& table, const char* operation)
35
{
1,332✔
36
    auto type = ObjectSchema::from_core_type(column);
1,332✔
37
    std::string_view collection_type =
1,332✔
38
        column.is_collection() ? collection_type_name(table.get_collection_type(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,214✔
46
Results::~Results() = default;
65,023✔
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
{
22,033✔
57
}
22,033✔
58

59
Results::Results(SharedRealm r, ConstTableRef table)
60
    : m_realm(std::move(r))
61
    , m_table(table)
62
    , m_table_view(m_table)
63
    , m_mode(Mode::Table)
64
    , m_mutex(m_realm && m_realm->is_frozen())
65
{
7,087✔
66
}
7,087✔
67

68
Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<CollectionBase> coll, util::Optional<Query> q,
69
                 SortDescriptor s)
70
    : m_realm(std::move(r))
71
    , m_table(coll->get_target_table())
72
    , m_collection(std::move(coll))
73
    , m_mode(Mode::Collection)
74
    , m_mutex(m_realm && m_realm->is_frozen())
75
{
13,891✔
76
    if (q) {
13,891✔
77
        m_query = std::move(*q);
17✔
78
        m_mode = Mode::Query;
17✔
79
    }
17✔
80
    m_descriptor_ordering.append_sort(std::move(s));
13,891✔
81
}
13,891✔
82

83
Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<CollectionBase> coll, DescriptorOrdering o)
84
    : m_realm(std::move(r))
85
    , m_table(coll->get_target_table())
86
    , m_descriptor_ordering(std::move(o))
87
    , m_collection(std::move(coll))
88
    , m_mode(Mode::Collection)
89
    , m_mutex(m_realm && m_realm->is_frozen())
90
{
2,614✔
91
}
2,614✔
92

93
Results::Results(std::shared_ptr<Realm> r, TableView tv, DescriptorOrdering o)
94
    : m_realm(std::move(r))
95
    , m_table_view(std::move(tv))
96
    , m_descriptor_ordering(std::move(o))
97
    , m_mode(Mode::TableView)
98
    , m_mutex(m_realm && m_realm->is_frozen())
99
{
106✔
100
    m_table = m_table_view.get_parent();
106✔
101
}
106✔
102

103
Results::Results(const Results&) = default;
2,916✔
UNCOV
104
Results& Results::operator=(const Results&) = default;
×
105
Results::Results(Results&&) = default;
13,162✔
106
Results& Results::operator=(Results&&) = default;
3,500✔
107

108
Results::Mode Results::get_mode() const noexcept
109
{
52✔
110
    util::CheckedUniqueLock lock(m_mutex);
52✔
111
    return m_mode;
52✔
112
}
52✔
113

114
bool Results::is_valid() const
115
{
146,319✔
116
    if (m_realm) {
146,319✔
117
        m_realm->verify_thread();
146,265✔
118
    }
146,265✔
119

72,795✔
120
    // Here we cannot just use if (m_table) as it combines a check if the
72,795✔
121
    // reference contains a value and if that value is valid.
72,795✔
122
    // First we check if a table is referenced ...
72,795✔
123
    if (m_table.unchecked_ptr() != nullptr)
146,319✔
124
        return bool(m_table); // ... and then we check if it is valid
106,221✔
125

20,049✔
126
    if (m_collection)
40,098✔
127
        // Since m_table was not set, this is a collection of primitives
20,002✔
128
        // and the results validity depend directly on the collection
20,002✔
129
        return m_collection->is_attached();
40,004✔
130

47✔
131
    return true;
94✔
132
}
94✔
133

134
void Results::validate_read() const
135
{
140,451✔
136
    // is_valid ensures that we're on the correct thread.
69,861✔
137
    if (!is_valid())
140,451✔
138
        throw StaleAccessor("Access to invalidated Results objects");
16✔
139
}
140,451✔
140

141
void Results::validate_write() const
142
{
548✔
143
    validate_read();
548✔
144
    if (!m_realm || !m_realm->is_in_transaction())
548✔
145
        throw WrongTransactionState("Must be in a write transaction");
4✔
146
}
548✔
147

148
size_t Results::size()
149
{
41,727✔
150
    util::CheckedUniqueLock lock(m_mutex);
41,727✔
151
    return do_size();
41,727✔
152
}
41,727✔
153

154
size_t Results::do_size()
155
{
42,319✔
156
    validate_read();
42,319✔
157
    ensure_up_to_date(EvaluateMode::Count);
42,319✔
158
    switch (m_mode) {
42,319✔
159
        case Mode::Empty:
12✔
160
            return 0;
12✔
161
        case Mode::Table:
4,757✔
162
            return m_table ? m_table->size() : 0;
4,753✔
163
        case Mode::Collection:
6,460✔
164
            return m_list_indices ? m_list_indices->size() : m_collection->size();
5,277✔
165
        case Mode::Query:
26,774✔
166
            return m_query.count(m_descriptor_ordering);
26,774✔
167
        case Mode::TableView:
4,310✔
168
            return m_table_view.size();
4,310✔
169
    }
×
170
    REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
171
}
×
172

173
const ObjectSchema& Results::get_object_schema() const
174
{
2,790✔
175
    validate_read();
2,790✔
176

1,395✔
177
    auto object_schema = m_object_schema.load();
2,790✔
178
    if (!object_schema) {
2,790✔
179
        REALM_ASSERT(m_realm);
2,704✔
180
        auto it = m_realm->schema().find(get_object_type());
2,704✔
181
        REALM_ASSERT(it != m_realm->schema().end());
2,704✔
182
        m_object_schema = object_schema = &*it;
2,704✔
183
    }
2,704✔
184

1,395✔
185
    return *object_schema;
2,790✔
186
}
2,790✔
187

188
StringData Results::get_object_type() const noexcept
189
{
2,746✔
190
    if (!m_table) {
2,746✔
191
        return StringData();
42✔
192
    }
42✔
193

1,352✔
194
    return ObjectStore::object_type_for_table_name(m_table->get_name());
2,704✔
195
}
2,704✔
196

197
bool Results::has_changed() REQUIRES(!m_mutex)
198
{
4,818✔
199
    util::CheckedUniqueLock lock(m_mutex);
4,818✔
200
    if (m_collection)
4,818✔
201
        return m_last_collection_content_version != m_collection->get_obj().get_table()->get_content_version();
4,376✔
202

221✔
203
    return m_table_view.has_changed();
442✔
204
}
442✔
205

206
void Results::ensure_up_to_date(EvaluateMode mode)
207
{
115,877✔
208
    if (m_update_policy == UpdatePolicy::Never) {
115,877✔
209
        REALM_ASSERT(m_mode == Mode::TableView);
450✔
210
        return;
450✔
211
    }
450✔
212

57,349✔
213
    switch (m_mode) {
115,427✔
214
        case Mode::Empty:
22✔
215
            return;
22✔
216
        case Mode::Table:
13,767✔
217
            // Tables are always up-to-date
6,848✔
218
            return;
13,767✔
219
        case Mode::Collection: {
29,284✔
220
            // Collections themselves are always up-to-date, but we may need
14,642✔
221
            // to apply sort descriptors
14,642✔
222
            if (m_descriptor_ordering.is_empty())
29,284✔
223
                return;
16,584✔
224

6,350✔
225
            // Collections of objects are sorted/distincted by converting them
6,350✔
226
            // to a TableView
6,350✔
227
            if (do_get_type() == PropertyType::Object) {
12,700✔
228
                m_query = do_get_query();
172✔
229
                m_mode = Mode::Query;
172✔
230
                ensure_up_to_date(mode);
172✔
231
                return;
172✔
232
            }
172✔
233

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

6,092✔
241
            bool needs_update = m_collection->has_changed();
12,184✔
242
            if (!m_list_indices) {
12,184✔
243
                m_list_indices = std::vector<size_t>{};
1,324✔
244
                needs_update = true;
1,324✔
245
            }
1,324✔
246
            if (!needs_update)
12,184✔
247
                return;
10,630✔
248

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

777✔
251
            if (m_collection->is_empty()) {
1,554✔
252
                m_list_indices->clear();
86✔
253
                return;
86✔
254
            }
86✔
255

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

734✔
272
            if (do_distinct)
1,468✔
273
                m_collection->distinct(*m_list_indices, sort_order);
344✔
274
            else if (sort_order)
1,124✔
275
                m_collection->sort(*m_list_indices, *sort_order);
1,124✔
276
            return;
1,468✔
277
        }
1,468✔
278

734✔
279
        case Mode::Query:
39,840✔
280
            // Everything except for size() requires evaluating the Query and
19,629✔
281
            // getting a TableView, and size() does as well if distinct is involved.
19,629✔
282
            if (mode == EvaluateMode::Count && !m_descriptor_ordering.will_apply_distinct()) {
39,840✔
283
                m_query.sync_view_if_needed();
26,774✔
284
                return;
26,774✔
285
            }
26,774✔
286

6,401✔
287
            // First we check if we ran the Query in the background and can
6,401✔
288
            // just use that
6,401✔
289
            if (m_notifier && m_notifier->get_tableview(m_table_view)) {
13,066✔
290
                m_mode = Mode::TableView;
2,264✔
291
                if (auto audit = m_realm->audit_context())
2,264✔
UNCOV
292
                    audit->record_query(m_realm->read_transaction_version(), m_table_view);
×
293
                return;
2,264✔
294
            }
2,264✔
295

5,269✔
296
            // We have to actually run the Query locally. We have an option
5,269✔
297
            // to disable this for testing purposes as it's otherwise very
5,269✔
298
            // difficult to determine if the async query is actually being
5,269✔
299
            // used.
5,269✔
300
            m_query.sync_view_if_needed();
10,802✔
301
            if (m_update_policy != UpdatePolicy::AsyncOnly)
10,802✔
302
                m_table_view = m_query.find_all(m_descriptor_ordering);
10,780✔
303
            m_mode = Mode::TableView;
10,802✔
304
            if (auto audit = m_realm->audit_context())
10,802✔
305
                audit->record_query(m_realm->read_transaction_version(), m_table_view);
159✔
306

5,269✔
307
            // Unless we're creating a snapshot, create an async notifier that'll
5,269✔
308
            // rerun this query in the background.
5,269✔
309
            if (mode != EvaluateMode::Snapshot && !m_notifier)
10,802✔
310
                prepare_async(ForCallback{false});
10,551✔
311
            return;
10,802✔
312

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

332
size_t Results::actual_index(size_t ndx) const noexcept
333
{
15,014✔
334
    if (auto& indices = m_list_indices) {
15,014✔
335
        return ndx < indices->size() ? (*indices)[ndx] : npos;
9,188✔
336
    }
9,188✔
337
    return ndx;
5,826✔
338
}
5,826✔
339

340
template <typename T>
341
static T get_unwraped(CollectionBase& collection, size_t ndx)
342
{
7,628✔
343
    using U = typename util::RemoveOptional<T>::type;
7,628✔
344
    Mixed mixed = collection.get_any(ndx);
7,628✔
345
    if (!mixed.is_null())
7,628✔
346
        return mixed.get<U>();
6,834✔
347
    return BPlusTree<T>::default_value(collection.get_col_key().is_nullable());
794✔
348
}
794✔
349

350
template <typename T>
351
util::Optional<T> Results::try_get(size_t ndx)
352
{
8,132✔
353
    validate_read();
8,132✔
354
    ensure_up_to_date();
8,132✔
355
    if (m_mode == Mode::Collection) {
8,132✔
356
        ndx = actual_index(ndx);
8,132✔
357
        if (ndx < m_collection->size()) {
8,132✔
358
            return get_unwraped<T>(*m_collection, ndx);
7,628✔
359
        }
7,628✔
360
    }
504✔
361
    return util::none;
504✔
362
}
504✔
363

364
Results::IteratorWrapper::IteratorWrapper(IteratorWrapper const& rgt)
365
{
2,916✔
366
    *this = rgt;
2,916✔
367
}
2,916✔
368

369
Results::IteratorWrapper& Results::IteratorWrapper::operator=(IteratorWrapper const& rgt)
370
{
2,916✔
371
    if (rgt.m_it)
2,916✔
UNCOV
372
        m_it = std::make_unique<Table::Iterator>(*rgt.m_it);
×
373
    return *this;
2,916✔
374
}
2,916✔
375

376
Obj Results::IteratorWrapper::get(Table const& table, size_t ndx)
377
{
8,858✔
378
    // Using a Table iterator is much faster for repeated access into a table
4,429✔
379
    // than indexing into it as the iterator caches the cluster the last accessed
4,429✔
380
    // object is stored in, but creating the iterator is somewhat expensive.
4,429✔
381
    if (!m_it) {
8,858✔
382
        if (table.size() <= 5)
712✔
383
            return const_cast<Table&>(table).get_object(ndx);
680✔
384
        m_it = std::make_unique<Table::Iterator>(table.begin());
32✔
385
    }
32✔
386
    m_it->go(ndx);
8,518✔
387
    return **m_it;
8,178✔
388
}
8,858✔
389

390
template <>
391
util::Optional<Obj> Results::try_get(size_t row_ndx)
392
{
50,193✔
393
    validate_read();
50,193✔
394
    ensure_up_to_date();
50,193✔
395
    switch (m_mode) {
50,193✔
396
        case Mode::Empty:
8✔
397
            break;
8✔
398
        case Mode::Table:
8,870✔
399
            if (m_table && row_ndx < m_table->size())
8,870✔
400
                return m_table_iterator.get(*m_table, row_ndx);
8,852✔
401
            break;
18✔
402
        case Mode::Collection:
890✔
403
            if (row_ndx < m_collection->size()) {
890✔
404
                auto m = m_collection->get_any(row_ndx);
866✔
405
                if (m.is_null())
866✔
406
                    return Obj();
84✔
407
                if (m.get_type() == type_Link)
782✔
UNCOV
408
                    return m_table->get_object(m.get<ObjKey>());
×
409
                if (m.get_type() == type_TypedLink)
782✔
410
                    return m_table->get_parent_group()->get_object(m.get_link());
782✔
411
            }
24✔
412
            break;
24✔
413
        case Mode::Query:
12✔
UNCOV
414
            REALM_UNREACHABLE();
×
415
        case Mode::TableView:
40,425✔
416
            if (row_ndx >= m_table_view.size())
40,425✔
417
                break;
9,250✔
418
            return m_table_view.get_object(row_ndx);
31,175✔
419
    }
9,300✔
420
    return util::none;
9,300✔
421
}
9,300✔
422

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

458
List Results::get_list(size_t ndx)
459
{
6✔
460
    util::CheckedUniqueLock lock(m_mutex);
6✔
461
    REALM_ASSERT(m_mode == Mode::Collection);
6✔
462
    ensure_up_to_date();
6✔
463
    if (size_t actual = actual_index(ndx); actual < m_collection->size()) {
6✔
464
        return List{m_realm, m_collection->get_list(m_collection->get_path_element(actual))};
6✔
465
    }
6✔
UNCOV
466
    throw OutOfBounds{"get_list() on Results", ndx, m_collection->size()};
×
UNCOV
467
}
×
468

469
object_store::Set Results::get_set(size_t ndx)
470
{
6✔
471
    util::CheckedUniqueLock lock(m_mutex);
6✔
472
    REALM_ASSERT(m_mode == Mode::Collection);
6✔
473
    ensure_up_to_date();
6✔
474
    if (size_t actual = actual_index(ndx); actual < m_collection->size()) {
6✔
475
        return object_store::Set{m_realm, m_collection->get_set(m_collection->get_path_element(actual))};
6✔
476
    }
6✔
UNCOV
477
    throw OutOfBounds{"get_set() on Results", ndx, m_collection->size()};
×
UNCOV
478
}
×
479

480
object_store::Dictionary Results::get_dictionary(size_t ndx)
481
{
6✔
482
    util::CheckedUniqueLock lock(m_mutex);
6✔
483
    REALM_ASSERT(m_mode == Mode::Collection);
6✔
484
    ensure_up_to_date();
6✔
485
    if (size_t actual = actual_index(ndx); actual < m_collection->size()) {
6✔
486
        return object_store::Dictionary{m_realm,
6✔
487
                                        m_collection->get_dictionary(m_collection->get_path_element(actual))};
6✔
488
    }
6✔
UNCOV
489
    throw OutOfBounds{"get_dictionary() on Results", ndx, m_collection->size()};
×
UNCOV
490
}
×
491

492
std::pair<StringData, Mixed> Results::get_dictionary_element(size_t ndx)
493
{
324✔
494
    util::CheckedUniqueLock lock(m_mutex);
324✔
495
    REALM_ASSERT(m_mode == Mode::Collection);
324✔
496
    auto& dict = static_cast<Dictionary&>(*m_collection);
324✔
497
    REALM_ASSERT(typeid(dict) == typeid(Dictionary));
324✔
498

162✔
499
    ensure_up_to_date();
324✔
500
    if (size_t actual = actual_index(ndx); actual < dict.size()) {
324✔
501
        auto val = dict.get_pair(ndx);
324✔
502
        return {val.first.get_string(), val.second};
324✔
503
    }
324✔
UNCOV
504
    throw OutOfBounds{"get_dictionary_element() on Results", ndx, dict.size()};
×
UNCOV
505
}
×
506

507
template <typename T>
508
T Results::get(size_t row_ndx)
509
{
42,716✔
510
    util::CheckedUniqueLock lock(m_mutex);
42,716✔
511
    if (auto row = try_get<T>(row_ndx)) {
42,716✔
512
        return *row;
42,592✔
513
    }
42,592✔
514
    throw OutOfBounds{"get() on Results", row_ndx, do_size()};
124✔
515
}
124✔
516

517
template <typename T>
518
util::Optional<T> Results::first()
519
{
15,147✔
520
    util::CheckedUniqueLock lock(m_mutex);
15,147✔
521
    return try_get<T>(0);
15,147✔
522
}
15,147✔
523

524
template <typename T>
525
util::Optional<T> Results::last()
526
{
462✔
527
    util::CheckedUniqueLock lock(m_mutex);
462✔
528
    validate_read();
462✔
529
    if (m_mode == Mode::Query)
462✔
530
        ensure_up_to_date(); // avoid running the query twice (for size() and for get())
12✔
531
    return try_get<T>(do_size() - 1);
462✔
532
}
462✔
533

534
void Results::evaluate_query_if_needed(bool wants_notifications)
535
{
26✔
536
    util::CheckedUniqueLock lock(m_mutex);
26✔
537
    validate_read();
26✔
538
    ensure_up_to_date(wants_notifications ? EvaluateMode::Normal : EvaluateMode::Snapshot);
25✔
539
}
26✔
540

541
template <>
542
size_t Results::index_of(Obj const& obj)
543
{
146✔
544
    if (!obj.is_valid()) {
146✔
545
        throw StaleAccessor{"Attempting to access an invalid object"};
12✔
546
    }
12✔
547
    if (m_table && obj.get_table() != m_table) {
134✔
548
        throw InvalidArgument(ErrorCodes::ObjectTypeMismatch,
26✔
549
                              util::format("Object of type '%1' does not match Results type '%2'",
26✔
550
                                           obj.get_table()->get_class_name(), m_table->get_class_name()));
26✔
551
    }
26✔
552
    return index_of(Mixed(obj.get_key()));
108✔
553
}
108✔
554

555
template <>
556
size_t Results::index_of(Mixed const& value)
557
{
2,276✔
558
    util::CheckedUniqueLock lock(m_mutex);
2,276✔
559
    validate_read();
2,276✔
560
    ensure_up_to_date();
2,276✔
561

1,138✔
562
    if (value.is_type(type_TypedLink)) {
2,276✔
563
        if (m_table && m_table->get_key() != value.get_link().get_table_key()) {
2✔
564
            return realm::not_found;
×
565
        }
×
566
    }
2,276✔
567

1,138✔
568
    switch (m_mode) {
2,276✔
569
        case Mode::Empty:
10✔
570
        case Mode::Table:
20✔
571
            if (value.is_type(type_Link, type_TypedLink)) {
20✔
572
                return m_table->get_object_ndx(value.get<ObjKey>());
20✔
573
            }
20✔
UNCOV
574
            break;
×
575
        case Mode::Collection:
2,208✔
576
            if (m_list_indices) {
2,208✔
577
                for (size_t i = 0; i < m_list_indices->size(); ++i) {
1,988✔
578
                    if (value == m_collection->get_any(m_list_indices->at(i)))
1,988✔
579
                        return i;
580✔
580
                }
1,988✔
581
                return not_found;
290✔
582
            }
1,628✔
583
            return m_collection->find_any(value);
1,628✔
584
        case Mode::Query:
814✔
585
        case Mode::TableView:
48✔
586
            if (value.is_type(type_Link, type_TypedLink)) {
48✔
587
                return m_table_view.find_by_source_ndx(value.get<ObjKey>());
44✔
588
            }
44✔
589
            break;
4✔
590
    }
4✔
591
    return realm::not_found;
4✔
592
}
4✔
593

594
size_t Results::index_of(Query&& q)
UNCOV
595
{
×
UNCOV
596
    if (m_descriptor_ordering.will_apply_sort()) {
×
UNCOV
597
        Results filtered(filter(std::move(q)));
×
UNCOV
598
        filtered.assert_unlocked();
×
UNCOV
599
        auto first = filtered.first();
×
UNCOV
600
        return first ? index_of(*first) : not_found;
×
UNCOV
601
    }
×
602

UNCOV
603
    auto query = get_query().and_query(std::move(q));
×
UNCOV
604
    query.sync_view_if_needed();
×
UNCOV
605
    ObjKey row = query.find();
×
UNCOV
606
    return row ? index_of(const_cast<Table&>(*m_table).get_object(row)) : not_found;
×
UNCOV
607
}
×
608

609
namespace {
610
struct CollectionAggregateAdaptor {
611
    const CollectionBase& list;
612
    util::Optional<Mixed> min(ColKey)
613
    {
472✔
614
        return list.min();
472✔
615
    }
472✔
616
    util::Optional<Mixed> max(ColKey)
617
    {
472✔
618
        return list.max();
472✔
619
    }
472✔
620
    util::Optional<Mixed> sum(ColKey)
621
    {
448✔
622
        return list.sum();
448✔
623
    }
448✔
624
    util::Optional<Mixed> avg(ColKey)
625
    {
448✔
626
        return list.avg();
448✔
627
    }
448✔
628
};
629
} // anonymous namespace
630

631
template <typename AggregateFunction>
632
util::Optional<Mixed> Results::aggregate(ColKey column, const char* name, AggregateFunction&& func)
633
{
3,950✔
634
    util::CheckedUniqueLock lock(m_mutex);
3,950✔
635
    validate_read();
3,950✔
636
    if (!m_table && !m_collection)
3,950✔
637
        return none;
32✔
638

1,959✔
639
    ensure_up_to_date();
3,918✔
640
    std::optional<Mixed> ret;
3,918✔
641
    switch (m_mode) {
3,918✔
642
        case Mode::Table:
112✔
643
            ret = func(*m_table);
112✔
644
            break;
112✔
UNCOV
645
        case Mode::Query:
✔
UNCOV
646
            ret = func(m_query);
×
UNCOV
647
            break;
×
648
        case Mode::Collection:
3,554✔
649
            if (do_get_type() != PropertyType::Object)
3,554✔
650
                ret = func(CollectionAggregateAdaptor{*m_collection});
1,840✔
651
            else
1,714✔
652
                ret = func(do_get_query());
1,714✔
653
            break;
3,554✔
654
        default:
252✔
655
            ret = func(m_table_view);
252✔
656
            break;
252✔
657
    }
3,870✔
658

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

666✔
666
    // We need to report the column and table actually being aggregated on,
666✔
667
    // which is the collection if it's not a link collection and the target
666✔
668
    // of the links otherwise
666✔
669
    if (m_mode == Mode::Collection && do_get_type() != PropertyType::Object) {
1,332✔
670
        unsupported_operation(m_collection->get_col_key(), *m_collection->get_table(), name);
704✔
671
    }
704✔
672
    else {
628✔
673
        unsupported_operation(column, *m_table, name);
628✔
674
    }
628✔
675
}
1,332✔
676

677
util::Optional<Mixed> Results::max(ColKey column)
678
{
1,010✔
679
    return aggregate(column, "max", [column](auto&& helper) {
1,006✔
680
        return helper.max(column);
1,002✔
681
    });
1,002✔
682
}
1,010✔
683

684
util::Optional<Mixed> Results::min(ColKey column)
685
{
1,010✔
686
    return aggregate(column, "min", [column](auto&& helper) {
1,006✔
687
        return helper.min(column);
1,002✔
688
    });
1,002✔
689
}
1,010✔
690

691
util::Optional<Mixed> Results::sum(ColKey column)
692
{
968✔
693
    return aggregate(column, "sum", [column](auto&& helper) {
964✔
694
        return helper.sum(column);
960✔
695
    });
960✔
696
}
968✔
697

698
util::Optional<Mixed> Results::average(ColKey column)
699
{
962✔
700
    return aggregate(column, "average", [column](auto&& helper) {
958✔
701
        return helper.avg(column);
954✔
702
    });
954✔
703
}
962✔
704

705
void Results::clear()
706
{
496✔
707
    util::CheckedUniqueLock lock(m_mutex);
496✔
708
    validate_write();
496✔
709
    ensure_up_to_date();
496✔
710
    switch (m_mode) {
496✔
UNCOV
711
        case Mode::Empty:
✔
UNCOV
712
            return;
×
713
        case Mode::Table:
2✔
714
            const_cast<Table&>(*m_table).clear();
2✔
715
            break;
2✔
716
        case Mode::Query:
12✔
717
            // Not using Query:remove() because building the tableview and
12✔
718
            // clearing it is actually significantly faster
12✔
719
        case Mode::TableView:
24✔
720
            switch (m_update_policy) {
24✔
721
                case UpdatePolicy::Auto:
20✔
722
                    m_table_view.clear();
20✔
723
                    break;
20✔
724
                case UpdatePolicy::AsyncOnly:
2✔
725
                case UpdatePolicy::Never: {
4✔
726
                    // Copy the TableView because a frozen Results shouldn't let its size() change.
2✔
727
                    TableView copy(m_table_view);
4✔
728
                    copy.clear();
4✔
729
                    break;
4✔
730
                }
24✔
731
            }
24✔
732
            break;
24✔
733
        case Mode::Collection:
466✔
734
            if (auto list = dynamic_cast<LnkLst*>(m_collection.get()))
466✔
735
                list->remove_all_target_rows();
4✔
736
            else if (auto set = dynamic_cast<LnkSet*>(m_collection.get()))
462✔
737
                set->remove_all_target_rows();
84✔
738
            else
378✔
739
                m_collection->clear();
378✔
740
            break;
466✔
741
    }
496✔
742
}
496✔
743

744
PropertyType Results::get_type() const
745
{
10,546✔
746
    util::CheckedUniqueLock lock(m_mutex);
10,546✔
747
    validate_read();
10,546✔
748
    return do_get_type();
10,546✔
749
}
10,546✔
750

751
PropertyType Results::do_get_type() const
752
{
34,041✔
753
    switch (m_mode) {
34,041✔
754
        case Mode::Empty:
3,239✔
755
        case Mode::Query:
4,660✔
756
        case Mode::TableView:
5,345✔
757
        case Mode::Table:
6,633✔
758
            return PropertyType::Object;
6,633✔
759
        case Mode::Collection:
27,408✔
760
            return ObjectSchema::from_core_type(m_collection->get_col_key());
27,408✔
UNCOV
761
    }
×
UNCOV
762
    REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
763
}
×
764

765
Query Results::get_query() const
766
{
3,862✔
767
    util::CheckedUniqueLock lock(m_mutex);
3,862✔
768
    return do_get_query();
3,862✔
769
}
3,862✔
770

771
const DescriptorOrdering& Results::get_ordering() const REQUIRES(!m_mutex)
772
{
16✔
773
    return m_descriptor_ordering;
16✔
774
}
16✔
775

776
ConstTableRef Results::get_table() const
777
{
18✔
778
    util::CheckedUniqueLock lock(m_mutex);
18✔
779
    validate_read();
18✔
780
    switch (m_mode) {
18✔
781
        case Mode::Empty:
7✔
782
        case Mode::Query:
14✔
783
            return const_cast<Query&>(m_query).get_table();
14✔
784
        case Mode::TableView:
7✔
UNCOV
785
            return m_table_view.get_target_table();
×
786
        case Mode::Collection:
7✔
UNCOV
787
            return m_collection->get_target_table();
×
788
        case Mode::Table:
9✔
789
            return m_table;
4✔
UNCOV
790
    }
×
UNCOV
791
    REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
792
}
×
793

794
Query Results::do_get_query() const
795
{
8,494✔
796
    validate_read();
8,494✔
797
    switch (m_mode) {
8,494✔
798
        case Mode::Empty:
1,936✔
799
        case Mode::Query:
3,439✔
800
        case Mode::TableView: {
3,872✔
801
            if (const_cast<Query&>(m_query).get_table())
3,872✔
802
                return m_query;
3,866✔
803

3✔
804
            // A TableView has an associated Query if it was produced by Query::find_all
3✔
805
            if (auto& query = m_table_view.get_query()) {
6✔
806
                return *query;
6✔
807
            }
6✔
808

809
            // The TableView has no associated query so create one with no conditions that is restricted
810
            // to the rows in the TableView.
811
            if (m_update_policy == UpdatePolicy::Auto) {
×
812
                m_table_view.sync_if_needed();
×
813
            }
×
UNCOV
814
            return Query(m_table, std::make_unique<TableView>(m_table_view));
×
UNCOV
815
        }
×
816
        case Mode::Collection:
2,050✔
817
            if (auto list = dynamic_cast<ObjList*>(m_collection.get())) {
2,050✔
818
                return m_table->where(*list);
1,420✔
819
            }
1,420✔
820
            if (auto dict = dynamic_cast<Dictionary*>(m_collection.get())) {
630✔
821
                if (dict->get_value_data_type() == type_Link) {
546✔
822
                    return m_table->where(*dict);
504✔
823
                }
504✔
824
            }
126✔
825
            return m_query;
126✔
826
        case Mode::Table:
2,572✔
827
            return m_table->where();
2,572✔
UNCOV
828
    }
×
UNCOV
829
    REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
830
}
×
831

832
TableView Results::get_tableview()
UNCOV
833
{
×
UNCOV
834
    util::CheckedUniqueLock lock(m_mutex);
×
UNCOV
835
    validate_read();
×
UNCOV
836
    ensure_up_to_date();
×
UNCOV
837
    switch (m_mode) {
×
UNCOV
838
        case Mode::Empty:
×
UNCOV
839
        case Mode::Collection:
×
UNCOV
840
            return do_get_query().find_all();
×
UNCOV
841
        case Mode::Query:
×
UNCOV
842
        case Mode::TableView:
×
UNCOV
843
            return m_table_view;
×
UNCOV
844
        case Mode::Table:
×
UNCOV
845
            return m_table->where().find_all();
×
UNCOV
846
    }
×
UNCOV
847
    REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
848
}
×
849

850
static std::vector<ExtendedColumnKey> parse_keypath(StringData keypath, Schema const& schema,
851
                                                    const ObjectSchema* object_schema)
852
{
2,580✔
853
    auto check = [&](bool condition, const char* fmt, auto... args) {
12,960✔
854
        if (!condition) {
12,960✔
855
            throw InvalidArgument(
36✔
856
                util::format("Cannot sort on key path '%1': %2.", keypath, util::format(fmt, args...)));
36✔
857
        }
36✔
858
    };
12,960✔
859
    auto is_sortable_type = [](PropertyType type) {
2,590✔
860
        return !is_collection(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data;
2,590✔
861
    };
2,590✔
862

1,290✔
863
    const char* begin = keypath.data();
2,580✔
864
    const char* end = keypath.data() + keypath.size();
2,580✔
865
    check(begin != end, "missing property name");
2,580✔
866

1,290✔
867
    std::vector<ExtendedColumnKey> indices;
2,580✔
868
    while (begin != end) {
5,186✔
869
        auto sep = std::find(begin, end, '.');
2,606✔
870
        check(sep != begin && sep + 1 != end, "missing property name");
2,606✔
871
        StringData key(begin, sep - begin);
2,606✔
872
        std::string index;
2,606✔
873
        auto begin_key = std::find(begin, sep, '[');
2,606✔
874
        if (begin_key != sep) {
2,606✔
875
            auto end_key = std::find(begin_key, sep, ']');
4✔
876
            check(end_key != sep, "missing ']'");
4✔
877
            index = std::string(begin_key + 1, end_key);
4✔
878
            key = StringData(begin, begin_key - begin);
4✔
879
        }
4✔
880
        begin = sep + (sep != end);
2,606✔
881

1,303✔
882
        auto prop = object_schema->property_for_public_name(key);
2,606✔
883
        check(prop, "property '%1.%2' does not exist", object_schema->name, key);
2,606✔
884
        if (is_dictionary(prop->type)) {
2,606✔
885
            check(index.length(), "missing dictionary key");
4✔
886
        }
4✔
887
        else {
2,602✔
888
            check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'", object_schema->name,
2,602✔
889
                  key, string_for_property_type(prop->type));
2,602✔
890
        }
2,602✔
891
        if (prop->type == PropertyType::Object)
2,606✔
892
            check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path",
32✔
893
                  object_schema->name, key);
32✔
894
        else
2,574✔
895
            check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path",
2,574✔
896
                  object_schema->name, key, prop->type_string());
2,574✔
897

1,303✔
898
        if (index.length()) {
2,606✔
899
            indices.emplace_back(ColKey(prop->column_key), index);
4✔
900
        }
4✔
901
        else {
2,602✔
902
            indices.emplace_back(ColKey(prop->column_key));
2,602✔
903
        }
2,602✔
904
        if (prop->type == PropertyType::Object)
2,606✔
905
            object_schema = &*schema.find(prop->object_type);
28✔
906
    }
2,606✔
907
    return indices;
2,580✔
908
}
2,580✔
909

910
Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const
911
{
4,434✔
912
    if (keypaths.empty())
4,434✔
913
        return *this;
42✔
914
    auto type = get_type();
4,392✔
915
    if (type != PropertyType::Object) {
4,392✔
916
        if (keypaths.size() != 1)
1,830✔
917
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
42✔
918
                                               string_for_property_type(type & ~PropertyType::Flags)));
42✔
919
        if (keypaths[0].first != "self")
1,788✔
920
            throw InvalidArgument(
42✔
921
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
42✔
922
                             keypaths[0].first, string_for_property_type(type & ~PropertyType::Flags)));
42✔
923
        return sort({{{}}, {keypaths[0].second}});
1,746✔
924
    }
1,746✔
925

1,281✔
926
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
2,562✔
927
    std::vector<bool> ascending;
2,562✔
928
    column_keys.reserve(keypaths.size());
2,562✔
929
    ascending.reserve(keypaths.size());
2,562✔
930

1,281✔
931
    for (auto& keypath : keypaths) {
2,570✔
932
        column_keys.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
2,570✔
933
        ascending.push_back(keypath.second);
2,570✔
934
    }
2,570✔
935
    return sort({std::move(column_keys), std::move(ascending)});
2,562✔
936
}
2,562✔
937

938
Results Results::sort(SortDescriptor&& sort) const
939
{
4,318✔
940
    util::CheckedUniqueLock lock(m_mutex);
4,318✔
941
    DescriptorOrdering new_order = m_descriptor_ordering;
4,318✔
942
    new_order.append_sort(std::move(sort));
4,318✔
943
    if (m_mode == Mode::Collection)
4,318✔
944
        return Results(m_realm, m_collection, std::move(new_order));
1,908✔
945
    return Results(m_realm, do_get_query(), std::move(new_order));
2,410✔
946
}
2,410✔
947

948
Results Results::filter(Query&& q) const
949
{
12✔
950
    if (m_descriptor_ordering.will_apply_limit())
12✔
951
        throw IllegalOperation("Filtering a Results with a limit is not yet implemented");
2✔
952
    return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
10✔
953
}
10✔
954

955
Results Results::limit(size_t max_count) const
956
{
80✔
957
    util::CheckedUniqueLock lock(m_mutex);
80✔
958
    auto new_order = m_descriptor_ordering;
80✔
959
    new_order.append_limit(max_count);
80✔
960
    if (m_mode == Mode::Collection)
80✔
UNCOV
961
        return Results(m_realm, m_collection, std::move(new_order));
×
962
    return Results(m_realm, do_get_query(), std::move(new_order));
80✔
963
}
80✔
964

965
Results Results::apply_ordering(DescriptorOrdering&& ordering)
966
{
174✔
967
    util::CheckedUniqueLock lock(m_mutex);
174✔
968
    DescriptorOrdering new_order = m_descriptor_ordering;
174✔
969
    new_order.append(std::move(ordering));
174✔
970
    if (m_mode == Mode::Collection)
174✔
971
        return Results(m_realm, m_collection, std::move(new_order));
168✔
972
    return Results(m_realm, do_get_query(), std::move(new_order));
6✔
973
}
6✔
974

975
Results Results::distinct(DistinctDescriptor&& uniqueness) const
976
{
354✔
977
    DescriptorOrdering new_order = m_descriptor_ordering;
354✔
978
    new_order.append_distinct(std::move(uniqueness));
354✔
979
    util::CheckedUniqueLock lock(m_mutex);
354✔
980
    if (m_mode == Mode::Collection)
354✔
981
        return Results(m_realm, m_collection, std::move(new_order));
308✔
982
    return Results(m_realm, do_get_query(), std::move(new_order));
46✔
983
}
46✔
984

985
Results Results::distinct(std::vector<std::string> const& keypaths) const
986
{
390✔
987
    if (keypaths.empty())
390✔
988
        return *this;
42✔
989
    auto type = get_type();
348✔
990
    if (type != PropertyType::Object) {
348✔
991
        if (keypaths.size() != 1)
338✔
992
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
42✔
993
                                               string_for_property_type(type & ~PropertyType::Flags)));
42✔
994
        if (keypaths[0] != "self")
296✔
995
            throw InvalidArgument(
42✔
996
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", keypaths[0],
42✔
997
                             string_for_property_type(type & ~PropertyType::Flags)));
42✔
998
        return distinct(DistinctDescriptor({{ColKey()}}));
254✔
999
    }
254✔
1000

5✔
1001
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
10✔
1002
    column_keys.reserve(keypaths.size());
10✔
1003
    for (auto& keypath : keypaths)
10✔
1004
        column_keys.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
10✔
1005
    return distinct({std::move(column_keys)});
10✔
1006
}
10✔
1007

1008
SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc&& section_key_func) REQUIRES(m_mutex)
1009
{
294✔
1010
    return SectionedResults(*this, std::move(section_key_func));
294✔
1011
}
294✔
1012

1013
SectionedResults Results::sectioned_results(SectionedResultsOperator op, util::Optional<StringData> prop_name)
1014
    REQUIRES(m_mutex)
1015
{
4✔
1016
    return SectionedResults(*this, op, prop_name.value_or(StringData()));
4✔
1017
}
4✔
1018

1019
Results Results::snapshot() const&
1020
{
214✔
1021
    validate_read();
214✔
1022
    auto clone = *this;
214✔
1023
    clone.assert_unlocked();
214✔
1024
    return static_cast<Results&&>(clone).snapshot();
214✔
1025
}
214✔
1026

1027
Results Results::snapshot() &&
1028
{
385✔
1029
    util::CheckedUniqueLock lock(m_mutex);
385✔
1030
    validate_read();
385✔
1031
    switch (m_mode) {
385✔
1032
        case Mode::Empty:
2✔
1033
            return Results();
2✔
1034

1035
        case Mode::Table:
130✔
1036
        case Mode::Collection:
204✔
1037
            m_query = do_get_query();
204✔
1038
            if (m_query.get_table()) {
204✔
1039
                m_mode = Mode::Query;
78✔
1040
            }
78✔
1041
            REALM_FALLTHROUGH;
204✔
1042
        case Mode::Query:
376✔
1043
        case Mode::TableView:
383✔
1044
            ensure_up_to_date(EvaluateMode::Snapshot);
383✔
1045
            m_notifier.reset();
383✔
1046
            if (do_get_type() == PropertyType::Object) {
383✔
1047
                m_update_policy = UpdatePolicy::Never;
257✔
1048
            }
257✔
1049
            return std::move(*this);
383✔
UNCOV
1050
    }
×
UNCOV
1051
    REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
1052
}
×
1053

1054
// This function cannot be called on frozen results and so does not require locking
1055
void Results::prepare_async(ForCallback force) NO_THREAD_SAFETY_ANALYSIS
1056
{
38,735✔
1057
    REALM_ASSERT(m_realm);
38,735✔
1058
    if (m_notifier)
38,735✔
1059
        return;
166✔
1060
    if (!m_realm->verify_notifications_available(force))
38,569✔
1061
        return;
20,728✔
1062
    if (m_update_policy == UpdatePolicy::Never) {
17,841✔
1063
        if (force)
2✔
1064
            throw LogicError(ErrorCodes::IllegalOperation,
2✔
1065
                             "Cannot create asynchronous query for snapshotted Results.");
2✔
UNCOV
1066
        return;
×
UNCOV
1067
    }
×
1068

8,867✔
1069
    REALM_ASSERT(!force || !m_realm->is_frozen());
17,839✔
1070
    if (!force) {
17,839✔
1071
        // Don't do implicit background updates if we can't actually deliver them
6,099✔
1072
        if (!m_realm->can_deliver_notifications())
12,303✔
1073
            return;
12,247✔
1074
        // Don't do implicit background updates if there isn't actually anything
28✔
1075
        // that needs to be run.
28✔
1076
        if (!m_query.get_table() && m_descriptor_ordering.is_empty())
56✔
1077
            return;
2✔
1078
    }
5,590✔
1079

2,795✔
1080
    if (do_get_type() != PropertyType::Object)
5,590✔
1081
        m_notifier = std::make_shared<_impl::ListResultsNotifier>(*this);
1,810✔
1082
    else
3,780✔
1083
        m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
3,780✔
1084
    _impl::RealmCoordinator::register_notifier(m_notifier);
5,590✔
1085
}
5,590✔
1086

1087
NotificationToken Results::add_notification_callback(CollectionChangeCallback callback,
1088
                                                     std::optional<KeyPathArray> key_path_array) &
1089
{
5,704✔
1090
    prepare_async(ForCallback{true});
5,704✔
1091
    return {m_notifier, m_notifier->add_callback(std::move(callback), std::move(key_path_array))};
5,704✔
1092
}
5,704✔
1093

1094
// This function cannot be called on frozen results and so does not require locking
1095
bool Results::is_in_table_order() const NO_THREAD_SAFETY_ANALYSIS
1096
{
3,792✔
1097
    REALM_ASSERT(!m_realm || !m_realm->is_frozen());
3,792✔
1098
    switch (m_mode) {
3,792✔
1099
        case Mode::Empty:
62✔
1100
        case Mode::Table:
124✔
1101
            return true;
124✔
1102
        case Mode::Collection:
69✔
1103
            return false;
14✔
1104
        case Mode::Query:
2,804✔
1105
            return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
2,804✔
1106
        case Mode::TableView:
850✔
1107
            return m_table_view.is_in_table_order();
850✔
UNCOV
1108
    }
×
UNCOV
1109
    REALM_COMPILER_HINT_UNREACHABLE();
×
UNCOV
1110
}
×
1111

1112
ColKey Results::key(StringData name) const
1113
{
2✔
1114
    return m_table->get_column_key(name);
2✔
1115
}
2✔
1116
#define REALM_RESULTS_TYPE(T)                                                                                        \
1117
    template T Results::get<T>(size_t);                                                                              \
1118
    template util::Optional<T> Results::first<T>();                                                                  \
1119
    template util::Optional<T> Results::last<T>();
1120

1121
REALM_RESULTS_TYPE(bool)
1122
REALM_RESULTS_TYPE(int64_t)
1123
REALM_RESULTS_TYPE(float)
1124
REALM_RESULTS_TYPE(double)
1125
REALM_RESULTS_TYPE(StringData)
1126
REALM_RESULTS_TYPE(BinaryData)
1127
REALM_RESULTS_TYPE(Timestamp)
1128
REALM_RESULTS_TYPE(ObjectId)
1129
REALM_RESULTS_TYPE(Decimal)
1130
REALM_RESULTS_TYPE(UUID)
1131
REALM_RESULTS_TYPE(Mixed)
1132
REALM_RESULTS_TYPE(Obj)
1133
REALM_RESULTS_TYPE(util::Optional<bool>)
1134
REALM_RESULTS_TYPE(util::Optional<int64_t>)
1135
REALM_RESULTS_TYPE(util::Optional<float>)
1136
REALM_RESULTS_TYPE(util::Optional<double>)
1137
REALM_RESULTS_TYPE(util::Optional<ObjectId>)
1138
REALM_RESULTS_TYPE(util::Optional<UUID>)
1139

1140
#undef REALM_RESULTS_TYPE
1141

1142
Results Results::import_copy_into_realm(std::shared_ptr<Realm> const& realm)
1143
{
124✔
1144
    util::CheckedUniqueLock lock(m_mutex);
124✔
1145
    if (m_mode == Mode::Empty)
124✔
1146
        return *this;
4✔
1147

60✔
1148
    validate_read();
120✔
1149

60✔
1150
    switch (m_mode) {
120✔
1151
        case Mode::Table:
14✔
1152
            return Results(realm, realm->import_copy_of(m_table));
14✔
1153
        case Mode::Collection:
94✔
1154
            if (std::shared_ptr<CollectionBase> collection = realm->import_copy_of(*m_collection)) {
94✔
1155
                return Results(realm, collection, m_descriptor_ordering);
92✔
1156
            }
92✔
1157
            // If collection is gone, fallback to empty selection on table.
1✔
1158
            return Results(realm, TableView(realm->import_copy_of(m_table)));
2✔
1159
            break;
1✔
1160
        case Mode::Query:
4✔
1161
            return Results(realm, *realm->import_copy_of(m_query, PayloadPolicy::Copy), m_descriptor_ordering);
4✔
1162
        case Mode::TableView: {
2✔
1163
            Results results(realm, *realm->import_copy_of(m_table_view, PayloadPolicy::Copy), m_descriptor_ordering);
2✔
1164
            results.assert_unlocked();
2✔
1165
            results.evaluate_query_if_needed(false);
2✔
1166
            return results;
2✔
1167
        }
2✔
1168
        default:
1✔
UNCOV
1169
            REALM_COMPILER_HINT_UNREACHABLE();
×
1170
    }
120✔
1171
}
120✔
1172

1173
Results Results::freeze(std::shared_ptr<Realm> const& frozen_realm)
1174
{
124✔
1175
    return import_copy_into_realm(frozen_realm);
124✔
1176
}
124✔
1177

1178
bool Results::is_frozen() const
1179
{
8,750✔
1180
    return !m_realm || m_realm->is_frozen();
8,750✔
1181
}
8,750✔
1182

1183
} // 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