Merge pull request #168 from realm/al-os-merge

Integrate new Results class
This commit is contained in:
Ari Lazier 2015-11-29 18:58:55 -08:00
commit 7ad2786359
8 changed files with 504 additions and 92 deletions

View File

@ -22,7 +22,9 @@ JSValueRef ResultsGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef
return JSValueMakeNumber(ctx, size);
}
return RJSObjectCreate(ctx, Object(results->realm, results->object_schema, results->get(RJSValidatedPositiveIndex(indexStr))));
return RJSObjectCreate(ctx, Object(results->get_realm(),
results->object_schema,
results->get(RJSValidatedPositiveIndex(indexStr))));
}
catch (std::out_of_range &exp) {
// getters for nonexistent properties in JS should always return undefined
@ -61,7 +63,7 @@ bool ResultsSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef proper
void ResultsPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) {
Results *results = RJSGetInternal<Results *>(object);
char str[32];
for (int i = 0; i < results->table_view.size(); i++) {
for (int i = 0; i < results->size(); i++) {
sprintf(str, "%i", i);
JSStringRef name = JSStringCreateWithUTF8CString(str);
JSPropertyNameAccumulatorAddName(propertyNames, name);
@ -74,7 +76,7 @@ JSValueRef SortByProperty(JSContextRef ctx, JSObjectRef function, JSObjectRef th
Results *results = RJSGetInternal<Results *>(thisObject);
RJSValidateArgumentRange(argumentCount, 1, 2);
std::string propName = RJSValidatedStringForValue(ctx, arguments[0]);
Property *prop = results->object_schema.property_for_name(propName);
const Property *prop = results->object_schema.property_for_name(propName);
if (!prop) {
throw std::runtime_error("Property '" + propName + "' does not exist on object type '" + results->object_schema.name + "'");
}
@ -84,8 +86,7 @@ JSValueRef SortByProperty(JSContextRef ctx, JSObjectRef function, JSObjectRef th
ascending = JSValueToBoolean(ctx, arguments[1]);
}
SortOrder sort = {{prop->table_column}, {ascending}};
results->setSort(sort);
*results = results->sort({{prop->table_column}, {ascending}});
}
catch (std::exception &exp) {
if (jsException) {
@ -101,7 +102,7 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl
if (object_schema == realm->config().schema->end()) {
throw std::runtime_error("Object type '" + className + "' not present in Realm.");
}
return RJSWrapObject<Results *>(ctx, RJSResultsClass(), new Results(realm, *object_schema, table->where()));
return RJSWrapObject<Results *>(ctx, RJSResultsClass(), new Results(realm, *object_schema, *table));
}

View File

@ -7,6 +7,7 @@
#include <string>
#include "shared_realm.hpp"
#include "schema.hpp"
#include "list.hpp"
namespace realm {

View File

@ -30,6 +30,7 @@
using namespace realm;
namespace {
const char * const c_metadataTableName = "metadata";
const char * const c_versionColumnName = "version";
const size_t c_versionColumnIndex = 0;
@ -42,8 +43,8 @@ const size_t c_primaryKeyPropertyNameColumnIndex = 1;
const size_t c_zeroRowIndex = 0;
const std::string c_object_table_prefix = "class_";
const size_t c_object_table_prefix_length = c_object_table_prefix.length();
const char c_object_table_prefix[] = "class_";
}
const uint64_t ObjectStore::NotVersioned = std::numeric_limits<uint64_t>::max();
@ -119,15 +120,15 @@ void ObjectStore::set_primary_key_for_object(Group *group, StringData object_typ
}
}
std::string ObjectStore::object_type_for_table_name(const std::string &table_name) {
if (table_name.size() >= c_object_table_prefix_length && table_name.compare(0, c_object_table_prefix_length, c_object_table_prefix) == 0) {
return table_name.substr(c_object_table_prefix_length, table_name.length() - c_object_table_prefix_length);
StringData ObjectStore::object_type_for_table_name(StringData table_name) {
if (table_name.begins_with(c_object_table_prefix)) {
return table_name.substr(sizeof(c_object_table_prefix) - 1);
}
return std::string();
return StringData();
}
std::string ObjectStore::table_name_for_object_type(const std::string &object_type) {
return c_object_table_prefix + object_type;
std::string ObjectStore::table_name_for_object_type(StringData object_type) {
return std::string(c_object_table_prefix) + object_type.data();
}
TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) {
@ -138,7 +139,7 @@ ConstTableRef ObjectStore::table_for_object_type(const Group *group, StringData
return group->get_table(table_name_for_object_type(object_type));
}
TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const StringData &object_type, bool &created) {
TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created) {
return group->get_or_add_table(table_name_for_object_type(object_type), &created);
}
@ -494,7 +495,7 @@ void ObjectStore::validate_primary_column_uniqueness(const Group *group, Schema
}
}
void ObjectStore::delete_data_for_object(Group *group, const StringData &object_type) {
void ObjectStore::delete_data_for_object(Group *group, StringData object_type) {
TableRef table = table_for_object_type(group, object_type);
if (table) {
group->remove_table(table->get_index_in_group());

View File

@ -19,7 +19,6 @@
#ifndef REALM_OBJECT_STORE_HPP
#define REALM_OBJECT_STORE_HPP
#include "schema.hpp"
#include "object_schema.hpp"
#include "property.hpp"
@ -30,6 +29,7 @@
namespace realm {
class ObjectSchemaValidationException;
class Schema;
class ObjectStore {
public:
@ -68,11 +68,14 @@ namespace realm {
static Schema schema_from_group(const Group *group);
// deletes the table for the given type
static void delete_data_for_object(Group *group, const StringData &object_type);
static void delete_data_for_object(Group *group, StringData object_type);
// indicates if this group contains any objects
static bool is_empty(const Group *group);
static std::string table_name_for_object_type(StringData class_name);
static StringData object_type_for_table_name(StringData table_name);
private:
// set a new schema version
static void set_schema_version(Group *group, uint64_t version);
@ -102,9 +105,7 @@ namespace realm {
// must be in write transaction to set
static void set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key);
static TableRef table_for_object_type_create_if_needed(Group *group, const StringData &object_type, bool &created);
static std::string table_name_for_object_type(const std::string &class_name);
static std::string object_type_for_table_name(const std::string &table_name);
static TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created);
// returns if any indexes were changed
static bool update_indexes(Group *group, Schema &schema);

View File

@ -3,42 +3,315 @@
*/
#include "results.hpp"
#import <stdexcept>
#include <stdexcept>
using namespace realm;
Results::Results(SharedRealm &r, ObjectSchema &o, Query q, SortOrder s) :
realm(r), object_schema(o), backing_query(q), table_view(backing_query.find_all())
#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_query(std::move(q))
, m_table(m_query.get_table().get())
, m_sort(std::move(s))
, m_mode(Mode::Query)
, object_schema(o)
{
setSort(std::move(s));
}
Results::Results(SharedRealm r, const ObjectSchema &o, Table& table)
: m_realm(std::move(r))
, m_table(&table)
, m_mode(Mode::Table)
, object_schema(o)
{
}
void Results::validate_read() const
{
if (m_realm)
m_realm->verify_thread();
if (m_table && !m_table->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");
}
size_t Results::size()
{
verify_attached();
return table_view.size();
}
void Results::setSort(SortOrder s)
{
sort_order = std::make_unique<SortOrder>(std::move(s));
table_view.sort(sort_order->columnIndices, sort_order->ascending);
}
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()) + ".");
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();
}
return table_view.get(row_ndx);
REALM_UNREACHABLE();
}
void Results::verify_attached()
RowExpr Results::get(size_t row_ndx)
{
if (!table_view.is_attached()) {
throw std::runtime_error("Tableview is not attached");
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_table_view.get(row_ndx);
break;
}
table_view.sync_if_needed();
throw OutOfBoundsIndexException{row_ndx, size()};
}
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::make_optional(m_table->front());
case Mode::Query:
update_tableview();
REALM_FALLTHROUGH;
case Mode::TableView:
return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.front());
}
REALM_UNREACHABLE();
}
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::make_optional(m_table->back());
case Mode::Query:
update_tableview();
REALM_FALLTHROUGH;
case Mode::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:
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(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:
if (!m_sort)
return m_query.count(row_ndx, row_ndx + 1) ? m_query.count(0, row_ndx) : not_found;
REALM_FALLTHROUGH;
case Mode::TableView:
update_tableview();
return m_table_view.find_by_source_ndx(row_ndx);
}
REALM_UNREACHABLE();
}
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 OutOfBoundsIndexException{column, m_table->get_column_count()};
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:
this->update_tableview();
if (return_none_for_empty && m_table_view.size() == 0)
return none;
return util::Optional<Mixed>(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<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 UnsupportedColumnTypeException{column, m_table}; });
}
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 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();
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();
}
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, object_schema, get_query(), std::move(sort));
}
Results Results::filter(Query&& q) const
{
return Results(m_realm, object_schema, get_query().and_query(std::move(q)), get_sort());
}
Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) :
column_index(column), column_name(table->get_column_name(column)), column_type(table->get_column_type(column)),
std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns")
{
}

