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

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

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

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

46
Results::Results() = default;
1,746✔
47
Results::~Results() = default;
15,548✔
48

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

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

65

66
Results::Results(SharedRealm r, ConstTableRef table)
67
    : m_realm(std::move(r))
1,444✔
68
    , m_table(table)
1,444✔
69
    , m_table_view(m_table)
1,444✔
70
    , m_mode(Mode::Table)
1,444✔
71
    , m_mutex(m_realm && m_realm->is_frozen())
1,444✔
72
{
1,444✔
73
}
1,444✔
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))
6,946✔
78
    , m_table(coll->get_target_table())
6,946✔
79
    , m_collection(std::move(coll))
6,946✔
80
    , m_mode(Mode::Collection)
6,946✔
81
    , m_mutex(m_realm && m_realm->is_frozen())
6,946✔
82
{
6,946✔
83
    if (q) {
6,946✔
84
        m_query = std::move(*q);
8✔
85
        m_mode = Mode::Query;
8✔
86
    }
8✔
87
    m_descriptor_ordering.append_sort(std::move(s));
6,946✔
88
}
6,946✔
89

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

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

110
Results::Results(const Results&) = default;
1,458✔
111
Results& Results::operator=(const Results&) = default;
×
112
Results::Results(Results&&) = default;
213✔
113
Results& Results::operator=(Results&&) = default;
1,957✔
114

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

121
bool Results::is_valid() const
122
{
53,672✔
123
    if (m_realm) {
53,672✔
124
        m_realm->verify_thread();
53,645✔
125
    }
53,645✔
126

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

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

138
    return true;
47✔
139
}
20,051✔
140

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

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

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

161
size_t Results::do_size()
162
{
8,474✔
163
    validate_read();
8,474✔
164
    ensure_up_to_date(EvaluateMode::Count);
8,474✔
165
    switch (m_mode) {
8,474✔
166
        case Mode::Empty:
6✔
167
            return 0;
6✔
168
        case Mode::Table:
243✔
169
            return m_table ? m_table->size() : 0;
243✔
170
        case Mode::Collection:
3,230✔
171
            return m_list_indices ? m_list_indices->size() : m_collection->size();
3,230✔
172
        case Mode::Query:
2,602✔
173
            return m_query.count(m_descriptor_ordering);
2,602✔
174
        case Mode::TableView:
2,390✔
175
            return m_table_view.size();
2,390✔
176
    }
8,474✔
177
    REALM_COMPILER_HINT_UNREACHABLE();
×
178
}
8,474✔
179

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

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

192
    return *object_schema;
1,399✔
193
}
1,399✔
194

195
StringData Results::get_object_type() const noexcept
196
{
1,376✔
197
    if (!m_table) {
1,376✔
198
        return StringData();
21✔
199
    }
21✔
200

201
    return ObjectStore::object_type_for_table_name(m_table->get_name());
1,355✔
202
}
1,376✔
203

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

210
    return m_table_view.has_changed();
221✔
211
}
2,409✔
212

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

