diff --git a/object_store.cpp b/object_store.cpp index 3e32b98c..3aed85c5 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -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::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()); diff --git a/object_store.hpp b/object_store.hpp index 60671f55..d41ec3fd 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -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); diff --git a/results.cpp b/results.cpp index 1977e5be..215fcc3c 100644 --- a/results.cpp +++ b/results.cpp @@ -1,44 +1,334 @@ -/* Copyright 2015 Realm Inc - All Rights Reserved - * Proprietary and Confidential - */ +//////////////////////////////////////////////////////////////////////////// +// +// 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" -#import + +#include 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, 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) { - 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->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(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 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 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{ + ObjectStore::object_type_for_table_name(m_table->get_name()), + ObjectStore::object_type_for_table_name(row.get_table()->get_name())}; + } + 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 +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(); + 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(); +} + +StringData Results::get_object_type() const noexcept +{ + return ObjectStore::object_type_for_table_name(m_table->get_name()); +} + +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(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); } diff --git a/results.hpp b/results.hpp index fff37124..b239f440 100644 --- a/results.hpp +++ b/results.hpp @@ -1,39 +1,168 @@ -/* Copyright 2015 Realm Inc - All Rights Reserved - * Proprietary and Confidential - */ +//////////////////////////////////////////////////////////////////////////// +// +// 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 -#import "shared_realm.hpp" -#import +#include "shared_realm.hpp" + +#include +#include +#include namespace realm { - struct SortOrder { - std::vector columnIndices; - std::vector ascending; +template class BasicRowExpr; +using RowExpr = BasicRowExpr; +class Mixed; - explicit operator bool() const { - return !columnIndices.empty(); - } +struct SortOrder { + std::vector columnIndices; + std::vector ascending; + + explicit operator bool() const + { + return !columnIndices.empty(); + } +}; + +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 = {}); + + // Results is copyable and moveable + 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 object type which will be returned by get() + StringData get_object_type() const noexcept; + + // 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() + RowExpr get(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() + util::Optional first(); + util::Optional 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 max(size_t column); + util::Optional min(size_t column); + util::Optional average(size_t column); + util::Optional 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 {}; + + // The input index parameter was out of bounds + struct OutOfBoundsIndexException { + size_t requested; + size_t valid_count; }; - static SortOrder s_defaultSort = {{}, {}}; + // The input Row object is not attached + struct DetatchedAccessorException { }; - struct Results { - Results(SharedRealm &r, ObjectSchema &o, Query q, SortOrder s = s_defaultSort); - 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 sort_order; - - void setSort(SortOrder s); + // The input Row object belongs to a different table + struct IncorrectTableException { + StringData expected; + StringData actual; }; + + // The requested aggregate operation is not supported for the column type + struct UnsupportedColumnTypeException { + 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 + util::Optional 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 */ diff --git a/shared_realm.cpp b/shared_realm.cpp index 9c0a4e1e..28bb81b6 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -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."); } } diff --git a/shared_realm.hpp b/shared_realm.hpp index 81b14bf7..0503282a 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -19,7 +19,6 @@ #ifndef REALM_REALM_HPP #define REALM_REALM_HPP -#include #include #include #include @@ -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 - { - public: - enum class Kind - { + class RealmFileException : public std::runtime_error { + public: + 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 @@ -169,32 +167,28 @@ namespace realm { Kind kind() const { return m_kind; } const std::string& path() const { return m_path; } - private: + private: Kind m_kind; std::string m_path; }; - class MismatchedConfigException : public std::runtime_error - { - public: + class MismatchedConfigException : public std::runtime_error { + public: MismatchedConfigException(std::string message) : std::runtime_error(message) {} }; - class InvalidTransactionException : public std::runtime_error - { - public: + class InvalidTransactionException : public std::runtime_error { + public: InvalidTransactionException(std::string message) : std::runtime_error(message) {} }; - class IncorrectThreadException : public std::runtime_error - { - public: - IncorrectThreadException(std::string message) : std::runtime_error(message) {} + class IncorrectThreadException : public std::runtime_error { + public: + IncorrectThreadException() : std::runtime_error("Realm accessed from incorrect thread.") {} }; - class UnitializedRealmException : public std::runtime_error - { - public: + class UnitializedRealmException : public std::runtime_error { + public: UnitializedRealmException(std::string message) : std::runtime_error(message) {} }; }