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

realm / realm-core / github_pull_request_285284

21 Nov 2023 01:56PM UTC coverage: 91.664% (-0.03%) from 91.689%
github_pull_request_285284

Pull #7123

Evergreen

jedelbo
Merge branch 'master' into jf/mql
Pull Request #7123: PoC: Add MQL translation skeleton

92364 of 169228 branches covered (0.0%)

264 of 308 new or added lines in 5 files covered. (85.71%)

102 existing lines in 21 files now uncovered.

231504 of 252558 relevant lines covered (91.66%)

5988933.97 hits per line

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

90.91
/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
#include <realm/util/bson/bson.hpp>
30
#include <realm/set.hpp>
31

32
#include <stdexcept>
33

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

45
Results::Results() = default;
3,212✔
46
Results::~Results() = default;
64,599✔
47

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

59
Results::Results(SharedRealm r, ConstTableRef table, const bson::BsonDocument& document)
60
    : Results(r, table->query(document))
NEW
61
{
×
NEW
62
}
×
63

64
Results::Results(SharedRealm r, ConstTableRef table, const std::string& document)
65
    : Results(r, table->query(static_cast<bson::BsonDocument>(bson::parse(document))))
NEW
66
{
×
NEW
67
}
×
68

69
Results::Results(const Class& cls)
70
    : Results(cls.get_realm(), cls.get_table())
71
{
×
72
}
×
73

74

75
Results::Results(SharedRealm r, ConstTableRef table)
76
    : m_realm(std::move(r))
77
    , m_table(table)
78
    , m_table_view(m_table)
79
    , m_mode(Mode::Table)
80
    , m_mutex(m_realm && m_realm->is_frozen())
81
{
7,033✔
82
}
7,033✔
83

84
Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<CollectionBase> coll, util::Optional<Query> q,
85
                 SortDescriptor s)
86
    : m_realm(std::move(r))
87
    , m_table(coll->get_target_table())
88
    , m_collection(std::move(coll))
89
    , m_mode(Mode::Collection)
90
    , m_mutex(m_realm && m_realm->is_frozen())
91
{
13,885✔
92
    if (q) {
13,885✔
93
        m_query = std::move(*q);
17✔
94
        m_mode = Mode::Query;
17✔
95
    }
17✔
96
    m_descriptor_ordering.append_sort(std::move(s));
13,885✔
97
}
13,885✔
98

99
Results::Results(std::shared_ptr<Realm> r, std::shared_ptr<CollectionBase> coll, DescriptorOrdering o)
100
    : m_realm(std::move(r))
101
    , m_table(coll->get_target_table())
102
    , m_descriptor_ordering(std::move(o))
103
    , m_collection(std::move(coll))
104
    , m_mode(Mode::Collection)
105
    , m_mutex(m_realm && m_realm->is_frozen())
106
{
2,614✔
107
}
2,614✔
108

109
Results::Results(std::shared_ptr<Realm> r, TableView tv, DescriptorOrdering o)
110
    : m_realm(std::move(r))
111
    , m_table_view(std::move(tv))
112
    , m_descriptor_ordering(std::move(o))
113
    , m_mode(Mode::TableView)
114
    , m_mutex(m_realm && m_realm->is_frozen())
115
{
106✔
116
    m_table = m_table_view.get_parent();
106✔
117
}
106✔
118

119
Results::Results(const Results&) = default;
2,916✔
120
Results& Results::operator=(const Results&) = default;
×
121
Results::Results(Results&&) = default;
12,968✔
122
Results& Results::operator=(Results&&) = default;
3,532✔
123

124
Results::Mode Results::get_mode() const noexcept
125
{
52✔
126
    util::CheckedUniqueLock lock(m_mutex);
52✔
127
    return m_mode;
52✔
128
}
52✔
129

130
bool Results::is_valid() const
131
{
145,933✔
132
    if (m_realm) {
145,933✔
133
        m_realm->verify_thread();
145,879✔
134
    }
145,879✔
135

72,602✔
136
    // Here we cannot just use if (m_table) as it combines a check if the
72,602✔
137
    // reference contains a value and if that value is valid.
72,602✔
138
    // First we check if a table is referenced ...
72,602✔
139
    if (m_table.unchecked_ptr() != nullptr)
145,933✔
140
        return bool(m_table); // ... and then we check if it is valid
105,861✔
141

20,036✔
142
    if (m_collection)
40,072✔
143
        // Since m_table was not set, this is a collection of primitives
19,989✔
144
        // and the results validity depend directly on the collection
19,989✔
145
        return m_collection->is_attached();
39,978✔
146

47✔
147
    return true;
94✔
148
}
94✔
149

150
void Results::validate_read() const
151
{
140,069✔
152
    // is_valid ensures that we're on the correct thread.
69,670✔
153
    if (!is_valid())
140,069✔
154
        throw StaleAccessor("Access to invalidated Results objects");
16✔
155
}
140,069✔
156

157
void Results::validate_write() const
158
{
548✔
159
    validate_read();
548✔
160
    if (!m_realm || !m_realm->is_in_transaction())
548✔
161
        throw WrongTransactionState("Must be in a write transaction");
4✔
162
}
548✔
163

164
size_t Results::size()
165
{
41,417✔
166
    util::CheckedUniqueLock lock(m_mutex);
41,417✔
167
    return do_size();
41,417✔
168
}
41,417✔
169