220
    switch (m_mode) {
38,064✔
221
        case Mode::Empty:
11✔
222
            return;
11✔
223
        case Mode::Table:
4,716✔
224
            // Tables are always up-to-date
225
            return;
4,716✔
226
        case Mode::Collection: {
14,640✔
227
            // Collections themselves are always up-to-date, but we may need
228
            // to apply sort descriptors
229
            if (m_descriptor_ordering.is_empty())
14,640✔
230
                return;
8,290✔
231

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

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

248
            bool needs_update = m_collection->has_changed();
6,092✔
249
            if (!m_list_indices) {
6,092✔
250
                m_list_indices = std::vector<size_t>{};
662✔
251
                needs_update = true;
662✔
252
            }
662✔
253
            if (!needs_update)
6,092✔
254
                return;
5,315✔
255

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

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

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

279
            if (do_distinct)
734✔
280
                m_collection->distinct(*m_list_indices, sort_order);
172✔
281
            else if (sort_order)
562✔
282
                m_collection->sort(*m_list_indices, *sort_order);
562✔
283
            return;
734✔
284
        }
777✔
285

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

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

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

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

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

339
size_t Results::actual_index(size_t ndx) const noexcept
340
{
7,505✔
341
    if (auto& indices = m_list_indices) {
7,505✔
342
        return ndx < indices->size() ? (*indices)[ndx] : npos;
4,594✔
343
    }
4,594✔
344
    return ndx;
2,911✔
345
}
7,505✔
346

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

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

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

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

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

397
template <>
398
util::Optional<Obj> Results::try_get(size_t row_ndx)
399
{
18,214✔
400
    validate_read();
18,214✔
401
    ensure_up_to_date();
18,214✔
402
    switch (m_mode) {
18,214✔
403
        case Mode::Empty:
4✔
404
            break;
4✔
405
        case Mode::Table:
4,403✔
406
            if (m_table && row_ndx < m_table->size())
4,403✔
407
                return m_table_iterator.get(*m_table, row_ndx);
4,394✔
408
            break;
9✔
409
        case Mode::Collection:
445✔
410
            if (row_ndx < m_collection->size()) {
445✔
411
                auto m = m_collection->get_any(row_ndx);
433✔
412
                if (m.is_null())
433✔
413
                    return Obj();
42✔
414
                if (m.get_type() == type_Link)
391✔
415
                    return m_table->get_object(m.get<ObjKey>());
×
416
                if (m.get_type() == type_TypedLink)
391✔
417
                    return m_table->get_parent_group()->get_object(m.get_link());
391✔
418
            }
391✔
419
            break;
12✔
420
        case Mode::Query:
12✔
421
            REALM_UNREACHABLE();
422
        case Mode::TableView:
13,362✔
423
            if (row_ndx >= m_table_view.size())
13,362✔
424
                break;
16✔
425
            return m_table_view.get_object(row_ndx);
13,346✔
426
    }
18,214✔
427
    return util::none;
41✔
428
}
18,214✔
429

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

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

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

488
std::pair<StringData, Mixed> Results::get_dictionary_element(size_t ndx)
489
{
162✔
490
    util::CheckedUniqueLock lock(m_mutex);
162✔
491
    REALM_ASSERT(m_mode == Mode::Collection);
162✔
492
    auto& dict = static_cast<Dictionary&>(*m_collection);
162✔
493
    REALM_ASSERT(typeid(dict) == typeid(Dictionary));
162✔
494

495
    ensure_up_to_date();
162✔
496
    if (size_t actual = actual_index(ndx); actual < dict.size()) {
162✔
497
        auto val = dict.get_pair(ndx);
162✔
498
        return {val.first.get_string(), val.second};
162✔
499
    }
162✔
500
    throw OutOfBounds{"get_dictionary_element() on Results", ndx, dict.size()};
×
501
}
162✔
502

503
template <typename T>
504
T Results::get(size_t row_ndx)
505
{
21,304✔
506
    util::CheckedUniqueLock lock(m_mutex);
21,304✔
507
    if (auto row = try_get<T>(row_ndx)) {
21,304✔
508
        return *row;
21,242✔
509
    }
21,242✔
510
    throw OutOfBounds{"get() on Results", row_ndx, do_size()};
62✔
511
}
21,304✔
512

513
template <typename T>
514
util::Optional<T> Results::first()
515
{
746✔
516
    util::CheckedUniqueLock lock(m_mutex);
746✔
517
    return try_get<T>(0);
746✔
518
}
746✔
519

520
template <typename T>
521
util::Optional<T> Results::last()
522
{
231✔
523
    util::CheckedUniqueLock lock(m_mutex);
231✔
524
    validate_read();
231✔
525
    if (m_mode == Mode::Query)
231✔
526
        ensure_up_to_date(); // avoid running the query twice (for size() and for get())
6✔
527
    return try_get<T>(do_size() - 1);
231✔
528
}
231✔
529

530
void Results::evaluate_query_if_needed(bool wants_notifications)
531
{
13✔
532
    util::CheckedUniqueLock lock(m_mutex);
13✔
533
    validate_read();
13✔
534
    ensure_up_to_date(wants_notifications ? EvaluateMode::Normal : EvaluateMode::Snapshot);
13✔
535
}
13✔
536

537
template <>
538
size_t Results::index_of(Obj const& obj)
539
{
73✔
540
    if (!obj.is_valid()) {
73✔
541
        throw StaleAccessor{"Attempting to access an invalid object"};
6✔
542
    }
6✔
543
    if (m_table && obj.get_table() != m_table) {
67✔
544
        throw InvalidArgument(ErrorCodes::ObjectTypeMismatch,
13✔
545
                              util::format("Object of type '%1' does not match Results type '%2'",
13✔
546
                                           obj.get_table()->get_class_name(), m_table->get_class_name()));
13✔
547
    }
13✔
548
    return index_of(Mixed(obj.get_key()));
54✔
549
}
67✔
550

551
template <>
552
size_t Results::index_of(Mixed const& value)
553
{
1,138✔
554
    util::CheckedUniqueLock lock(m_mutex);
1,138✔
555
    validate_read();
1,138✔
556
    ensure_up_to_date();
1,138✔
557

558
    if (value.is_type(type_TypedLink)) {
1,138✔
559
        if (m_table && m_table->get_key() != value.get_link().get_table_key()) {
1✔
560
            return realm::not_found;
×
561
        }
×
562
    }
1✔
563

564
    switch (m_mode) {
1,138✔
565
        case Mode::Empty:
✔
566
        case Mode::Table:
10✔
567
            if (value.is_type(type_Link, type_TypedLink)) {
10✔
568
                return m_table->get_object_ndx(value.get<ObjKey>());
10✔
569
            }
10✔
570
            break;
×
571
        case Mode::Collection:
1,104✔
572
            if (m_list_indices) {
1,104✔
573
                for (size_t i = 0; i < m_list_indices->size(); ++i) {
994✔
574
                    if (value == m_collection->get_any(m_list_indices->at(i)))
994✔
575
                        return i;
290✔
576
                }
994✔
577
                return not_found;
×
578
            }
290✔
579
            return m_collection->find_any(value);
814✔
580
        case Mode::Query:
✔
581
        case Mode::TableView:
24✔
582
            if (value.is_type(type_Link, type_TypedLink)) {
24✔
583
                return m_table_view.find_by_source_ndx(value.get<ObjKey>());
22✔
584
            }
22✔
585
            break;
2✔
586
    }
1,138✔
587
    return realm::not_found;
2✔
588
}
1,138✔
589

590
size_t Results::index_of(Query&& q)
591
{
×
592
    if (m_descriptor_ordering.will_apply_sort()) {
×
593
        Results filtered(filter(std::move(q)));
×
594
        filtered.assert_unlocked();
×
595
        auto first = filtered.first();
×
596
        return first ? index_of(*first) : not_found;
×
597
    }
×
598

599
    auto query = get_query().and_query(std::move(q));
×
600
    query.sync_view_if_needed();
×
601
    ObjKey row = query.find();
×
602
    return row ? index_of(const_cast<Table&>(*m_table).get_object(row)) : not_found;
×
603
}
×
604

605
namespace {
606
struct CollectionAggregateAdaptor {
607
    const CollectionBase& list;
608
    util::Optional<Mixed> min(ColKey)
609
    {
236✔
610
        return list.min();
236✔
611
    }
236✔
612
    util::Optional<Mixed> max(ColKey)
613
    {
236✔
614
        return list.max();
236✔
615
    }
236✔
616
    util::Optional<Mixed> sum(ColKey)
617
    {
224✔
618
        return list.sum();
224✔
619
    }
224✔
620
    util::Optional<Mixed> avg(ColKey)
621
    {
224✔
622
        return list.avg();
224✔
623
    }
224✔
624
};
625
} // anonymous namespace
626

627
template <typename AggregateFunction>
628
util::Optional<Mixed> Results::aggregate(ColKey column, const char* name, AggregateFunction&& func)
629
{
1,975✔
630
    util::CheckedUniqueLock lock(m_mutex);
1,975✔
631
    validate_read();
1,975✔
632
    if (!m_table && !m_collection)
1,975✔
633
        return none;
16✔
634

635
    ensure_up_to_date();
1,959✔
636
    std::optional<Mixed> ret;
1,959✔
637
    switch (m_mode) {
1,959✔
638
        case Mode::Table:
56✔
639
            ret = func(*m_table);
56✔
640
            break;
56✔
641
        case Mode::Query:
✔
642
            ret = func(m_query);
×
643
            break;
×
644
        case Mode::Collection:
1,777✔
645
            if (do_get_type() != PropertyType::Object)
1,777✔
646
                ret = func(CollectionAggregateAdaptor{*m_collection});
920✔
647
            else
857✔
648
                ret = func(do_get_query());
857✔
649
            break;
1,777✔
650
        default:
126✔
651
            ret = func(m_table_view);
126✔
652
            break;
126✔
653
    }
1,959✔
654

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

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

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

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

687
util::Optional<Mixed> Results::sum(ColKey column)
688
{
484✔
689
    return aggregate(column, "sum", [column](auto&& helper) {
484✔
690
        return helper.sum(column);
480✔
691
    });
480✔
692
}
484✔
693

694
util::Optional<Mixed> Results::average(ColKey column)
695
{
481✔
696
    return aggregate(column, "average", [column](auto&& helper) {
481✔
697
        return helper.avg(column);
477✔
698
    });
477✔
699
}
481✔
700

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

740
PropertyType Results::get_type() const
741
{
5,280✔
742
    util::CheckedUniqueLock lock(m_mutex);
5,280✔
743
    validate_read();
5,280✔
744
    return do_get_type();
5,280✔
745
}
5,280✔
746

747
PropertyType Results::do_get_type() const
748
{
17,077✔
749
    switch (m_mode) {
17,077✔
750
        case Mode::Empty:
2✔
751
        case Mode::Query:
1,427✔
752
        case Mode::TableView:
2,077✔
753
        case Mode::Table:
3,369✔
754
            return PropertyType::Object;
3,369✔
755
        case Mode::Collection:
13,708✔
756
            return ObjectSchema::from_core_type(m_collection->get_col_key());
13,708✔
757
    }
17,077✔
758
    REALM_COMPILER_HINT_UNREACHABLE();
×
759
}
17,077✔
760

761
Query Results::get_query() const
762
{
2,059✔
763
    util::CheckedUniqueLock lock(m_mutex);
2,059✔
764
    return do_get_query();
2,059✔
765
}
2,059✔
766

767
const DescriptorOrdering& Results::get_ordering() const REQUIRES(!m_mutex)
768
{
8✔
769
    return m_descriptor_ordering;
8✔
770
}
8✔
771

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

790
Query Results::do_get_query() const
791
{
4,397✔
792
    validate_read();
4,397✔
793
    switch (m_mode) {
4,397✔
794
        case Mode::Empty:
✔
795
        case Mode::Query:
1,524✔
796
        case Mode::TableView: {
2,082✔
797
            if (const_cast<Query&>(m_query).get_table())
2,082✔
798
                return m_query;
2,079✔
799

800
            // A TableView has an associated Query if it was produced by Query::find_all
801
            if (auto& query = m_table_view.get_query()) {
3✔
802
                return *query;
3✔
803
            }
3✔
804

805
            // The TableView has no associated query so create one with no conditions that is restricted
806
            // to the rows in the TableView.
807
            if (m_update_policy == UpdatePolicy::Auto) {
×
808
                m_table_view.sync_if_needed();
×
809
            }
×
810
            return Query(m_table, std::make_unique<TableView>(m_table_view));
×
811
        }
3✔
812
        case Mode::Collection:
1,025✔
813
            if (auto objlist = m_collection->clone_as_obj_list()) {
1,025✔
814
                return m_table->where(std::move(objlist));
962✔
815
            }
962✔
816
            return m_query;
63✔
817
        case Mode::Table:
1,290✔
818
            return m_table->where();
1,290✔
819
    }
4,397✔
820
    REALM_COMPILER_HINT_UNREACHABLE();
×
821
}
4,397✔
822

823
TableView Results::get_tableview()
824
{
×
825
    util::CheckedUniqueLock lock(m_mutex);
×
826
    validate_read();
×
827
    ensure_up_to_date();
×
828
    switch (m_mode) {
×
829
        case Mode::Empty:
×
830
        case Mode::Collection:
×
831
            return do_get_query().find_all();
×
832
        case Mode::Query:
×
833
        case Mode::TableView:
×
834
            return m_table_view;
×
835
        case Mode::Table:
×
836
            return m_table->where().find_all();
×
837
    }
×
838
    REALM_COMPILER_HINT_UNREACHABLE();
×
839
}
×
840

841
static std::vector<ExtendedColumnKey> parse_keypath(StringData keypath, Schema const& schema,
842
                                                    const ObjectSchema* object_schema)
843
{
1,294✔
844
    auto check = [&](bool condition, const char* fmt, auto... args) {
6,500✔
845
        if (!condition) {
6,500✔
846
            throw InvalidArgument(
18✔
847
                util::format("Cannot sort on key path '%1': %2.", keypath, util::format(fmt, args...)));
18✔
848
        }
18✔
849
    };
6,500✔
850
    auto is_sortable_type = [](PropertyType type) {
1,299✔
851
        return !is_collection(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data;
1,299✔
852
    };
1,299✔
853

854
    const char* begin = keypath.data();
1,294✔
855
    const char* end = keypath.data() + keypath.size();
1,294✔
856
    check(begin != end, "missing property name");
1,294✔
857

858
    std::vector<ExtendedColumnKey> indices;
1,294✔
859
    while (begin != end) {
2,601✔
860
        auto sep = std::find(begin, end, '.');
1,307✔
861
        check(sep != begin && sep + 1 != end, "missing property name");
1,307✔
862
        StringData key(begin, sep - begin);
1,307✔
863
        std::string index;
1,307✔
864
        auto begin_key = std::find(begin, sep, '[');
1,307✔
865
        if (begin_key != sep) {
1,307✔
866
            auto end_key = std::find(begin_key, sep, ']');
2✔
867
            check(end_key != sep, "missing ']'");
2✔
868
            index = std::string(begin_key + 1, end_key);
2✔
869
            key = StringData(begin, begin_key - begin);
2✔
870
        }
2✔
871
        begin = sep + (sep != end);
1,307✔
872

873
        auto prop = object_schema->property_for_public_name(key);
1,307✔
874
        if (!prop) {
1,307✔
875
            prop = object_schema->property_for_name(key);
6✔
876
        }
6✔
877
        check(prop, "property '%1.%2' does not exist", object_schema->name, key);
1,307✔
878
        if (is_dictionary(prop->type)) {
1,307✔
879
            check(index.length(), "missing dictionary key");
2✔
880
        }
2✔
881
        else {
1,305✔
882
            check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'", object_schema->name,
1,305✔
883
                  key, string_for_property_type(prop->type));
1,305✔
884
        }
1,305✔
885
        if (prop->type == PropertyType::Object)
1,307✔
886
            check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path",
16✔
887
                  object_schema->name, key);
16✔
888
        else
1,291✔
889
            check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path",
1,291✔
890
                  object_schema->name, key, prop->type_string());
1,291✔
891

892
        if (index.length()) {
1,307✔
893
            indices.emplace_back(ColKey(prop->column_key), index);
2✔
894
        }
2✔
895
        else {
1,305✔
896
            indices.emplace_back(ColKey(prop->column_key));
1,305✔
897
        }
1,305✔
898
        if (prop->type == PropertyType::Object)
1,307✔
899
            object_schema = &*schema.find(prop->object_type);
14✔
900
    }
1,307✔
901
    return indices;
1,294✔
902
}
1,294✔
903

