Port most of RLMResults's functionality to realm::Results

This commit is contained in:
Thomas Goyne 2015-08-02 15:31:22 -07:00 committed by Ari Lazier
parent 73b605d62c
commit ab7f3dcaa6
4 changed files with 404 additions and 48 deletions

View File

@ -17,42 +17,288 @@
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
#include "results.hpp" #include "results.hpp"
#import <stdexcept>
#include <stdexcept>
using namespace realm; using namespace realm;
Results::Results(SharedRealm &r, ObjectSchema &o, Query q, SortOrder s) : Results::Results(SharedRealm r, Query q, SortOrder s)
realm(r), object_schema(o), backing_query(q), table_view(backing_query.find_all()) : m_realm(std::move(r))
, m_query(std::move(q))
, m_table(m_query.get_table().get())
, m_sort(std::move(s))
, m_mode(Mode::Query)
{ {
setSort(std::move(s)); }
Results::Results(SharedRealm r, Table& table)
: m_realm(std::move(r))
, m_table(&table)
, m_mode(Mode::Table)
{
}
void Results::validate_read() const
{
if (m_realm && !m_realm->check_thread())
throw Error::IncorrectThread;
if (m_table && !m_table->is_attached())
throw Error::Invalidated;
}
void Results::validate_write() const
{
validate_read();
if (!m_realm || !m_realm->is_in_transaction())
throw Error::NotInWrite;
} }
size_t Results::size() size_t Results::size()
{ {
verify_attached(); validate_read();
return table_view.size(); switch (m_mode) {
} case Mode::Empty: return 0;
case Mode::Table: return m_table->size();
void Results::setSort(SortOrder s) case Mode::Query: return m_query.count();
{ case Mode::TableView:
sort_order = std::make_unique<SortOrder>(std::move(s)); update_tableview();
table_view.sort(sort_order->columnIndices, sort_order->ascending); return m_table_view.size();
}
Row Results::get(std::size_t row_ndx)
{
verify_attached();
if (row_ndx >= table_view.size()) {
throw std::out_of_range(std::string("Index ") + std::to_string(row_ndx) + " is outside of range 0..." +
std::to_string(table_view.size()) + ".");
} }
return table_view.get(row_ndx);
} }
void Results::verify_attached() RowExpr Results::get(size_t row_ndx)
{ {
if (!table_view.is_attached()) { validate_read();
throw std::runtime_error("Tableview is not attached"); 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_table_view.get(row_ndx);
break;
} }
table_view.sync_if_needed();
throw Error::OutOfBoundsIndex;
}
util::Optional<RowExpr> Results::first()
{
validate_read();
switch (m_mode) {
case Mode::Empty:
return none;
case Mode::Table:
return m_table->size() == 0 ? util::none : util::Optional<RowExpr>(m_table->front());
case Mode::Query:
update_tableview();
[[clang::fallthrough]];
case Mode::TableView:
return m_table_view.size() == 0 ? util::none : util::Optional<RowExpr>(m_table_view.front());
}
}
util::Optional<RowExpr> Results::last()
{
validate_read();
switch (m_mode) {
case Mode::Empty:
return none;
case Mode::Table:
return m_table->size() == 0 ? util::none : util::Optional<RowExpr>(m_table->back());
case Mode::Query:
update_tableview();
[[clang::fallthrough]];
case Mode::TableView:
return m_table_view.size() == 0 ? util::none : util::Optional<RowExpr>(m_table_view.back());
}
}
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:
m_table_view.sync_if_needed();
break;
}
}
size_t Results::index_of(Row const& row)
{
validate_read();
if (!row) {
throw Error::DetachedAccessor;
}
if (m_table && row.get_table() != m_table) {
throw Error::IncorrectTable;
}
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:
if (!m_sort)
return m_query.count(row_ndx, row_ndx + 1) ? m_query.count(0, row_ndx) : not_found;
[[clang::fallthrough]];
case Mode::TableView:
update_tableview();
return m_table_view.find_by_source_ndx(row_ndx);
}
}
template<typename Int, typename Float, typename Double, typename DateTime>
util::Optional<Mixed> 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 Error::OutOfBoundsIndex;
auto do_agg = [&](auto const& getter) -> util::Optional<Mixed> {
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<Mixed>(getter(*m_table));
case Mode::Query:
case Mode::TableView:
update_tableview();
if (return_none_for_empty && m_table_view.size() == 0)
return none;
return util::Optional<Mixed>(getter(m_table_view));
}
};
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 Error::UnsupportedColumnType;
}
}
util::Optional<Mixed> 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<Mixed> 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<Mixed> 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 Error::UnsupportedColumnType; });
}
util::Optional<Mixed> 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 Error::UnsupportedColumnType; });
}
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 bulding the tableview and
// clearing it is actually significantly faster
case Mode::TableView:
validate_write();
update_tableview();
m_table_view.clear();
break;
}
}
Query Results::get_query() const
{
validate_read();
switch (m_mode) {
case Mode::Empty:
case Mode::Query:
case Mode::TableView:
return m_query;
case Mode::Table:
return m_table->where();
}
}
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_query(), std::move(sort));
}
Results Results::filter(Query&& q) const
{
return Results(m_realm, get_query().and_query(q), get_sort());
} }

View File

