added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / results.cpp
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2015 Realm Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #include "results.hpp"
20
21 #include "impl/realm_coordinator.hpp"
22 #include "impl/results_notifier.hpp"
23 #include "object_schema.hpp"
24 #include "object_store.hpp"
25 #include "schema.hpp"
26
27 #include <stdexcept>
28
29 namespace realm {
30
31 Results::Results() = default;
32 Results::~Results() = default;
33
34 Results::Results(SharedRealm r, Query q, DescriptorOrdering o)
35 : m_realm(std::move(r))
36 , m_query(std::move(q))
37 , m_table(m_query.get_table())
38 , m_descriptor_ordering(std::move(o))
39 , m_mode(Mode::Query)
40 {
41 }
42
43 Results::Results(SharedRealm r, Table& table)
44 : m_realm(std::move(r))
45 , m_mode(Mode::Table)
46 {
47     m_table.reset(&table);
48 }
49
50 Results::Results(SharedRealm r, LinkViewRef lv, util::Optional<Query> q, SortDescriptor s)
51 : m_realm(std::move(r))
52 , m_link_view(lv)
53 , m_mode(Mode::LinkView)
54 {
55     m_table.reset(&lv->get_target_table());
56     if (q) {
57         m_query = std::move(*q);
58         m_mode = Mode::Query;
59     }
60     m_descriptor_ordering.append_sort(std::move(s));
61 }
62
63 Results::Results(SharedRealm r, TableView tv, DescriptorOrdering o)
64 : m_realm(std::move(r))
65 , m_table_view(std::move(tv))
66 , m_descriptor_ordering(std::move(o))
67 , m_mode(Mode::TableView)
68 {
69     m_table.reset(&m_table_view.get_parent());
70 }
71
72 Results::Results(const Results&) = default;
73 Results& Results::operator=(const Results&) = default;
74
75 Results::Results(Results&& other)
76 : m_realm(std::move(other.m_realm))
77 , m_object_schema(std::move(other.m_object_schema))
78 , m_query(std::move(other.m_query))
79 , m_table_view(std::move(other.m_table_view))
80 , m_link_view(std::move(other.m_link_view))
81 , m_table(std::move(other.m_table))
82 , m_descriptor_ordering(std::move(other.m_descriptor_ordering))
83 , m_notifier(std::move(other.m_notifier))
84 , m_mode(other.m_mode)
85 , m_update_policy(other.m_update_policy)
86 , m_has_used_table_view(other.m_has_used_table_view)
87 , m_wants_background_updates(other.m_wants_background_updates)
88 {
89     if (m_notifier) {
90         m_notifier->target_results_moved(other, *this);
91     }
92 }
93
94 Results& Results::operator=(Results&& other)
95 {
96     this->~Results();
97     new (this) Results(std::move(other));
98     return *this;
99 }
100
101 bool Results::is_valid() const
102 {
103     if (m_realm)
104         m_realm->verify_thread();
105
106     if (m_table && !m_table->is_attached())
107         return false;
108
109     return true;
110 }
111
112 void Results::validate_read() const
113 {
114     // is_valid ensures that we're on the correct thread.
115     if (!is_valid())
116         throw InvalidatedException();
117 }
118
119 void Results::validate_write() const
120 {
121     validate_read();
122     if (!m_realm || !m_realm->is_in_transaction())
123         throw InvalidTransactionException("Must be in a write transaction");
124 }
125
126 size_t Results::size()
127 {
128     validate_read();
129     switch (m_mode) {
130         case Mode::Empty:    return 0;
131         case Mode::Table:    return m_table->size();
132         case Mode::LinkView: return m_link_view->size();
133         case Mode::Query:
134             m_query.sync_view_if_needed();
135             if (!m_descriptor_ordering.will_apply_distinct())
136                 return m_query.count();
137             REALM_FALLTHROUGH;
138         case Mode::TableView:
139             evaluate_query_if_needed();
140             return m_table_view.size();
141     }
142     REALM_COMPILER_HINT_UNREACHABLE();
143 }
144
145 const ObjectSchema& Results::get_object_schema() const
146 {
147     validate_read();
148
149     if (!m_object_schema) {
150         REALM_ASSERT(m_realm);
151         auto it = m_realm->schema().find(get_object_type());
152         REALM_ASSERT(it != m_realm->schema().end());
153         m_object_schema = &*it;
154     }
155
156     return *m_object_schema;
157 }
158
159
160 StringData Results::get_object_type() const noexcept
161 {
162     if (!m_table) {
163         return StringData();
164     }
165
166     return ObjectStore::object_type_for_table_name(m_table->get_name());
167 }
168
169 namespace {
170 template<typename T>
171 auto get(Table& table, size_t row)
172 {
173     return table.get<T>(0, row);
174 }
175
176 template<>
177 auto get<RowExpr>(Table& table, size_t row)
178 {
179     return table.get(row);
180 }
181 }
182
183 template<typename T>
184 util::Optional<T> Results::try_get(size_t row_ndx)
185 {
186     validate_read();
187     switch (m_mode) {
188         case Mode::Empty: break;
189         case Mode::Table:
190             if (row_ndx < m_table->size())
191                 return realm::get<T>(*m_table, row_ndx);
192             break;
193         case Mode::LinkView:
194             if (update_linkview()) {
195                 if (row_ndx < m_link_view->size())
196                     return realm::get<T>(*m_table, m_link_view->get(row_ndx).get_index());
197                 break;
198             }
199             REALM_FALLTHROUGH;
200         case Mode::Query:
201         case Mode::TableView:
202             evaluate_query_if_needed();
203             if (row_ndx >= m_table_view.size())
204                 break;
205             if (m_update_policy == UpdatePolicy::Never && !m_table_view.is_row_attached(row_ndx))
206                 return T{};
207             return realm::get<T>(*m_table, m_table_view.get(row_ndx).get_index());
208     }
209     return util::none;
210 }
211
212 template<typename T>
213 T Results::get(size_t row_ndx)
214 {
215     if (auto row = try_get<T>(row_ndx))
216         return *row;
217     throw OutOfBoundsIndexException{row_ndx, size()};
218 }
219
220 template<typename T>
221 util::Optional<T> Results::first()
222 {
223     return try_get<T>(0);
224 }
225
226 template<typename T>
227 util::Optional<T> Results::last()
228 {
229     validate_read();
230     if (m_mode == Mode::Query)
231         evaluate_query_if_needed(); // avoid running the query twice (for size() and for get())
232     return try_get<T>(size() - 1);
233 }
234
235 bool Results::update_linkview()
236 {
237     REALM_ASSERT(m_update_policy == UpdatePolicy::Auto);
238
239     if (!m_descriptor_ordering.is_empty()) {
240         m_query = get_query();
241         m_mode = Mode::Query;
242         evaluate_query_if_needed();
243         return false;
244     }
245     return true;
246 }
247
248 void Results::evaluate_query_if_needed(bool wants_notifications)
249 {
250     if (m_update_policy == UpdatePolicy::Never) {
251         REALM_ASSERT(m_mode == Mode::TableView);
252         return;
253     }
254
255     switch (m_mode) {
256         case Mode::Empty:
257         case Mode::Table:
258         case Mode::LinkView:
259             return;
260         case Mode::Query:
261             m_query.sync_view_if_needed();
262             m_table_view = m_query.find_all();
263             if (!m_descriptor_ordering.is_empty()) {
264                 m_table_view.apply_descriptor_ordering(m_descriptor_ordering);
265             }
266             m_mode = Mode::TableView;
267             REALM_FALLTHROUGH;
268         case Mode::TableView:
269             if (wants_notifications && !m_notifier && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) {
270                 m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
271                 _impl::RealmCoordinator::register_notifier(m_notifier);
272             }
273             m_has_used_table_view = true;
274             m_table_view.sync_if_needed();
275             break;
276     }
277 }
278
279 template<>
280 size_t Results::index_of(RowExpr const& row)
281 {
282     validate_read();
283     if (!row) {
284         throw DetatchedAccessorException{};
285     }
286     if (m_table && row.get_table() != m_table) {
287         throw IncorrectTableException(
288             ObjectStore::object_type_for_table_name(m_table->get_name()),
289             ObjectStore::object_type_for_table_name(row.get_table()->get_name()),
290             "Attempting to get the index of a Row of the wrong type"
291         );
292     }
293
294     switch (m_mode) {
295         case Mode::Empty:
296             return not_found;
297         case Mode::Table:
298             return row.get_index();
299         case Mode::LinkView:
300             if (update_linkview())
301                 return m_link_view->find(row.get_index());
302             REALM_FALLTHROUGH;
303         case Mode::Query:
304         case Mode::TableView:
305             evaluate_query_if_needed();
306             return m_table_view.find_by_source_ndx(row.get_index());
307     }
308     REALM_COMPILER_HINT_UNREACHABLE();
309 }
310
311 template<typename T>
312 size_t Results::index_of(T const& value)
313 {
314     validate_read();
315     switch (m_mode) {
316         case Mode::Empty:
317             return not_found;
318         case Mode::Table:
319             return m_table->find_first(0, value);
320         case Mode::LinkView:
321             REALM_UNREACHABLE();
322         case Mode::Query:
323         case Mode::TableView:
324             evaluate_query_if_needed();
325             return m_table_view.find_first(0, value);
326     }
327     REALM_COMPILER_HINT_UNREACHABLE();
328 }
329
330 size_t Results::index_of(Query&& q)
331 {
332     if (m_descriptor_ordering.will_apply_sort()) {
333         auto first = filter(std::move(q)).first();
334         return first ? index_of(*first) : not_found;
335     }
336
337     auto query = get_query().and_query(std::move(q));
338     query.sync_view_if_needed();
339     size_t row = query.find();
340     return row != not_found ? index_of(m_table->get(row)) : row;
341 }
342
343 void Results::prepare_for_aggregate(size_t column, const char* name)
344 {
345     if (column > m_table->get_column_count())
346         throw OutOfBoundsIndexException{column, m_table->get_column_count()};
347     switch (m_mode) {
348         case Mode::Empty: break;
349         case Mode::Table: break;
350         case Mode::LinkView:
351             m_query = this->get_query();
352             m_mode = Mode::Query;
353             REALM_FALLTHROUGH;
354         case Mode::Query:
355         case Mode::TableView:
356             evaluate_query_if_needed();
357             break;
358         default:
359             REALM_COMPILER_HINT_UNREACHABLE();
360     }
361     switch (m_table->get_column_type(column)) {
362         case type_Timestamp: case type_Double: case type_Float: case type_Int: break;
363         default: throw UnsupportedColumnTypeException{column, m_table.get(), name};
364     }
365 }
366
367 template<typename Int, typename Float, typename Double, typename Timestamp>
368 util::Optional<Mixed> Results::aggregate(size_t column,
369                                          const char* name,
370                                          Int agg_int, Float agg_float,
371                                          Double agg_double, Timestamp agg_timestamp)
372 {
373     validate_read();
374     if (!m_table)
375         return none;
376     prepare_for_aggregate(column, name);
377
378     auto do_agg = [&](auto const& getter) {
379         return Mixed(m_mode == Mode::Table ? getter(*m_table) : getter(m_table_view));
380     };
381     switch (m_table->get_column_type(column)) {
382         case type_Timestamp: return do_agg(agg_timestamp);
383         case type_Double:    return do_agg(agg_double);
384         case type_Float:     return do_agg(agg_float);
385         case type_Int:       return do_agg(agg_int);
386         default: REALM_COMPILER_HINT_UNREACHABLE();
387     }
388 }
389
390 util::Optional<Mixed> Results::max(size_t column)
391 {
392     size_t return_ndx = npos;
393     auto results = aggregate(column, "max",
394                              [&](auto const& table) { return table.maximum_int(column, &return_ndx); },
395                              [&](auto const& table) { return table.maximum_float(column, &return_ndx); },
396                              [&](auto const& table) { return table.maximum_double(column, &return_ndx); },
397                              [&](auto const& table) { return table.maximum_timestamp(column, &return_ndx); });
398     return return_ndx == npos ? none : results;
399 }
400
401 util::Optional<Mixed> Results::min(size_t column)
402 {
403     size_t return_ndx = npos;
404     auto results = aggregate(column, "min",
405                              [&](auto const& table) { return table.minimum_int(column, &return_ndx); },
406                              [&](auto const& table) { return table.minimum_float(column, &return_ndx); },
407                              [&](auto const& table) { return table.minimum_double(column, &return_ndx); },
408                              [&](auto const& table) { return table.minimum_timestamp(column, &return_ndx); });
409     return return_ndx == npos ? none : results;
410 }
411
412 util::Optional<Mixed> Results::sum(size_t column)
413 {
414     return aggregate(column, "sum",
415                      [=](auto const& table) { return table.sum_int(column); },
416                      [=](auto const& table) { return table.sum_float(column); },
417                      [=](auto const& table) { return table.sum_double(column); },
418                      [=](auto const&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "sum"}; });
419 }
420
421 util::Optional<double> Results::average(size_t column)
422 {
423     size_t value_count = 0;
424     auto results = aggregate(column, "average",
425                              [&](auto const& table) { return table.average_int(column, &value_count); },
426                              [&](auto const& table) { return table.average_float(column, &value_count); },
427                              [&](auto const& table) { return table.average_double(column, &value_count); },
428                              [&](auto const&) -> Timestamp { throw UnsupportedColumnTypeException{column, m_table.get(), "average"}; });
429     return value_count == 0 ? none : util::make_optional(results->get_double());
430 }
431
432 void Results::clear()
433 {
434     switch (m_mode) {
435         case Mode::Empty:
436             return;
437         case Mode::Table:
438             validate_write();
439             m_table->clear();
440             break;
441         case Mode::Query:
442             // Not using Query:remove() because building the tableview and
443             // clearing it is actually significantly faster
444         case Mode::TableView:
445             validate_write();
446             evaluate_query_if_needed();
447
448             switch (m_update_policy) {
449                 case UpdatePolicy::Auto:
450                     m_table_view.clear(RemoveMode::unordered);
451                     break;
452                 case UpdatePolicy::Never: {
453                     // Copy the TableView because a frozen Results shouldn't let its size() change.
454                     TableView copy(m_table_view);
455                     copy.clear(RemoveMode::unordered);
456                     break;
457                 }
458             }
459             break;
460         case Mode::LinkView:
461             validate_write();
462             m_link_view->remove_all_target_rows();
463             break;
464     }
465 }
466
467 PropertyType Results::get_type() const
468 {
469     validate_read();
470     switch (m_mode) {
471         case Mode::Empty:
472         case Mode::LinkView:
473             return PropertyType::Object;
474         case Mode::Query:
475         case Mode::TableView:
476         case Mode::Table:
477             if (m_table->get_index_in_group() != npos)
478                 return PropertyType::Object;
479             return ObjectSchema::from_core_type(*m_table->get_descriptor(), 0);
480     }
481     REALM_COMPILER_HINT_UNREACHABLE();
482 }
483
484 Query Results::get_query() const
485 {
486     validate_read();
487     switch (m_mode) {
488         case Mode::Empty:
489         case Mode::Query:
490             return m_query;
491         case Mode::TableView: {
492             // A TableView has an associated Query if it was produced by Query::find_all. This is indicated
493             // by TableView::get_query returning a Query with a non-null table.
494             Query query = m_table_view.get_query();
495             if (query.get_table()) {
496                 return query;
497             }
498
499             // The TableView has no associated query so create one with no conditions that is restricted
500             // to the rows in the TableView.
501             if (m_update_policy == UpdatePolicy::Auto) {
502                 m_table_view.sync_if_needed();
503             }
504             return Query(*m_table, std::unique_ptr<TableViewBase>(new TableView(m_table_view)));
505         }
506         case Mode::LinkView:
507             return m_table->where(m_link_view);
508         case Mode::Table:
509             return m_table->where();
510     }
511     REALM_COMPILER_HINT_UNREACHABLE();
512 }
513
514 TableView Results::get_tableview()
515 {
516     validate_read();
517     switch (m_mode) {
518         case Mode::Empty:
519             return {};
520         case Mode::LinkView:
521             if (update_linkview())
522                 return m_table->where(m_link_view).find_all();
523             REALM_FALLTHROUGH;
524         case Mode::Query:
525         case Mode::TableView:
526             evaluate_query_if_needed();
527             return m_table_view;
528         case Mode::Table:
529             return m_table->where().find_all();
530     }
531     REALM_COMPILER_HINT_UNREACHABLE();
532 }
533
534 static std::vector<size_t> parse_keypath(StringData keypath, Schema const& schema, const ObjectSchema *object_schema)
535 {
536     auto check = [&](bool condition, const char* fmt, auto... args) {
537         if (!condition) {
538             throw std::invalid_argument(util::format("Cannot sort on key path '%1': %2.",
539                                                      keypath, util::format(fmt, args...)));
540         }
541     };
542     auto is_sortable_type = [](PropertyType type) {
543         return !is_array(type) && type != PropertyType::LinkingObjects && type != PropertyType::Data;
544     };
545
546     const char* begin = keypath.data();
547     const char* end = keypath.data() + keypath.size();
548     check(begin != end, "missing property name");
549
550     std::vector<size_t> indices;
551     while (begin != end) {
552         auto sep = std::find(begin, end, '.');
553         check(sep != begin && sep + 1 != end, "missing property name");
554         StringData key(begin, sep - begin);
555         begin = sep + (sep != end);
556
557         auto prop = object_schema->property_for_name(key);
558         check(prop, "property '%1.%2' does not exist", object_schema->name, key);
559         check(is_sortable_type(prop->type), "property '%1.%2' is of unsupported type '%3'",
560               object_schema->name, key, string_for_property_type(prop->type));
561         if (prop->type == PropertyType::Object)
562             check(begin != end, "property '%1.%2' of type 'object' cannot be the final property in the key path",
563                   object_schema->name, key);
564         else
565             check(begin == end, "property '%1.%2' of type '%3' may only be the final property in the key path",
566                   object_schema->name, key, prop->type_string());
567
568         indices.push_back(prop->table_column);
569         if (prop->type == PropertyType::Object)
570             object_schema = &*schema.find(prop->object_type);
571     }
572     return indices;
573 }
574
575 Results Results::sort(std::vector<std::pair<std::string, bool>> const& keypaths) const
576 {
577     if (keypaths.empty())
578         return *this;
579     if (get_type() != PropertyType::Object) {
580         if (keypaths.size() != 1)
581             throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path",
582                                                      string_for_property_type(get_type())));
583         if (keypaths[0].first != "self")
584             throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
585                                                      keypaths[0].first, string_for_property_type(get_type())));
586         return sort({*m_table, {{0}}, {keypaths[0].second}});
587     }
588
589     std::vector<std::vector<size_t>> column_indices;
590     std::vector<bool> ascending;
591     column_indices.reserve(keypaths.size());
592     ascending.reserve(keypaths.size());
593
594     for (auto& keypath : keypaths) {
595         column_indices.push_back(parse_keypath(keypath.first, m_realm->schema(), &get_object_schema()));
596         ascending.push_back(keypath.second);
597     }
598     return sort({*m_table, std::move(column_indices), std::move(ascending)});
599 }
600
601 Results Results::sort(SortDescriptor&& sort) const
602 {
603     if (m_mode == Mode::LinkView)
604         return Results(m_realm, m_link_view, util::none, std::move(sort));
605     DescriptorOrdering new_order = m_descriptor_ordering;
606     new_order.append_sort(std::move(sort));
607     return Results(m_realm, get_query(), std::move(new_order));
608 }
609
610 Results Results::filter(Query&& q) const
611 {
612     return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
613 }
614
615 Results Results::distinct(DistinctDescriptor&& uniqueness) const
616 {
617     DescriptorOrdering new_order = m_descriptor_ordering;
618     new_order.append_distinct(std::move(uniqueness));
619     return Results(m_realm, get_query(), std::move(new_order));
620 }
621
622 Results Results::distinct(std::vector<std::string> const& keypaths) const
623 {
624     if (keypaths.empty())
625         return *this;
626     if (get_type() != PropertyType::Object) {
627         if (keypaths.size() != 1)
628             throw std::invalid_argument(util::format("Cannot sort array of '%1' on more than one key path",
629                                                      string_for_property_type(get_type())));
630         if (keypaths[0] != "self")
631             throw std::invalid_argument(util::format("Cannot sort on key path '%1': arrays of '%2' can only be sorted on 'self'",
632                                                      keypaths[0], string_for_property_type(get_type())));
633         return distinct({*m_table, {{0}}});
634     }
635
636     std::vector<std::vector<size_t>> column_indices;
637     column_indices.reserve(keypaths.size());
638     for (auto& keypath : keypaths)
639         column_indices.push_back(parse_keypath(keypath, m_realm->schema(), &get_object_schema()));
640     return distinct({*m_table, std::move(column_indices)});
641 }
642
643 Results Results::snapshot() const &
644 {
645     validate_read();
646     return Results(*this).snapshot();
647 }
648
649 Results Results::snapshot() &&
650 {
651     validate_read();
652
653     switch (m_mode) {
654         case Mode::Empty:
655             return Results();
656
657         case Mode::Table:
658         case Mode::LinkView:
659             m_query = get_query();
660             m_mode = Mode::Query;
661
662             REALM_FALLTHROUGH;
663         case Mode::Query:
664         case Mode::TableView:
665             evaluate_query_if_needed(false);
666             m_notifier.reset();
667             m_update_policy = UpdatePolicy::Never;
668             return std::move(*this);
669     }
670     REALM_COMPILER_HINT_UNREACHABLE();
671 }
672
673 void Results::prepare_async()
674 {
675     if (m_notifier) {
676         return;
677     }
678     if (m_realm->config().immutable()) {
679         throw InvalidTransactionException("Cannot create asynchronous query for immutable Realms");
680     }
681     if (m_realm->is_in_transaction()) {
682         throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction");
683     }
684     if (m_update_policy == UpdatePolicy::Never) {
685         throw std::logic_error("Cannot create asynchronous query for snapshotted Results.");
686     }
687
688     m_wants_background_updates = true;
689     m_notifier = std::make_shared<_impl::ResultsNotifier>(*this);
690     _impl::RealmCoordinator::register_notifier(m_notifier);
691 }
692
693 NotificationToken Results::add_notification_callback(CollectionChangeCallback cb) &
694 {
695     prepare_async();
696     return {m_notifier, m_notifier->add_callback(std::move(cb))};
697 }
698
699 bool Results::is_in_table_order() const
700 {
701     switch (m_mode) {
702         case Mode::Empty:
703         case Mode::Table:
704             return true;
705         case Mode::LinkView:
706             return false;
707         case Mode::Query:
708             return m_query.produces_results_in_table_order() && !m_descriptor_ordering.will_apply_sort();
709         case Mode::TableView:
710             return m_table_view.is_in_table_order();
711     }
712     REALM_COMPILER_HINT_UNREACHABLE();
713 }
714
715 void Results::Internal::set_table_view(Results& results, TableView &&tv)
716 {
717     REALM_ASSERT(results.m_update_policy != UpdatePolicy::Never);
718     // If the previous TableView was never actually used, then stop generating
719     // new ones until the user actually uses the Results object again
720     if (results.m_mode == Mode::TableView) {
721         results.m_wants_background_updates = results.m_has_used_table_view;
722     }
723
724     results.m_table_view = std::move(tv);
725     results.m_mode = Mode::TableView;
726     results.m_has_used_table_view = false;
727     REALM_ASSERT(results.m_table_view.is_in_sync());
728     REALM_ASSERT(results.m_table_view.is_attached());
729 }
730
731 #define REALM_RESULTS_TYPE(T) \
732     template T Results::get<T>(size_t); \
733     template util::Optional<T> Results::first<T>(); \
734     template util::Optional<T> Results::last<T>(); \
735     template size_t Results::index_of<T>(T const&);
736
737 template RowExpr Results::get<RowExpr>(size_t);
738 template util::Optional<RowExpr> Results::first<RowExpr>();
739 template util::Optional<RowExpr> Results::last<RowExpr>();
740
741 REALM_RESULTS_TYPE(bool)
742 REALM_RESULTS_TYPE(int64_t)
743 REALM_RESULTS_TYPE(float)
744 REALM_RESULTS_TYPE(double)
745 REALM_RESULTS_TYPE(StringData)
746 REALM_RESULTS_TYPE(BinaryData)
747 REALM_RESULTS_TYPE(Timestamp)
748 REALM_RESULTS_TYPE(util::Optional<bool>)
749 REALM_RESULTS_TYPE(util::Optional<int64_t>)
750 REALM_RESULTS_TYPE(util::Optional<float>)
751 REALM_RESULTS_TYPE(util::Optional<double>)
752
753 #undef REALM_RESULTS_TYPE
754
755 Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c)
756 : std::out_of_range(util::format("Requested index %1 greater than max %2", r, c - 1))
757 , requested(r), valid_count(c) {}
758
759 static std::string unsupported_operation_msg(size_t column, const Table* table, const char* operation)
760 {
761     const char* column_type = string_for_property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column));
762     if (table->is_group_level())
763         return util::format("Cannot %1 property '%2': operation not supported for '%3' properties",
764                             operation, table->get_column_name(column), column_type);
765     return util::format("Cannot %1 '%2' array: operation not supported",
766                         operation, column_type);
767 }
768
769 Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation)
770 : std::logic_error(unsupported_operation_msg(column, table, operation))
771 , column_index(column)
772 , column_name(table->get_column_name(column))
773 , property_type(ObjectSchema::from_core_type(*table->get_descriptor(), column))
774 {
775 }
776
777 } // namespace realm