904
Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const
905
{
2,219✔
906
    if (keypaths.empty())
2,219✔
907
        return *this;
21✔
908
    auto type = get_type();
2,198✔
909
    if (type != PropertyType::Object) {
2,198✔
910
        if (keypaths.size() != 1)
915✔
911
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
21✔
912
                                               string_for_property_type(type & ~PropertyType::Flags)));
21✔
913
        if (keypaths[0].first != "self")
894✔
914
            throw InvalidArgument(
21✔
915
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
21✔
916
                             keypaths[0].first, string_for_property_type(type & ~PropertyType::Flags)));
21✔
917
        return sort({{{}}, {keypaths[0].second}});
873✔
918
    }
894✔
919

920
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
1,283✔
921
    std::vector<bool> ascending;
1,283✔
922
    column_keys.reserve(keypaths.size());
1,283✔
923
    ascending.reserve(keypaths.size());
1,283✔
924

925
    for (auto& keypath : keypaths) {
1,287✔
926
        column_keys.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
1,287✔
927
        ascending.push_back(keypath.second);
1,287✔
928
    }
1,287✔
929
    return sort({std::move(column_keys), std::move(ascending)});
1,283✔
930
}
2,198✔
931

932
Results Results::sort(SortDescriptor&& sort) const
933
{
2,162✔
934
    util::CheckedUniqueLock lock(m_mutex);
2,162✔
935
    DescriptorOrdering new_order = m_descriptor_ordering;
2,162✔
936
    new_order.append_sort(std::move(sort));
2,162✔
937
    if (m_mode == Mode::Collection)
2,162✔
938
        return Results(m_realm, m_collection, std::move(new_order));
954✔
939
    return Results(m_realm, do_get_query(), std::move(new_order));
1,208✔
940
}
2,162✔
941