170
size_t Results::do_size()
171
{
42,009✔
172
    validate_read();
42,009✔
173
    ensure_up_to_date(EvaluateMode::Count);
42,009✔
174
    switch (m_mode) {
42,009✔
175
        case Mode::Empty:
12✔
176
            return 0;
12✔
177
        case Mode::Table:
4,693✔
178
            return m_table ? m_table->size() : 0;
4,689✔
179
        case Mode::Collection:
6,456✔
180
            return m_list_indices ? m_list_indices->size() : m_collection->size();
5,273✔
181
        case Mode::Query:
26,532✔
182
            return m_query.count(m_descriptor_ordering);
26,532✔
183
        case Mode::TableView:
4,310✔
184
            return m_table_view.size();
4,310✔
185
    }
×
186
    REALM_COMPILER_HINT_UNREACHABLE();
×
187
}
×
188

189
const ObjectSchema& Results::get_object_schema() const
190
{
2,790✔
191
    validate_read();
2,790✔
192

1,395✔
193
    auto object_schema = m_object_schema.load();
2,790✔
194
    if (!object_schema) {
2,790✔
195
        REALM_ASSERT(m_realm);
2,704✔
196
        auto it = m_realm->schema().find(get_object_type());
2,704✔
197
        REALM_ASSERT(it != m_realm->schema().end());
2,704✔
198
        m_object_schema = object_schema = &*it;
2,704✔
199
    }
2,704✔
200

1,395✔
201
    return *object_schema;
2,790✔
202
}
2,790✔
203

204
StringData Results::get_object_type() const noexcept
205
{
2,746✔
206
    if (!m_table) {
2,746✔
207
        return StringData();
42✔
208
    }
42✔
209

1,352✔
210
    return ObjectStore::object_type_for_table_name(m_table->get_name());
2,704✔
211
}
2,704✔
212

213
bool Results::has_changed() REQUIRES(!m_mutex)
214
{
4,818✔
215
    util::CheckedUniqueLock lock(m_mutex);
4,818✔
216
    if (m_collection)
4,818✔
217
        return m_last_collection_content_version != m_collection->get_obj().get_table()->get_content_version();
4,376✔
218

221✔
219
    return m_table_view.has_changed();
442✔
220
}
442✔
221

222
void Results::ensure_up_to_date(EvaluateMode mode)
223
{
115,433✔
224
    if (m_update_policy == UpdatePolicy::Never) {
115,433✔
225
        REALM_ASSERT(m_mode == Mode::TableView);
450✔
226
        return;
450✔
227
    }
450✔
228

57,127✔
229
    switch (m_mode) {
114,983✔
230
        case Mode::Empty:
22✔
231
            return;
22✔
232
        case Mode::Table:
13,693✔
233
            // Tables are always up-to-date
6,811✔
234
            return;
13,693✔
235
        case Mode::Collection: {
29,244✔
236
            // Collections themselves are always up-to-date, but we may need
14,622✔
237
            // to apply sort descriptors
14,622✔
238
            if (m_descriptor_ordering.is_empty())
29,244✔
239
                return;
16,544✔
240

6,350✔
241
            // Collections of objects are sorted/distincted by converting them
6,350✔
242
            // to a TableView
6,350✔
243
            if (do_get_type() == PropertyType::Object) {
12,700✔
244
                m_query = do_get_query();
172✔
245
                m_mode = Mode::Query;
172✔
246
                ensure_up_to_date(mode);
172✔
247
                return;
172✔
248
            }
172✔
249

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

6,092✔
257
            bool needs_update = m_collection->has_changed();
12,184✔
258
            if (!m_list_indices) {
12,184✔
259
                m_list_indices = std::vector<size_t>{};
1,324✔
260
                needs_update = true;
1,324✔
261
            }
1,324✔
262
            if (!needs_update)
12,184✔
263
                return;
10,630✔
264

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

777✔
267
            if (m_collection->is_empty()) {
1,554✔
268
                m_list_indices->clear();
86✔
269
                return;
86✔
270
            }
86✔
271

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

734✔
288
            if (do_distinct)
1,468✔
289
                m_collection->distinct(*m_list_indices, sort_order);
344✔
290
            else if (sort_order)
1,124✔
291
                m_collection->sort(*m_list_indices, *sort_order);
1,124✔
292
            return;
1,468✔
293
        }
1,468✔
294

734✔
295
        case Mode::Query:
39,518✔
296
            // Everything except for size() requires evaluating the Query and
19,468✔
297
            // getting a TableView, and size() does as well if distinct is involved.
19,468✔
298
            if (mode == EvaluateMode::Count && !m_descriptor_ordering.will_apply_distinct()) {
39,518✔
299
                m_query.sync_view_if_needed();
26,532✔
300
                return;
26,532✔
301
            }
26,532✔
302

6,361✔
303
            // First we check if we ran the Query in the background and can
6,361✔
304
            // just use that
6,361✔
305
            if (m_notifier && m_notifier->get_tableview(m_table_view)) {
12,986✔
306
                m_mode = Mode::TableView;
2,264✔
307
                if (auto audit = m_realm->audit_context())
2,264✔
308
                    audit->record_query(m_realm->read_transaction_version(), m_table_view);
×
309
                return;
2,264✔
310
            }
2,264✔
311

5,229✔
312
            // We have to actually run the Query locally. We have an option
5,229✔
313
            // to disable this for testing purposes as it's otherwise very
5,229✔
314
            // difficult to determine if the async query is actually being
5,229✔
315
            // used.
5,229✔
316
            m_query.sync_view_if_needed();
10,722✔
317
            if (m_update_policy != UpdatePolicy::AsyncOnly)
10,722✔
318
                m_table_view = m_query.find_all(m_descriptor_ordering);
10,700✔
319
            m_mode = Mode::TableView;
10,722✔
320
            if (auto audit = m_realm->audit_context())
10,722✔
321
                audit->record_query(m_realm->read_transaction_version(), m_table_view);
159✔
322

5,229✔
323
            // Unless we're creating a snapshot, create an async notifier that'll
5,229✔
324
            // rerun this query in the background.
5,229✔
325
            if (mode != EvaluateMode::Snapshot && !m_notifier)
10,722✔
326
                prepare_async(ForCallback{false});
10,471✔
327
            return;
10,722✔
328

5,229✔
329
        case Mode::TableView:
32,506✔
330
            // Unless we're creating a snapshot, create an async notifier that'll
16,215✔
331
            // rerun this query in the background.
16,215✔
332
            if (mode != EvaluateMode::Snapshot && !m_notifier)
32,506✔
333
                prepare_async(ForCallback{false});
22,472✔
334
            // First check if we have an up-to-date TableView waiting for us
5,017✔
335
            // which was generated on the background thread
5,017✔
336
            else if (m_notifier)
10,034✔
337
                m_notifier->get_tableview(m_table_view);
10,020✔
338
            // This option is here so that tests can verify that the notifier
16,215✔
339
            // is actually being used.
16,215✔
340
            if (m_update_policy == UpdatePolicy::Auto)
32,506✔
341
                m_table_view.sync_if_needed();
32,464✔
342
            if (auto audit = m_realm->audit_context())
32,506✔
343
                audit->record_query(m_realm->read_transaction_version(), m_table_view);
×
344
            return;
32,506✔
345
    }
114,983✔
346
}
114,983✔
347

