added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / impl / transact_log_handler.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 "impl/transact_log_handler.hpp"
20
21 #include "binding_context.hpp"
22 #include "impl/collection_notifier.hpp"
23 #include "index_set.hpp"
24 #include "shared_realm.hpp"
25
26 #include <realm/group_shared.hpp>
27 #include <realm/lang_bind_helper.hpp>
28
29 #include <algorithm>
30 #include <numeric>
31
32 using namespace realm;
33
34 namespace {
35
36 class KVOAdapter : public _impl::TransactionChangeInfo {
37 public:
38     KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context);
39
40     void before(SharedGroup& sg);
41     void after(SharedGroup& sg);
42
43 private:
44     BindingContext* m_context;
45     std::vector<BindingContext::ObserverState>& m_observers;
46     std::vector<void *> m_invalidated;
47
48     struct ListInfo {
49         BindingContext::ObserverState* observer;
50         _impl::CollectionChangeBuilder builder;
51         size_t col;
52         size_t initial_size;
53     };
54     std::vector<ListInfo> m_lists;
55     VersionID m_version;
56
57     size_t new_table_ndx(size_t ndx) const { return ndx < table_indices.size() ? table_indices[ndx] : ndx; }
58 };
59
60 KVOAdapter::KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context)
61 : _impl::TransactionChangeInfo{}
62 , m_context(context)
63 , m_observers(observers)
64 {
65     if (m_observers.empty())
66         return;
67
68     std::vector<size_t> tables_needed;
69     for (auto& observer : observers) {
70         tables_needed.push_back(observer.table_ndx);
71     }
72     std::sort(begin(tables_needed), end(tables_needed));
73     tables_needed.erase(std::unique(begin(tables_needed), end(tables_needed)), end(tables_needed));
74
75     auto realm = context->realm.lock();
76     auto& group = realm->read_group();
77     for (auto& observer : observers) {
78         auto table = group.get_table(observer.table_ndx);
79         for (size_t i = 0, count = table->get_column_count(); i < count; ++i) {
80             auto type = table->get_column_type(i);
81             if (type == type_LinkList)
82                 m_lists.push_back({&observer, {}, i, size_t(-1)});
83             else if (type == type_Table)
84                 m_lists.push_back({&observer, {}, i, table->get_subtable_size(i, observer.row_ndx)});
85         }
86     }
87
88     auto max = std::max_element(begin(tables_needed), end(tables_needed));
89     if (*max >= table_modifications_needed.size())
90         table_modifications_needed.resize(*max + 1, false);
91     if (*max >= table_moves_needed.size())
92         table_moves_needed.resize(*max + 1, false);
93     for (auto& tbl : tables_needed) {
94         table_modifications_needed[tbl] = true;
95         table_moves_needed[tbl] = true;
96     }
97     for (auto& list : m_lists)
98         lists.push_back({list.observer->table_ndx, list.observer->row_ndx, list.col, &list.builder});
99 }
100
101 void KVOAdapter::before(SharedGroup& sg)
102 {
103     if (!m_context)
104         return;
105
106     m_version = sg.get_version_of_current_transaction();
107     if (tables.empty())
108         return;
109
110     for (auto& observer : m_observers) {
111         size_t table_ndx = new_table_ndx(observer.table_ndx);
112         if (table_ndx >= tables.size())
113             continue;
114
115         auto const& table = tables[table_ndx];
116         auto const& moves = table.moves;
117         auto idx = observer.row_ndx;
118         auto it = lower_bound(begin(moves), end(moves), idx,
119                               [](auto const& a, auto b) { return a.from < b; });
120         if (it != moves.end() && it->from == idx)
121             idx = it->to;
122         else if (table.deletions.contains(idx)) {
123             m_invalidated.push_back(observer.info);
124             continue;
125         }
126         else
127             idx = table.insertions.shift(table.deletions.unshift(idx));
128         if (table.modifications.contains(idx)) {
129             observer.changes.resize(table.columns.size());
130             size_t i = 0;
131             for (auto& c : table.columns) {
132                 auto& change = observer.changes[i];
133                 if (table_ndx >= column_indices.size() || column_indices[table_ndx].empty())
134                     change.initial_column_index = i;
135                 else if (i >= column_indices[table_ndx].size())
136                     change.initial_column_index = i - column_indices[table_ndx].size() + column_indices[table_ndx].back() + 1;
137                 else
138                     change.initial_column_index = column_indices[table_ndx][i];
139                 if (change.initial_column_index != npos && c.contains(idx))
140                     change.kind = BindingContext::ColumnInfo::Kind::Set;
141                 ++i;
142             }
143         }
144     }
145
146     for (auto& list : m_lists) {
147         if (list.builder.empty()) {
148             // We may have pre-emptively marked the column as modified if the
149             // LinkList was selected but the actual changes made ended up being
150             // a no-op
151             if (list.col < list.observer->changes.size())
152                 list.observer->changes[list.col].kind = BindingContext::ColumnInfo::Kind::None;
153             continue;
154         }
155         // If the containing row was deleted then changes will be empty
156         if (list.observer->changes.empty()) {
157             REALM_ASSERT_DEBUG(tables[new_table_ndx(list.observer->table_ndx)].deletions.contains(list.observer->row_ndx));
158             continue;
159         }
160         // otherwise the column should have been marked as modified
161         REALM_ASSERT(list.col < list.observer->changes.size());
162         auto& builder = list.builder;
163         auto& changes = list.observer->changes[list.col];
164
165         builder.modifications.remove(builder.insertions);
166
167         // KVO can't express moves (becaue NSArray doesn't have them), so
168         // transform them into a series of sets on each affected index when possible
169         if (!builder.moves.empty() && builder.insertions.count() == builder.moves.size() && builder.deletions.count() == builder.moves.size()) {
170             changes.kind = BindingContext::ColumnInfo::Kind::Set;
171             changes.indices = builder.modifications;
172             changes.indices.add(builder.deletions);
173
174             // Iterate over each of the rows which may have been shifted by
175             // the moves and check if it actually has been, or if it's ended
176             // up in the same place as it started (either because the moves were
177             // actually a swap that doesn't effect the rows in between, or the
178             // combination of moves happen to leave some intermediate rows in
179             // the same place)
180             auto in_range = [](auto& it, auto end, size_t i) {
181                 if (it != end && i >= it->second)
182                     ++it;
183                 return it != end && i >= it->first && i < it->second;
184             };
185
186             auto del_it = builder.deletions.begin(), del_end = builder.deletions.end();
187             auto ins_it = builder.insertions.begin(), ins_end = builder.insertions.end();
188             size_t start = std::min(ins_it->first, del_it->first);
189             size_t end = std::max(std::prev(ins_end)->second, std::prev(del_end)->second);
190             ptrdiff_t shift = 0;
191             for (size_t i = start; i < end; ++i) {
192                 if (in_range(del_it, del_end, i))
193                     --shift;
194                 else if (in_range(ins_it, ins_end, i + shift))
195                     ++shift;
196                 if (shift != 0)
197                     changes.indices.add(i);
198             }
199         }
200         // KVO can't express multiple types of changes at once
201         else if (builder.insertions.empty() + builder.modifications.empty() + builder.deletions.empty() < 2) {
202             changes.kind = BindingContext::ColumnInfo::Kind::SetAll;
203         }
204         else if (!builder.insertions.empty()) {
205             changes.kind = BindingContext::ColumnInfo::Kind::Insert;
206             changes.indices = builder.insertions;
207         }
208         else if (!builder.modifications.empty()) {
209             changes.kind = BindingContext::ColumnInfo::Kind::Set;
210             changes.indices = builder.modifications;
211         }
212         else {
213             REALM_ASSERT(!builder.deletions.empty());
214             changes.kind = BindingContext::ColumnInfo::Kind::Remove;
215             // Table clears don't come with the size, so we need to fix up the
216             // notification to make it just delete all rows that actually existed
217             if (std::prev(builder.deletions.end())->second > list.initial_size)
218                 changes.indices.set(list.initial_size);
219             else
220                 changes.indices = builder.deletions;
221         }
222     }
223     m_context->will_change(m_observers, m_invalidated);
224 }
225
226 void KVOAdapter::after(SharedGroup& sg)
227 {
228     if (!m_context)
229         return;
230     m_context->did_change(m_observers, m_invalidated,
231                           m_version != VersionID{} &&
232                           m_version != sg.get_version_of_current_transaction());
233 }
234
235 template<typename Derived>
236 struct MarkDirtyMixin  {
237     bool mark_dirty(size_t row, size_t col, _impl::Instruction instr=_impl::instr_Set)
238     {
239         // Ignore SetDefault and SetUnique as those conceptually cannot be
240         // changes to existing rows
241         if (instr == _impl::instr_Set)
242             static_cast<Derived *>(this)->mark_dirty(row, col);
243         return true;
244     }
245
246     bool set_int(size_t c, size_t r, int_fast64_t, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); }
247     bool set_bool(size_t c, size_t r, bool, _impl::Instruction i) { return mark_dirty(r, c, i); }
248     bool set_float(size_t c, size_t r, float, _impl::Instruction i) { return mark_dirty(r, c, i); }
249     bool set_double(size_t c, size_t r, double, _impl::Instruction i) { return mark_dirty(r, c, i); }
250     bool set_string(size_t c, size_t r, StringData, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); }
251     bool set_binary(size_t c, size_t r, BinaryData, _impl::Instruction i) { return mark_dirty(r, c, i); }
252     bool set_olddatetime(size_t c, size_t r, OldDateTime, _impl::Instruction i) { return mark_dirty(r, c, i); }
253     bool set_timestamp(size_t c, size_t r, Timestamp, _impl::Instruction i) { return mark_dirty(r, c, i); }
254     bool set_table(size_t c, size_t r, _impl::Instruction i) { return mark_dirty(r, c, i); }
255     bool set_mixed(size_t c, size_t r, const Mixed&, _impl::Instruction i) { return mark_dirty(r, c, i); }
256     bool set_link(size_t c, size_t r, size_t, size_t, _impl::Instruction i) { return mark_dirty(r, c, i); }
257     bool set_null(size_t c, size_t r, _impl::Instruction i, size_t) { return mark_dirty(r, c, i); }
258
259     bool add_int(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); }
260     bool nullify_link(size_t col, size_t row, size_t) { return mark_dirty(row, col); }
261     bool insert_substring(size_t col, size_t row, size_t, StringData) { return mark_dirty(row, col); }
262     bool erase_substring(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); }
263
264     bool set_int_unique(size_t, size_t, size_t, int_fast64_t) { return true; }
265     bool set_string_unique(size_t, size_t, size_t, StringData) { return true; }
266
267     bool add_row_with_key(size_t, size_t, size_t, int64_t) { return true; }
268 };
269
270 class TransactLogValidationMixin {
271     // Index of currently selected table
272     size_t m_current_table = 0;
273
274     REALM_NORETURN
275     REALM_NOINLINE
276     void schema_error()
277     {
278         throw std::logic_error("Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way");
279     }
280
281 protected:
282     size_t current_table() const noexcept { return m_current_table; }
283
284 public:
285
286     bool select_table(size_t group_level_ndx, int, const size_t*) noexcept
287     {
288         m_current_table = group_level_ndx;
289         return true;
290     }
291
292     // Removing or renaming things while a Realm is open is never supported
293     bool erase_group_level_table(size_t, size_t) { schema_error(); }
294     bool rename_group_level_table(size_t, StringData) { schema_error(); }
295     bool erase_column(size_t) { schema_error(); }
296     bool erase_link_column(size_t, size_t, size_t) { schema_error(); }
297     bool rename_column(size_t, StringData) { schema_error(); }
298
299     // Schema changes which don't involve a change in the schema version are
300     // allowed
301     bool add_search_index(size_t) { return true; }
302     bool remove_search_index(size_t) { return true; }
303
304     // Additive changes and reorderings are supported
305     bool insert_group_level_table(size_t, size_t, StringData) { return true; }
306     bool insert_column(size_t, DataType, StringData, bool) { return true; }
307     bool insert_link_column(size_t, DataType, StringData, size_t, size_t) { return true; }
308     bool set_link_type(size_t, LinkType) { return true; }
309     bool move_column(size_t, size_t) { return true; }
310     bool move_group_level_table(size_t, size_t) { return true; }
311
312     // Non-schema changes are all allowed
313     void parse_complete() { }
314     bool select_descriptor(int, const size_t*) { return true; }
315     bool select_link_list(size_t, size_t, size_t) { return true; }
316     bool insert_empty_rows(size_t, size_t, size_t, bool) { return true; }
317     bool erase_rows(size_t, size_t, size_t, bool) { return true; }
318     bool swap_rows(size_t, size_t) { return true; }
319     bool move_row(size_t, size_t) { return true; }
320     bool clear_table(size_t=0) noexcept { return true; }
321     bool link_list_set(size_t, size_t, size_t) { return true; }
322     bool link_list_insert(size_t, size_t, size_t) { return true; }
323     bool link_list_erase(size_t, size_t) { return true; }
324     bool link_list_nullify(size_t, size_t) { return true; }
325     bool link_list_clear(size_t) { return true; }
326     bool link_list_move(size_t, size_t) { return true; }
327     bool link_list_swap(size_t, size_t) { return true; }
328     bool merge_rows(size_t, size_t) { return true; }
329     bool optimize_table() { return true; }
330 };
331
332
333 // A transaction log handler that just validates that all operations made are
334 // ones supported by the object store
335 struct TransactLogValidator : public TransactLogValidationMixin, public MarkDirtyMixin<TransactLogValidator> {
336     void mark_dirty(size_t, size_t) { }
337 };
338
339 // Move the value at container[from] to container[to], shifting everything in
340 // between, or do nothing if either are out of bounds
341 template<typename Container>
342 void rotate(Container& container, size_t from, size_t to)
343 {
344     REALM_ASSERT(from != to);
345     if (from >= container.size() && to >= container.size())
346         return;
347     if (from >= container.size() || to >= container.size())
348         container.resize(std::max(from, to) + 1);
349     if (from < to)
350         std::rotate(begin(container) + from, begin(container) + from + 1, begin(container) + to + 1);
351     else
352         std::rotate(begin(container) + to, begin(container) + from, begin(container) + from + 1);
353 }
354
355 // Insert a default-initialized value at pos if there is anything after pos in the container.
356 template<typename Container>
357 void insert_empty_at(Container& container, size_t pos)
358 {
359     if (pos < container.size())
360         container.emplace(container.begin() + pos);
361 }
362
363 // Shift `value` to reflect a move from `from` to `to`
364 void adjust_for_move(size_t& value, size_t from, size_t to)
365 {
366     if (value == from)
367         value = to;
368     else if (value > from && value <= to)
369         --value;
370     else if (value < from && value >= to)
371         ++value;
372 }
373
374 void adjust_for_move(std::vector<size_t>& values, size_t from, size_t to)
375 {
376     for (auto& value : values)
377         adjust_for_move(value, from, to);
378 }
379
380 void expand_to(std::vector<size_t>& cols, size_t i)
381 {
382     auto old_size = cols.size();
383     if (old_size > i)
384         return;
385
386     cols.resize(std::max(old_size * 2, i + 1));
387     std::iota(begin(cols) + old_size, end(cols), old_size == 0 ? 0 : cols[old_size - 1] + 1);
388 }
389
390 void adjust_ge(std::vector<size_t>& values, size_t i)
391 {
392     for (auto& value : values) {
393         if (value >= i)
394             ++value;
395     }
396 }
397
398 // Extends TransactLogValidator to track changes made to LinkViews
399 class TransactLogObserver : public TransactLogValidationMixin, public MarkDirtyMixin<TransactLogObserver> {
400     _impl::TransactionChangeInfo& m_info;
401     _impl::CollectionChangeBuilder* m_active_list = nullptr;
402     _impl::CollectionChangeBuilder* m_active_table = nullptr;
403     _impl::CollectionChangeBuilder* m_active_descriptor = nullptr;
404
405     bool m_need_move_info = false;
406     bool m_is_top_level_table = true;
407
408     _impl::CollectionChangeBuilder* find_list(size_t tbl, size_t col, size_t row)
409     {
410         // When there are multiple source versions there could be multiple
411         // change objects for a single LinkView, in which case we need to use
412         // the last one
413         for (auto it = m_info.lists.rbegin(), end = m_info.lists.rend(); it != end; ++it) {
414             if (it->table_ndx == tbl && it->row_ndx == row && it->col_ndx == col)
415                 return it->changes;
416         }
417         return nullptr;
418     }
419
420 public:
421     TransactLogObserver(_impl::TransactionChangeInfo& info)
422     : m_info(info) { }
423
424     void mark_dirty(size_t row, size_t col)
425     {
426         if (m_active_table)
427             m_active_table->modify(row, col);
428     }
429
430     void parse_complete()
431     {
432         for (auto& table : m_info.tables)
433             table.parse_complete();
434         for (auto& list : m_info.lists)
435             list.changes->clean_up_stale_moves();
436     }
437
438     bool select_descriptor(int levels, const size_t*) noexcept
439     {
440         if (levels == 0) // schema of selected table is being modified
441             m_active_descriptor = m_active_table;
442         else // schema of subtable is being modified; currently don't need to track this
443             m_active_descriptor = nullptr;
444         return true;
445     }
446
447     bool select_table(size_t group_level_ndx, int len, size_t const* path) noexcept
448     {
449         TransactLogValidationMixin::select_table(group_level_ndx, len, path);
450         m_active_table = nullptr;
451         m_is_top_level_table = true;
452
453         // Nested subtables currently not supported
454         if (len > 1) {
455             m_is_top_level_table = false;
456             return true;
457         }
458
459         auto tbl_ndx = current_table();
460         if (!m_info.track_all && (tbl_ndx >= m_info.table_modifications_needed.size() || !m_info.table_modifications_needed[tbl_ndx]))
461             return true;
462
463         m_need_move_info = m_info.track_all || (tbl_ndx < m_info.table_moves_needed.size() &&
464                                                 m_info.table_moves_needed[tbl_ndx]);
465         if (m_info.tables.size() <= tbl_ndx)
466             m_info.tables.resize(std::max(m_info.tables.size() * 2, tbl_ndx + 1));
467         m_active_table = &m_info.tables[tbl_ndx];
468
469         if (len == 1) {
470             // Mark the cell containing the subtable as modified since selecting
471             // a table is always followed by a modification of some sort
472             size_t col = path[0];
473             size_t row = path[1];
474             mark_dirty(row, col);
475
476             m_active_table = nullptr;
477             m_is_top_level_table = false;
478             if (auto table = find_list(current_table(), col, row)) {
479                 m_active_table = table;
480                 m_need_move_info = true;
481             }
482         }
483         return true;
484     }
485
486     bool select_link_list(size_t col, size_t row, size_t)
487     {
488         mark_dirty(row, col);
489         m_active_list = find_list(current_table(), col, row);
490         return true;
491     }
492
493     bool link_list_set(size_t index, size_t, size_t)
494     {
495         if (m_active_list)
496             m_active_list->modify(index);
497         return true;
498     }
499
500     bool link_list_insert(size_t index, size_t, size_t)
501     {
502         if (m_active_list)
503             m_active_list->insert(index);
504         return true;
505     }
506
507     bool link_list_erase(size_t index, size_t)
508     {
509         if (m_active_list)
510             m_active_list->erase(index);
511         return true;
512     }
513
514     bool link_list_nullify(size_t index, size_t prior_size)
515     {
516         return link_list_erase(index, prior_size);
517     }
518
519     bool link_list_swap(size_t index1, size_t index2)
520     {
521         link_list_set(index1, 0, npos);
522         link_list_set(index2, 0, npos);
523         return true;
524     }
525
526     bool link_list_clear(size_t old_size)
527     {
528         if (m_active_list)
529             m_active_list->clear(old_size);
530         return true;
531     }
532
533     bool link_list_move(size_t from, size_t to)
534     {
535         if (m_active_list)
536             m_active_list->move(from, to);
537         return true;
538     }
539
540     bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t, bool)
541     {
542         if (m_active_table)
543             m_active_table->insert(row_ndx, num_rows_to_insert, m_need_move_info);
544         if (!m_is_top_level_table)
545             return true;
546         for (auto& list : m_info.lists) {
547             if (list.table_ndx == current_table() && list.row_ndx >= row_ndx)
548                 list.row_ndx += num_rows_to_insert;
549         }
550         return true;
551     }
552
553     bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t, int64_t)
554     {
555         insert_empty_rows(row_ndx, 1, prior_num_rows, false);
556         return true;
557     }
558
559     bool erase_rows(size_t row_ndx, size_t, size_t prior_num_rows, bool unordered)
560     {
561         if (!unordered) {
562             if (m_active_table)
563                 m_active_table->erase(row_ndx);
564             return true;
565         }
566         size_t last_row = prior_num_rows - 1;
567         if (m_active_table)
568             m_active_table->move_over(row_ndx, last_row, m_need_move_info);
569
570         if (!m_is_top_level_table)
571             return true;
572         for (size_t i = 0; i < m_info.lists.size(); ++i) {
573             auto& list = m_info.lists[i];
574             if (list.table_ndx != current_table())
575                 continue;
576             if (list.row_ndx == row_ndx) {
577                 if (i + 1 < m_info.lists.size())
578                     m_info.lists[i] = std::move(m_info.lists.back());
579                 m_info.lists.pop_back();
580                 continue;
581             }
582             if (list.row_ndx == last_row)
583                 list.row_ndx = row_ndx;
584         }
585
586         return true;
587     }
588
589     bool swap_rows(size_t row_ndx_1, size_t row_ndx_2) {
590         REALM_ASSERT(row_ndx_1 < row_ndx_2);
591         if (!m_is_top_level_table) {
592             if (m_active_table) {
593                 m_active_table->move(row_ndx_1, row_ndx_2);
594                 if (row_ndx_1 + 1 != row_ndx_2)
595                     m_active_table->move(row_ndx_2 - 1, row_ndx_1);
596             }
597             return true;
598         }
599
600         if (m_active_table)
601             m_active_table->swap(row_ndx_1, row_ndx_2, m_need_move_info);
602         for (auto& list : m_info.lists) {
603             if (list.table_ndx == current_table()) {
604                 if (list.row_ndx == row_ndx_1)
605                     list.row_ndx = row_ndx_2;
606                 else if (list.row_ndx == row_ndx_2)
607                     list.row_ndx = row_ndx_1;
608             }
609         }
610         return true;
611     }
612
613     bool move_row(size_t from_ndx, size_t to_ndx) {
614         // Move row is not supported for top level tables:
615         REALM_ASSERT(!m_active_table || !m_is_top_level_table);
616
617         if (m_active_table)
618             m_active_table->move(from_ndx, to_ndx);
619         return true;
620     }
621
622     bool merge_rows(size_t from, size_t to)
623     {
624         if (m_active_table)
625             m_active_table->subsume(from, to, m_need_move_info);
626         if (!m_is_top_level_table)
627             return true;
628         for (auto& list : m_info.lists) {
629             if (list.table_ndx == current_table() && list.row_ndx == from)
630                 list.row_ndx = to;
631         }
632         return true;
633     }
634
635     bool clear_table(size_t=0)
636     {
637         auto tbl_ndx = current_table();
638         if (m_active_table)
639             m_active_table->clear(std::numeric_limits<size_t>::max());
640         if (!m_is_top_level_table)
641             return true;
642         auto it = remove_if(begin(m_info.lists), end(m_info.lists),
643                             [&](auto const& lv) { return lv.table_ndx == tbl_ndx; });
644         m_info.lists.erase(it, end(m_info.lists));
645         return true;
646     }
647
648     bool insert_column(size_t ndx, DataType, StringData, bool)
649     {
650         m_info.schema_changed = true;
651
652         if (m_active_descriptor)
653             m_active_descriptor->insert_column(ndx);
654         if (m_active_descriptor != m_active_table || !m_is_top_level_table)
655             return true;
656         for (auto& list : m_info.lists) {
657             if (list.table_ndx == current_table() && list.col_ndx >= ndx)
658                 ++list.col_ndx;
659         }
660         if (m_info.column_indices.size() <= current_table())
661             m_info.column_indices.resize(current_table() + 1);
662         auto& indices = m_info.column_indices[current_table()];
663         expand_to(indices, ndx);
664         insert_empty_at(indices, ndx);
665         indices[ndx] = npos;
666         return true;
667     }
668
669     void prepare_table_indices()
670     {
671         if (m_info.table_indices.empty() && !m_info.table_modifications_needed.empty()) {
672             m_info.table_indices.resize(m_info.table_modifications_needed.size());
673             std::iota(begin(m_info.table_indices), end(m_info.table_indices), 0);
674         }
675     }
676
677     bool insert_group_level_table(size_t ndx, size_t, StringData)
678     {
679         m_info.schema_changed = true;
680
681         for (auto& list : m_info.lists) {
682             if (list.table_ndx >= ndx)
683                 ++list.table_ndx;
684         }
685         prepare_table_indices();
686         adjust_ge(m_info.table_indices, ndx);
687         insert_empty_at(m_info.tables, ndx);
688         insert_empty_at(m_info.table_moves_needed, ndx);
689         insert_empty_at(m_info.table_modifications_needed, ndx);
690         return true;
691     }
692
693     bool move_column(size_t from, size_t to)
694     {
695         m_info.schema_changed = true;
696
697         if (m_active_descriptor)
698             m_active_descriptor->move_column(from, to);
699         if (m_active_descriptor != m_active_table || !m_is_top_level_table)
700             return true;
701         for (auto& list : m_info.lists) {
702             if (list.table_ndx == current_table())
703                 adjust_for_move(list.col_ndx, from, to);
704         }
705         if (m_info.column_indices.size() <= current_table())
706             m_info.column_indices.resize(current_table() + 1);
707         expand_to(m_info.column_indices[current_table()], std::max(from, to) + 1);
708         rotate(m_info.column_indices[current_table()], from, to);
709         return true;
710     }
711
712     bool move_group_level_table(size_t from, size_t to)
713     {
714         m_info.schema_changed = true;
715
716         for (auto& list : m_info.lists)
717             adjust_for_move(list.table_ndx, from, to);
718
719         prepare_table_indices();
720         adjust_for_move(m_info.table_indices, from, to);
721         rotate(m_info.tables, from, to);
722         rotate(m_info.table_modifications_needed, from, to);
723         rotate(m_info.table_moves_needed, from, to);
724         return true;
725     }
726
727     bool insert_link_column(size_t ndx, DataType type, StringData name, size_t, size_t) { return insert_column(ndx, type, name, false); }
728 };
729
730 class KVOTransactLogObserver : public TransactLogObserver {
731     KVOAdapter m_adapter;
732     _impl::NotifierPackage& m_notifiers;
733     SharedGroup& m_sg;
734
735 public:
736     KVOTransactLogObserver(std::vector<BindingContext::ObserverState>& observers,
737                            BindingContext* context,
738                            _impl::NotifierPackage& notifiers,
739                            SharedGroup& sg)
740     : TransactLogObserver(m_adapter)
741     , m_adapter(observers, context)
742     , m_notifiers(notifiers)
743     , m_sg(sg)
744     {
745     }
746
747     ~KVOTransactLogObserver()
748     {
749         m_adapter.after(m_sg);
750     }
751
752     void parse_complete()
753     {
754         TransactLogObserver::parse_complete();
755         m_adapter.before(m_sg);
756
757         using sgf = _impl::SharedGroupFriend;
758         m_notifiers.package_and_wait(sgf::get_version_of_latest_snapshot(m_sg));
759         m_notifiers.before_advance();
760     }
761 };
762
763 template<typename Func>
764 void advance_with_notifications(BindingContext* context, const std::unique_ptr<SharedGroup>& sg,
765                                 Func&& func, _impl::NotifierPackage& notifiers)
766 {
767     auto old_version = sg->get_version_of_current_transaction();
768     std::vector<BindingContext::ObserverState> observers;
769     if (context) {
770         observers = context->get_observed_rows();
771     }
772
773     // Advancing to the latest version with notifiers requires using the full
774     // transaction log observer so that we have a point where we know what
775     // version we're going to before we actually advance to that version
776     if (observers.empty() && (!notifiers || notifiers.version())) {
777         notifiers.before_advance();
778         func(TransactLogValidator());
779         auto new_version = sg->get_version_of_current_transaction();
780         if (context && old_version != new_version)
781             context->did_change({}, {});
782         // did_change() could close the Realm. Just return if it does.
783         if (!sg)
784             return;
785         // did_change() can change the read version, and if it does we can't
786         // deliver notifiers
787         if (new_version == sg->get_version_of_current_transaction())
788             notifiers.deliver(*sg);
789         notifiers.after_advance();
790         return;
791     }
792
793     func(KVOTransactLogObserver(observers, context, notifiers, *sg));
794     notifiers.package_and_wait(sg->get_version_of_current_transaction().version); // is a no-op if parse_complete() was called
795     notifiers.deliver(*sg);
796     notifiers.after_advance();
797 }
798
799 } // anonymous namespace
800
801 namespace realm {
802 namespace _impl {
803
804 namespace transaction {
805 void advance(SharedGroup& sg, BindingContext*, VersionID version)
806 {
807     LangBindHelper::advance_read(sg, TransactLogValidator(), version);
808 }
809
810 void advance(const std::unique_ptr<SharedGroup>& sg, BindingContext* context, NotifierPackage& notifiers)
811 {
812     advance_with_notifications(context, sg, [&](auto&&... args) {
813         LangBindHelper::advance_read(*sg, std::move(args)..., notifiers.version().value_or(VersionID{}));
814     }, notifiers);
815 }
816
817 void begin_without_validation(SharedGroup& sg)
818 {
819     LangBindHelper::promote_to_write(sg);
820 }
821
822 void begin(const std::unique_ptr<SharedGroup>& sg, BindingContext* context, NotifierPackage& notifiers)
823 {
824     advance_with_notifications(context, sg, [&](auto&&... args) {
825         LangBindHelper::promote_to_write(*sg, std::move(args)...);
826     }, notifiers);
827 }
828
829 void commit(SharedGroup& sg)
830 {
831     LangBindHelper::commit_and_continue_as_read(sg);
832 }
833
834 void cancel(SharedGroup& sg, BindingContext* context)
835 {
836     std::vector<BindingContext::ObserverState> observers;
837     if (context) {
838         observers = context->get_observed_rows();
839     }
840     if (observers.empty()) {
841         LangBindHelper::rollback_and_continue_as_read(sg);
842         return;
843     }
844
845     _impl::NotifierPackage notifiers;
846     LangBindHelper::rollback_and_continue_as_read(sg, KVOTransactLogObserver(observers, context, notifiers, sg));
847 }
848
849 void advance(SharedGroup& sg, TransactionChangeInfo& info, VersionID version)
850 {
851     if (!info.track_all && info.table_modifications_needed.empty() && info.lists.empty()) {
852         LangBindHelper::advance_read(sg, version);
853     }
854     else {
855         LangBindHelper::advance_read(sg, TransactLogObserver(info), version);
856     }
857
858 }
859
860 } // namespace transaction
861 } // namespace _impl
862 } // namespace realm