942
Results Results::filter(Query&& q) const
943
{
6✔
944
    if (m_descriptor_ordering.will_apply_limit())
6✔
945
        throw IllegalOperation("Filtering a Results with a limit is not yet implemented");
1✔
946
    return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
5✔
947
}
6✔
948

949
Results Results::limit(size_t max_count) const
950
{
53✔
951
    util::CheckedUniqueLock lock(m_mutex);
53✔
952
    auto new_order = m_descriptor_ordering;
53✔
953
    new_order.append_limit(max_count);
53✔
954
    if (m_mode == Mode::Collection)
53✔
955
        return Results(m_realm, m_collection, std::move(new_order));
×
956
    return Results(m_realm, do_get_query(), std::move(new_order));
53✔
957
}
53✔
958

959
Results Results::apply_ordering(DescriptorOrdering&& ordering)
960
{
87✔
961
    util::CheckedUniqueLock lock(m_mutex);
87✔
962
    DescriptorOrdering new_order = m_descriptor_ordering;
87✔
963
    new_order.append(std::move(ordering));
87✔
964
    if (m_mode == Mode::Collection)
87✔
965
        return Results(m_realm, m_collection, std::move(new_order));
84✔
966
    return Results(m_realm, do_get_query(), std::move(new_order));
3✔
967
}
87✔
968