348
size_t Results::actual_index(size_t ndx) const noexcept
349
{
14,978✔
350
    if (auto& indices = m_list_indices) {
14,978✔
351
        return ndx < indices->size() ? (*indices)[ndx] : npos;
9,188✔
352
    }
9,188✔
353
    return ndx;
5,790✔
354
}
5,790✔
355

356
template <typename T>
357
static T get_unwraped(CollectionBase& collection, size_t ndx)
358
{
7,622✔
359
    using U = typename util::RemoveOptional<T>::type;
7,622✔
360
    Mixed mixed = collection.get_any(ndx);
7,622✔
361
    if (!mixed.is_null())
7,622✔
362
        return mixed.get<U>();
6,828✔
363
    return BPlusTree<T>::default_value(collection.get_col_key().is_nullable());
794✔
364
}
794✔
365

366
template <typename T>
367
util::Optional<T> Results::try_get(size_t ndx)
368
{
8,126✔
369
    validate_read();
8,126✔
370
    ensure_up_to_date();
8,126✔
371
    if (m_mode == Mode::Collection) {
8,126✔
372
        ndx = actual_index(ndx);
8,126✔
373
        if (ndx < m_collection->size()) {
8,126✔
374
            return get_unwraped<T>(*m_collection, ndx);
7,622✔
375
        }
7,622✔
376
    }
504✔
377
    return util::none;
504✔
378
}
504✔
379

380
Results::IteratorWrapper::IteratorWrapper(IteratorWrapper const& rgt)
381
{
2,916✔
382
    *this = rgt;
2,916✔
383
}
2,916✔
384

385
Results::IteratorWrapper& Results::IteratorWrapper::operator=(IteratorWrapper const& rgt)
386
{
2,916✔
387
    if (rgt.m_it)
2,916✔
388
        m_it = std::make_unique<Table::Iterator>(*rgt.m_it);
×
389
    return *this;
2,916✔
390
}
2,916✔
391

392
Obj Results::IteratorWrapper::get(Table const& table, size_t ndx)
393
{
8,848✔
394
    // Using a Table iterator is much faster for repeated access into a table
4,424✔
395
    // than indexing into it as the iterator caches the cluster the last accessed
4,424✔
396
    // object is stored in, but creating the iterator is somewhat expensive.
4,424✔
397
    if (!m_it) {
8,848✔
398
        if (table.size() <= 5)
702✔
399
            return const_cast<Table&>(table).get_object(ndx);
670✔
400
        m_it = std::make_unique<Table::Iterator>(table.begin());
32✔
401
    }
32✔
402
    m_it->go(ndx);
8,513✔
403
    return **m_it;
8,178✔
404
}
8,848✔
405

406
template <>
407
util::Optional<Obj> Results::try_get(size_t row_ndx)
408
{
50,095✔
409
    validate_read();
50,095✔
410
    ensure_up_to_date();
50,095✔
411
    switch (m_mode) {
50,095✔
412
        case Mode::Empty:
8✔
413
            break;
8✔
414
        case Mode::Table:
8,860✔
415
            if (m_table && row_ndx < m_table->size())
8,860✔
416
                return m_table_iterator.get(*m_table, row_ndx);
8,842✔
417
            break;
18✔
418
        case Mode::Collection:
890✔
419
            if (row_ndx < m_collection->size()) {
890✔
420
                auto m = m_collection->get_any(row_ndx);
866✔
421
                if (m.is_null())
866✔
422
                    return Obj();
84✔
423
                if (m.get_type() == type_Link)
782✔
424
                    return m_table->get_object(m.get<ObjKey>());
×
425
                if (m.get_type() == type_TypedLink)
782✔
426
                    return m_table->get_parent_group()->get_object(m.get_link());
782✔
427
            }
24✔
428
            break;
24✔
429
        case Mode::Query:
12✔
430
            REALM_UNREACHABLE();
431
        case Mode::TableView:
40,337✔
432
            if (row_ndx >= m_table_view.size())
40,337✔
433
                break;
9,166✔
434
            return m_table_view.get_object(row_ndx);
31,171✔
435
    }
9,216✔
436
    return util::none;
9,216✔
437
}
9,216✔
438

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