View File

@ -5,35 +5,167 @@
#ifndef REALM_RESULTS_HPP
#define REALM_RESULTS_HPP
#import "shared_realm.hpp"
#import <realm/table_view.hpp>
#include "shared_realm.hpp"
#include <realm/table_view.hpp>
#include <realm/table.hpp>
#include <realm/util/optional.hpp>
namespace realm {
struct SortOrder {
template<typename T> class BasicRowExpr;
using RowExpr = BasicRowExpr<Table>;
class Mixed;
struct SortOrder {
std::vector<size_t> columnIndices;
std::vector<bool> ascending;
explicit operator bool() const {
explicit operator bool() const
{
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, const ObjectSchema &o, Table& table);
Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s = {});
struct Results {
Results(SharedRealm &r, ObjectSchema &o, Query q, SortOrder s = s_defaultSort);
// Results is copyable and moveable
Results(Results const&) = default;
Results(Results&&) = default;
Results& operator=(Results const&) = default;
Results& operator=(Results&&) = default;
// Get the Realm
SharedRealm get_realm() const { return m_realm; }
// Object schema describing the vendored object type
ObjectSchema object_schema;
// 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 object type which will be returned by get()
StringData get_object_type() const noexcept { return object_schema.name; }
// Get the size of this results
// Can be either O(1) or O(N) depending on the state of things
size_t size();
Row get(std::size_t row_ndx);
void verify_attached();
SharedRealm realm;
ObjectSchema &object_schema;
Query backing_query;
TableView table_view;
std::unique_ptr<SortOrder> sort_order;
// Get the row accessor for the given index
// Throws OutOfBoundsIndexException if index >= size()
RowExpr get(size_t index);
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 DetachedAccessorException if row is not attached
// Throws IncorrectTableException 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 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(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 UnsupportedColumnTypeException for sum/average on datetime or non-numeric column
// Throws OutOfBoundsIndexException 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; }
// The Results object has been invalidated (due to the Realm being invalidated)
// All non-noexcept functions can throw this
struct InvalidatedException : public std::runtime_error
{
InvalidatedException() : std::runtime_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) : requested(r), valid_count(c),
std::out_of_range((std::string)"Requested index " + std::to_string(r) +
" greater than max " + std::to_string(c)) {}
const size_t requested;
const size_t valid_count;
};
// The input Row object is not attached
struct DetatchedAccessorException : public std::runtime_error {
DetatchedAccessorException() : std::runtime_error("Atempting to access an invalid object") {}
};
// The input Row object belongs to a different table
struct IncorrectTableException : public std::runtime_error {
IncorrectTableException(StringData e, StringData a, const std::string &error) :
expected(e), actual(a), std::runtime_error(error) {}
const StringData expected;
const StringData actual;
};
// The requested aggregate operation is not supported for the column type
struct UnsupportedColumnTypeException : public std::runtime_error {
size_t column_index;
StringData column_name;
DataType column_type;
UnsupportedColumnTypeException(size_t column, const Table* table);
};
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 */

View File

@ -47,6 +47,8 @@ Realm::Config::Config(const Config& c)
}
}
Realm::Config::Config() = default;
Realm::Config::Config(Config&&) = default;
Realm::Config::~Config() = default;
Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c)
@ -237,7 +239,14 @@ static void check_read_write(Realm *realm)
void Realm::verify_thread() const
{
if (m_thread_id != std::this_thread::get_id()) {
throw IncorrectThreadException("Realm accessed from incorrect thread.");
throw IncorrectThreadException();
}
}
void Realm::verify_in_write() const
{
if (!is_in_transaction()) {
throw InvalidTransactionException("Cannot modify persisted objects outside of a write transaction.");
}
}