969
Results Results::distinct(DistinctDescriptor&& uniqueness) const
970
{
179✔
971
    DescriptorOrdering new_order = m_descriptor_ordering;
179✔
972
    new_order.append_distinct(std::move(uniqueness));
179✔
973
    util::CheckedUniqueLock lock(m_mutex);
179✔
974
    if (m_mode == Mode::Collection)
179✔
975
        return Results(m_realm, m_collection, std::move(new_order));
154✔
976
    return Results(m_realm, do_get_query(), std::move(new_order));
25✔
977
}
179✔
978

979
Results Results::distinct(std::vector<std::string> const& keypaths) const
980
{
197✔
981
    if (keypaths.empty())
197✔
982
        return *this;
21✔
983
    auto type = get_type();
176✔
984
    if (type != PropertyType::Object) {
176✔
985
        if (keypaths.size() != 1)
169✔
986
            throw InvalidArgument(util::format("Cannot sort array of '%1' on more than one key path",
21✔
987
                                               string_for_property_type(type & ~PropertyType::Flags)));
21✔
988
        if (keypaths[0] != "self")
148✔
989
            throw InvalidArgument(
21✔
990
                util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'", keypaths[0],
21✔
991
                             string_for_property_type(type & ~PropertyType::Flags)));
21✔
992
        return distinct(DistinctDescriptor({{ColKey()}}));
127✔
993
    }