474
std::pair<StringData, Mixed> Results::get_dictionary_element(size_t ndx)
475
{
324✔
476
    util::CheckedUniqueLock lock(m_mutex);
324✔
477
    REALM_ASSERT(m_mode == Mode::Collection);
324✔
478
    auto& dict = static_cast<Dictionary&>(*m_collection);
324✔
479
    REALM_ASSERT(typeid(dict) == typeid(Dictionary));
324✔
480

162✔
481
    ensure_up_to_date();
324✔
482
    if (size_t actual = actual_index(ndx); actual < dict.size()) {
324✔
483
        auto val = dict.get_pair(ndx);
324✔
484
        return {val.first.get_string(), val.second};
324✔
485
    }
324✔
486
    throw OutOfBounds{"get_dictionary_element() on Results", ndx, dict.size()};
×
487
}
×
488

489
template <typename T>
490
T Results::get(size_t row_ndx)
491
{
42,740✔
492
    util::CheckedUniqueLock lock(m_mutex);
42,740✔
493
    if (auto row = try_get<T>(row_ndx)) {
42,740✔
494
        return *row;
42,616✔
495
    }
42,616✔
496
    throw OutOfBounds{"get() on Results", row_ndx, do_size()};
124✔
497
}
124✔
498

499
template <typename T>
500
util::Optional<T> Results::first()
501
{
15,019✔
502
    util::CheckedUniqueLock lock(m_mutex);
15,019✔
503
    return try_get<T>(0);
15,019✔
504
}
15,019✔
505

506
template <typename T>
507
util::Optional<T> Results::last()
508
{
462✔
509
    util::CheckedUniqueLock lock(m_mutex);
462✔
510
    validate_read();
462✔
511
    if (m_mode == Mode::Query)
462✔
512
        ensure_up_to_date(); // avoid running the query twice (for size() and for get())
12✔
513
    return try_get<T>(do_size() - 1);
462✔
514
}
462✔
515

516
void Results::evaluate_query_if_needed(bool wants_notifications)
517
{
26✔
518
    util::CheckedUniqueLock lock(m_mutex);
26✔
519
    validate_read();
26✔
520
    ensure_up_to_date(wants_notifications ? EvaluateMode::Normal : EvaluateMode::Snapshot);
25✔
521
}
26✔
522

523
template <>
524
size_t Results::index_of(Obj const& obj)
525
{
146✔
526
    if (!obj.is_valid()) {
146✔
527
        throw StaleAccessor{"Attempting to access an invalid object"};
12✔
528
    }
12✔
529
    if (m_table && obj.get_table() != m_table) {
134✔
530
        throw InvalidArgument(ErrorCodes::ObjectTypeMismatch,
26✔
531
                              util::format("Object of type '%1' does not match Results type '%2'",
26✔
532
                                           obj.get_table()->get_class_name(), m_table->get_class_name()));
26✔
533
    }
26✔
534
    return index_of(Mixed(obj.get_key()));
108✔
535
}
108✔
536

