/************************************************************************* * * Copyright 2016 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_TABLE_VIEW_HPP #define REALM_TABLE_VIEW_HPP #include #include #include #include #include #include namespace realm { // Views, tables and synchronization between them: // // Views are built through queries against either tables or another view. // Views may be restricted to only hold entries provided by another view. // this other view is called the "restricting view". // Views may be sorted in ascending or descending order of values in one ore more columns. // // Views remember the query from which it was originally built. // Views remember the table from which it was originally built. // Views remember a restricting view if one was used when it was originally built. // Views remember the sorting criteria (columns and direction) // // A view may be operated in one of two distinct modes: *reflective* and *imperative*. // Sometimes the term "reactive" is used instead of "reflective" with the same meaning. // // Reflective views: // - A reflective view *always* *reflect* the result of running the query. // If the underlying tables or tableviews change, the reflective view changes as well. // A reflective view may need to rerun the query it was generated from, a potentially // costly operation which happens on demand. // - It does not matter whether changes are explicitly done within the transaction, or // occur implicitly as part of advance_read() or promote_to_write(). // // Imperative views: // - An imperative view only *initially* holds the result of the query. An imperative // view *never* reruns the query. To force the view to match it's query (by rerunning it), // the view must be operated in reflective mode. // An imperative view can be modified explicitly. References can be added, removed or // changed. // // - In imperative mode, the references in the view tracks movement of the referenced data: // If you delete an entry which is referenced from a view, said reference is detached, // not removed. // - It does not matter whether the delete is done in-line (as part of the current transaction), // or if it is done implicitly as part of advance_read() or promote_to_write(). // // The choice between reflective and imperative views might eventually be represented by a // switch on the tableview, but isn't yet. For now, clients (bindings) must call sync_if_needed() // to get reflective behavior. // // Use cases: // // 1. Presenting data // The first use case (and primary motivator behind the reflective view) is to just track // and present the state of the database. In this case, the view is operated in reflective // mode, it is not modified within the transaction, and it is not used to modify data in // other parts of the database. // // 2. Background execution // This is the second use case. The implicit rerun of the query in our first use case // may be too costly to be acceptable on the main thread. Instead you want to run the query // on a worker thread, but display it on the main thread. To achieve this, you need two // Transactions locked on to the same version of the database. If you have that, you can // import_copy_of() a view from one transaction to the other. See also db.hpp for more // information. Technically, you can also import_copy_of into a transaction locked to a // different version. The imported view will automatically match the importing version. // // 3. Iterating a view and changing data // The third use case (and a motivator behind the imperative view) is when you want // to make changes to the database in accordance with a query result. Imagine you want to // find all employees with a salary below a limit and raise their salaries to the limit (pseudocode): // // promote_to_write(); // view = table.where().less_than(salary_column,limit).find_all(); // for (size_t i = 0; i < view.size(); ++i) { // view[i].set(salary_column, limit); // // add this to get reflective mode: view.sync_if_needed(); // } // commit_and_continue_as_read(); // // This is idiomatic imperative code and it works if the view is operated in imperative mode. // // If the view is operated in reflective mode, the behaviour surprises most people: When the // first salary is changed, the entry no longer fullfills the query, so it is dropped from the // view implicitly. view[0] is removed, view[1] moves to view[0] and so forth. But the next // loop iteration has i=1 and refers to view[1], thus skipping view[0]. The end result is that // every other employee get a raise, while the others don't. // // 4. Iterating intermixed with implicit updates // This leads us to use case 4, which is similar to use case 3, but uses promote_to_write() // intermixed with iterating a view. This is actually quite important to some, who do not want // to end up with a large write transaction. // // view = table.where().less_than(salary_column,limit).find_all(); // for (size_t i = 0; i < view.size(); ++i) { // promote_to_write(); // view[i].set(salary_column, limit); // commit_and_continue_as_write(); // } // // Anything can happen at the call to promote_to_write(). The key question then becomes: how // do we support a safe way of realising the original goal (raising salaries) ? // // using the imperative operating mode: // // view = table.where().less_than(salary_column,limit).find_all(); // for (size_t i = 0; i < view.size(); ++i) { // promote_to_write(); // // add r.sync_if_needed(); to get reflective mode // if (r.is_obj_valid(i)) { // auto r = view[i]; // view[i].set(salary_column, limit); // } // commit_and_continue_as_write(); // } // // This is safe, and we just aim for providing low level safety: is_obj_valid() can tell // if the reference is valid, and the references in the view continue to point to the // same object at all times, also following implicit updates. The rest is up to the // application logic. // // It is important to see, that there is no guarantee that all relevant employees get // their raise in cases whith concurrent updates. At every call to promote_to_write() new // employees may be added to the underlying table, but as the view is in imperative mode, // these new employees are not added to the view. Also at promote_to_write() an existing // employee could recieve a (different, larger) raise which would then be overwritten and lost. // However, these are problems that you should expect, since the activity is spread over multiple // transactions. class TableView : public ObjList { public: /// Construct null view (no memory allocated). TableView() {} /// Construct empty view, ready for addition of row indices. TableView(ConstTableRef parent); TableView(ConstTableRef parent, Query& query, size_t start, size_t end, size_t limit); TableView(ConstTableRef parent, ColKey column, const Obj& obj); TableView(ConstTableRef parent, LinkCollectionPtr collection); /// Copy constructor. TableView(const TableView&); /// Move constructor. TableView(TableView&&) noexcept; TableView& operator=(const TableView&); TableView& operator=(TableView&&) noexcept; TableView(TableView& source, Transaction* tr, PayloadPolicy mode); ~TableView() {} TableRef get_parent() noexcept { return m_table.cast_away_const(); } TableRef get_target_table() const final { return m_table.cast_away_const(); } size_t size() const final { return m_key_values.size(); } bool is_empty() const noexcept { return m_key_values.size() == 0; } // Tells if the table that this TableView points at still exists or has been deleted. bool is_attached() const noexcept { return bool(m_table); } ObjKey get_key(size_t ndx) const final { return m_key_values.get(ndx); } bool is_obj_valid(size_t ndx) const noexcept final { return m_table->is_valid(get_key(ndx)); } Obj get(size_t row_ndx) const { REALM_ASSERT(row_ndx < size()); ObjKey key(m_key_values.get(row_ndx)); REALM_ASSERT(key); return m_table->get_object(key); } Obj get_object(size_t ndx) const final { return get(ndx); } // Get the query used to create this TableView // The query will have a null source table if this tv was not created from // a query const Query& get_query() const noexcept { return m_query; } void clear(); // Change the TableView to be backed by another query // only works if the TableView is already backed by a query, and both // queries points to the same Table void update_query(const Query& q); std::unique_ptr clone() const { return std::unique_ptr(new TableView(*this)); } LinkCollectionPtr clone_obj_list() const final { return std::unique_ptr(new TableView(*this)); } // import_copy_of() machinery entry points based on dynamic type. These methods: // a) forward their calls to the static type entry points. // b) new/delete patch data structures. std::unique_ptr clone_for_handover(Transaction* tr, PayloadPolicy mode) { std::unique_ptr retval(new TableView(*this, tr, mode)); return retval; } template R aggregate(ColKey column_key, size_t* result_count = nullptr, ObjKey* return_key = nullptr) const; template size_t aggregate_count(ColKey column_key, T count_target) const; int64_t sum_int(ColKey column_key) const; int64_t maximum_int(ColKey column_key, ObjKey* return_key = nullptr) const; int64_t minimum_int(ColKey column_key, ObjKey* return_key = nullptr) const; double average_int(ColKey column_key, size_t* value_count = nullptr) const; size_t count_int(ColKey column_key, int64_t target) const; double sum_float(ColKey column_key) const; float maximum_float(ColKey column_key, ObjKey* return_key = nullptr) const; float minimum_float(ColKey column_key, ObjKey* return_key = nullptr) const; double average_float(ColKey column_key, size_t* value_count = nullptr) const; size_t count_float(ColKey column_key, float target) const; double sum_double(ColKey column_key) const; double maximum_double(ColKey column_key, ObjKey* return_key = nullptr) const; double minimum_double(ColKey column_key, ObjKey* return_key = nullptr) const; double average_double(ColKey column_key, size_t* value_count = nullptr) const; size_t count_double(ColKey column_key, double target) const; Timestamp minimum_timestamp(ColKey column_key, ObjKey* return_key = nullptr) const; Timestamp maximum_timestamp(ColKey column_key, ObjKey* return_key = nullptr) const; size_t count_timestamp(ColKey column_key, Timestamp target) const; Decimal128 sum_decimal(ColKey column_key) const; Decimal128 maximum_decimal(ColKey column_key, ObjKey* return_key = nullptr) const; Decimal128 minimum_decimal(ColKey column_key, ObjKey* return_key = nullptr) const; Decimal128 average_decimal(ColKey column_key, size_t* value_count = nullptr) const; size_t count_decimal(ColKey column_key, Decimal128 target) const; Decimal128 sum_mixed(ColKey column_key) const; Mixed maximum_mixed(ColKey column_key, ObjKey* return_key = nullptr) const; Mixed minimum_mixed(ColKey column_key, ObjKey* return_key = nullptr) const; Decimal128 average_mixed(ColKey column_key, size_t* value_count = nullptr) const; size_t count_mixed(ColKey column_key, Mixed target) const; /// Search this view for the specified key. If found, the index of that row /// within this view is returned, otherwise `realm::not_found` is returned. size_t find_by_source_ndx(ObjKey key) const noexcept { return m_key_values.find_first(key); } // Conversion void to_json(std::ostream&, size_t link_depth = 0) const; // Determine if the view is 'in sync' with the underlying table // as well as other views used to generate the view. Note that updates // through views maintains synchronization between view and table. // It doesnt by itself maintain other views as well. So if a view // is generated from another view (not a table), updates may cause // that view to be outdated, AND as the generated view depends upon // it, it too will become outdated. bool is_in_sync() const final; // A TableView is frozen if it is a) obtained from a query against a frozen table // and b) is synchronized (is_in_sync()) bool is_frozen() { return m_table->is_frozen() && is_in_sync(); } // Tells if this TableView depends on a LinkList or row that has been deleted. bool depends_on_deleted_object() const; // Synchronize a view to match a table or tableview from which it // has been derived. Synchronization is achieved by rerunning the // query used to generate the view. If derived from another view, that // view will be synchronized as well. // // "live" or "reactive" views are implemented by calling sync_if_needed() // before any of the other access-methods whenever the view may have become // outdated. void sync_if_needed() const final; // Return the version of the source it was created from. TableVersions get_dependency_versions() const { TableVersions ret; get_dependencies(ret); return ret; } // Sort m_key_values according to one column void sort(ColKey column, bool ascending = true); // Sort m_key_values according to multiple columns void sort(SortDescriptor order); // Get the number of total results which have been filtered out because a number of "LIMIT" operations have // been applied. This number only applies to the last sync. size_t get_num_results_excluded_by_limit() const noexcept { return m_limit_count; } // Remove rows that are duplicated with respect to the column set passed as argument. // distinct() will preserve the original order of the row pointers, also if the order is a result of sort() // If two rows are indentical (for the given set of distinct-columns), then the last row is removed. // You can call sync_if_needed() to update the distinct view, just like you can for a sorted view. // Each time you call distinct() it will compound on the previous calls void distinct(ColKey column); void distinct(DistinctDescriptor columns); void limit(LimitDescriptor limit); // Replace the order of sort and distinct operations, bypassing manually // calling sort and distinct. This is a convenience method for bindings. void apply_descriptor_ordering(const DescriptorOrdering& new_ordering); // Gets a readable and parsable string which completely describes the sort and // distinct operations applied to this view. std::string get_descriptor_ordering_description() const; // Returns whether the rows are guaranteed to be in table order. // This is true only of unsorted TableViews created from either: // - Table::find_all() // - Query::find_all() when the query is not restricted to a view. bool is_in_table_order() const; bool is_backlink_view() const { return m_source_column_key != ColKey(); } protected: // This TableView can be "born" from 4 different sources: // - LinkView // - Query::find_all() // - Table::get_distinct_view() // - Table::get_backlink_view() void get_dependencies(TableVersions&) const final; void do_sync(); void do_sort(const DescriptorOrdering&); mutable ConstTableRef m_table; // The source column index that this view contain backlinks for. ColKey m_source_column_key; // The target object that rows in this view link to. ObjKey m_linked_obj_key; ConstTableRef m_linked_table; // If this TableView was created from an Object Collection, then this reference points to it. Otherwise it's 0 mutable LinkCollectionPtr m_collection_source; // Stores the ordering criteria of applied sort and distinct operations. DescriptorOrdering m_descriptor_ordering; size_t m_limit_count = 0; // A valid query holds a reference to its table which must match our m_table. // hence we can use a query with a null table reference to indicate that the view // was NOT generated by a query, but follows a table directly. Query m_query; // parameters for findall, needed to rerun the query size_t m_start = 0; size_t m_end = size_t(-1); size_t m_limit = size_t(-1); // FIXME: This class should eventually be replaced by std::vector // It implements a vector of ObjKey, where the elements are held in the // heap (default allocator is the only option) class KeyValues : public KeyColumn { public: KeyValues() : KeyColumn(Allocator::get_default()) { } KeyValues(const KeyValues&) = delete; ~KeyValues() { destroy(); } void move_from(KeyValues&); void copy_from(const KeyValues&); }; mutable TableVersions m_last_seen_versions; KeyValues m_key_values; private: ObjKey find_first_integer(ColKey column_key, int64_t value) const; template Timestamp minmax_timestamp(ColKey column_key, ObjKey* return_key) const; util::RaceDetector m_race_detector; friend class Table; friend class Obj; friend class Query; friend class DB; friend class ObjList; friend class LnkLst; }; // ================================================================================================ // TableView Implementation: inline TableView::TableView(ConstTableRef parent) : m_table(parent) // Throws { m_key_values.create(); if (m_table) { m_last_seen_versions.emplace_back(m_table->get_key(), m_table->get_content_version()); } } inline TableView::TableView(ConstTableRef parent, Query& query, size_t start, size_t end, size_t lim) : m_table(parent) , m_query(query) , m_start(start) , m_end(end) , m_limit(lim) { m_key_values.create(); } inline TableView::TableView(ConstTableRef src_table, ColKey src_column_key, const Obj& obj) : m_table(src_table) // Throws , m_source_column_key(src_column_key) , m_linked_obj_key(obj.get_key()) , m_linked_table(obj.get_table()) { m_key_values.create(); if (m_table) { m_last_seen_versions.emplace_back(m_table->get_key(), m_table->get_content_version()); m_last_seen_versions.emplace_back(obj.get_table()->get_key(), obj.get_table()->get_content_version()); } } inline TableView::TableView(ConstTableRef parent, LinkCollectionPtr collection) : m_table(parent) // Throws , m_collection_source(std::move(collection)) { REALM_ASSERT(m_collection_source); m_key_values.create(); if (m_table) { m_last_seen_versions.emplace_back(m_table->get_key(), m_table->get_content_version()); } } inline TableView::TableView(const TableView& tv) : m_table(tv.m_table) , m_source_column_key(tv.m_source_column_key) , m_linked_obj_key(tv.m_linked_obj_key) , m_linked_table(tv.m_linked_table) , m_collection_source(tv.m_collection_source ? tv.m_collection_source->clone_obj_list() : LinkCollectionPtr{}) , m_descriptor_ordering(tv.m_descriptor_ordering) , m_query(tv.m_query) , m_start(tv.m_start) , m_end(tv.m_end) , m_limit(tv.m_limit) , m_last_seen_versions(tv.m_last_seen_versions) { m_key_values.copy_from(tv.m_key_values); m_limit_count = tv.m_limit_count; } inline TableView::TableView(TableView&& tv) noexcept : m_table(tv.m_table) , m_source_column_key(tv.m_source_column_key) , m_linked_obj_key(tv.m_linked_obj_key) , m_linked_table(tv.m_linked_table) , m_collection_source(std::move(tv.m_collection_source)) , m_descriptor_ordering(std::move(tv.m_descriptor_ordering)) , m_query(std::move(tv.m_query)) , m_start(tv.m_start) , m_end(tv.m_end) , m_limit(tv.m_limit) // if we are created from a table view which is outdated, take care to use the outdated // version number so that we can later trigger a sync if needed. , m_last_seen_versions(std::move(tv.m_last_seen_versions)) { m_key_values.move_from(tv.m_key_values); m_limit_count = tv.m_limit_count; } inline TableView& TableView::operator=(TableView&& tv) noexcept { m_table = std::move(tv.m_table); m_key_values.move_from(tv.m_key_values); m_query = std::move(tv.m_query); m_last_seen_versions = tv.m_last_seen_versions; m_start = tv.m_start; m_end = tv.m_end; m_limit = tv.m_limit; m_limit_count = tv.m_limit_count; m_source_column_key = tv.m_source_column_key; m_linked_obj_key = tv.m_linked_obj_key; m_linked_table = tv.m_linked_table; m_collection_source = std::move(tv.m_collection_source); m_descriptor_ordering = std::move(tv.m_descriptor_ordering); return *this; } inline TableView& TableView::operator=(const TableView& tv) { if (this == &tv) return *this; m_key_values.copy_from(tv.m_key_values); m_query = tv.m_query; m_last_seen_versions = tv.m_last_seen_versions; m_start = tv.m_start; m_end = tv.m_end; m_limit = tv.m_limit; m_limit_count = tv.m_limit_count; m_source_column_key = tv.m_source_column_key; m_linked_obj_key = tv.m_linked_obj_key; m_linked_table = tv.m_linked_table; m_collection_source = tv.m_collection_source ? tv.m_collection_source->clone_obj_list() : LinkCollectionPtr{}; m_descriptor_ordering = tv.m_descriptor_ordering; return *this; } } // namespace realm #endif // REALM_TABLE_VIEW_HPP