148✔
994

995
    std::vector<std::vector<ExtendedColumnKey>> column_keys;
7✔
996
    column_keys.reserve(keypaths.size());
7✔
997
    for (auto& keypath : keypaths)
7✔
998
        column_keys.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
7✔
999
    return distinct({std::move(column_keys)});
7✔
1000
}
176✔
1001

1002
Results Results::filter_by_method(std::function<bool(const Obj&)>&& predicate) const
1003
{
4✔
1004
    DescriptorOrdering new_order = m_descriptor_ordering;
4✔
1005
    new_order.append_filter(FilterDescriptor(std::move(predicate)));
4✔
1006
    util::CheckedUniqueLock lock(m_mutex);
4✔
1007
    if (m_mode == Mode::Collection)
4✔
1008
        return Results(m_realm, m_collection, std::move(new_order));
×
1009
    return Results(m_realm, do_get_query(), std::move(new_order));
4✔
1010
}
4✔
1011

1012
SectionedResults Results::sectioned_results(SectionedResults::SectionKeyFunc&& section_key_func) REQUIRES(m_mutex)
1013
{
147✔
1014
    return SectionedResults(*this, std::move(section_key_func));
147✔
1015
}
147✔
1016

1017
SectionedResults Results::sectioned_results(SectionedResultsOperator op, util::Optional<StringData> prop_name)
1018
    REQUIRES(m_mutex)