537
template <>
538
size_t Results::index_of(Mixed const& value)
539
{
2,276✔
540
    util::CheckedUniqueLock lock(m_mutex);
2,276✔
541
    validate_read();
2,276✔
542
    ensure_up_to_date();
2,276✔
543

1,138✔
544
    if (value.is_type(type_TypedLink)) {
2,276✔
545
        if (m_table && m_table->get_key() != value.get_link().get_table_key()) {
2✔
546
            return realm::not_found;
×
547
        }
×
548
    }
2,276✔
549

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

576
size_t Results::index_of(Query&& q)
577
{
×
578
    if (m_descriptor_ordering.will_apply_sort()) {
×
579
        Results filtered(filter(std::move(q)));
×
580
        filtered.assert_unlocked();
×
581
        auto first = filtered.first();
×
582
        return first ? index_of(*first) : not_found;
×
583
    }
×
584

585
    auto query = get_query().and_query(std::move(q));
×
586
    query.sync_view_if_needed();
×
587
    ObjKey row = query.find();
×
588
    return row ? index_of(const_cast<Table&>(*m_table).get_object(row)) : not_found;
×
589
}
×
590

591
namespace {
592
struct CollectionAggregateAdaptor {
593
    const CollectionBase& list;
594
    util::Optional<Mixed> min(ColKey)
595
    {
472✔
596
        return list.min();
472✔
597
    }
472✔
598
    util::Optional<Mixed> max(ColKey)
599
    {
472✔
600
        return list.max();
472✔
601
    }
472✔
602
    util::Optional<Mixed> sum(ColKey)
603
    {
448✔
604
        return list.sum();
448✔
605
    }
448✔
606
    util::Optional<Mixed> avg(ColKey)
607
    {
448✔
608
        return list.avg();
448✔
609
    }
448✔
610
};
611
} // anonymous namespace
612

613
template <typename AggregateFunction>
614
util::Optional<Mixed> Results::aggregate(ColKey column, const char* name, AggregateFunction&& func)
615
{
3,950✔
616
    util::CheckedUniqueLock lock(m_mutex);
3,950✔
617
    validate_read();
3,950✔
618
    if (!m_table && !m_collection)
3,950✔
619
        return none;
32✔
620

1,959✔
621
    ensure_up_to_date();
3,918✔
622
    std::optional<Mixed> ret;
3,918✔
623
    switch (m_mode) {
3,918✔
624
        case Mode::Table:
112✔
625
            ret = func(*m_table);
112✔
626
            break;
112✔
627
        case Mode::Query:
✔
628
            ret = func(m_query);
×
629
            break;
×
630
        case Mode::Collection:
3,554✔
631
            if (do_get_type() != PropertyType::Object)
3,554✔
632
                ret = func(CollectionAggregateAdaptor{*m_collection});
1,840✔
633
            else
1,714✔
634
                ret = func(do_get_query());
1,714✔
635
            break;
3,554✔
636
        default:
252✔
637
            ret = func(m_table_view);
252✔
638
            break;
252✔
639
    }
3,870✔
640

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

666✔
648
    // We need to report the column and table actually being aggregated on,
666✔
649
    // which is the collection if it's not a link collection and the target
666✔
650
    // of the links otherwise
666✔
651
    if (m_mode == Mode::Collection && do_get_type() != PropertyType::Object) {
1,332✔
652
        unsupported_operation(m_collection->get_col_key(), *m_collection->get_table(), name);
704✔
653
    }
704✔
654
    else {
628✔
655
        unsupported_operation(column, *m_table, name);
628✔
656
    }
628✔
657
}
1,332✔
658

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

666
util::Optional<Mixed> Results::min(ColKey column)
667
{
1,010✔
668
    return aggregate(column, "min", [column](auto&& helper) {
1,006✔
669
        return helper.min(column);
1,002✔
670
    });
1,002✔
671
}
1,010✔
672

673
util::Optional<Mixed> Results::sum(ColKey column)
674
{
968✔
675
    return aggregate(column, "sum", [column](auto&& helper) {
964✔
676
        return helper.sum(column);
960✔
677
    });
960✔
678
}
968✔
679

680
util::Optional<Mixed> Results::average(ColKey column)
681
{
962✔
682
    return aggregate(column, "average", [column](auto&& helper) {
958✔
683
        return helper.avg(column);
954✔
684
    });
954✔
685
}
962✔
686

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

726
PropertyType Results::get_type() const
727
{
10,546✔
728
    util::CheckedUniqueLock lock(m_mutex);
10,546✔
729
    validate_read();
10,546✔
730
    return do_get_type();
10,546✔
731
}
10,546✔
732

733
PropertyType Results::do_get_type() const
734
{
34,047✔
735
    switch (m_mode) {
34,047✔
736
        case Mode::Empty:
3,242✔
737
        case Mode::Query:
4,665✔
738
        case Mode::TableView:
5,350✔
739
        case Mode::Table:
6,639✔
740
            return PropertyType::Object;
6,639✔
741
        case Mode::Collection:
27,408✔
742
            return ObjectSchema::from_core_type(m_collection->get_col_key());
27,408✔
743
    }
×
744
    REALM_COMPILER_HINT_UNREACHABLE();
×
745
}
×
746

747
Query Results::get_query() const
748
{
3,896✔
749
    util::CheckedUniqueLock lock(m_mutex);
3,896✔
750
    return do_get_query();
3,896✔
751
}
3,896✔
752

753
const DescriptorOrdering& Results::get_ordering() const REQUIRES(!m_mutex)
754
{
16✔
755
    return m_descriptor_ordering;
16✔
756
}
16✔
757

758
ConstTableRef Results::get_table() const
759
{
18✔
760
    util::CheckedUniqueLock lock(m_mutex);
18✔
761
    validate_read();
18✔
762
    switch (m_mode) {
18✔
763
        case Mode::Empty:
7✔
764
        case Mode::Query:
14✔
765
            return const_cast<Query&>(m_query).get_table();
14✔
766
        case Mode::TableView:
7✔
767
            return m_table_view.get_target_table();
×
768
        case Mode::Collection:
7✔
769
            return m_collection->get_target_table();
×
770
        case Mode::Table:
9✔
771
            return m_table;
4✔
772
    }
×
773
    REALM_COMPILER_HINT_UNREACHABLE();
×
774
}
×
775

776
Query Results::do_get_query() const
777
{
8,538✔
778
    validate_read();
8,538✔
779
    switch (m_mode) {
8,538✔
780
        case Mode::Empty:
1,943✔
781
        case Mode::Query:
3,452✔
782
        case Mode::TableView: {
3,886✔
783
            if (const_cast<Query&>(m_query).get_table())
3,886✔
784
                return m_query;
3,880✔
785

3✔
786
            // A TableView has an associated Query if it was produced by Query::find_all
3✔
787
            if (auto& query = m_table_view.get_query()) {
6✔
788
                return *query;
6✔
789
            }
6✔
790

791
            // The TableView has no associated query so create one with no conditions that is restricted
792
            // to the rows in the TableView.
793
            if (m_update_policy == UpdatePolicy::Auto) {
×
794
                m_table_view.sync_if_needed();
×
795
            }
×
796
            return Query(m_table, std::make_unique<TableView>(m_table_view));
×
797
        }
×
798
        case Mode::Collection:
2,050✔
799
            if (auto list = dynamic_cast<ObjList*>(m_collection.get())) {
2,050✔
800
                return m_table->where(*list);
1,420✔
801
            }
1,420✔
802
            if (auto dict = dynamic_cast<Dictionary*>(m_collection.get())) {
630✔
803
                if (dict->get_value_data_type() == type_Link) {
546✔
804
                    return m_table->where(*dict);
504✔
805
                }
504✔
806
            }
126✔
807
            return m_query;
126✔
808
        case Mode::Table:
2,602✔
809
            return m_table->where();
2,602✔
810
    }
×
811
    REALM_COMPILER_HINT_UNREACHABLE();
×
812
}
×
813

814
TableView Results::get_tableview()
815
{
×
816
    util::CheckedUniqueLock lock(m_mutex);
×
817
    validate_read();
×
818
    ensure_up_to_date();
×
819
    switch (m_mode) {
×
820
        case Mode::Empty:
×
821
        case Mode::Collection:
×
822
            return do_get_query().find_all();
×
823
        case Mode::Query:
×
824
        case Mode::TableView:
×
825
            return m_table_view;
×
826
        case Mode::Table:
×
827
            return m_table->where().find_all();
×
828
    }
×
829
    REALM_COMPILER_HINT_UNREACHABLE();
×
830
}
×
831

832
static std::vector<ExtendedColumnKey> parse_keypath(StringData keypath, Schema const& schema,
833
                                                    const ObjectSchema* object_schema)
834
{
2,580✔
835
    auto check = [&](bool condition, const char* fmt, auto... args) {
12,960✔
836
        if (!condition) {
12,960✔
837
            throw InvalidArgument(
36✔
838
                util::format("Cannot sort on key path '%1': %2.", keypath, util::format(fmt, args...)));
36✔
839
        }
36✔
840
    };
12,960✔
841
    auto is_sortable_type = [](PropertyType type) {
2,590✔
842
        return !is_collection(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data;
2,590✔
843
    };
2,590✔
844

1,290✔
845
    const char* begin = keypath.data();
2,580✔
846
    const char* end = keypath.data() + keypath.size();
2,580✔
847
    check(begin != end, "missing property name");
2,580✔
848

1,290✔
849
    std::vector<ExtendedColumnKey> indices;
2,580✔
850
    while (begin != end) {
5,186✔
851
        auto sep = std::find(begin, end, '.');
2,606✔
852
        check(sep != begin && sep + 1 != end, "missing property name");
2,606✔
853
        StringData key(begin, sep - begin);
2,606✔
854
        std::string index;
2,606✔
855
        auto begin_key = std::find(begin, sep, '[');
2,606✔
856
        if (begin_key != sep) {
2,606✔
857
            auto end_key = std::find(begin_key, sep, ']');
4✔
858
            check(end_key != sep, "missing ']'");
4✔
859
            index = std::string(begin_key + 1, end_key);
4✔
860
            key = StringData(begin, begin_key - begin);
4✔
861
        }
4✔
862
        begin = sep + (sep != end);
2,606✔
863

1,303✔
864
        auto prop = object_schema->property_for_public_name(key);
2,606✔
865
        check(prop, "property '%1.%2' does not exist", object_schema->name, key);
2,606✔
866
        if (is_dictionary(prop->type)) {
2,606✔
867
            check(index.length(), "missing dictionary key");
4✔
868
        }
4✔
869
        else {
2,602✔
870
            check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'", object_schema->name,
2,602✔
871
                  key, string_for_property_type(prop->type));
2,602✔
872
        }
2,602✔
873
        if (prop->type == PropertyType::Object)
2,606✔
874
            check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path",
32✔
875
                  object_schema->name, key);
32✔
876
        else
2,574✔
877
            check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path",
2,574✔
878
                  object_schema->name, key, prop->type_string());
2,574✔
879

1,303✔
880
        if (index.length()) {
2,606✔
881
            indices.emplace_back(ColKey(prop->column_key), index);
4✔
882
        }
4✔
883
        else {
2,602✔
884
            indices.emplace_back(ColKey(prop->column_key));
2,602✔
885
        }
2,602✔
886
        if (prop->type == PropertyType::Object)
2,606✔
887
            object_schema = &*schema.find(prop->object_type);
28✔
888
    }
