//////////////////////////////////////////////////////////////////////////// // // Copyright 2015 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #include "results.hpp" #include "impl/async_query.hpp" #include "impl/realm_coordinator.hpp" #include "object_store.hpp" #include using namespace realm; #ifdef __has_cpp_attribute #define REALM_HAS_CCP_ATTRIBUTE(attr) __has_cpp_attribute(attr) #else #define REALM_HAS_CCP_ATTRIBUTE(attr) 0 #endif #if REALM_HAS_CCP_ATTRIBUTE(clang::fallthrough) #define REALM_FALLTHROUGH [[clang::fallthrough]] #else #define REALM_FALLTHROUGH #endif Results::Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s) : m_realm(std::move(r)) , m_object_schema(&o) , m_query(std::move(q)) , m_table(m_query.get_table().get()) , m_sort(std::move(s)) , m_mode(Mode::Query) { } Results::Results(SharedRealm r, const ObjectSchema &o, Table& table) : m_realm(std::move(r)) , m_object_schema(&o) , m_table(&table) , m_mode(Mode::Table) { } Results::~Results() { if (m_background_query) { m_background_query->unregister(); } } void Results::validate_read() const { if (m_realm) m_realm->verify_thread(); if (m_table && !m_table->is_attached()) throw InvalidatedException(); if (m_mode == Mode::TableView && !m_table_view.is_attached()) throw InvalidatedException(); } void Results::validate_write() const { validate_read(); if (!m_realm || !m_realm->is_in_transaction()) throw InvalidTransactionException("Must be in a write transaction"); } void Results::set_live(bool live) { if (!live && m_mode == Mode::Table) { m_query = m_table->where(); m_mode = Mode::Query; } update_tableview(); m_live = live; } size_t Results::size() { validate_read(); switch (m_mode) { case Mode::Empty: return 0; case Mode::Table: return m_table->size(); case Mode::Query: return m_query.count(); case Mode::TableView: update_tableview(); return m_table_view.size(); } REALM_UNREACHABLE(); } StringData Results::get_object_type() const noexcept { return get_object_schema().name; } RowExpr Results::get(size_t row_ndx) { validate_read(); switch (m_mode) { case Mode::Empty: break; case Mode::Table: if (row_ndx < m_table->size()) return m_table->get(row_ndx); break; case Mode::Query: case Mode::TableView: update_tableview(); if (row_ndx < m_table_view.size()) return (!m_live && !m_table_view.is_row_attached(row_ndx)) ? RowExpr() : m_table_view.get(row_ndx); break; } throw OutOfBoundsIndexException{row_ndx, size()}; } util::Optional Results::first() { validate_read(); switch (m_mode) { case Mode::Empty: return none; case Mode::Table: return m_table->size() == 0 ? util::none : util::make_optional(m_table->front()); case Mode::Query: case Mode::TableView: update_tableview(); return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.front()); } REALM_UNREACHABLE(); } util::Optional Results::last() { validate_read(); switch (m_mode) { case Mode::Empty: return none; case Mode::Table: return m_table->size() == 0 ? util::none : util::make_optional(m_table->back()); case Mode::Query: case Mode::TableView: update_tableview(); return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.back()); } REALM_UNREACHABLE(); } void Results::update_tableview() { validate_read(); switch (m_mode) { case Mode::Empty: case Mode::Table: return; case Mode::Query: m_table_view = m_query.find_all(); if (m_sort) { m_table_view.sort(m_sort.columnIndices, m_sort.ascending); } m_mode = Mode::TableView; break; case Mode::TableView: if (m_live) { m_table_view.sync_if_needed(); } break; } } size_t Results::index_of(Row const& row) { validate_read(); if (!row) { throw DetatchedAccessorException{}; } if (m_table && row.get_table() != m_table) { throw IncorrectTableException(m_object_schema->name, ObjectStore::object_type_for_table_name(row.get_table()->get_name()), "Attempting to get the index of a Row of the wrong type" ); } return index_of(row.get_index()); } size_t Results::index_of(size_t row_ndx) { validate_read(); switch (m_mode) { case Mode::Empty: return not_found; case Mode::Table: return row_ndx; case Mode::Query: case Mode::TableView: update_tableview(); return m_table_view.find_by_source_ndx(row_ndx); } REALM_UNREACHABLE(); } template util::Optional Results::aggregate(size_t column, bool return_none_for_empty, Int agg_int, Float agg_float, Double agg_double, DateTime agg_datetime) { validate_read(); if (!m_table) return none; if (column > m_table->get_column_count()) throw OutOfBoundsIndexException{column, m_table->get_column_count()}; auto do_agg = [&](auto const& getter) -> util::Optional { switch (m_mode) { case Mode::Empty: return none; case Mode::Table: if (return_none_for_empty && m_table->size() == 0) return none; return util::Optional(getter(*m_table)); case Mode::Query: case Mode::TableView: this->update_tableview(); if (return_none_for_empty && m_table_view.size() == 0) return none; return util::Optional(getter(m_table_view)); } REALM_UNREACHABLE(); }; switch (m_table->get_column_type(column)) { case type_DateTime: return do_agg(agg_datetime); case type_Double: return do_agg(agg_double); case type_Float: return do_agg(agg_float); case type_Int: return do_agg(agg_int); default: throw UnsupportedColumnTypeException{column, m_table}; } } util::Optional Results::max(size_t column) { return aggregate(column, true, [=](auto const& table) { return table.maximum_int(column); }, [=](auto const& table) { return table.maximum_float(column); }, [=](auto const& table) { return table.maximum_double(column); }, [=](auto const& table) { return table.maximum_datetime(column); }); } util::Optional Results::min(size_t column) { return aggregate(column, true, [=](auto const& table) { return table.minimum_int(column); }, [=](auto const& table) { return table.minimum_float(column); }, [=](auto const& table) { return table.minimum_double(column); }, [=](auto const& table) { return table.minimum_datetime(column); }); } util::Optional Results::sum(size_t column) { return aggregate(column, false, [=](auto const& table) { return table.sum_int(column); }, [=](auto const& table) { return table.sum_float(column); }, [=](auto const& table) { return table.sum_double(column); }, [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; }); } util::Optional Results::average(size_t column) { return aggregate(column, true, [=](auto const& table) { return table.average_int(column); }, [=](auto const& table) { return table.average_float(column); }, [=](auto const& table) { return table.average_double(column); }, [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; }); } void Results::clear() { switch (m_mode) { case Mode::Empty: return; case Mode::Table: validate_write(); m_table->clear(); break; case Mode::Query: // Not using Query:remove() because building the tableview and // clearing it is actually significantly faster case Mode::TableView: validate_write(); update_tableview(); m_table_view.clear(RemoveMode::unordered); break; } } Query Results::get_query() const { validate_read(); switch (m_mode) { case Mode::Empty: case Mode::Query: return m_query; case Mode::TableView: return m_table_view.get_query(); case Mode::Table: return m_table->where(); } REALM_UNREACHABLE(); } TableView Results::get_tableview() { validate_read(); switch (m_mode) { case Mode::Empty: return {}; case Mode::Query: case Mode::TableView: update_tableview(); return m_table_view; case Mode::Table: return m_table->where().find_all(); } REALM_UNREACHABLE(); } Results Results::sort(realm::SortOrder&& sort) const { return Results(m_realm, get_object_schema(), get_query(), std::move(sort)); } Results Results::filter(Query&& q) const { return Results(m_realm, get_object_schema(), get_query().and_query(std::move(q)), get_sort()); } AsyncQueryCancelationToken Results::async(std::function target) { if (m_realm->config().read_only) { throw InvalidTransactionException("Cannot create asynchronous query for read-only Realms"); } if (m_realm->is_in_transaction()) { throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction"); } if (!m_background_query) { m_background_query = std::make_shared<_impl::AsyncQuery>(*this); _impl::RealmCoordinator::register_query(m_background_query); } return {m_background_query, m_background_query->add_callback(std::move(target))}; } void Results::AsyncFriend::set_table_view(Results& results, realm::TableView &&tv) { results.m_table_view = std::move(tv); results.m_mode = Mode::TableView; REALM_ASSERT(results.m_table_view.is_in_sync()); } Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) : std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns") , column_index(column) , column_name(table->get_column_name(column)) , column_type(table->get_column_type(column)) { } AsyncQueryCancelationToken::AsyncQueryCancelationToken(std::shared_ptr<_impl::AsyncQuery> query, size_t token) : m_query(std::move(query)), m_token(token) { } AsyncQueryCancelationToken::~AsyncQueryCancelationToken() { // m_query itself (and not just the pointed-to thing) needs to be accessed // atomically to ensure that there are no data races when the token is // destroyed after being modified on a different thread. // This is needed despite the token not being thread-safe in general as // users find it very surpringing for obj-c objects to care about what // thread they are deallocated on. if (auto query = std::atomic_load(&m_query)) { query->remove_callback(m_token); } } AsyncQueryCancelationToken::AsyncQueryCancelationToken(AsyncQueryCancelationToken&& rgt) : m_query(std::atomic_exchange(&rgt.m_query, {})), m_token(rgt.m_token) { } AsyncQueryCancelationToken& AsyncQueryCancelationToken::operator=(realm::AsyncQueryCancelationToken&& rgt) { if (this != &rgt) { if (auto query = std::atomic_load(&m_query)) { query->remove_callback(m_token); } std::atomic_store(&m_query, std::atomic_exchange(&rgt.m_query, {})); m_token = rgt.m_token; } return *this; }