View File

@ -19,7 +19,6 @@
#ifndef REALM_REALM_HPP
#define REALM_REALM_HPP
#include <map>
#include <memory>
#include <thread>
#include <vector>
@ -56,8 +55,8 @@ namespace realm {
MigrationFunction migration_function;
Config() = default;
Config(Config&&) = default;
Config();
Config(Config&&);
Config(const Config& c);
~Config();
@ -100,6 +99,7 @@ namespace realm {
std::thread::id thread_id() const { return m_thread_id; }
void verify_thread() const;
void verify_in_write() const;
// Close this Realm and remove it from the cache. Continuing to use a
// Realm after closing it will produce undefined behavior.
@ -145,11 +145,9 @@ namespace realm {
std::mutex m_mutex;
};
class RealmFileException : public std::runtime_error
{
class RealmFileException : public std::runtime_error {
public:
enum class Kind
{
enum class Kind {
/** Thrown for any I/O related exception scenarios when a realm is opened. */
AccessError,
/** Thrown if the user does not have permission to open or create
@ -174,26 +172,22 @@ namespace realm {
std::string m_path;
};
class MismatchedConfigException : public std::runtime_error
{
class MismatchedConfigException : public std::runtime_error {
public:
MismatchedConfigException(std::string message) : std::runtime_error(message) {}
};
class InvalidTransactionException : public std::runtime_error
{
class InvalidTransactionException : public std::runtime_error {
public:
InvalidTransactionException(std::string message) : std::runtime_error(message) {}
};
class IncorrectThreadException : public std::runtime_error
{
class IncorrectThreadException : public std::runtime_error {
public:
IncorrectThreadException(std::string message) : std::runtime_error(message) {}
IncorrectThreadException() : std::runtime_error("Realm accessed from incorrect thread.") {}
};
class UnitializedRealmException : public std::runtime_error
{
class UnitializedRealmException : public std::runtime_error {
public:
UnitializedRealmException(std::string message) : std::runtime_error(message) {}
};