2,606✔
889
    return indices;
2,580✔
890
}
2,580✔
891

892
Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const
893
{
4,434✔
894
    if (keypaths.empty())
4,434✔
895
        return *this;
42✔
896
    auto type = get_type();
4,392✔
897
    if (type != PropertyType::Object) {
4,392✔
898
        if (keypaths.size() != 1)
1,830✔
899
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
42✔
900
                                               string_for_property_type(type & ~PropertyType::Flags)));
42✔
901
        if (keypaths[0].first != "self")
1,788✔
902
            throw InvalidArgument(
42✔
903
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
42✔
904
                             keypaths[0].first, string_for_property_type(type & ~PropertyType::Flags)));
42✔
905
        return sort({{{}}, {keypaths[0].second}});
1,746✔
906
    }
1,746✔
907

1,281✔
908
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
2,562✔
909
    std::vector<bool> ascending;
2,562✔
910
    column_keys.reserve(keypaths.size());
2,562✔
911
    ascending.reserve(keypaths.size());
2,562✔
912

1,281✔
913
    for (auto& keypath : keypaths) {
2,570✔
914
        column_keys.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
2,570✔
915
        ascending.push_back(keypath.second);
2,570✔
916
    }
2,570✔
917
    return sort({std::move(column_keys), std::move(ascending)});
2,562✔
918
}
2,562✔
919

920
Results Results::sort(SortDescriptor&& sort) const
921
{
4,320✔
922
    util::CheckedUniqueLock lock(m_mutex);
4,320✔
923
    DescriptorOrdering new_order = m_descriptor_ordering;
4,320✔
924
    new_order.append_sort(std::move(sort));
4,320✔
925
    if (m_mode == Mode::Collection)
4,320✔
926
        return Results(m_realm, m_collection, std::move(new_order));
1,908✔
927
    return Results(m_realm, do_get_query(), std::move(new_order));
2,412✔
928
}
2,412✔
929

