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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

90.74
/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 =
1,332✔
39
        column.is_collection() ? collection_type_name(table.get_collection_type(column)) : "property";
1,038✔
40
    const char* column_type = string_for_property_type(type & ~PropertyType::Collection);
1,332✔
41
    throw IllegalOperation(util::format("Operation '%1' not supported for %2%3 %4 '%5.%6'", operation, column_type,
1,332✔
42
                                        column.is_nullable() ? "?" : "", collection_type, table.get_class_name(),
1,004✔
43
                                        table.get_column_name(column)));
1,332✔
44
}
1,332✔
45

46
Results::Results() = default;
3,210✔
47
Results::~Results() = default;
64,497✔
48

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

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

65

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

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

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

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

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

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

121
bool Results::is_valid() const
122
{
145,779✔
123
    if (m_realm) {
145,779✔
124
        m_realm->verify_thread();
145,725✔
125
    }
145,725✔
126

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

20,049✔
133
    if (m_collection)
40,098✔
134
        // Since m_table was not set, this is a collection of primitives
20,002✔
135
        // and the results validity depend directly on the collection
20,002✔
136
        return m_collection->is_attached();
40,004✔
137

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

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

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

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

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

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

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

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

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

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

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

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

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

57,071✔
220
    switch (m_mode) {
114,871✔
221
        case Mode::Empty:
22✔
222
            return;
22✔
223
        case Mode::Table:
13,701✔
224
            // Tables are always up-to-date
6,815✔
225
            return;
13,701✔
226
        case Mode::Collection: {
29,284✔
227
            // Collections themselves are always up-to-date, but we may need
14,642✔
228
            // to apply sort descriptors
14,642✔
229
            if (m_descriptor_ordering.is_empty())
29,284✔
230
                return;
16,584✔
231

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

465
List Results::get_list(size_t ndx)
466
{
6✔
467
    util::CheckedUniqueLock lock(m_mutex);
6✔
468
    REALM_ASSERT(m_mode == Mode::Collection);
6✔
469
    ensure_up_to_date();
6✔
470
    if (size_t actual = actual_index(ndx); actual < m_collection->size()) {
6✔
471
        return List{m_realm, m_collection->get_list(m_collection->get_path_element(actual))};
6✔
472
    }
6✔
NEW
473
    throw OutOfBounds{"get_list() on Results", ndx, m_collection->size()};
×
NEW
474
}
×
475

476
object_store::Set Results::get_set(size_t ndx)
477
{
6✔
478
    util::CheckedUniqueLock lock(m_mutex);
6✔
479
    REALM_ASSERT(m_mode == Mode::Collection);
6✔
480
    ensure_up_to_date();
6✔
481
    if (size_t actual = actual_index(ndx); actual < m_collection->size()) {
6✔
482
        return object_store::Set{m_realm, m_collection->get_set(m_collection->get_path_element(actual))};
6✔
483
    }
6✔
NEW
484
    throw OutOfBounds{"get_set() on Results", ndx, m_collection->size()};
×
NEW
485
}
×
486

487
object_store::Dictionary Results::get_dictionary(size_t ndx)
488
{
6✔
489
    util::CheckedUniqueLock lock(m_mutex);
6✔
490
    REALM_ASSERT(m_mode == Mode::Collection);
6✔
491
    ensure_up_to_date();
6✔
492
    if (size_t actual = actual_index(ndx); actual < m_collection->size()) {
6✔
493
        return object_store::Dictionary{m_realm,
6✔
494
                                        m_collection->get_dictionary(m_collection->get_path_element(actual))};
6✔
495
    }
6✔
NEW
496
    throw OutOfBounds{"get_dictionary() on Results", ndx, m_collection->size()};
×
NEW
497
}
×
498

499
std::pair<StringData, Mixed> Results::get_dictionary_element(size_t ndx)
500
{
324✔
501
    util::CheckedUniqueLock lock(m_mutex);
324✔
502
    REALM_ASSERT(m_mode == Mode::Collection);
324✔
503
    auto& dict = static_cast<Dictionary&>(*m_collection);
324✔
504
    REALM_ASSERT(typeid(dict) == typeid(Dictionary));
324✔
505

162✔
506
    ensure_up_to_date();
324✔
507
    if (size_t actual = actual_index(ndx); actual < dict.size()) {
324✔
508
        auto val = dict.get_pair(ndx);
324✔
509
        return {val.first.get_string(), val.second};
324✔
510
    }
324✔
511
    throw OutOfBounds{"get_dictionary_element() on Results", ndx, dict.size()};
×
512
}
×
513

514
template <typename T>
515
T Results::get(size_t row_ndx)
516
{
42,758✔
517
    util::CheckedUniqueLock lock(m_mutex);
42,758✔
518
    if (auto row = try_get<T>(row_ndx)) {
42,758✔
519
        return *row;
42,634✔
520
    }
42,634✔
521
    throw OutOfBounds{"get() on Results", row_ndx, do_size()};
124✔
522
}
124✔
523

524
template <typename T>
525
util::Optional<T> Results::first()
526
{
14,947✔
527
    util::CheckedUniqueLock lock(m_mutex);
14,947✔
528
    return try_get<T>(0);
14,947✔
529
}
14,947✔
530

531
template <typename T>
532
util::Optional<T> Results::last()
533
{
462✔
534
    util::CheckedUniqueLock lock(m_mutex);
462✔
535
    validate_read();
462✔
536
    if (m_mode == Mode::Query)
462✔
537
        ensure_up_to_date(); // avoid running the query twice (for size() and for get())
12✔
538
    return try_get<T>(do_size() - 1);
462✔
539
}
462✔
540

541
void Results::evaluate_query_if_needed(bool wants_notifications)
542
{
26✔
543
    util::CheckedUniqueLock lock(m_mutex);
26✔
544
    validate_read();
26✔
545
    ensure_up_to_date(wants_notifications ? EvaluateMode::Normal : EvaluateMode::Snapshot);
25✔
546
}
26✔
547

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

562
template <>
563
size_t Results::index_of(Mixed const& value)
564
{
2,276✔
565
    util::CheckedUniqueLock lock(m_mutex);
2,276✔
566
    validate_read();
2,276✔
567
    ensure_up_to_date();
2,276✔
568

1,138✔
569
    if (value.is_type(type_TypedLink)) {
2,276✔
570
        if (m_table && m_table->get_key() != value.get_link().get_table_key()) {
2✔
571
            return realm::not_found;
×
572
        }
×
573
    }
2,276✔
574

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

601
size_t Results::index_of(Query&& q)
602
{
×
603
    if (m_descriptor_ordering.will_apply_sort()) {
×
604
        Results filtered(filter(std::move(q)));
×
605
        filtered.assert_unlocked();
×
606
        auto first = filtered.first();
×
607
        return first ? index_of(*first) : not_found;
×
608
    }
×
609

610
    auto query = get_query().and_query(std::move(q));
×
611
    query.sync_view_if_needed();
×
612
    ObjKey row = query.find();
×
613
    return row ? index_of(const_cast<Table&>(*m_table).get_object(row)) : not_found;
×
614
}
×
615

616
namespace {
617
struct CollectionAggregateAdaptor {
618
    const CollectionBase& list;
619
    util::Optional<Mixed> min(ColKey)
620
    {
472✔
621
        return list.min();
472✔
622
    }
472✔
623
    util::Optional<Mixed> max(ColKey)
624
    {
472✔
625
        return list.max();
472✔
626
    }
472✔
627
    util::Optional<Mixed> sum(ColKey)
628
    {
448✔
629
        return list.sum();
448✔
630
    }
448✔
631
    util::Optional<Mixed> avg(ColKey)
632
    {
448✔
633
        return list.avg();
448✔
634
    }
448✔
635
};
636
} // anonymous namespace
637

638
template <typename AggregateFunction>
639
util::Optional<Mixed> Results::aggregate(ColKey column, const char* name, AggregateFunction&& func)
640
{
3,950✔
641
    util::CheckedUniqueLock lock(m_mutex);
3,950✔
642
    validate_read();
3,950✔
643
    if (!m_table && !m_collection)
3,950✔
644
        return none;
32✔
645

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

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

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

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

691
util::Optional<Mixed> Results::min(ColKey column)
692
{
1,010✔
693
    return aggregate(column, "min", [column](auto&& helper) {
1,006✔
694
        return helper.min(column);
1,002✔
695
    });
1,002✔
696
}
1,010✔
697

698
util::Optional<Mixed> Results::sum(ColKey column)
699
{
968✔
700
    return aggregate(column, "sum", [column](auto&& helper) {
964✔
701
        return helper.sum(column);
960✔
702
    });
960✔
703
}
968✔
704

705
util::Optional<Mixed> Results::average(ColKey column)
706
{
962✔
707
    return aggregate(column, "average", [column](auto&& helper) {
958✔
708
        return helper.avg(column);
954✔
709
    });
954✔
710
}
962✔
711

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

751
PropertyType Results::get_type() const
752
{
10,546✔
753
    util::CheckedUniqueLock lock(m_mutex);
10,546✔
754
    validate_read();
10,546✔
755
    return do_get_type();
10,546✔
756
}
10,546✔
757

758
PropertyType Results::do_get_type() const
759
{
34,047✔
760
    switch (m_mode) {
34,047✔
761
        case Mode::Empty:
3,242✔
762
        case Mode::Query:
4,666✔
763
        case Mode::TableView:
5,351✔
764
        case Mode::Table:
6,639✔
765
            return PropertyType::Object;
6,639✔
766
        case Mode::Collection:
27,408✔
767
            return ObjectSchema::from_core_type(m_collection->get_col_key());
27,408✔
768
    }
×
769
    REALM_COMPILER_HINT_UNREACHABLE();
×
770
}
×
771

772
Query Results::get_query() const
773
{
3,868✔
774
    util::CheckedUniqueLock lock(m_mutex);
3,868✔
775
    return do_get_query();
3,868✔
776
}
3,868✔
777

778
const DescriptorOrdering& Results::get_ordering() const REQUIRES(!m_mutex)
779
{
16✔
780
    return m_descriptor_ordering;
16✔
781
}
16✔
782

783
ConstTableRef Results::get_table() const
784
{
18✔
785
    util::CheckedUniqueLock lock(m_mutex);
18✔
786
    validate_read();
18✔
787
    switch (m_mode) {
18✔
788
        case Mode::Empty:
7✔
789
        case Mode::Query:
14✔
790
            return const_cast<Query&>(m_query).get_table();
14✔
791
        case Mode::TableView:
7✔
792
            return m_table_view.get_target_table();
×
793
        case Mode::Collection:
7✔
794
            return m_collection->get_target_table();
×
795
        case Mode::Table:
9✔
796
            return m_table;
4✔
797
    }
×
798
    REALM_COMPILER_HINT_UNREACHABLE();
×
799
}
×
800

801
Query Results::do_get_query() const
802
{
8,510✔
803
    validate_read();
8,510✔
804
    switch (m_mode) {
8,510✔
805
        case Mode::Empty:
1,944✔
806
        case Mode::Query:
3,454✔
807
        case Mode::TableView: {
3,888✔
808
            if (const_cast<Query&>(m_query).get_table())
3,888✔
809
                return m_query;
3,882✔
810

3✔
811
            // A TableView has an associated Query if it was produced by Query::find_all
3✔
812
            if (auto& query = m_table_view.get_query()) {
6✔
813
                return *query;
6✔
814
            }
6✔
815

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

839
TableView Results::get_tableview()
840
{
×
841
    util::CheckedUniqueLock lock(m_mutex);
×
842
    validate_read();
×
843
    ensure_up_to_date();
×
844
    switch (m_mode) {
×
845
        case Mode::Empty:
×
846
        case Mode::Collection:
×
847
            return do_get_query().find_all();
×
848
        case Mode::Query:
×
849
        case Mode::TableView:
×
850
            return m_table_view;
×
851
        case Mode::Table:
×
852
            return m_table->where().find_all();
×
853
    }
×
854
    REALM_COMPILER_HINT_UNREACHABLE();
×
855
}
×
856

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

1,290✔
870
    const char* begin = keypath.data();
2,580✔
871
    const char* end = keypath.data() + keypath.size();
2,580✔
872
    check(begin != end, "missing property name");
2,580✔
873

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

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

1,303✔
905
        if (index.length()) {
2,606✔
906
            indices.emplace_back(ColKey(prop->column_key), index);
4✔
907
        }
4✔
908
        else {
2,602✔
909
            indices.emplace_back(ColKey(prop->column_key));
2,602✔
910
        }
2,602✔
911
        if (prop->type == PropertyType::Object)
2,606✔
912
            object_schema = &*schema.find(prop->object_type);
28✔
913
    }
2,606✔
914
    return indices;
2,580✔
915
}
2,580✔
916

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

1,281✔
933
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
2,562✔
934
    std::vector<bool> ascending;
2,562✔
935
    column_keys.reserve(keypaths.size());
2,562✔
936
    ascending.reserve(keypaths.size());
2,562✔
937

1,281✔
938
    for (auto& keypath : keypaths) {
2,570✔
939
        column_keys.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
2,570✔
940
        ascending.push_back(keypath.second);
2,570✔
941
    }
2,570✔
942
    return sort({std::move(column_keys), std::move(ascending)});
2,562✔
943
}
2,562✔
944

945
Results Results::sort(SortDescriptor&& sort) const
946
{
4,320✔
947
    util::CheckedUniqueLock lock(m_mutex);
4,320✔
948
    DescriptorOrdering new_order = m_descriptor_ordering;
4,320✔
949
    new_order.append_sort(std::move(sort));
4,320✔
950
    if (m_mode == Mode::Collection)
4,320✔
951
        return Results(m_realm, m_collection, std::move(new_order));
1,908✔
952
    return Results(m_realm, do_get_query(), std::move(new_order));
2,412✔
953
}
2,412✔
954

955
Results Results::filter(Query&& q) const
956
{
12✔
957
    if (m_descriptor_ordering.will_apply_limit())
12✔
958
        throw IllegalOperation("Filtering a Results with a limit is not yet implemented");
2✔
959
    return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
10✔
960
}
10✔
961

962
Results Results::limit(size_t max_count) const
963
{
80✔
964
    util::CheckedUniqueLock lock(m_mutex);
80✔
965
    auto new_order = m_descriptor_ordering;
80✔
966
    new_order.append_limit(max_count);
80✔
967
    if (m_mode == Mode::Collection)
80✔
968
        return Results(m_realm, m_collection, std::move(new_order));
×
969
    return Results(m_realm, do_get_query(), std::move(new_order));
80✔
970
}
80✔
971

972
Results Results::apply_ordering(DescriptorOrdering&& ordering)
973
{
174✔
974
    util::CheckedUniqueLock lock(m_mutex);
174✔
975
    DescriptorOrdering new_order = m_descriptor_ordering;
174✔
976
    new_order.append(std::move(ordering));
174✔
977
    if (m_mode == Mode::Collection)
174✔
978
        return Results(m_realm, m_collection, std::move(new_order));
168✔
979
    return Results(m_realm, do_get_query(), std::move(new_order));
6✔
980
}
6✔
981

982
Results Results::distinct(DistinctDescriptor&& uniqueness) const
983
{
354✔
984
    DescriptorOrdering new_order = m_descriptor_ordering;
354✔
985
    new_order.append_distinct(std::move(uniqueness));
354✔
986
    util::CheckedUniqueLock lock(m_mutex);
354✔
987
    if (m_mode == Mode::Collection)
354✔
988
        return Results(m_realm, m_collection, std::move(new_order));
308✔
989
    return Results(m_realm, do_get_query(), std::move(new_order));
46✔
990
}
46✔
991

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

5✔
1008
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
10✔
1009
    column_keys.reserve(keypaths.size());
10✔
1010
    for (auto& keypath : keypaths)
10✔
1011
        column_keys.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
10✔
1012
    return distinct({std::move(column_keys)});
10✔
1013
}
10✔
1014

1015
Results Results::filter_by_method(std::function<bool(const Obj&)>&& predicate) const
1016
{
8✔
1017
    DescriptorOrdering new_order = m_descriptor_ordering;
8✔
1018
    new_order.append_filter(FilterDescriptor(std::move(predicate)));
8✔
1019
    util::CheckedUniqueLock lock(m_mutex);
8✔
1020
    if (m_mode == Mode::Collection)
8✔
1021
        return Results(m_realm, m_collection, std::move(new_order));
×
1022
    return Results(m_realm, do_get_query(), std::move(new_order));
8✔
1023
}
8✔
1024

1025
SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc&& section_key_func) REQUIRES(m_mutex)
1026
{
294✔
1027
    return SectionedResults(*this, std::move(section_key_func));
294✔
1028
}
294✔
1029

1030
SectionedResults Results::sectioned_results(SectionedResultsOperator op, util::Optional<StringData> prop_name)
1031
    REQUIRES(m_mutex)
1032
{
4✔
1033
    return SectionedResults(*this, op, prop_name.value_or(StringData()));
4✔
1034
}
4✔
1035

1036
Results Results::snapshot() const&
1037
{
214✔
1038
    validate_read();
214✔
1039
    auto clone = *this;
214✔
1040
    clone.assert_unlocked();
214✔
1041
    return static_cast<Results&&>(clone).snapshot();
214✔
1042
}
214✔
1043

1044
Results Results::snapshot() &&
1045
{
385✔
1046
    util::CheckedUniqueLock lock(m_mutex);
385✔
1047
    validate_read();
385✔
1048
    switch (m_mode) {
385✔
1049
        case Mode::Empty:
2✔
1050
            return Results();
2✔
1051

1052
        case Mode::Table:
130✔
1053
        case Mode::Collection:
204✔
1054
            m_query = do_get_query();
204✔
1055
            if (m_query.get_table()) {
204✔
1056
                m_mode = Mode::Query;
78✔
1057
            }
78✔
1058
            REALM_FALLTHROUGH;
204✔
1059
        case Mode::Query:
376✔
1060
        case Mode::TableView:
383✔
1061
            ensure_up_to_date(EvaluateMode::Snapshot);
383✔
1062
            m_notifier.reset();
383✔
1063
            if (do_get_type() == PropertyType::Object) {
383✔
1064
                m_update_policy = UpdatePolicy::Never;
257✔
1065
            }
257✔
1066
            return std::move(*this);
383✔
1067
    }
×
1068
    REALM_COMPILER_HINT_UNREACHABLE();
×
1069
}
×
1070

1071
// This function cannot be called on frozen results and so does not require locking
1072
void Results::prepare_async(ForCallback force) NO_THREAD_SAFETY_ANALYSIS
1073
{
38,581✔
1074
    REALM_ASSERT(m_realm);
38,581✔
1075
    if (m_notifier)
38,581✔
1076
        return;
166✔
1077
    if (!m_realm->verify_notifications_available(force))
38,415✔
1078
        return;
20,662✔
1079
    if (m_update_policy == UpdatePolicy::Never) {
17,753✔
1080
        if (force)
2✔
1081
            throw LogicError(ErrorCodes::IllegalOperation,
2✔
1082
                             "Cannot create asynchronous query for snapshotted Results.");
2✔
1083
        return;
×
1084
    }
×
1085

8,823✔
1086
    REALM_ASSERT(!force || !m_realm->is_frozen());
17,751✔
1087
    if (!force) {
17,751✔
1088
        // Don't do implicit background updates if we can't actually deliver them
6,052✔
1089
        if (!m_realm->can_deliver_notifications())
12,209✔
1090
            return;
12,153✔
1091
        // Don't do implicit background updates if there isn't actually anything
28✔
1092
        // that needs to be run.
28✔
1093
        if (!m_query.get_table() && m_descriptor_ordering.is_empty())
56✔
1094
            return;
2✔
1095
    }
5,596✔
1096

2,798✔
1097
    if (do_get_type() != PropertyType::Object)
5,596✔
1098
        m_notifier = std::make_shared<_impl::ListResultsNotifier>(*this);
1,810✔
1099
    else
3,786✔
1100
        m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
3,786✔
1101
    _impl::RealmCoordinator::register_notifier(m_notifier);
5,596✔
1102
}
5,596✔
1103

1104
NotificationToken Results::add_notification_callback(CollectionChangeCallback callback,
1105
                                                     std::optional<KeyPathArray> key_path_array) &
1106
{
5,710✔
1107
    prepare_async(ForCallback{true});
5,710✔
1108
    return {m_notifier, m_notifier->add_callback(std::move(callback), std::move(key_path_array))};
5,710✔
1109
}
5,710✔
1110

1111
// This function cannot be called on frozen results and so does not require locking
1112
bool Results::is_in_table_order() const NO_THREAD_SAFETY_ANALYSIS
1113
{
3,798✔
1114
    REALM_ASSERT(!m_realm || !m_realm->is_frozen());
3,798✔
1115
    switch (m_mode) {
3,798✔
1116
        case Mode::Empty:
62✔
1117
        case Mode::Table:
124✔
1118
            return true;
124✔
1119
        case Mode::Collection:
69✔
1120
            return false;
14✔
1121
        case Mode::Query:
2,810✔
1122
            return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
2,810✔
1123
        case Mode::TableView:
850✔
1124
            return m_table_view.is_in_table_order();
850✔
1125
    }
×
1126
    REALM_COMPILER_HINT_UNREACHABLE();
×
1127
}
×
1128

1129
ColKey Results::key(StringData name) const
1130
{
2✔
1131
    return m_table->get_column_key(name);
2✔
1132
}
2✔
1133
#define REALM_RESULTS_TYPE(T)                                                                                        \
1134
    template T Results::get<T>(size_t);                                                                              \
1135
    template util::Optional<T> Results::first<T>();                                                                  \
1136
    template util::Optional<T> Results::last<T>();
1137

1138
REALM_RESULTS_TYPE(bool)
1139
REALM_RESULTS_TYPE(int64_t)
1140
REALM_RESULTS_TYPE(float)
1141
REALM_RESULTS_TYPE(double)
1142
REALM_RESULTS_TYPE(StringData)
1143
REALM_RESULTS_TYPE(BinaryData)
1144
REALM_RESULTS_TYPE(Timestamp)
1145
REALM_RESULTS_TYPE(ObjectId)
1146
REALM_RESULTS_TYPE(Decimal)
1147
REALM_RESULTS_TYPE(UUID)
1148
REALM_RESULTS_TYPE(Mixed)
1149
REALM_RESULTS_TYPE(Obj)
1150
REALM_RESULTS_TYPE(util::Optional<bool>)
1151
REALM_RESULTS_TYPE(util::Optional<int64_t>)
1152
REALM_RESULTS_TYPE(util::Optional<float>)
1153
REALM_RESULTS_TYPE(util::Optional<double>)
1154
REALM_RESULTS_TYPE(util::Optional<ObjectId>)
1155
REALM_RESULTS_TYPE(util::Optional<UUID>)
1156

1157
#undef REALM_RESULTS_TYPE
1158

1159
Results Results::import_copy_into_realm(std::shared_ptr<Realm> const& realm)
1160
{
124✔
1161
    util::CheckedUniqueLock lock(m_mutex);
124✔
1162
    if (m_mode == Mode::Empty)
124✔
1163
        return *this;
4✔
1164

60✔
1165
    validate_read();
120✔
1166

60✔
1167
    switch (m_mode) {
120✔
1168
        case Mode::Table:
14✔
1169
            return Results(realm, realm->import_copy_of(m_table));
14✔
1170
        case Mode::Collection:
94✔
1171
            if (std::shared_ptr<CollectionBase> collection = realm->import_copy_of(*m_collection)) {
94✔
1172
                return Results(realm, collection, m_descriptor_ordering);
92✔
1173
            }
92✔
1174
            // If collection is gone, fallback to empty selection on table.
1✔
1175
            return Results(realm, TableView(realm->import_copy_of(m_table)));
2✔
1176
            break;
1✔
1177
        case Mode::Query:
4✔
1178
            return Results(realm, *realm->import_copy_of(m_query, PayloadPolicy::Copy), m_descriptor_ordering);
4✔
1179
        case Mode::TableView: {
2✔
1180
            Results results(realm, *realm->import_copy_of(m_table_view, PayloadPolicy::Copy), m_descriptor_ordering);
2✔
1181
            results.assert_unlocked();
2✔
1182
            results.evaluate_query_if_needed(false);
2✔
1183
            return results;
2✔
1184
        }
2✔
1185
        default:
1✔
1186
            REALM_COMPILER_HINT_UNREACHABLE();
×
1187
    }
120✔
1188
}
120✔
1189

1190
Results Results::freeze(std::shared_ptr<Realm> const& frozen_realm)
1191
{
124✔
1192
    return import_copy_into_realm(frozen_realm);
124✔
1193
}
124✔
1194

1195
bool Results::is_frozen() const
1196
{
8,750✔
1197
    return !m_realm || m_realm->is_frozen();
8,750✔
1198
}
8,750✔
1199

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

© 2025 Coveralls, Inc