1019
{
2✔
1020
    return SectionedResults(*this, op, prop_name.value_or(StringData()));
2✔
1021
}
2✔
1022

1023
Results Results::snapshot() const&
1024
{
107✔
1025
    validate_read();
107✔
1026
    auto clone = *this;
107✔
1027
    clone.assert_unlocked();
107✔
1028
    return static_cast<Results&&>(clone).snapshot();
107✔
1029
}
107✔
1030

1031
Results Results::snapshot() &&
1032
{
113✔
1033
    util::CheckedUniqueLock lock(m_mutex);
113✔
1034
    validate_read();
113✔
1035
    switch (m_mode) {
113✔
1036
        case Mode::Empty:
1✔
1037
            return Results();
1✔
1038

1039
        case Mode::Table:
28✔
1040
        case Mode::Collection:
102✔
1041
            m_query = do_get_query();
102✔
1042
            if (m_query.get_table()) {
102✔
1043
                m_mode = Mode::Query;
39✔
1044
            }
39✔
1045
            REALM_FALLTHROUGH;
102✔
1046
        case Mode::Query:
105✔
1047
        case Mode::TableView:
112✔
1048
            ensure_up_to_date(EvaluateMode::Snapshot);
112✔
1049
            m_notifier.reset();
112✔
1050
            if (do_get_type() == PropertyType::Object) {
112✔
1051
                m_update_policy = UpdatePolicy::Never;
49✔
1052
            }
49✔
1053
            return std::move(*this);
112✔
1054
    }
113✔
1055
    REALM_COMPILER_HINT_UNREACHABLE();
×
1056
}
113✔
1057