930
Results Results::filter(Query&& q) const
931
{
40✔
932
    if (m_descriptor_ordering.will_apply_limit())
40✔
933
        throw IllegalOperation("Filtering a Results with a limit is not yet implemented");
2✔
934
    return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
38✔
935
}
38✔
936

937
Results Results::limit(size_t max_count) const
938
{
80✔
939
    util::CheckedUniqueLock lock(m_mutex);
80✔
940
    auto new_order = m_descriptor_ordering;
80✔
941
    new_order.append_limit(max_count);
80✔
942
    if (m_mode == Mode::Collection)
80✔
943
        return Results(m_realm, m_collection, std::move(new_order));
×
944
    return Results(m_realm, do_get_query(), std::move(new_order));
80✔
945
}
80✔
946

947
Results Results::apply_ordering(DescriptorOrdering&& ordering)
948
{
174✔
949
    util::CheckedUniqueLock lock(m_mutex);
174✔
950
    DescriptorOrdering new_order = m_descriptor_ordering;
174✔
951
    new_order.append(std::move(ordering));
174✔
952
    if (m_mode == Mode::Collection)
174✔
953
        return Results(m_realm, m_collection, std::move(new_order));
168✔
954
    return Results(m_realm, do_get_query(), std::move(new_order));
6✔
955
}
6✔
956

957
Results Results::distinct(DistinctDescriptor&& uniqueness) const
958
{
354✔
959
    DescriptorOrdering new_order = m_descriptor_ordering;
354✔
960
    new_order.append_distinct(std::move(uniqueness));
354✔
961
    util::CheckedUniqueLock lock(m_mutex);
354✔
962
    if (m_mode == Mode::Collection)
354✔
963
        return Results(m_realm, m_collection, std::move(new_order));
308✔
964
    return Results(m_realm, do_get_query(), std::move(new_order));
46✔
965
}
46✔
966

967
Results Results::distinct(std::vector<std::string> const& keypaths) const
968
{
390✔
969
    if (keypaths.empty())
390✔
970
        return *this;
42✔
971
    auto type = get_type();
348✔
972
    if (type != PropertyType::Object) {
348✔
973
        if (keypaths.size() != 1)
338✔
974
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
42✔
975
                                               string_for_property_type(type & ~PropertyType::Flags)));
42✔
976
        if (keypaths[0] != "self")
296✔
977
            throw InvalidArgument(
42✔
978
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", keypaths[0],
42✔
979
                             string_for_property_type(type & ~PropertyType::Flags)));
42✔
980
        return distinct(DistinctDescriptor({{ColKey()}}));
254✔
981
    }
254✔
982

5✔
983
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
10✔
984
    column_keys.reserve(keypaths.size());
10✔
985
    for (auto& keypath : keypaths)
10✔
986
        column_keys.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
10✔
987
    return distinct({std::move(column_keys)});
10✔
988
}
10✔
989

990
Results Results::filter_by_method(std::function<bool(const Obj&)>&& predicate) const
991
{
8✔
992
    DescriptorOrdering new_order = m_descriptor_ordering;
8✔
993
    new_order.append_filter(FilterDescriptor(std::move(predicate)));
8✔
994
    util::CheckedUniqueLock lock(m_mutex);
8✔
995
    if (m_mode == Mode::Collection)
8✔
996
        return Results(m_realm, m_collection, std::move(new_order));
×
997
    return Results(m_realm, do_get_query(), std::move(new_order));
8✔
998
}
8✔
999

1000
SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc&& section_key_func) REQUIRES(m_mutex)
1001
{
294✔
1002
    return SectionedResults(*this, std::move(section_key_func));
294✔
1003
}
294✔
1004

1005
SectionedResults Results::sectioned_results(SectionedResultsOperator op, util::Optional<StringData> prop_name)
1006
    REQUIRES(m_mutex)
1007
{
4✔
1008
    return SectionedResults(*this, op, prop_name.value_or(StringData()));
4✔
1009
}
4✔
1010

1011
Results Results::snapshot() const&
1012
{
214✔
1013
    validate_read();
214✔
1014
    auto clone = *this;
214✔
1015
    clone.assert_unlocked();
214✔
1016
    return static_cast<Results&&>(clone).snapshot();
214✔
1017
}
214✔
1018

1019
Results Results::snapshot() &&
1020
{
385✔
1021
    util::CheckedUniqueLock lock(m_mutex);
385✔
1022
    validate_read();
385✔
1023
    switch (m_mode) {
385✔
1024
        case Mode::Empty:
2✔
1025
            return Results();
2✔
1026

1027
        case Mode::Table:
130✔
1028
        case Mode::Collection:
204✔
1029
            m_query = do_get_query();
204✔
1030
            if (m_query.get_table()) {
204✔
1031
                m_mode = Mode::Query;
78✔
1032
            }
78✔
1033
            REALM_FALLTHROUGH;
204✔
1034
        case Mode::Query:
376✔
1035
        case Mode::TableView:
383✔
1036
            ensure_up_to_date(EvaluateMode::Snapshot);
383✔
1037
            m_notifier.reset();
383✔
1038
            if (do_get_type() == PropertyType::Object) {
383✔
1039
                m_update_policy = UpdatePolicy::Never;
257✔
1040
            }
257✔
1041
            return std::move(*this);
383✔
1042
    }
×
1043
    REALM_COMPILER_HINT_UNREACHABLE();
×
1044
}
×
1045

