realm-js/impl/transact_log_handler.cpp
Ari Lazier b91c69a3b5 Squashed 'src/object-store/' changes from 043f5ff..7701ba1
7701ba1 Merge pull request #16 from realm/tg-close
883ef12 Add Realm::close() and call it in RealmCache::clear()
e9ca54e Merge pull request #12 from realm/tg-core-0.94.4
0823a62 Merge pull request #13 from realm/tg-is-empty
62f59d9 Merge pull request #15 from realm/tg-file-error-path
b93e5ce Include the path of the file which actually failed to open in exceptions
e1e9cd8 Add ObjectStore::is_empty()
52e7e61 Update for core 0.94.4
271432b Merge pull request #11 from realm/kd-rename-delegate-to-bindingcontext
db36ca5 Remove Realm suffix
70e1967 Rename realm delegate in transact_log_handler as well
4973827 Rename RealmDelegate to RealmBindingContext
347145b Merge pull request #8 from realm/mar-migrate-required-to-optional
0b45772 Add a test showing our behavior when migrating from an optional column to a required column.
9f1702a Support migrating required columns to optional, preserving their contents.
f5b790c Merge pull request #6 from realm/tg-impl
6dfeaf8 Move things which are not part of the API to an impl directory/namespace
513e483 Merge pull request #4 from realm/tg-realm
f646777 Send changes_available() even if autorefresh is enabled
21d32bf Add a bit of documentation for RealmDelegate
95c80c9 Fix error in cleanup after an error during a migration
b7936bb Simplify column shifting for removed properties a little
a0f1dab Add a Schema class, move lookup by name and internal-consistency checks there
b381437 Make Realm::compact() more robust
6133eeb Reduce the scope of a variable
0c111a2 Fix a comment
ba278c5 Fix checks for what types of columns can be optional
7de20ea USe more const refs to avoid copies
dbac77f Make a bunch of things const
1400450 Remove an unused function
06e0ff8 Share ExternalCommitHelpers between Realm instances for a single path
f79dec9 Allow more nullable property types when supported
0eb0bd1 Honor is_nullable when creating columns
ea5c475 Refactor schema initialization a bit
e4f29fe Move the interprocess notification functionality to the object store
b129ebe Shuffle stuff around and clean some things up
eeb2ddd Improve array KVO performance a bit
c3649fb Skip PK uniqueness checking when first creating a Realm file
0a41c85 Improve performance of realm_requires_update() and make more things const
efdfa08 Port some of the KVO support functionality to the object store
65e1eb5 Add the ability to bypass the Realm cache entirely
3f226cf Rework change notifications
045c7b2 Add Realm::get_schema_version()
e4377bb Change realm::Schema to a vector rather than a map
cae4cf2 Remove property.hpp include from object_schema.hpp
55e6cca Convert RLMRealmConfiguration to a wrapper around Realm::Config
563a837 Use NSDMIs for realm::Property
0ae1bb1 Don't cache dynamic realms in the ObjectStore cache either
25a6734 Eliminate some copies
45890f2 Use NSDMIs for Realm
348f4a7 Reduce s_init_mutex's scope
b4f856b Use NSDMIs for Realm::Config and make it moveable
a91839b Store a copy of the encryption key
0700428 Merge pull request #3 from realm/al-bugfixes
b084335 clear Realm cache between tests
cb8364c property copy schema from cached realms
8712c8b fixes for latest object store changes
453e4d8 Fix crash when adding a property to a model without updating the schema version.

git-subtree-dir: src/object-store
git-subtree-split: 7701ba173d6c8d928a4f736a33c2850343b050e3
2015-11-13 17:34:33 -08:00

357 lines
12 KiB
C++