1058
// This function cannot be called on frozen results and so does not require locking
1059
void Results::prepare_async(ForCallback force) NO_THREAD_SAFETY_ANALYSIS
1060
{
12,697✔
1061
    REALM_ASSERT(m_realm);
12,697✔
1062
    if (m_notifier)
12,697✔
1063
        return;
83✔
1064
    if (!m_realm->verify_notifications_available(force))
12,614✔
1065
        return;
8,076✔
1066
    if (m_update_policy == UpdatePolicy::Never) {
4,538✔
1067
        if (force)
1✔
1068
            throw LogicError(ErrorCodes::IllegalOperation,
1✔
1069
                             "Cannot create asynchronous query for snapshotted Results.");
1✔
1070
        return;
×
1071
    }
1✔
1072

1073
    REALM_ASSERT(!force || !m_realm->is_frozen());
4,537✔
1074
    if (!force) {
4,537✔
1075
        // Don't do implicit background updates if we can't actually deliver them
1076
        if (!m_realm->can_deliver_notifications())
1,640✔
1077
            return;
1,612✔
1078
        // Don't do implicit background updates if there isn't actually anything
1079
        // that needs to be run.
1080
        if (!m_query.get_table() && m_descriptor_ordering.is_empty())
28✔
1081
            return;
1✔
1082
    }
28✔
1083

1084
    if (do_get_type() != PropertyType::Object)
2,924✔
1085
        m_notifier = std::make_shared<_impl::ListResultsNotifier>(*this);
906✔
1086
    else
2,018✔
1087
        m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
2,018✔
1088
    _impl::RealmCoordinator::register_notifier(m_notifier);
2,924✔
1089
}
2,924✔
1090

1091
NotificationToken Results::add_notification_callback(CollectionChangeCallback callback,
1092
                                                     std::optional<KeyPathArray> key_path_array) &
1093
{
2,981✔
1094
    prepare_async(ForCallback{true});
2,981✔
1095
    return {m_notifier, m_notifier->add_callback(std::move(callback), std::move(key_path_array))};
2,981✔
1096
}
2,981✔
1097

1098
// This function cannot be called on frozen results and so does not require locking
1099
bool Results::is_in_table_order() const NO_THREAD_SAFETY_ANALYSIS
1100
{
2,024✔
1101
    REALM_ASSERT(!m_realm || !m_realm->is_frozen());
2,024✔
1102
    switch (m_mode) {
2,024✔
1103
        case Mode::Empty:
✔
1104
        case Mode::Table:
63✔
1105
            return true;
63✔
1106
        case Mode::Collection:
7✔
1107
            return false;
7✔
1108
        case Mode::Query:
1,405✔
1109
            return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
1,405✔
1110
        case Mode::TableView:
549✔
1111
            return m_table_view.is_in_table_order();
549✔
1112
    }
2,024✔
1113
    REALM_COMPILER_HINT_UNREACHABLE();
×
1114
}
2,024✔
1115

1116
ColKey Results::key(StringData name) const
1117
{
1✔
1118
    return m_table->get_column_key(name);
1✔
1119
}
1✔
1120
#define REALM_RESULTS_TYPE(T)                                                                                        \
1121
    template T Results::get<T>(size_t);                                                                              \
1122
    template util::Optional<T> Results::first<T>();                                                                  \
1123
    template util::Optional<T> Results::last<T>();
1124

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

1144
#undef REALM_RESULTS_TYPE
1145

1146
Results Results::import_copy_into_realm(std::shared_ptr<Realm> const& realm)
1147
{
62✔
1148
    util::CheckedUniqueLock lock(m_mutex);
62✔
1149
    if (m_mode == Mode::Empty)
62✔
1150
        return *this;
2✔
1151

1152
    validate_read();
60✔
1153

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

1177
Results Results::freeze(std::shared_ptr<Realm> const& frozen_realm)
1178
{
62✔
1179
    return import_copy_into_realm(frozen_realm);
62✔
1180
}
62✔
1181

1182
bool Results::is_frozen() const
1183
{
4,375✔
1184
    return !m_realm || m_realm->is_frozen();
4,375✔
1185
}
4,375✔
1186

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