added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / results.cpp
diff --git a/iOS/Pods/Realm/Realm/ObjectStore/src/results.cpp b/iOS/Pods/Realm/Realm/ObjectStore/src/results.cpp
new file mode 100644 (file)
index 0000000..aac1159
--- /dev/null
@@ -0,0 +1,777 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// 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"
+
+#include "impl/realm_coordinator.hpp"
+#include "impl/results_notifier.hpp"
+#include "object_schema.hpp"
+#include "object_store.hpp"
+#include "schema.hpp"
+
+#include <stdexcept>
+
+namespace realm {
+
+Results::Results() = default;
+Results::~Results() = default;
+
+Results::Results(SharedRealm r, Query q, DescriptorOrdering o)
+: m_realm(std::move(r))
+, m_query(std::move(q))
+, m_table(m_query.get_table())
+, m_descriptor_ordering(std::move(o))
+, m_mode(Mode::Query)
+{
+}
+
+Results::Results(SharedRealm r, Table& table)
+: m_realm(std::move(r))
+, m_mode(Mode::Table)
+{
+    m_table.reset(&table);
+}
+
+Results::Results(SharedRealm r, LinkViewRef lv, util::Optional<Query> q, SortDescriptor s)
+: m_realm(std::move(r))
+, m_link_view(lv)
+, m_mode(Mode::LinkView)
+{
+    m_table.reset(&lv->get_target_table());
+    if (q) {
+        m_query = std::move(*q);
+        m_mode = Mode::Query;
+    }
+    m_descriptor_ordering.append_sort(std::move(s));
+}
+
+Results::Results(SharedRealm r, TableView tv, DescriptorOrdering o)
+: m_realm(std::move(r))
+, m_table_view(std::move(tv))
+, m_descriptor_ordering(std::move(o))
+, m_mode(Mode::TableView)
+{
+    m_table.reset(&m_table_view.get_parent());
+}
+
+Results::Results(const Results&) = default;
+Results& Results::operator=(const Results&) = default;
+
+Results::Results(Results&& other)
+: m_realm(std::move(other.m_realm))
+, m_object_schema(std::move(other.m_object_schema))
+, m_query(std::move(other.m_query))
+, m_table_view(std::move(other.m_table_view))
+, m_link_view(std::move(other.m_link_view))
+, m_table(std::move(other.m_table))
+, m_descriptor_ordering(std::move(other.m_descriptor_ordering))
+, m_notifier(std::move(other.m_notifier))
+, m_mode(other.m_mode)
+, m_update_policy(other.m_update_policy)
+, m_has_used_table_view(other.m_has_used_table_view)
+, m_wants_background_updates(other.m_wants_background_updates)
+{
+    if (m_notifier) {
+        m_notifier->target_results_moved(other, *this);
+    }
+}
+
+Results& Results::operator=(Results&& other)
+{
+    this->~Results();
+    new (this) Results(std::move(other));
+    return *this;
+}
+
+bool Results::is_valid() const
+{
+    if (m_realm)
+        m_realm->verify_thread();
+
+    if (m_table && !m_table->is_attached())
+        return false;
+
+    return true;
+}
+
+void Results::validate_read() const
+{
+    // is_valid ensures that we're on the correct thread.
+    if (!is_valid())
+        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()
+{
+    validate_read();
+    switch (m_mode) {
+        case Mode::Empty:    return 0;
+        case Mode::Table:    return m_table->size();
+        case Mode::LinkView: return m_link_view->size();
+        case Mode::Query:
+            m_query.sync_view_if_needed();
+            if (!m_descriptor_ordering.will_apply_distinct())
+                return m_query.count();
+            REALM_FALLTHROUGH;
+        case Mode::TableView:
+            evaluate_query_if_needed();
+            return m_table_view.size();
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+const ObjectSchema& Results::get_object_schema() const
+{
+    validate_read();
+
+    if (!m_object_schema) {
+        REALM_ASSERT(m_realm);
+        auto it = m_realm->schema().find(get_object_type());
+        REALM_ASSERT(it != m_realm->schema().end());
+        m_object_schema = &*it;
+    }
+
+    return *m_object_schema;
+}
+
+
+StringData Results::get_object_type() const noexcept
+{
+    if (!m_table) {
+        return StringData();
+    }
+
+    return ObjectStore::object_type_for_table_name(m_table->get_name());
+}
+
+namespace {
+template<typename T>
+auto get(Table& table, size_t row)
+{
+    return table.get<T>(0, row);
+}
+
+template<>
+auto get<RowExpr>(Table& table, size_t row)
+{
+    return table.get(row);
+}
+}
+
+template<typename T>
+util::Optional<T> Results::try_get(size_t row_ndx)
+{
+    validate_read();
+    switch (m_mode) {
+        case Mode::Empty: break;
+        case Mode::Table:
+            if (row_ndx < m_table->size())
+                return realm::get<T>(*m_table, row_ndx);
+            break;
+        case Mode::LinkView:
+            if (update_linkview()) {
+                if (row_ndx < m_link_view->size())
+                    return realm::get<T>(*m_table, m_link_view->get(row_ndx).get_index());
+                break;
+            }
+            REALM_FALLTHROUGH;
+        case Mode::Query:
+        case Mode::TableView:
+            evaluate_query_if_needed();
+            if (row_ndx >= m_table_view.size())
+                break;
+            if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_row_attached(row_ndx))
+                return T{};
+            return realm::get<T>(*m_table, m_table_view.get(row_ndx).get_index());
+    }
+    return util::none;
+}
+
+template<typename T>
+T Results::get(size_t row_ndx)
+{
+    if (auto row = try_get<T>(row_ndx))
+        return *row;
+    throw OutOfBoundsIndexException{row_ndx, size()};
+}
+
+template<typename T>
+util::Optional<T> Results::first()
+{
+    return try_get<T>(0);
+}
+
+template<typename T>
+util::Optional<T> Results::last()
+{
+    validate_read();
+    if (m_mode == Mode::Query)
+        evaluate_query_if_needed(); // avoid running the query twice (for size() and for get())
+    return try_get<T>(size() - 1);
+}
+
+bool Results::update_linkview()
+{
+    REALM_ASSERT(m_update_policy == UpdatePolicy::Auto);
+
+    if (!m_descriptor_ordering.is_empty()) {
+        m_query = get_query();
+        m_mode = Mode::Query;
+        evaluate_query_if_needed();
+        return false;
+    }
+    return true;
+}
+
+void Results::evaluate_query_if_needed(bool wants_notifications)
+{
+    if (m_update_policy == UpdatePolicy::Never) {
+        REALM_ASSERT(m_mode == Mode::TableView);
+        return;
+    }
+
+    switch (m_mode) {
+        case Mode::Empty:
+        case Mode::Table:
+        case Mode::LinkView:
+            return;
+        case Mode::Query:
+            m_query.sync_view_if_needed();
+            m_table_view = m_query.find_all();
+            if (!m_descriptor_ordering.is_empty()) {
+                m_table_view.apply_descriptor_ordering(m_descriptor_ordering);
+            }
+            m_mode = Mode::TableView;
+            REALM_FALLTHROUGH;
+        case Mode::TableView:
+            if (wants_notifications && !m_notifier && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) {
+                m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
+                _impl::RealmCoordinator::register_notifier(m_notifier);
+            }
+            m_has_used_table_view = true;
+            m_table_view.sync_if_needed();
+            break;
+    }
+}
+
+template<>
+size_t Results::index_of(RowExpr 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()),
+            "Attempting to get the index of a Row of the wrong type"
+        );
+    }
+
+    switch (m_mode) {
+        case Mode::Empty:
+            return not_found;
+        case Mode::Table:
+            return row.get_index();
+        case Mode::LinkView:
+            if (update_linkview())
+                return m_link_view->find(row.get_index());
+            REALM_FALLTHROUGH;
+        case Mode::Query:
+        case Mode::TableView:
+            evaluate_query_if_needed();
+            return m_table_view.find_by_source_ndx(row.get_index());
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+template<typename T>
+size_t Results::index_of(T const& value)
+{
+    validate_read();
+    switch (m_mode) {
+        case Mode::Empty:
+            return not_found;
+        case Mode::Table:
+            return m_table->find_first(0, value);
+        case Mode::LinkView:
+            REALM_UNREACHABLE();
+        case Mode::Query:
+        case Mode::TableView:
+            evaluate_query_if_needed();
+            return m_table_view.find_first(0, value);
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+size_t Results::index_of(Query&& q)
+{
+    if (m_descriptor_ordering.will_apply_sort()) {
+        auto first = filter(std::move(q)).first();
+        return first ? index_of(*first) : not_found;
+    }
+
+    auto query = get_query().and_query(std::move(q));
+    query.sync_view_if_needed();
+    size_t row = query.find();
+    return row != not_found ? index_of(m_table->get(row)) : row;
+}
+
+void Results::prepare_for_aggregate(size_t column, const char* name)
+{
+    if (column > m_table->get_column_count())
+        throw OutOfBoundsIndexException{column, m_table->get_column_count()};
+    switch (m_mode) {
+        case Mode::Empty: break;
+        case Mode::Table: break;
+        case Mode::LinkView:
+            m_query = this->get_query();
+            m_mode = Mode::Query;
+            REALM_FALLTHROUGH;
+        case Mode::Query:
+        case Mode::TableView:
+            evaluate_query_if_needed();
+            break;
+        default:
+            REALM_COMPILER_HINT_UNREACHABLE();
+    }
+    switch (m_table->get_column_type(column)) {
+        case type_Timestamp: case type_Double: case type_Float: case type_Int: break;
+        default: throw UnsupportedColumnTypeException{column, m_table.get(), name};
+    }
+}
+
+template<typename Int, typename Float, typename Double, typename Timestamp>
+util::Optional<Mixed> Results::aggregate(size_t column,
+                                         const char* name,
+                                         Int agg_int, Float agg_float,
+                                         Double agg_double, Timestamp agg_timestamp)
+{
+    validate_read();
+    if (!m_table)
+        return none;
+    prepare_for_aggregate(column, name);
+
+    auto do_agg = [&](auto const& getter) {
+        return Mixed(m_mode == Mode::Table ? getter(*m_table) : getter(m_table_view));
+    };
+    switch (m_table->get_column_type(column)) {
+        case type_Timestamp: return do_agg(agg_timestamp);
+        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: REALM_COMPILER_HINT_UNREACHABLE();
+    }
+}
+
+util::Optional<Mixed> Results::max(size_t column)
+{
+    size_t return_ndx = npos;
+    auto results = aggregate(column, "max",
+                             [&](auto const& table) { return table.maximum_int(column, &return_ndx); },
+                             [&](auto const& table) { return table.maximum_float(column, &return_ndx); },
+                             [&](auto const& table) { return table.maximum_double(column, &return_ndx); },
+                             [&](auto const& table) { return table.maximum_timestamp(column, &return_ndx); });
+    return return_ndx == npos ? none : results;
+}
+
+util::Optional<Mixed> Results::min(size_t column)
+{
+    size_t return_ndx = npos;
+    auto results = aggregate(column, "min",
+                             [&](auto const& table) { return table.minimum_int(column, &return_ndx); },
+                             [&](auto const& table) { return table.minimum_float(column, &return_ndx); },
+                             [&](auto const& table) { return table.minimum_double(column, &return_ndx); },
+                             [&](auto const& table) { return table.minimum_timestamp(column, &return_ndx); });
+    return return_ndx == npos ? none : results;
+}
+
+util::Optional<Mixed> Results::sum(size_t column)
+{
+    return aggregate(column, "sum",
+                     [=](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&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "sum"}; });
+}
+
+util::Optional<double> Results::average(size_t column)
+{
+    size_t value_count = 0;
+    auto results = aggregate(column, "average",
+                             [&](auto const& table) { return table.average_int(column, &value_count); },
+                             [&](auto const& table) { return table.average_float(column, &value_count); },
+                             [&](auto const& table) { return table.average_double(column, &value_count); },
+                             [&](auto const&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "average"}; });
+    return value_count == 0 ? none : util::make_optional(results->get_double());
+}
+
+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();
+            evaluate_query_if_needed();
+
+            switch (m_update_policy) {
+                case UpdatePolicy::Auto:
+                    m_table_view.clear(RemoveMode::unordered);
+                    break;
+                case UpdatePolicy::Never: {
+                    // Copy the TableView because a frozen Results shouldn't let its size() change.
+                    TableView copy(m_table_view);
+                    copy.clear(RemoveMode::unordered);
+                    break;
+                }
+            }
+            break;
+        case Mode::LinkView:
+            validate_write();
+            m_link_view->remove_all_target_rows();
+            break;
+    }
+}
+
+PropertyType Results::get_type() const
+{
+    validate_read();
+    switch (m_mode) {
+        case Mode::Empty:
+        case Mode::LinkView:
+            return PropertyType::Object;
+        case Mode::Query:
+        case Mode::TableView:
+        case Mode::Table:
+            if (m_table->get_index_in_group() != npos)
+                return PropertyType::Object;
+            return ObjectSchema::from_core_type(*m_table->get_descriptor(), 0);
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+Query Results::get_query() const
+{
+    validate_read();
+    switch (m_mode) {
+        case Mode::Empty:
+        case Mode::Query:
+            return m_query;
+        case Mode::TableView: {
+            // A TableView has an associated Query if it was produced by Query::find_all. This is indicated
+            // by TableView::get_query returning a Query with a non-null table.
+            Query query = m_table_view.get_query();
+            if (query.get_table()) {
+                return query;
+            }
+
+            // The TableView has no associated query so create one with no conditions that is restricted
+            // to the rows in the TableView.
+            if (m_update_policy == UpdatePolicy::Auto) {
+                m_table_view.sync_if_needed();
+            }
+            return Query(*m_table, std::unique_ptr<TableViewBase>(new TableView(m_table_view)));
+        }
+        case Mode::LinkView:
+            return m_table->where(m_link_view);
+        case Mode::Table:
+            return m_table->where();
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+TableView Results::get_tableview()
+{
+    validate_read();
+    switch (m_mode) {
+        case Mode::Empty:
+            return {};
+        case Mode::LinkView:
+            if (update_linkview())
+                return m_table->where(m_link_view).find_all();
+            REALM_FALLTHROUGH;
+        case Mode::Query:
+        case Mode::TableView:
+            evaluate_query_if_needed();
+            return m_table_view;
+        case Mode::Table:
+            return m_table->where().find_all();
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+static std::vector<size_t> parse_keypath(StringData keypath, Schema const& schema, const ObjectSchema *object_schema)
+{
+    auto check = [&](bool condition, const char* fmt, auto... args) {
+        if (!condition) {
+            throw std::invalid_argument(util::format("Cannot sort on key path '%1': %2.",
+                                                     keypath, util::format(fmt, args...)));
+        }
+    };
+    auto is_sortable_type = [](PropertyType type) {
+        return !is_array(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data;
+    };
+
+    const char* begin = keypath.data();
+    const char* end = keypath.data() + keypath.size();
+    check(begin != end, "missing property name");
+
+    std::vector<size_t> indices;
+    while (begin != end) {
+        auto sep = std::find(begin, end, '.');
+        check(sep != begin && sep + 1 != end, "missing property name");
+        StringData key(begin, sep - begin);
+        begin = sep + (sep != end);
+
+        auto prop = object_schema->property_for_name(key);
+        check(prop, "property '%1.%2' does not exist", object_schema->name, key);
+        check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'",
+              object_schema->name, key, string_for_property_type(prop->type));
+        if (prop->type == PropertyType::Object)
+            check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path",
+                  object_schema->name, key);
+        else
+            check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path",
+                  object_schema->name, key, prop->type_string());
+
+        indices.push_back(prop->table_column);
+        if (prop->type == PropertyType::Object)
+            object_schema = &*schema.find(prop->object_type);
+    }
+    return indices;
+}
+
+Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const
+{
+    if (keypaths.empty())
+        return *this;
+    if (get_type() != PropertyType::Object) {
+        if (keypaths.size() != 1)
+            throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path",
+                                                     string_for_property_type(get_type())));
+        if (keypaths[0].first != "self")
+            throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
+                                                     keypaths[0].first, string_for_property_type(get_type())));
+        return sort({*m_table, {{0}}, {keypaths[0].second}});
+    }
+
+    std::vector<std::vector<size_t>> column_indices;
+    std::vector<bool> ascending;
+    column_indices.reserve(keypaths.size());
+    ascending.reserve(keypaths.size());
+
+    for (auto& keypath : keypaths) {
+        column_indices.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
+        ascending.push_back(keypath.second);
+    }
+    return sort({*m_table, std::move(column_indices), std::move(ascending)});
+}
+
+Results Results::sort(SortDescriptor&& sort) const
+{
+    if (m_mode == Mode::LinkView)
+        return Results(m_realm, m_link_view, util::none, std::move(sort));
+    DescriptorOrdering new_order = m_descriptor_ordering;
+    new_order.append_sort(std::move(sort));
+    return Results(m_realm, get_query(), std::move(new_order));
+}
+
+Results Results::filter(Query&& q) const
+{
+    return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
+}
+
+Results Results::distinct(DistinctDescriptor&& uniqueness) const
+{
+    DescriptorOrdering new_order = m_descriptor_ordering;
+    new_order.append_distinct(std::move(uniqueness));
+    return Results(m_realm, get_query(), std::move(new_order));
+}
+
+Results Results::distinct(std::vector<std::string> const& keypaths) const
+{
+    if (keypaths.empty())
+        return *this;
+    if (get_type() != PropertyType::Object) {
+        if (keypaths.size() != 1)
+            throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path",
+                                                     string_for_property_type(get_type())));
+        if (keypaths[0] != "self")
+            throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
+                                                     keypaths[0], string_for_property_type(get_type())));
+        return distinct({*m_table, {{0}}});
+    }
+
+    std::vector<std::vector<size_t>> column_indices;
+    column_indices.reserve(keypaths.size());
+    for (auto& keypath : keypaths)
+        column_indices.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
+    return distinct({*m_table, std::move(column_indices)});
+}
+
+Results Results::snapshot() const &
+{
+    validate_read();
+    return Results(*this).snapshot();
+}
+
+Results Results::snapshot() &&
+{
+    validate_read();
+
+    switch (m_mode) {
+        case Mode::Empty:
+            return Results();
+
+        case Mode::Table:
+        case Mode::LinkView:
+            m_query = get_query();
+            m_mode = Mode::Query;
+
+            REALM_FALLTHROUGH;
+        case Mode::Query:
+        case Mode::TableView:
+            evaluate_query_if_needed(false);
+            m_notifier.reset();
+            m_update_policy = UpdatePolicy::Never;
+            return std::move(*this);
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+void Results::prepare_async()
+{
+    if (m_notifier) {
+        return;
+    }
+    if (m_realm->config().immutable()) {
+        throw InvalidTransactionException("Cannot create asynchronous query for immutable Realms");
+    }
+    if (m_realm->is_in_transaction()) {
+        throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction");
+    }
+    if (m_update_policy == UpdatePolicy::Never) {
+        throw std::logic_error("Cannot create asynchronous query for snapshotted Results.");
+    }
+
+    m_wants_background_updates = true;
+    m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
+    _impl::RealmCoordinator::register_notifier(m_notifier);
+}
+
+NotificationToken Results::add_notification_callback(CollectionChangeCallback cb) &
+{
+    prepare_async();
+    return {m_notifier, m_notifier->add_callback(std::move(cb))};
+}
+
+bool Results::is_in_table_order() const
+{
+    switch (m_mode) {
+        case Mode::Empty:
+        case Mode::Table:
+            return true;
+        case Mode::LinkView:
+            return false;
+        case Mode::Query:
+            return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
+        case Mode::TableView:
+            return m_table_view.is_in_table_order();
+    }
+    REALM_COMPILER_HINT_UNREACHABLE();
+}
+
+void Results::Internal::set_table_view(Results& results, TableView &&tv)
+{
+    REALM_ASSERT(results.m_update_policy != UpdatePolicy::Never);
+    // If the previous TableView was never actually used, then stop generating
+    // new ones until the user actually uses the Results object again
+    if (results.m_mode == Mode::TableView) {
+        results.m_wants_background_updates = results.m_has_used_table_view;
+    }
+
+    results.m_table_view = std::move(tv);
+    results.m_mode = Mode::TableView;
+    results.m_has_used_table_view = false;
+    REALM_ASSERT(results.m_table_view.is_in_sync());
+    REALM_ASSERT(results.m_table_view.is_attached());
+}
+
+#define REALM_RESULTS_TYPE(T) \
+    template T Results::get<T>(size_t); \
+    template util::Optional<T> Results::first<T>(); \
+    template util::Optional<T> Results::last<T>(); \
+    template size_t Results::index_of<T>(T const&);
+
+template RowExpr Results::get<RowExpr>(size_t);
+template util::Optional<RowExpr> Results::first<RowExpr>();
+template util::Optional<RowExpr> Results::last<RowExpr>();
+
+REALM_RESULTS_TYPE(bool)
+REALM_RESULTS_TYPE(int64_t)
+REALM_RESULTS_TYPE(float)
+REALM_RESULTS_TYPE(double)
+REALM_RESULTS_TYPE(StringData)
+REALM_RESULTS_TYPE(BinaryData)
+REALM_RESULTS_TYPE(Timestamp)
+REALM_RESULTS_TYPE(util::Optional<bool>)
+REALM_RESULTS_TYPE(util::Optional<int64_t>)
+REALM_RESULTS_TYPE(util::Optional<float>)
+REALM_RESULTS_TYPE(util::Optional<double>)
+
+#undef REALM_RESULTS_TYPE
+
+Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c)
+: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c - 1))
+, requested(r), valid_count(c) {}
+
+static std::string unsupported_operation_msg(size_t column, const Table* table, const char* operation)
+{
+    const char* column_type = string_for_property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column));
+    if (table->is_group_level())
+        return util::format("Cannot %1 property '%2': operation not supported for '%3' properties",
+                            operation, table->get_column_name(column), column_type);
+    return util::format("Cannot %1 '%2' array: operation not supported",
+                        operation, column_type);
+}
+
+Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation)
+: std::logic_error(unsupported_operation_msg(column, table, operation))
+, column_index(column)
+, column_name(table->get_column_name(column))
+, property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column))
+{
+}
+
+} // namespace realm