////////////////////////////////////////////////////////////////////////////
//
// 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 "transact_log_handler.hpp"
#include "../realm_binding_context.hpp"
#include <realm/commit_log.hpp>
#include <realm/group_shared.hpp>
#include <realm/lang_bind_helper.hpp>
using namespace realm;
namespace {
class TransactLogHandler {
using ColumnInfo = RealmBindingContext::ColumnInfo;
using ObserverState = RealmBindingContext::ObserverState;
// Observed table rows which need change information
std::vector<ObserverState> m_observers;
// Userdata pointers for rows which have been deleted
std::vector<void *> invalidated;
// Delegate to send change information to
RealmBindingContext* m_binding_context;
// Index of currently selected table
size_t m_current_table = 0;
// Change information for the currently selected LinkList, if any
ColumnInfo* m_active_linklist = nullptr;
// Get the change info for the given column, creating it if needed
static ColumnInfo& get_change(ObserverState& state, size_t i)
{
if (state.changes.size() <= i) {
state.changes.resize(std::max(state.changes.size() * 2, i + 1));
}
return state.changes[i];
}
// Loop over the columns which were changed in an observer state
template<typename Func>
static void for_each(ObserverState& state, Func&& f)
{
for (size_t i = 0; i < state.changes.size(); ++i) {
auto const& change = state.changes[i];
if (change.changed) {
f(i, change);
}
}
}
// Mark the given row/col as needing notifications sent
bool mark_dirty(size_t row_ndx, size_t col_ndx)
{
auto it = lower_bound(begin(m_observers), end(m_observers), ObserverState{m_current_table, row_ndx, nullptr});
if (it != end(m_observers) && it->table_ndx == m_current_table && it->row_ndx == row_ndx) {
get_change(*it, col_ndx).changed = true;
}
return true;
}
// Remove the given observer from the list of observed objects and add it
// to the listed of invalidated objects
void invalidate(ObserverState *o)
{
invalidated.push_back(o->info);
m_observers.erase(m_observers.begin() + (o - &m_observers[0]));
}
public:
template<typename Func>
TransactLogHandler(RealmBindingContext* binding_context, SharedGroup& sg, Func&& func)
: m_binding_context(binding_context)
{
if (!binding_context) {
func();
return;
}
m_observers = binding_context->get_observed_rows();
if (m_observers.empty()) {
auto old_version = sg.get_version_of_current_transaction();
func();
if (old_version != sg.get_version_of_current_transaction()) {
binding_context->did_change({}, {});
}
return;
}
func(*this);
binding_context->did_change(m_observers, invalidated);
}
// Called at the end of the transaction log immediately before the version
// is advanced
void parse_complete()
{
m_binding_context->will_change(m_observers, invalidated);
}
// These would require having an observer before schema init
// Maybe do something here to throw an error when multiple processes have different schemas?
bool insert_group_level_table(size_t, size_t, StringData) { return false; }
bool erase_group_level_table(size_t, size_t) { return false; }
bool rename_group_level_table(size_t, StringData) { return false; }
bool insert_column(size_t, DataType, StringData, bool) { return false; }
bool insert_link_column(size_t, DataType, StringData, size_t, size_t) { return false; }
bool erase_column(size_t) { return false; }
bool erase_link_column(size_t, size_t, size_t) { return false; }
bool rename_column(size_t, StringData) { return false; }
bool add_search_index(size_t) { return false; }
bool remove_search_index(size_t) { return false; }
bool add_primary_key(size_t) { return false; }
bool remove_primary_key() { return false; }
bool set_link_type(size_t, LinkType) { return false; }
bool select_table(size_t group_level_ndx, int, const size_t*) noexcept
{
m_current_table = group_level_ndx;
return true;
}
bool insert_empty_rows(size_t, size_t, size_t, bool)
{
// rows are only inserted at the end, so no need to do anything
return true;
}
bool erase_rows(size_t row_ndx, size_t, size_t last_row_ndx, bool unordered)
{
for (size_t i = 0; i < m_observers.size(); ++i) {
auto& o = m_observers[i];
if (o.table_ndx == m_current_table) {
if (o.row_ndx == row_ndx) {
invalidate(&o);
--i;
}
else if (unordered && o.row_ndx == last_row_ndx) {
o.row_ndx = row_ndx;
}
else if (!unordered && o.row_ndx > row_ndx) {
o.row_ndx -= 1;
}
}
}
return true;
}
bool clear_table()
{
for (size_t i = 0; i < m_observers.size(); ) {
auto& o = m_observers[i];
if (o.table_ndx == m_current_table) {
invalidate(&o);
}
else {
++i;
}
}
return true;
}
bool select_link_list(size_t col, size_t row, size_t)
{
m_active_linklist = nullptr;
for (auto& o : m_observers) {
if (o.table_ndx == m_current_table && o.row_ndx == row) {
m_active_linklist = &get_change(o, col);
break;
}
}
return true;
}
void append_link_list_change(ColumnInfo::Kind kind, size_t index) {
ColumnInfo *o = m_active_linklist;
if (!o || o->kind == ColumnInfo::Kind::SetAll) {
// Active LinkList isn't observed or already has multiple kinds of changes
return;
}
if (o->kind == ColumnInfo::Kind::None) {
o->kind = kind;
o->changed = true;
o->indices.add(index);
}
else if (o->kind == kind) {
if (kind == ColumnInfo::Kind::Remove) {
o->indices.add_shifted(index);
}
else if (kind == ColumnInfo::Kind::Insert) {
o->indices.insert_at(index);
}
else {
o->indices.add(index);
}
}
else {
// Array KVO can only send a single kind of change at a time, so
// if there's multiple just give up and send "Set"
o->indices.set(0);
o->kind = ColumnInfo::Kind::SetAll;
}
}
bool link_list_set(size_t index, size_t)
{
append_link_list_change(ColumnInfo::Kind::Set, index);
return true;
}
bool link_list_insert(size_t index, size_t)
{
append_link_list_change(ColumnInfo::Kind::Insert, index);
return true;
}
bool link_list_erase(size_t index)
{
append_link_list_change(ColumnInfo::Kind::Remove, index);
return true;
}
bool link_list_nullify(size_t index)
{
append_link_list_change(ColumnInfo::Kind::Remove, index);
return true;
}
bool link_list_swap(size_t index1, size_t index2)
{
append_link_list_change(ColumnInfo::Kind::Set, index1);
append_link_list_change(ColumnInfo::Kind::Set, index2);
return true;
}
bool link_list_clear(size_t old_size)
{
ColumnInfo *o = m_active_linklist;
if (!o || o->kind == ColumnInfo::Kind::SetAll) {
return true;
}
if (o->kind == ColumnInfo::Kind::Remove)
old_size += o->indices.size();
else if (o->kind == ColumnInfo::Kind::Insert)
old_size -= o->indices.size();
o->indices.set(old_size);
o->kind = ColumnInfo::Kind::Remove;
o->changed = true;
return true;
}
bool link_list_move(size_t from, size_t to)
{
ColumnInfo *o = m_active_linklist;
if (!o || o->kind == ColumnInfo::Kind::SetAll) {
return true;
}
if (from > to) {
std::swap(from, to);
}
if (o->kind == ColumnInfo::Kind::None) {
o->kind = ColumnInfo::Kind::Set;
o->changed = true;
}
if (o->kind == ColumnInfo::Kind::Set) {
for (size_t i = from; i <= to; ++i)
o->indices.add(i);
}
else {
o->indices.set(0);
o->kind = ColumnInfo::Kind::SetAll;
}
return true;
}
// Things that just mark the field as modified
bool set_int(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); }
bool set_bool(size_t col, size_t row, bool) { return mark_dirty(row, col); }
bool set_float(size_t col, size_t row, float) { return mark_dirty(row, col); }
bool set_double(size_t col, size_t row, double) { return mark_dirty(row, col); }
bool set_string(size_t col, size_t row, StringData) { return mark_dirty(row, col); }
bool set_binary(size_t col, size_t row, BinaryData) { return mark_dirty(row, col); }
bool set_date_time(size_t col, size_t row, DateTime) { return mark_dirty(row, col); }
bool set_table(size_t col, size_t row) { return mark_dirty(row, col); }
bool set_mixed(size_t col, size_t row, const Mixed&) { return mark_dirty(row, col); }
bool set_link(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); }
bool set_null(size_t col, size_t row) { return mark_dirty(row, col); }
bool nullify_link(size_t col, size_t row, size_t) { return mark_dirty(row, col); }
// Doesn't change any data
bool optimize_table() { return true; }
// Used for subtables, which we currently don't support
bool select_descriptor(int, const size_t*) { return false; }
// Not implemented
bool insert_substring(size_t, size_t, size_t, StringData) { return false; }
bool erase_substring(size_t, size_t, size_t, size_t) { return false; }
bool swap_rows(size_t, size_t) { return false; }
bool move_column(size_t, size_t) { return false; }
bool move_group_level_table(size_t, size_t) { return false; }
};
} // anonymous namespace
namespace realm {
namespace _impl {
namespace transaction {
void advance(SharedGroup& sg, ClientHistory& history, RealmBindingContext* binding_context) {
TransactLogHandler(binding_context, sg, [&](auto&&... args) {
LangBindHelper::advance_read(sg, history, std::move(args)...);
});
}
void begin(SharedGroup& sg, ClientHistory& history, RealmBindingContext* binding_context) {
TransactLogHandler(binding_context, sg, [&](auto&&... args) {
LangBindHelper::promote_to_write(sg, history, std::move(args)...);
});
}
void commit(SharedGroup& sg, ClientHistory&, RealmBindingContext* binding_context) {
LangBindHelper::commit_and_continue_as_read(sg);
if (binding_context) {
binding_context->did_change({}, {});
}
}
void cancel(SharedGroup& sg, ClientHistory& history, RealmBindingContext* binding_context) {
TransactLogHandler(binding_context, sg, [&](auto&&... args) {
LangBindHelper::rollback_and_continue_as_read(sg, history, std::move(args)...);
});
}
} // namespace transaction
} // namespace _impl
} // namespace realm