1046
// This function cannot be called on frozen results and so does not require locking
1047
void Results::prepare_async(ForCallback force) NO_THREAD_SAFETY_ANALYSIS
1048
{
38,653✔
1049
    REALM_ASSERT(m_realm);
38,653✔
1050
    if (m_notifier)
38,653✔
1051
        return;
166✔
1052
    if (!m_realm->verify_notifications_available(force))
38,487✔
1053
        return;
20,686✔
1054
    if (m_update_policy == UpdatePolicy::Never) {
17,801✔
1055
        if (force)
2✔
1056
            throw LogicError(ErrorCodes::IllegalOperation,
2✔
1057
                             "Cannot create asynchronous query for snapshotted Results.");
2✔
1058
        return;
×
1059
    }
×
1060

8,847✔
1061
    REALM_ASSERT(!force || !m_realm->is_frozen());
17,799✔
1062
    if (!force) {
17,799✔
1063
        // Don't do implicit background updates if we can't actually deliver them
6,076✔
1064
        if (!m_realm->can_deliver_notifications())
12,257✔
1065
            return;
12,201✔
1066
        // Don't do implicit background updates if there isn't actually anything
28✔
1067
        // that needs to be run.
28✔
1068
        if (!m_query.get_table() && m_descriptor_ordering.is_empty())
56✔
1069
            return;
2✔
1070
    }
5,596✔
1071

2,798✔
1072
    if (do_get_type() != PropertyType::Object)
5,596✔
1073
        m_notifier = std::make_shared<_impl::ListResultsNotifier>(*this);
1,810✔
1074
    else
3,786✔
1075
        m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
3,786✔
1076
    _impl::RealmCoordinator::register_notifier(m_notifier);
5,596✔
1077
}
5,596✔
1078

1079
NotificationToken Results::add_notification_callback(CollectionChangeCallback callback,
1080
                                                     std::optional<KeyPathArray> key_path_array) &
1081
{
5,710✔
1082
    prepare_async(ForCallback{true});
5,710✔
1083
    return {m_notifier, m_notifier->add_callback(std::move(callback), std::move(key_path_array))};
5,710✔
1084
}
5,710✔
1085

1086
// This function cannot be called on frozen results and so does not require locking
1087
bool Results::is_in_table_order() const NO_THREAD_SAFETY_ANALYSIS
1088
{
3,798✔
1089
    REALM_ASSERT(!m_realm || !m_realm->is_frozen());
3,798✔
1090
    switch (m_mode) {
3,798✔
1091
        case Mode::Empty:
63✔
1092
        case Mode::Table:
126✔
1093
            return true;
126✔
1094
        case Mode::Collection:
70✔
1095
            return false;
14✔
1096
        case Mode::Query:
2,808✔
1097
            return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
2,808✔
1098
        case Mode::TableView:
850✔
1099
            return m_table_view.is_in_table_order();
850✔
1100
    }
×
1101
    REALM_COMPILER_HINT_UNREACHABLE();
×
1102
}
×
1103

1104
ColKey Results::key(StringData name) const
1105
{
2✔
1106
    return m_table->get_column_key(name);
2✔
1107
}
2✔
1108
#define REALM_RESULTS_TYPE(T)                                                                                        \
1109
    template T Results::get<T>(size_t);                                                                              \
1110
    template util::Optional<T> Results::first<T>();                                                                  \
1111
    template util::Optional<T> Results::last<T>();
1112

1113
REALM_RESULTS_TYPE(bool)
1114
REALM_RESULTS_TYPE(int64_t)
1115
REALM_RESULTS_TYPE(float)
1116
REALM_RESULTS_TYPE(double)
1117
REALM_RESULTS_TYPE(StringData)
1118
REALM_RESULTS_TYPE(BinaryData)
1119
REALM_RESULTS_TYPE(Timestamp)
1120
REALM_RESULTS_TYPE(ObjectId)
1121
REALM_RESULTS_TYPE(Decimal)
1122
REALM_RESULTS_TYPE(UUID)
1123
REALM_RESULTS_TYPE(Mixed)
1124
REALM_RESULTS_TYPE(Obj)
1125
REALM_RESULTS_TYPE(util::Optional<bool>)
1126
REALM_RESULTS_TYPE(util::Optional<int64_t>)
1127
REALM_RESULTS_TYPE(util::Optional<float>)
1128
REALM_RESULTS_TYPE(util::Optional<double>)
1129
REALM_RESULTS_TYPE(util::Optional<ObjectId>)
1130
REALM_RESULTS_TYPE(util::Optional<UUID>)
1131

1132
#undef REALM_RESULTS_TYPE
1133

1134
Results Results::import_copy_into_realm(std::shared_ptr<Realm> const& realm)
1135
{
124✔
1136
    util::CheckedUniqueLock lock(m_mutex);
124✔
1137
    if (m_mode == Mode::Empty)
124✔
1138
        return *this;
4✔
1139

60✔
1140
    validate_read();
120✔
1141

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

1165
Results Results::freeze(std::shared_ptr<Realm> const& frozen_realm)
1166
{
124✔
1167
    return import_copy_into_realm(frozen_realm);
124✔
1168
}
124✔
1169

1170
bool Results::is_frozen() const
1171
{
8,750✔
1172
    return !m_realm || m_realm->is_frozen();
8,750✔
1173
}
8,750✔
1174

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