@ -19,35 +19,140 @@
#ifndef REALM_RESULTS_HPP #ifndef REALM_RESULTS_HPP
#define REALM_RESULTS_HPP #define REALM_RESULTS_HPP
#import "shared_realm.hpp" #include "shared_realm.hpp"
#import <realm/table_view.hpp>
#include <realm/table_view.hpp>
#include <realm/table.hpp>
#include <realm/util/optional.hpp>
namespace realm { namespace realm {
struct SortOrder { template<typename T> class BasicRowExpr;
using RowExpr = BasicRowExpr<Table>;
class Mixed;
struct SortOrder {
std::vector<size_t> columnIndices; std::vector<size_t> columnIndices;
std::vector<bool> ascending; std::vector<bool> ascending;
explicit operator bool() const { explicit operator bool() const
{
return !columnIndices.empty(); return !columnIndices.empty();
} }
}; };
static SortOrder s_defaultSort = {{}, {}}; 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() = default;
Results(SharedRealm r, Table& table);
Results(SharedRealm r, Query q, SortOrder s = {});
struct Results { // Results is copyable and moveable
Results(SharedRealm &r, ObjectSchema &o, Query q, SortOrder s = s_defaultSort); Results(Results const&) = default;
Results(Results&&) = default;
Results& operator=(Results const&) = default;
Results& operator=(Results&&) = default;
// 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 currently applied sort order for this Results
SortOrder const& get_sort() const noexcept { return m_sort; }
// Get a tableview containing the same rows as this Results
TableView get_tableview();
// Get the size of this results
// Can be either O(1) or O(N) depending on the state of things
size_t size(); size_t size();
Row get(std::size_t row_ndx);
void verify_attached();
SharedRealm realm; // Get the row accessor for the given index
ObjectSchema &object_schema; // Throws OutOfBoundsIndex if index >= size()
Query backing_query; RowExpr get(size_t index);
TableView table_view;
std::unique_ptr<SortOrder> sort_order;
void setSort(SortOrder s); // Get a row accessor for the first/last row, or none if the results are empty
// More efficient than calling size()+get()
util::Optional<RowExpr> first();
util::Optional<RowExpr> last();
// Get the first index of the given row in this results, or not_found
// Throws DetachedAccessor if row is not attached
// Throws IncorrectTable if row belongs to a different table
size_t index_of(size_t row_ndx);
size_t index_of(Row const& row);
// Delete all of the rows in this Results from the Realm
// size() will always be zero afterwards
// Throws NotInWrite
void clear();
// Create a new Results by further filtering or sorting this Results
Results filter(Query&& q) const;
Results sort(SortOrder&& sort) const;
// 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 UnsupportedColumnType for sum/average on datetime or non-numeric column
// Throws OutOfBoundsIndex for an out-of-bounds column
util::Optional<Mixed> max(size_t column);
util::Optional<Mixed> min(size_t column);
util::Optional<Mixed> average(size_t column);
util::Optional<Mixed> sum(size_t column);
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
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; }
// All errors thrown by Results are one of the following error codes
// Invalidated and IncorrectThread can be thrown by all non-noexcept functions
// Other errors are thrown only where indicated
enum class Error {
// The Results object has been invalidated (due to the Realm being invalidated)
Invalidated,
// The Results object is being accessed from the wrong thread
IncorrectThread,
// The containing Realm is not in a write transaction
NotInWrite,
// The input index parameter was out of bounds
OutOfBoundsIndex,
// The input Row object belongs to a different table
IncorrectTable,
// The input Row object is not attached
DetachedAccessor,
// The requested aggregate operation is not supported for the column type
UnsupportedColumnType
};
private:
SharedRealm m_realm;
Query m_query;
TableView m_table_view;
Table* m_table = nullptr;
SortOrder m_sort;
Mode m_mode = Mode::Empty;
void validate_read() const;
void validate_write() const;
void update_tableview();
template<typename Int, typename Float, typename Double, typename DateTime>
util::Optional<Mixed> aggregate(size_t column, bool return_none_for_empty,
Int agg_int, Float agg_float,
Double agg_double, DateTime agg_datetime);
};
} }
#endif /* REALM_RESULTS_HPP */ #endif /* REALM_RESULTS_HPP */

View File

@ -236,9 +236,14 @@ static void check_read_write(Realm *realm)
} }
} }
bool Realm::check_thread() const noexcept
{
return m_thread_id == std::this_thread::get_id();
}
void Realm::verify_thread() const void Realm::verify_thread() const
{ {
if (m_thread_id != std::this_thread::get_id()) { if (!check_thread()) {
throw IncorrectThreadException("Realm accessed from incorrect thread."); throw IncorrectThreadException("Realm accessed from incorrect thread.");
} }
} }

View File

@ -19,7 +19,6 @@
#ifndef REALM_REALM_HPP #ifndef REALM_REALM_HPP
#define REALM_REALM_HPP #define REALM_REALM_HPP
#include <map>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <vector> #include <vector>
@ -99,6 +98,7 @@ namespace realm {
bool compact(); bool compact();
std::thread::id thread_id() const { return m_thread_id; } std::thread::id thread_id() const { return m_thread_id; }
bool check_thread() const noexcept;
void verify_thread() const; void verify_thread() const;
// Close this Realm and remove it from the cache. Continuing to use a // Close this Realm and remove it from the cache. Continuing to use a