//////////////////////////////////////////////////////////////////////////// // // 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. // //////////////////////////////////////////////////////////////////////////// #ifndef REALM_RESULTS_HPP #define REALM_RESULTS_HPP #include "collection_notifications.hpp" #include "impl/collection_notifier.hpp" #include "list.hpp" #include "object.hpp" #include "object_schema.hpp" #include "property.hpp" #include "shared_realm.hpp" #include #include namespace realm { class Mixed; class ObjectSchema; namespace _impl { class ResultsNotifier; } class Results { public: // Results can be either be backed by nothing, a thin wrapper around a table, // or a wrapper around a query and a sort order which creates and updates // the tableview as needed Results(); Results(std::shared_ptr r, Table& table); Results(std::shared_ptr r, Query q, DescriptorOrdering o = {}); Results(std::shared_ptr r, TableView tv, DescriptorOrdering o = {}); Results(std::shared_ptr r, LinkViewRef lv, util::Optional q = {}, SortDescriptor s = {}); ~Results(); // Results is copyable and moveable Results(Results&&); Results& operator=(Results&&); Results(const Results&); Results& operator=(const Results&); // Get the Realm std::shared_ptr get_realm() const { return m_realm; } // Object schema describing the vendored object type const ObjectSchema &get_object_schema() const; // Get a query which will match the same rows as is contained in this Results // Returned query will not be valid if the current mode is Empty Query get_query() const; // Get the list of sort and distinct operations applied for this Results. DescriptorOrdering const& get_descriptor_ordering() const noexcept { return m_descriptor_ordering; } // Get a tableview containing the same rows as this Results TableView get_tableview(); // Get the object type which will be returned by get() StringData get_object_type() const noexcept; PropertyType get_type() const; // Get the LinkView this Results is derived from, if any LinkViewRef get_linkview() const { return m_link_view; } // Get the size of this results // Can be either O(1) or O(N) depending on the state of things size_t size(); // Get the row accessor for the given index // Throws OutOfBoundsIndexException if index >= size() template T get(size_t index); // Get the boxed row accessor for the given index // Throws OutOfBoundsIndexException if index >= size() template auto get(Context&, size_t index); // Get a row accessor for the first/last row, or none if the results are empty // More efficient than calling size()+get() template util::Optional first(); template util::Optional last(); // Get the index of the first row matching the query in this table size_t index_of(Query&& q); // Get the first index of the given value in this results, or not_found // Throws DetachedAccessorException if row is not attached // Throws IncorrectTableException if row belongs to a different table template size_t index_of(T const& value); // Delete all of the rows in this Results from the Realm // size() will always be zero afterwards // Throws InvalidTransactionException if not in a write transaction void clear(); // Create a new Results by further filtering or sorting this Results Results filter(Query&& q) const; Results sort(SortDescriptor&& sort) const; Results sort(std::vector> const& keypaths) const; // Create a new Results by removing duplicates Results distinct(DistinctDescriptor&& uniqueness) const; Results distinct(std::vector const& keypaths) const; // Create a new Results with only the first `max_count` entries Results limit(size_t max_count) const; // Create a new Results by adding sort and distinct combinations Results apply_ordering(DescriptorOrdering&& ordering); // Return a snapshot of this Results that never updates to reflect changes in the underlying data. Results snapshot() const &; Results snapshot() &&; // Get the min/max/average/sum of the given column // All but sum() returns none when there are zero matching rows // sum() returns 0, except for when it returns none // Throws UnsupportedColumnTypeException for sum/average on timestamp or non-numeric column // Throws OutOfBoundsIndexException for an out-of-bounds column util::Optional max(size_t column=0); util::Optional min(size_t column=0); util::Optional average(size_t column=0); util::Optional sum(size_t column=0); enum class Mode { Empty, // Backed by nothing (for missing tables) Table, // Backed directly by a Table Query, // Backed by a query that has not yet been turned into a TableView LinkView, // Backed directly by a LinkView TableView, // Backed by a TableView created from a Query }; // Get the currrent mode of the Results // Ideally this would not be public but it's needed for some KVO stuff Mode get_mode() const { return m_mode; } // Is this Results associated with a Realm that has not been invalidated? bool is_valid() const; // The Results object has been invalidated (due to the Realm being invalidated) // All non-noexcept functions can throw this struct InvalidatedException : public std::logic_error { InvalidatedException() : std::logic_error("Access to invalidated Results objects") {} }; // The input index parameter was out of bounds struct OutOfBoundsIndexException : public std::out_of_range { OutOfBoundsIndexException(size_t r, size_t c); const size_t requested; const size_t valid_count; }; // The input Row object is not attached struct DetatchedAccessorException : public std::logic_error { DetatchedAccessorException() : std::logic_error("Atempting to access an invalid object") {} }; // The input Row object belongs to a different table struct IncorrectTableException : public std::logic_error { IncorrectTableException(StringData e, StringData a, const std::string &error) : std::logic_error(error), expected(e), actual(a) {} const StringData expected; const StringData actual; }; // The requested aggregate operation is not supported for the column type struct UnsupportedColumnTypeException : public std::logic_error { size_t column_index; StringData column_name; PropertyType property_type; UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation); }; // The property request does not exist in the schema struct InvalidPropertyException : public std::logic_error { InvalidPropertyException(const std::string& object_type, const std::string& property_name); const std::string object_type; const std::string property_name; }; // The requested operation is valid, but has not yet been implemented struct UnimplementedOperationException : public std::logic_error { UnimplementedOperationException(const char *message); }; // Create an async query from this Results // The query will be run on a background thread and delivered to the callback, // and then rerun after each commit (if needed) and redelivered if it changed template NotificationToken async(Func&& target); NotificationToken add_notification_callback(CollectionChangeCallback cb) &; bool wants_background_updates() const { return m_wants_background_updates; } // Returns whether the rows are guaranteed to be in table order. bool is_in_table_order() const; // Helper type to let ResultsNotifier update the tableview without giving access // to any other privates or letting anyone else do so class Internal { friend class _impl::ResultsNotifier; static void set_table_view(Results& results, TableView&& tv); }; template auto first(Context&); template auto last(Context&); template size_t index_of(Context&, T value); // Batch updates all items in this collection with the provided value // Must be called inside a transaction // Throws an exception if the value does not match the type for given prop_name template void set_property_value(ContextType& ctx, StringData prop_name, ValueType value); // Execute the query immediately if needed. When the relevant query is slow, size() // may cost similar time compared with creating the tableview. Use this function to // avoid running the query twice for size() and other accessors. void evaluate_query_if_needed(bool wants_notifications = true); private: enum class UpdatePolicy { Auto, // Update automatically to reflect changes in the underlying data. Never, // Never update. }; std::shared_ptr m_realm; mutable const ObjectSchema *m_object_schema = nullptr; Query m_query; TableView m_table_view; LinkViewRef m_link_view; TableRef m_table; DescriptorOrdering m_descriptor_ordering; _impl::CollectionNotifier::Handle<_impl::ResultsNotifier> m_notifier; Mode m_mode = Mode::Empty; UpdatePolicy m_update_policy = UpdatePolicy::Auto; bool m_has_used_table_view = false; bool m_wants_background_updates = true; bool update_linkview(); void validate_read() const; void validate_write() const; using ForCallback = util::TaggedBool; void prepare_async(ForCallback); template util::Optional try_get(size_t); template util::Optional aggregate(size_t column, const char* name, Int agg_int, Float agg_float, Double agg_double, Timestamp agg_timestamp); void prepare_for_aggregate(size_t column, const char* name); void set_table_view(TableView&& tv); template auto dispatch(Fn&&) const; }; template NotificationToken Results::async(Func&& target) { return this->add_notification_callback([target = std::forward(target)](CollectionChangeSet const&, std::exception_ptr e) { target(e); }); } template auto Results::dispatch(Fn&& fn) const { return switch_on_type(get_type(), std::forward(fn)); } template auto Results::get(Context& ctx, size_t row_ndx) { return dispatch([&](auto t) { return ctx.box(this->get>(row_ndx)); }); } template auto Results::first(Context& ctx) { // GCC 4.9 complains about `ctx` not being defined within the lambda without this goofy capture return dispatch([this, ctx = &ctx](auto t) { auto value = this->first>(); return value ? static_castno_value())>(ctx->box(*value)) : ctx->no_value(); }); } template auto Results::last(Context& ctx) { return dispatch([&](auto t) { auto value = this->last>(); return value ? static_cast(ctx.box(*value)) : ctx.no_value(); }); } template size_t Results::index_of(Context& ctx, T value) { return dispatch([&](auto t) { return this->index_of(ctx.template unbox>(value)); }); } template void Results::set_property_value(ContextType& ctx, StringData prop_name, ValueType value) { // Check invariants for calling this method validate_write(); const ObjectSchema& object_schema = get_object_schema(); const Property* prop = object_schema.property_for_name(prop_name); if (!prop) { throw InvalidPropertyException(object_schema.name, prop_name); } if (prop->is_primary && !m_realm->is_in_migration()) { throw ModifyPrimaryKeyException(m_object_schema->name, prop->name); } // Update all objects in this ResultSets. Use snapshot to avoid correctness problems if the // object is removed from the TableView after the property update as well as avoiding to // re-evaluating the query too many times. auto snapshot = this->snapshot(); size_t size = snapshot.size(); for (size_t i = 0; i < size; ++i) { Object obj(m_realm, *m_object_schema, snapshot.get(i)); obj.set_property_value_impl(ctx, *prop, value, true, false, false); } } } // namespace realm #endif // REALM_RESULTS_HPP