1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2015 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #include "results.hpp"
21 #include "impl/realm_coordinator.hpp"
22 #include "impl/results_notifier.hpp"
23 #include "object_schema.hpp"
24 #include "object_store.hpp"
31 Results::Results() = default;
32 Results::~Results() = default;
34 Results::Results(SharedRealm r, Query q, DescriptorOrdering o)
35 : m_realm(std::move(r))
36 , m_query(std::move(q))
37 , m_table(m_query.get_table())
38 , m_descriptor_ordering(std::move(o))
43 Results::Results(SharedRealm r, Table& table)
44 : m_realm(std::move(r))
47 m_table.reset(&table);
50 Results::Results(SharedRealm r, LinkViewRef lv, util::Optional<Query> q, SortDescriptor s)
51 : m_realm(std::move(r))
53 , m_mode(Mode::LinkView)
55 m_table.reset(&lv->get_target_table());
57 m_query = std::move(*q);
60 m_descriptor_ordering.append_sort(std::move(s));
63 Results::Results(SharedRealm r, TableView tv, DescriptorOrdering o)
64 : m_realm(std::move(r))
65 , m_table_view(std::move(tv))
66 , m_descriptor_ordering(std::move(o))
67 , m_mode(Mode::TableView)
69 m_table.reset(&m_table_view.get_parent());
72 Results::Results(const Results&) = default;
73 Results& Results::operator=(const Results&) = default;
75 Results::Results(Results&& other)
76 : m_realm(std::move(other.m_realm))
77 , m_object_schema(std::move(other.m_object_schema))
78 , m_query(std::move(other.m_query))
79 , m_table_view(std::move(other.m_table_view))
80 , m_link_view(std::move(other.m_link_view))
81 , m_table(std::move(other.m_table))
82 , m_descriptor_ordering(std::move(other.m_descriptor_ordering))
83 , m_notifier(std::move(other.m_notifier))
84 , m_mode(other.m_mode)
85 , m_update_policy(other.m_update_policy)
86 , m_has_used_table_view(other.m_has_used_table_view)
87 , m_wants_background_updates(other.m_wants_background_updates)
90 m_notifier->target_results_moved(other, *this);
94 Results& Results::operator=(Results&& other)
97 new (this) Results(std::move(other));
101 bool Results::is_valid() const
104 m_realm->verify_thread();
106 if (m_table && !m_table->is_attached())
112 void Results::validate_read() const
114 // is_valid ensures that we're on the correct thread.
116 throw InvalidatedException();
119 void Results::validate_write() const
122 if (!m_realm || !m_realm->is_in_transaction())
123 throw InvalidTransactionException("Must be in a write transaction");
126 size_t Results::size()
130 case Mode::Empty: return 0;
131 case Mode::Table: return m_table->size();
132 case Mode::LinkView: return m_link_view->size();
134 m_query.sync_view_if_needed();
135 if (!m_descriptor_ordering.will_apply_distinct())
136 return m_query.count();
138 case Mode::TableView:
139 evaluate_query_if_needed();
140 return m_table_view.size();
142 REALM_COMPILER_HINT_UNREACHABLE();
145 const ObjectSchema& Results::get_object_schema() const
149 if (!m_object_schema) {
150 REALM_ASSERT(m_realm);
151 auto it = m_realm->schema().find(get_object_type());
152 REALM_ASSERT(it != m_realm->schema().end());
153 m_object_schema = &*it;
156 return *m_object_schema;
160 StringData Results::get_object_type() const noexcept
166 return ObjectStore::object_type_for_table_name(m_table->get_name());
171 auto get(Table& table, size_t row)
173 return table.get<T>(0, row);
177 auto get<RowExpr>(Table& table, size_t row)
179 return table.get(row);
184 util::Optional<T> Results::try_get(size_t row_ndx)
188 case Mode::Empty: break;
190 if (row_ndx < m_table->size())
191 return realm::get<T>(*m_table, row_ndx);
194 if (update_linkview()) {
195 if (row_ndx < m_link_view->size())
196 return realm::get<T>(*m_table, m_link_view->get(row_ndx).get_index());
201 case Mode::TableView:
202 evaluate_query_if_needed();
203 if (row_ndx >= m_table_view.size())
205 if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_row_attached(row_ndx))
207 return realm::get<T>(*m_table, m_table_view.get(row_ndx).get_index());
213 T Results::get(size_t row_ndx)
215 if (auto row = try_get<T>(row_ndx))
217 throw OutOfBoundsIndexException{row_ndx, size()};
221 util::Optional<T> Results::first()
223 return try_get<T>(0);
227 util::Optional<T> Results::last()
230 if (m_mode == Mode::Query)
231 evaluate_query_if_needed(); // avoid running the query twice (for size() and for get())
232 return try_get<T>(size() - 1);
235 bool Results::update_linkview()
237 REALM_ASSERT(m_update_policy == UpdatePolicy::Auto);
239 if (!m_descriptor_ordering.is_empty()) {
240 m_query = get_query();
241 m_mode = Mode::Query;
242 evaluate_query_if_needed();
248 void Results::evaluate_query_if_needed(bool wants_notifications)
250 if (m_update_policy == UpdatePolicy::Never) {
251 REALM_ASSERT(m_mode == Mode::TableView);
261 m_query.sync_view_if_needed();
262 m_table_view = m_query.find_all();
263 if (!m_descriptor_ordering.is_empty()) {
264 m_table_view.apply_descriptor_ordering(m_descriptor_ordering);
266 m_mode = Mode::TableView;
268 case Mode::TableView:
269 if (wants_notifications && !m_notifier && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) {
270 m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
271 _impl::RealmCoordinator::register_notifier(m_notifier);
273 m_has_used_table_view = true;
274 m_table_view.sync_if_needed();
280 size_t Results::index_of(RowExpr const& row)
284 throw DetatchedAccessorException{};
286 if (m_table && row.get_table() != m_table) {
287 throw IncorrectTableException(
288 ObjectStore::object_type_for_table_name(m_table->get_name()),
289 ObjectStore::object_type_for_table_name(row.get_table()->get_name()),
290 "Attempting to get the index of a Row of the wrong type"
298 return row.get_index();
300 if (update_linkview())
301 return m_link_view->find(row.get_index());
304 case Mode::TableView:
305 evaluate_query_if_needed();
306 return m_table_view.find_by_source_ndx(row.get_index());
308 REALM_COMPILER_HINT_UNREACHABLE();
312 size_t Results::index_of(T const& value)
319 return m_table->find_first(0, value);
323 case Mode::TableView:
324 evaluate_query_if_needed();
325 return m_table_view.find_first(0, value);
327 REALM_COMPILER_HINT_UNREACHABLE();
330 size_t Results::index_of(Query&& q)
332 if (m_descriptor_ordering.will_apply_sort()) {
333 auto first = filter(std::move(q)).first();
334 return first ? index_of(*first) : not_found;
337 auto query = get_query().and_query(std::move(q));
338 query.sync_view_if_needed();
339 size_t row = query.find();
340 return row != not_found ? index_of(m_table->get(row)) : row;
343 void Results::prepare_for_aggregate(size_t column, const char* name)
345 if (column > m_table->get_column_count())
346 throw OutOfBoundsIndexException{column, m_table->get_column_count()};
348 case Mode::Empty: break;
349 case Mode::Table: break;
351 m_query = this->get_query();
352 m_mode = Mode::Query;
355 case Mode::TableView:
356 evaluate_query_if_needed();
359 REALM_COMPILER_HINT_UNREACHABLE();
361 switch (m_table->get_column_type(column)) {
362 case type_Timestamp: case type_Double: case type_Float: case type_Int: break;
363 default: throw UnsupportedColumnTypeException{column, m_table.get(), name};
367 template<typename Int, typename Float, typename Double, typename Timestamp>
368 util::Optional<Mixed> Results::aggregate(size_t column,
370 Int agg_int, Float agg_float,
371 Double agg_double, Timestamp agg_timestamp)
376 prepare_for_aggregate(column, name);
378 auto do_agg = [&](auto const& getter) {
379 return Mixed(m_mode == Mode::Table ? getter(*m_table) : getter(m_table_view));
381 switch (m_table->get_column_type(column)) {
382 case type_Timestamp: return do_agg(agg_timestamp);
383 case type_Double: return do_agg(agg_double);
384 case type_Float: return do_agg(agg_float);
385 case type_Int: return do_agg(agg_int);
386 default: REALM_COMPILER_HINT_UNREACHABLE();
390 util::Optional<Mixed> Results::max(size_t column)
392 size_t return_ndx = npos;
393 auto results = aggregate(column, "max",
394 [&](auto const& table) { return table.maximum_int(column, &return_ndx); },
395 [&](auto const& table) { return table.maximum_float(column, &return_ndx); },
396 [&](auto const& table) { return table.maximum_double(column, &return_ndx); },
397 [&](auto const& table) { return table.maximum_timestamp(column, &return_ndx); });
398 return return_ndx == npos ? none : results;
401 util::Optional<Mixed> Results::min(size_t column)
403 size_t return_ndx = npos;
404 auto results = aggregate(column, "min",
405 [&](auto const& table) { return table.minimum_int(column, &return_ndx); },
406 [&](auto const& table) { return table.minimum_float(column, &return_ndx); },
407 [&](auto const& table) { return table.minimum_double(column, &return_ndx); },
408 [&](auto const& table) { return table.minimum_timestamp(column, &return_ndx); });
409 return return_ndx == npos ? none : results;
412 util::Optional<Mixed> Results::sum(size_t column)
414 return aggregate(column, "sum",
415 [=](auto const& table) { return table.sum_int(column); },
416 [=](auto const& table) { return table.sum_float(column); },
417 [=](auto const& table) { return table.sum_double(column); },
418 [=](auto const&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "sum"}; });
421 util::Optional<double> Results::average(size_t column)
423 size_t value_count = 0;
424 auto results = aggregate(column, "average",
425 [&](auto const& table) { return table.average_int(column, &value_count); },
426 [&](auto const& table) { return table.average_float(column, &value_count); },
427 [&](auto const& table) { return table.average_double(column, &value_count); },
428 [&](auto const&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "average"}; });
429 return value_count == 0 ? none : util::make_optional(results->get_double());
432 void Results::clear()
442 // Not using Query:remove() because building the tableview and
443 // clearing it is actually significantly faster
444 case Mode::TableView:
446 evaluate_query_if_needed();
448 switch (m_update_policy) {
449 case UpdatePolicy::Auto:
450 m_table_view.clear(RemoveMode::unordered);
452 case UpdatePolicy::Never: {
453 // Copy the TableView because a frozen Results shouldn't let its size() change.
454 TableView copy(m_table_view);
455 copy.clear(RemoveMode::unordered);
462 m_link_view->remove_all_target_rows();
467 PropertyType Results::get_type() const
473 return PropertyType::Object;
475 case Mode::TableView:
477 if (m_table->get_index_in_group() != npos)
478 return PropertyType::Object;
479 return ObjectSchema::from_core_type(*m_table->get_descriptor(), 0);
481 REALM_COMPILER_HINT_UNREACHABLE();
484 Query Results::get_query() const
491 case Mode::TableView: {
492 // A TableView has an associated Query if it was produced by Query::find_all. This is indicated
493 // by TableView::get_query returning a Query with a non-null table.
494 Query query = m_table_view.get_query();
495 if (query.get_table()) {
499 // The TableView has no associated query so create one with no conditions that is restricted
500 // to the rows in the TableView.
501 if (m_update_policy == UpdatePolicy::Auto) {
502 m_table_view.sync_if_needed();
504 return Query(*m_table, std::unique_ptr<TableViewBase>(new TableView(m_table_view)));
507 return m_table->where(m_link_view);
509 return m_table->where();
511 REALM_COMPILER_HINT_UNREACHABLE();
514 TableView Results::get_tableview()
521 if (update_linkview())
522 return m_table->where(m_link_view).find_all();
525 case Mode::TableView:
526 evaluate_query_if_needed();
529 return m_table->where().find_all();
531 REALM_COMPILER_HINT_UNREACHABLE();
534 static std::vector<size_t> parse_keypath(StringData keypath, Schema const& schema, const ObjectSchema *object_schema)
536 auto check = [&](bool condition, const char* fmt, auto... args) {
538 throw std::invalid_argument(util::format("Cannot sort on key path '%1': %2.",
539 keypath, util::format(fmt, args...)));
542 auto is_sortable_type = [](PropertyType type) {
543 return !is_array(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data;
546 const char* begin = keypath.data();
547 const char* end = keypath.data() + keypath.size();
548 check(begin != end, "missing property name");
550 std::vector<size_t> indices;
551 while (begin != end) {
552 auto sep = std::find(begin, end, '.');
553 check(sep != begin && sep + 1 != end, "missing property name");
554 StringData key(begin, sep - begin);
555 begin = sep + (sep != end);
557 auto prop = object_schema->property_for_name(key);
558 check(prop, "property '%1.%2' does not exist", object_schema->name, key);
559 check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'",
560 object_schema->name, key, string_for_property_type(prop->type));
561 if (prop->type == PropertyType::Object)
562 check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path",
563 object_schema->name, key);
565 check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path",
566 object_schema->name, key, prop->type_string());
568 indices.push_back(prop->table_column);
569 if (prop->type == PropertyType::Object)
570 object_schema = &*schema.find(prop->object_type);
575 Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const
577 if (keypaths.empty())
579 if (get_type() != PropertyType::Object) {
580 if (keypaths.size() != 1)
581 throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path",
582 string_for_property_type(get_type())));
583 if (keypaths[0].first != "self")
584 throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
585 keypaths[0].first, string_for_property_type(get_type())));
586 return sort({*m_table, {{0}}, {keypaths[0].second}});
589 std::vector<std::vector<size_t>> column_indices;
590 std::vector<bool> ascending;
591 column_indices.reserve(keypaths.size());
592 ascending.reserve(keypaths.size());
594 for (auto& keypath : keypaths) {
595 column_indices.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
596 ascending.push_back(keypath.second);
598 return sort({*m_table, std::move(column_indices), std::move(ascending)});
601 Results Results::sort(SortDescriptor&& sort) const
603 if (m_mode == Mode::LinkView)
604 return Results(m_realm, m_link_view, util::none, std::move(sort));
605 DescriptorOrdering new_order = m_descriptor_ordering;
606 new_order.append_sort(std::move(sort));
607 return Results(m_realm, get_query(), std::move(new_order));
610 Results Results::filter(Query&& q) const
612 return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
615 Results Results::distinct(DistinctDescriptor&& uniqueness) const
617 DescriptorOrdering new_order = m_descriptor_ordering;
618 new_order.append_distinct(std::move(uniqueness));
619 return Results(m_realm, get_query(), std::move(new_order));
622 Results Results::distinct(std::vector<std::string> const& keypaths) const
624 if (keypaths.empty())
626 if (get_type() != PropertyType::Object) {
627 if (keypaths.size() != 1)
628 throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path",
629 string_for_property_type(get_type())));
630 if (keypaths[0] != "self")
631 throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
632 keypaths[0], string_for_property_type(get_type())));
633 return distinct({*m_table, {{0}}});
636 std::vector<std::vector<size_t>> column_indices;
637 column_indices.reserve(keypaths.size());
638 for (auto& keypath : keypaths)
639 column_indices.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
640 return distinct({*m_table, std::move(column_indices)});
643 Results Results::snapshot() const &
646 return Results(*this).snapshot();
649 Results Results::snapshot() &&
659 m_query = get_query();
660 m_mode = Mode::Query;
664 case Mode::TableView:
665 evaluate_query_if_needed(false);
667 m_update_policy = UpdatePolicy::Never;
668 return std::move(*this);
670 REALM_COMPILER_HINT_UNREACHABLE();
673 void Results::prepare_async()
678 if (m_realm->config().immutable()) {
679 throw InvalidTransactionException("Cannot create asynchronous query for immutable Realms");
681 if (m_realm->is_in_transaction()) {
682 throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction");
684 if (m_update_policy == UpdatePolicy::Never) {
685 throw std::logic_error("Cannot create asynchronous query for snapshotted Results.");
688 m_wants_background_updates = true;
689 m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
690 _impl::RealmCoordinator::register_notifier(m_notifier);
693 NotificationToken Results::add_notification_callback(CollectionChangeCallback cb) &
696 return {m_notifier, m_notifier->add_callback(std::move(cb))};
699 bool Results::is_in_table_order() const
708 return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
709 case Mode::TableView:
710 return m_table_view.is_in_table_order();
712 REALM_COMPILER_HINT_UNREACHABLE();
715 void Results::Internal::set_table_view(Results& results, TableView &&tv)
717 REALM_ASSERT(results.m_update_policy != UpdatePolicy::Never);
718 // If the previous TableView was never actually used, then stop generating
719 // new ones until the user actually uses the Results object again
720 if (results.m_mode == Mode::TableView) {
721 results.m_wants_background_updates = results.m_has_used_table_view;
724 results.m_table_view = std::move(tv);
725 results.m_mode = Mode::TableView;
726 results.m_has_used_table_view = false;
727 REALM_ASSERT(results.m_table_view.is_in_sync());
728 REALM_ASSERT(results.m_table_view.is_attached());
731 #define REALM_RESULTS_TYPE(T) \
732 template T Results::get<T>(size_t); \
733 template util::Optional<T> Results::first<T>(); \
734 template util::Optional<T> Results::last<T>(); \
735 template size_t Results::index_of<T>(T const&);
737 template RowExpr Results::get<RowExpr>(size_t);
738 template util::Optional<RowExpr> Results::first<RowExpr>();
739 template util::Optional<RowExpr> Results::last<RowExpr>();
741 REALM_RESULTS_TYPE(bool)
742 REALM_RESULTS_TYPE(int64_t)
743 REALM_RESULTS_TYPE(float)
744 REALM_RESULTS_TYPE(double)
745 REALM_RESULTS_TYPE(StringData)
746 REALM_RESULTS_TYPE(BinaryData)
747 REALM_RESULTS_TYPE(Timestamp)
748 REALM_RESULTS_TYPE(util::Optional<bool>)
749 REALM_RESULTS_TYPE(util::Optional<int64_t>)
750 REALM_RESULTS_TYPE(util::Optional<float>)
751 REALM_RESULTS_TYPE(util::Optional<double>)
753 #undef REALM_RESULTS_TYPE
755 Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c)
756 : std::out_of_range(util::format("Requested index %1 greater than max %2", r, c - 1))
757 , requested(r), valid_count(c) {}
759 static std::string unsupported_operation_msg(size_t column, const Table* table, const char* operation)
761 const char* column_type = string_for_property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column));
762 if (table->is_group_level())
763 return util::format("Cannot %1 property '%2': operation not supported for '%3' properties",
764 operation, table->get_column_name(column), column_type);
765 return util::format("Cannot %1 '%2' array: operation not supported",
766 operation, column_type);
769 Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation)
770 : std::logic_error(unsupported_operation_msg(column, table, operation))
771 , column_index(column)
772 , column_name(table->get_column_name(column))
773 , property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column))