1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2015 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #include "impl/transact_log_handler.hpp"
21 #include "binding_context.hpp"
22 #include "impl/collection_notifier.hpp"
23 #include "index_set.hpp"
24 #include "shared_realm.hpp"
26 #include <realm/group_shared.hpp>
27 #include <realm/lang_bind_helper.hpp>
32 using namespace realm;
36 class KVOAdapter : public _impl::TransactionChangeInfo {
38 KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context);
40 void before(SharedGroup& sg);
41 void after(SharedGroup& sg);
44 BindingContext* m_context;
45 std::vector<BindingContext::ObserverState>& m_observers;
46 std::vector<void *> m_invalidated;
49 BindingContext::ObserverState* observer;
50 _impl::CollectionChangeBuilder builder;
54 std::vector<ListInfo> m_lists;
57 size_t new_table_ndx(size_t ndx) const { return ndx < table_indices.size() ? table_indices[ndx] : ndx; }
60 KVOAdapter::KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context)
61 : _impl::TransactionChangeInfo{}
63 , m_observers(observers)
65 if (m_observers.empty())
68 std::vector<size_t> tables_needed;
69 for (auto& observer : observers) {
70 tables_needed.push_back(observer.table_ndx);
72 std::sort(begin(tables_needed), end(tables_needed));
73 tables_needed.erase(std::unique(begin(tables_needed), end(tables_needed)), end(tables_needed));
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)});
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;
97 for (auto& list : m_lists)
98 lists.push_back({list.observer->table_ndx, list.observer->row_ndx, list.col, &list.builder});
101 void KVOAdapter::before(SharedGroup& sg)
106 m_version = sg.get_version_of_current_transaction();
110 for (auto& observer : m_observers) {
111 size_t table_ndx = new_table_ndx(observer.table_ndx);
112 if (table_ndx >= tables.size())
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)
122 else if (table.deletions.contains(idx)) {
123 m_invalidated.push_back(observer.info);
127 idx = table.insertions.shift(table.deletions.unshift(idx));
128 if (table.modifications.contains(idx)) {
129 observer.changes.resize(table.columns.size());
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;
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;
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
151 if (list.col < list.observer->changes.size())
152 list.observer->changes[list.col].kind = BindingContext::ColumnInfo::Kind::None;
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));
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];
165 builder.modifications.remove(builder.insertions);
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);
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
180 auto in_range = [](auto& it, auto end, size_t i) {
181 if (it != end && i >= it->second)
183 return it != end && i >= it->first && i < it->second;
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);
191 for (size_t i = start; i < end; ++i) {
192 if (in_range(del_it, del_end, i))
194 else if (in_range(ins_it, ins_end, i + shift))
197 changes.indices.add(i);
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;
204 else if (!builder.insertions.empty()) {
205 changes.kind = BindingContext::ColumnInfo::Kind::Insert;
206 changes.indices = builder.insertions;
208 else if (!builder.modifications.empty()) {
209 changes.kind = BindingContext::ColumnInfo::Kind::Set;
210 changes.indices = builder.modifications;
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);
220 changes.indices = builder.deletions;
223 m_context->will_change(m_observers, m_invalidated);
226 void KVOAdapter::after(SharedGroup& sg)
230 m_context->did_change(m_observers, m_invalidated,
231 m_version != VersionID{} &&
232 m_version != sg.get_version_of_current_transaction());
235 template<typename Derived>
236 struct MarkDirtyMixin {
237 bool mark_dirty(size_t row, size_t col, _impl::Instruction instr=_impl::instr_Set)
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);
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); }
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); }
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; }
267 bool add_row_with_key(size_t, size_t, size_t, int64_t) { return true; }
270 class TransactLogValidationMixin {
271 // Index of currently selected table
272 size_t m_current_table = 0;
278 throw std::logic_error("Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way");
282 size_t current_table() const noexcept { return m_current_table; }
286 bool select_table(size_t group_level_ndx, int, const size_t*) noexcept
288 m_current_table = group_level_ndx;
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(); }
299 // Schema changes which don't involve a change in the schema version are
301 bool add_search_index(size_t) { return true; }
302 bool remove_search_index(size_t) { return true; }
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; }
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; }
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) { }
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)
344 REALM_ASSERT(from != to);
345 if (from >= container.size() && to >= container.size())
347 if (from >= container.size() || to >= container.size())
348 container.resize(std::max(from, to) + 1);
350 std::rotate(begin(container) + from, begin(container) + from + 1, begin(container) + to + 1);
352 std::rotate(begin(container) + to, begin(container) + from, begin(container) + from + 1);
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)
359 if (pos < container.size())
360 container.emplace(container.begin() + pos);
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)
368 else if (value > from && value <= to)
370 else if (value < from && value >= to)
374 void adjust_for_move(std::vector<size_t>& values, size_t from, size_t to)
376 for (auto& value : values)
377 adjust_for_move(value, from, to);
380 void expand_to(std::vector<size_t>& cols, size_t i)
382 auto old_size = cols.size();
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);
390 void adjust_ge(std::vector<size_t>& values, size_t i)
392 for (auto& value : values) {
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;
405 bool m_need_move_info = false;
406 bool m_is_top_level_table = true;
408 _impl::CollectionChangeBuilder* find_list(size_t tbl, size_t col, size_t row)
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
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)
421 TransactLogObserver(_impl::TransactionChangeInfo& info)
424 void mark_dirty(size_t row, size_t col)
427 m_active_table->modify(row, col);
430 void parse_complete()
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();
438 bool select_descriptor(int levels, const size_t*) noexcept
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;
447 bool select_table(size_t group_level_ndx, int len, size_t const* path) noexcept
449 TransactLogValidationMixin::select_table(group_level_ndx, len, path);
450 m_active_table = nullptr;
451 m_is_top_level_table = true;
453 // Nested subtables currently not supported
455 m_is_top_level_table = false;
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]))
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];
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);
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;
486 bool select_link_list(size_t col, size_t row, size_t)
488 mark_dirty(row, col);
489 m_active_list = find_list(current_table(), col, row);
493 bool link_list_set(size_t index, size_t, size_t)
496 m_active_list->modify(index);
500 bool link_list_insert(size_t index, size_t, size_t)
503 m_active_list->insert(index);
507 bool link_list_erase(size_t index, size_t)
510 m_active_list->erase(index);
514 bool link_list_nullify(size_t index, size_t prior_size)
516 return link_list_erase(index, prior_size);
519 bool link_list_swap(size_t index1, size_t index2)
521 link_list_set(index1, 0, npos);
522 link_list_set(index2, 0, npos);
526 bool link_list_clear(size_t old_size)
529 m_active_list->clear(old_size);
533 bool link_list_move(size_t from, size_t to)
536 m_active_list->move(from, to);
540 bool insert_empty_rows(size_t row_ndx, size_t num_rows_to_insert, size_t, bool)
543 m_active_table->insert(row_ndx, num_rows_to_insert, m_need_move_info);
544 if (!m_is_top_level_table)
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;
553 bool add_row_with_key(size_t row_ndx, size_t prior_num_rows, size_t, int64_t)
555 insert_empty_rows(row_ndx, 1, prior_num_rows, false);
559 bool erase_rows(size_t row_ndx, size_t, size_t prior_num_rows, bool unordered)
563 m_active_table->erase(row_ndx);
566 size_t last_row = prior_num_rows - 1;
568 m_active_table->move_over(row_ndx, last_row, m_need_move_info);
570 if (!m_is_top_level_table)
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())
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();
582 if (list.row_ndx == last_row)
583 list.row_ndx = row_ndx;
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);
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;
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);
618 m_active_table->move(from_ndx, to_ndx);
622 bool merge_rows(size_t from, size_t to)
625 m_active_table->subsume(from, to, m_need_move_info);
626 if (!m_is_top_level_table)
628 for (auto& list : m_info.lists) {
629 if (list.table_ndx == current_table() && list.row_ndx == from)
635 bool clear_table(size_t=0)
637 auto tbl_ndx = current_table();
639 m_active_table->clear(std::numeric_limits<size_t>::max());
640 if (!m_is_top_level_table)
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));
648 bool insert_column(size_t ndx, DataType, StringData, bool)
650 m_info.schema_changed = true;
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)
656 for (auto& list : m_info.lists) {
657 if (list.table_ndx == current_table() && list.col_ndx >= ndx)
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);
669 void prepare_table_indices()
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);
677 bool insert_group_level_table(size_t ndx, size_t, StringData)
679 m_info.schema_changed = true;
681 for (auto& list : m_info.lists) {
682 if (list.table_ndx >= ndx)
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);
693 bool move_column(size_t from, size_t to)
695 m_info.schema_changed = true;
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)
701 for (auto& list : m_info.lists) {
702 if (list.table_ndx == current_table())
703 adjust_for_move(list.col_ndx, from, to);
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);
712 bool move_group_level_table(size_t from, size_t to)
714 m_info.schema_changed = true;
716 for (auto& list : m_info.lists)
717 adjust_for_move(list.table_ndx, from, to);
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);
727 bool insert_link_column(size_t ndx, DataType type, StringData name, size_t, size_t) { return insert_column(ndx, type, name, false); }
730 class KVOTransactLogObserver : public TransactLogObserver {
731 KVOAdapter m_adapter;
732 _impl::NotifierPackage& m_notifiers;
736 KVOTransactLogObserver(std::vector<BindingContext::ObserverState>& observers,
737 BindingContext* context,
738 _impl::NotifierPackage& notifiers,
740 : TransactLogObserver(m_adapter)
741 , m_adapter(observers, context)
742 , m_notifiers(notifiers)
747 ~KVOTransactLogObserver()
749 m_adapter.after(m_sg);
752 void parse_complete()
754 TransactLogObserver::parse_complete();
755 m_adapter.before(m_sg);
757 using sgf = _impl::SharedGroupFriend;
758 m_notifiers.package_and_wait(sgf::get_version_of_latest_snapshot(m_sg));
759 m_notifiers.before_advance();
763 template<typename Func>
764 void advance_with_notifications(BindingContext* context, const std::unique_ptr<SharedGroup>& sg,
765 Func&& func, _impl::NotifierPackage& notifiers)
767 auto old_version = sg->get_version_of_current_transaction();
768 std::vector<BindingContext::ObserverState> observers;
770 observers = context->get_observed_rows();
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.
785 // did_change() can change the read version, and if it does we can't
787 if (new_version == sg->get_version_of_current_transaction())
788 notifiers.deliver(*sg);
789 notifiers.after_advance();
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();
799 } // anonymous namespace
804 namespace transaction {
805 void advance(SharedGroup& sg, BindingContext*, VersionID version)
807 LangBindHelper::advance_read(sg, TransactLogValidator(), version);
810 void advance(const std::unique_ptr<SharedGroup>& sg, BindingContext* context, NotifierPackage& notifiers)
812 advance_with_notifications(context, sg, [&](auto&&... args) {
813 LangBindHelper::advance_read(*sg, std::move(args)..., notifiers.version().value_or(VersionID{}));
817 void begin_without_validation(SharedGroup& sg)
819 LangBindHelper::promote_to_write(sg);
822 void begin(const std::unique_ptr<SharedGroup>& sg, BindingContext* context, NotifierPackage& notifiers)
824 advance_with_notifications(context, sg, [&](auto&&... args) {
825 LangBindHelper::promote_to_write(*sg, std::move(args)...);
829 void commit(SharedGroup& sg)
831 LangBindHelper::commit_and_continue_as_read(sg);
834 void cancel(SharedGroup& sg, BindingContext* context)
836 std::vector<BindingContext::ObserverState> observers;
838 observers = context->get_observed_rows();
840 if (observers.empty()) {
841 LangBindHelper::rollback_and_continue_as_read(sg);
845 _impl::NotifierPackage notifiers;
846 LangBindHelper::rollback_and_continue_as_read(sg, KVOTransactLogObserver(observers, context, notifiers, sg));
849 void advance(SharedGroup& sg, TransactionChangeInfo& info, VersionID version)
851 if (!info.track_all && info.table_modifications_needed.empty() && info.lists.empty()) {
852 LangBindHelper::advance_read(sg, version);
855 LangBindHelper::advance_read(sg, TransactLogObserver(info), version);
860 } // namespace transaction