Add 'src/object-store/' from commit '043f5ff4ab77bf4a9177e4643bc097d1ff487347'
git-subtree-dir: src/object-store git-subtree-mainline:12b942fecd
git-subtree-split:043f5ff4ab
This commit is contained in:
commit
58e1064db7
|
@ -0,0 +1,67 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "object_schema.hpp"
|
||||||
|
#include "object_store.hpp"
|
||||||
|
|
||||||
|
#include <realm/group_shared.hpp>
|
||||||
|
#include <realm/link_view.hpp>
|
||||||
|
|
||||||
|
using namespace realm;
|
||||||
|
|
||||||
|
ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) {
|
||||||
|
TableRef tableRef = ObjectStore::table_for_object_type(group, name);
|
||||||
|
Table *table = tableRef.get();
|
||||||
|
|
||||||
|
size_t count = table->get_column_count();
|
||||||
|
properties.reserve(count);
|
||||||
|
for (size_t col = 0; col < count; col++) {
|
||||||
|
Property property;
|
||||||
|
property.name = table->get_column_name(col).data();
|
||||||
|
property.type = (PropertyType)table->get_column_type(col);
|
||||||
|
property.is_indexed = table->has_search_index(col);
|
||||||
|
property.is_primary = false;
|
||||||
|
property.is_nullable = table->is_nullable(col) || property.type == PropertyTypeObject;
|
||||||
|
property.table_column = col;
|
||||||
|
if (property.type == PropertyTypeObject || property.type == PropertyTypeArray) {
|
||||||
|
// set link type for objects and arrays
|
||||||
|
realm::TableRef linkTable = table->get_link_target(col);
|
||||||
|
property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data());
|
||||||
|
}
|
||||||
|
properties.push_back(std::move(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
primary_key = realm::ObjectStore::get_primary_key_for_object(group, name);
|
||||||
|
if (primary_key.length()) {
|
||||||
|
auto primary_key_prop = primary_key_property();
|
||||||
|
if (!primary_key_prop) {
|
||||||
|
throw InvalidPrimaryKeyException(name, primary_key);
|
||||||
|
}
|
||||||
|
primary_key_prop->is_primary = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Property *ObjectSchema::property_for_name(const std::string &name) {
|
||||||
|
for (auto& prop:properties) {
|
||||||
|
if (prop.name == name) {
|
||||||
|
return ∝
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef REALM_OBJECT_SCHEMA_HPP
|
||||||
|
#define REALM_OBJECT_SCHEMA_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "property.hpp"
|
||||||
|
|
||||||
|
namespace realm {
|
||||||
|
class Group;
|
||||||
|
|
||||||
|
class ObjectSchema {
|
||||||
|
public:
|
||||||
|
ObjectSchema() {}
|
||||||
|
|
||||||
|
// create object schema from existing table
|
||||||
|
// if no table is provided it is looked up in the group
|
||||||
|
ObjectSchema(Group *group, const std::string &name);
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::vector<Property> properties;
|
||||||
|
std::string primary_key;
|
||||||
|
|
||||||
|
Property *property_for_name(const std::string &name);
|
||||||
|
Property *primary_key_property() {
|
||||||
|
return property_for_name(primary_key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* defined(REALM_OBJECT_SCHEMA_HPP) */
|
|
@ -0,0 +1,552 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "object_store.hpp"
|
||||||
|
|
||||||
|
#include <realm/group.hpp>
|
||||||
|
#include <realm/table.hpp>
|
||||||
|
#include <realm/link_view.hpp>
|
||||||
|
#include <realm/table_view.hpp>
|
||||||
|
#include <realm/util/assert.hpp>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
using namespace realm;
|
||||||
|
|
||||||
|
const char * const c_metadataTableName = "metadata";
|
||||||
|
const char * const c_versionColumnName = "version";
|
||||||
|
const size_t c_versionColumnIndex = 0;
|
||||||
|
|
||||||
|
const char * const c_primaryKeyTableName = "pk";
|
||||||
|
const char * const c_primaryKeyObjectClassColumnName = "pk_table";
|
||||||
|
const size_t c_primaryKeyObjectClassColumnIndex = 0;
|
||||||
|
const char * const c_primaryKeyPropertyNameColumnName = "pk_property";
|
||||||
|
const size_t c_primaryKeyPropertyNameColumnIndex = 1;
|
||||||
|
|
||||||
|
const size_t c_zeroRowIndex = 0;
|
||||||
|
|
||||||
|
const std::string c_object_table_prefix = "class_";
|
||||||
|
const size_t c_object_table_prefix_length = c_object_table_prefix.length();
|
||||||
|
|
||||||
|
const uint64_t ObjectStore::NotVersioned = std::numeric_limits<uint64_t>::max();
|
||||||
|
|
||||||
|
bool ObjectStore::has_metadata_tables(Group *group) {
|
||||||
|
return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ObjectStore::create_metadata_tables(Group *group) {
|
||||||
|
bool changed = false;
|
||||||
|
TableRef table = group->get_or_add_table(c_primaryKeyTableName);
|
||||||
|
if (table->get_column_count() == 0) {
|
||||||
|
table->add_column(type_String, c_primaryKeyObjectClassColumnName);
|
||||||
|
table->add_column(type_String, c_primaryKeyPropertyNameColumnName);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
table = group->get_or_add_table(c_metadataTableName);
|
||||||
|
if (table->get_column_count() == 0) {
|
||||||
|
table->add_column(type_Int, c_versionColumnName);
|
||||||
|
|
||||||
|
// set initial version
|
||||||
|
table->add_empty_row();
|
||||||
|
table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ObjectStore::get_schema_version(Group *group) {
|
||||||
|
TableRef table = group->get_table(c_metadataTableName);
|
||||||
|
if (!table || table->get_column_count() == 0) {
|
||||||
|
return ObjectStore::NotVersioned;
|
||||||
|
}
|
||||||
|
return table->get_int(c_versionColumnIndex, c_zeroRowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectStore::set_schema_version(Group *group, uint64_t version) {
|
||||||
|
TableRef table = group->get_or_add_table(c_metadataTableName);
|
||||||
|
table->set_int(c_versionColumnIndex, c_zeroRowIndex, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringData ObjectStore::get_primary_key_for_object(Group *group, StringData object_type) {
|
||||||
|
TableRef table = group->get_table(c_primaryKeyTableName);
|
||||||
|
if (!table) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
|
||||||
|
if (row == not_found) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return table->get_string(c_primaryKeyPropertyNameColumnIndex, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectStore::set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key) {
|
||||||
|
TableRef table = group->get_table(c_primaryKeyTableName);
|
||||||
|
|
||||||
|
// get row or create if new object and populate
|
||||||
|
size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
|
||||||
|
if (row == not_found && primary_key.size()) {
|
||||||
|
row = table->add_empty_row();
|
||||||
|
table->set_string(c_primaryKeyObjectClassColumnIndex, row, object_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set if changing, or remove if setting to nil
|
||||||
|
if (primary_key.size() == 0) {
|
||||||
|
if (row != not_found) {
|
||||||
|
table->remove(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ObjectStore::object_type_for_table_name(const std::string &table_name) {
|
||||||
|
if (table_name.size() >= c_object_table_prefix_length && table_name.compare(0, c_object_table_prefix_length, c_object_table_prefix) == 0) {
|
||||||
|
return table_name.substr(c_object_table_prefix_length, table_name.length() - c_object_table_prefix_length);
|
||||||
|
}
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ObjectStore::table_name_for_object_type(const std::string &object_type) {
|
||||||
|
return c_object_table_prefix + object_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) {
|
||||||
|
return group->get_table(table_name_for_object_type(object_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const StringData &object_type, bool &created) {
|
||||||
|
return group->get_or_add_table(table_name_for_object_type(object_type), &created);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool property_has_changed(Property &p1, Property &p2) {
|
||||||
|
return p1.type != p2.type || p1.name != p2.name || p1.object_type != p2.object_type || p1.is_nullable != p2.is_nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectStore::verify_schema(Group *group, Schema &target_schema, bool allow_missing_tables) {
|
||||||
|
std::vector<ObjectSchemaValidationException> errors;
|
||||||
|
for (auto &object_schema : target_schema) {
|
||||||
|
if (!table_for_object_type(group, object_schema.first)) {
|
||||||
|
if (!allow_missing_tables) {
|
||||||
|
errors.emplace_back(ObjectSchemaValidationException(object_schema.first,
|
||||||
|
"Missing table for object type '" + object_schema.first + "'."));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto more_errors = verify_object_schema(group, object_schema.second, target_schema);
|
||||||
|
errors.insert(errors.end(), more_errors.begin(), more_errors.end());
|
||||||
|
}
|
||||||
|
if (errors.size()) {
|
||||||
|
throw SchemaValidationException(errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ObjectSchemaValidationException> ObjectStore::verify_object_schema(Group *group, ObjectSchema &target_schema, Schema &schema) {
|
||||||
|
std::vector<ObjectSchemaValidationException> exceptions;
|
||||||
|
ObjectSchema table_schema(group, target_schema.name);
|
||||||
|
|
||||||
|
// check to see if properties are the same
|
||||||
|
Property *primary = nullptr;
|
||||||
|
for (auto& current_prop : table_schema.properties) {
|
||||||
|
auto target_prop = target_schema.property_for_name(current_prop.name);
|
||||||
|
|
||||||
|
if (!target_prop) {
|
||||||
|
exceptions.emplace_back(MissingPropertyException(table_schema.name, current_prop));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (property_has_changed(current_prop, *target_prop)) {
|
||||||
|
exceptions.emplace_back(MismatchedPropertiesException(table_schema.name, current_prop, *target_prop));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check object_type existence
|
||||||
|
if (current_prop.object_type.length() && schema.find(current_prop.object_type) == schema.end()) {
|
||||||
|
exceptions.emplace_back(MissingObjectTypeException(table_schema.name, current_prop));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check nullablity
|
||||||
|
if (current_prop.type == PropertyTypeObject) {
|
||||||
|
if (!current_prop.is_nullable) {
|
||||||
|
exceptions.emplace_back(InvalidNullabilityException(table_schema.name, current_prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (current_prop.is_nullable) {
|
||||||
|
exceptions.emplace_back(InvalidNullabilityException(table_schema.name, current_prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check primary keys
|
||||||
|
if (current_prop.is_primary) {
|
||||||
|
if (primary) {
|
||||||
|
exceptions.emplace_back(DuplicatePrimaryKeysException(table_schema.name));
|
||||||
|
}
|
||||||
|
primary = ¤t_prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check indexable
|
||||||
|
if (current_prop.is_indexed) {
|
||||||
|
if (current_prop.type != PropertyTypeString && current_prop.type != PropertyTypeInt) {
|
||||||
|
exceptions.emplace_back(PropertyTypeNotIndexableException(table_schema.name, current_prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new property with aligned column
|
||||||
|
target_prop->table_column = current_prop.table_column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for change to primary key
|
||||||
|
if (table_schema.primary_key != target_schema.primary_key) {
|
||||||
|
exceptions.emplace_back(ChangedPrimaryKeyException(table_schema.name, table_schema.primary_key, target_schema.primary_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for new missing properties
|
||||||
|
for (auto& target_prop : target_schema.properties) {
|
||||||
|
if (!table_schema.property_for_name(target_prop.name)) {
|
||||||
|
exceptions.emplace_back(ExtraPropertyException(table_schema.name, target_prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectStore::update_column_mapping(Group *group, ObjectSchema &target_schema) {
|
||||||
|
ObjectSchema table_schema(group, target_schema.name);
|
||||||
|
for (auto& target_prop : target_schema.properties) {
|
||||||
|
auto table_prop = table_schema.property_for_name(target_prop.name);
|
||||||
|
REALM_ASSERT_DEBUG(table_prop);
|
||||||
|
|
||||||
|
target_prop.table_column = table_prop->table_column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set references to tables on targetSchema and create/update any missing or out-of-date tables
|
||||||
|
// if update existing is true, updates existing tables, otherwise validates existing tables
|
||||||
|
// NOTE: must be called from within write transaction
|
||||||
|
bool ObjectStore::create_tables(Group *group, Schema &target_schema, bool update_existing) {
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
// first pass to create missing tables
|
||||||
|
std::vector<ObjectSchema *> to_update;
|
||||||
|
for (auto& object_schema : target_schema) {
|
||||||
|
bool created = false;
|
||||||
|
ObjectStore::table_for_object_type_create_if_needed(group, object_schema.first, created);
|
||||||
|
|
||||||
|
// we will modify tables for any new objectSchema (table was created) or for all if update_existing is true
|
||||||
|
if (update_existing || created) {
|
||||||
|
to_update.push_back(&object_schema.second);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// second pass adds/removes columns for out of date tables
|
||||||
|
for (auto& target_object_schema : to_update) {
|
||||||
|
TableRef table = table_for_object_type(group, target_object_schema->name);
|
||||||
|
ObjectSchema current_schema(group, target_object_schema->name);
|
||||||
|
std::vector<Property> &target_props = target_object_schema->properties;
|
||||||
|
|
||||||
|
// add missing columns
|
||||||
|
for (auto& target_prop : target_props) {
|
||||||
|
auto current_prop = current_schema.property_for_name(target_prop.name);
|
||||||
|
|
||||||
|
// add any new properties (new name or different type)
|
||||||
|
if (!current_prop || property_has_changed(*current_prop, target_prop)) {
|
||||||
|
switch (target_prop.type) {
|
||||||
|
// for objects and arrays, we have to specify target table
|
||||||
|
case PropertyTypeObject:
|
||||||
|
case PropertyTypeArray: {
|
||||||
|
TableRef link_table = ObjectStore::table_for_object_type(group, target_prop.object_type);
|
||||||
|
target_prop.table_column = table->add_column_link(DataType(target_prop.type), target_prop.name, *link_table);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove extra columns
|
||||||
|
sort(begin(current_schema.properties), end(current_schema.properties), [](Property &i, Property &j) {
|
||||||
|
return j.table_column < i.table_column;
|
||||||
|
});
|
||||||
|
for (auto& current_prop : current_schema.properties) {
|
||||||
|
auto target_prop = target_object_schema->property_for_name(current_prop.name);
|
||||||
|
if (!target_prop || property_has_changed(current_prop, *target_prop)) {
|
||||||
|
table->remove_column(current_prop.table_column);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update table metadata
|
||||||
|
if (target_object_schema->primary_key.length()) {
|
||||||
|
// if there is a primary key set, check if it is the same as the old key
|
||||||
|
if (current_schema.primary_key != target_object_schema->primary_key) {
|
||||||
|
set_primary_key_for_object(group, target_object_schema->name, target_object_schema->primary_key);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (current_schema.primary_key.length()) {
|
||||||
|
// there is no primary key, so if there was one nil out
|
||||||
|
set_primary_key_for_object(group, target_object_schema->name, "");
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ObjectStore::is_schema_at_version(Group *group, uint64_t version) {
|
||||||
|
uint64_t old_version = get_schema_version(group);
|
||||||
|
if (old_version > version && old_version != NotVersioned) {
|
||||||
|
throw InvalidSchemaVersionException(old_version, version);
|
||||||
|
}
|
||||||
|
return old_version == version;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ObjectStore::realm_requires_update(Group *group, uint64_t version, Schema &schema) {
|
||||||
|
if (!is_schema_at_version(group, version)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (auto& target_schema : schema) {
|
||||||
|
TableRef table = table_for_object_type(group, target_schema.first);
|
||||||
|
if (!table) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!indexes_are_up_to_date(group, schema)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ObjectStore::update_realm_with_schema(Group *group,
|
||||||
|
uint64_t version,
|
||||||
|
Schema &schema,
|
||||||
|
MigrationFunction migration) {
|
||||||
|
// Recheck the schema version after beginning the write transaction as
|
||||||
|
// another process may have done the migration after we opened the read
|
||||||
|
// transaction
|
||||||
|
bool migrating = !is_schema_at_version(group, version);
|
||||||
|
|
||||||
|
// create tables
|
||||||
|
bool changed = create_metadata_tables(group);
|
||||||
|
changed = create_tables(group, schema, migrating) || changed;
|
||||||
|
|
||||||
|
verify_schema(group, schema);
|
||||||
|
|
||||||
|
changed = update_indexes(group, schema) || changed;
|
||||||
|
|
||||||
|
if (!migrating) {
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the migration block if provided and there's any old data
|
||||||
|
if (get_schema_version(group) != ObjectStore::NotVersioned) {
|
||||||
|
migration(group, schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_primary_column_uniqueness(group, schema);
|
||||||
|
|
||||||
|
set_schema_version(group, version);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema ObjectStore::schema_from_group(Group *group) {
|
||||||
|
Schema schema;
|
||||||
|
for (size_t i = 0; i < group->size(); i++) {
|
||||||
|
std::string object_type = object_type_for_table_name(group->get_table_name(i));
|
||||||
|
if (object_type.length()) {
|
||||||
|
schema.emplace(object_type, std::move(ObjectSchema(group, object_type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ObjectStore::indexes_are_up_to_date(Group *group, Schema &schema) {
|
||||||
|
for (auto &object_schema : schema) {
|
||||||
|
TableRef table = table_for_object_type(group, object_schema.first);
|
||||||
|
if (!table) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_column_mapping(group, object_schema.second);
|
||||||
|
for (auto& property : object_schema.second.properties) {
|
||||||
|
if (property.requires_index() != table->has_search_index(property.table_column)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ObjectStore::update_indexes(Group *group, Schema &schema) {
|
||||||
|
bool changed = false;
|
||||||
|
for (auto& object_schema : schema) {
|
||||||
|
TableRef table = table_for_object_type(group, object_schema.first);
|
||||||
|
if (!table) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& property : object_schema.second.properties) {
|
||||||
|
if (property.requires_index() == table->has_search_index(property.table_column)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
if (property.requires_index()) {
|
||||||
|
try {
|
||||||
|
table->add_search_index(property.table_column);
|
||||||
|
}
|
||||||
|
catch (LogicError const&) {
|
||||||
|
throw PropertyTypeNotIndexableException(object_schema.first, property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
table->remove_search_index(property.table_column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schema) {
|
||||||
|
for (auto& object_schema : schema) {
|
||||||
|
auto primary_prop = object_schema.second.primary_key_property();
|
||||||
|
if (!primary_prop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableRef table = table_for_object_type(group, object_schema.first);
|
||||||
|
if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) {
|
||||||
|
throw DuplicatePrimaryKeyValueException(object_schema.first, *primary_prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectStore::delete_data_for_object(Group *group, const StringData &object_type) {
|
||||||
|
TableRef table = table_for_object_type(group, object_type);
|
||||||
|
if (table) {
|
||||||
|
group->remove_table(table->get_index_in_group());
|
||||||
|
set_primary_key_for_object(group, object_type, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version) :
|
||||||
|
m_old_version(old_version), m_new_version(new_version)
|
||||||
|
{
|
||||||
|
m_what = "Provided schema version " + std::to_string(old_version) + " is less than last set version " + std::to_string(new_version) + ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string object_type, Property &property) :
|
||||||
|
m_object_type(object_type), m_property(property)
|
||||||
|
{
|
||||||
|
m_what = "Primary key property '" + property.name + "' has duplicate values after migration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> errors) :
|
||||||
|
m_validation_errors(errors)
|
||||||
|
{
|
||||||
|
m_what ="Migration is required due to the following errors: ";
|
||||||
|
for (auto error : errors) {
|
||||||
|
m_what += std::string("\n- ") + error.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyTypeNotIndexableException::PropertyTypeNotIndexableException(std::string object_type, Property &property) :
|
||||||
|
ObjectSchemaPropertyException(object_type, property)
|
||||||
|
{
|
||||||
|
m_what = "Can't index property " + object_type + "." + property.name + ": indexing a property of type '" + string_for_property_type(property.type) + "' is currently not supported";
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtraPropertyException::ExtraPropertyException(std::string object_type, Property &property) :
|
||||||
|
ObjectSchemaPropertyException(object_type, property)
|
||||||
|
{
|
||||||
|
m_what = "Property '" + property.name + "' has been added to latest object model.";
|
||||||
|
}
|
||||||
|
|
||||||
|
MissingPropertyException::MissingPropertyException(std::string object_type, Property &property) :
|
||||||
|
ObjectSchemaPropertyException(object_type, property)
|
||||||
|
{
|
||||||
|
m_what = "Property '" + property.name + "' is missing from latest object model.";
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidNullabilityException::InvalidNullabilityException(std::string object_type, Property &property) :
|
||||||
|
ObjectSchemaPropertyException(object_type, property)
|
||||||
|
{
|
||||||
|
if (property.type == PropertyTypeObject) {
|
||||||
|
if (!property.is_nullable) {
|
||||||
|
m_what = "'Object' property '" + property.name + "' must be nullable.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (property.is_nullable) {
|
||||||
|
m_what = "Only 'Object' property types are nullable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MissingObjectTypeException::MissingObjectTypeException(std::string object_type, Property &property) :
|
||||||
|
ObjectSchemaPropertyException(object_type, property)
|
||||||
|
{
|
||||||
|
m_what = "Target type '" + property.object_type + "' doesn't exist for property '" + property.name + "'.";
|
||||||
|
}
|
||||||
|
|
||||||
|
MismatchedPropertiesException::MismatchedPropertiesException(std::string object_type, Property &old_property, Property &new_property) :
|
||||||
|
ObjectSchemaValidationException(object_type), m_old_property(old_property), m_new_property(new_property)
|
||||||
|
{
|
||||||
|
if (new_property.type != old_property.type) {
|
||||||
|
m_what = "Property types for '" + old_property.name + "' property do not match. Old type '" + string_for_property_type(old_property.type) +
|
||||||
|
"', new type '" + string_for_property_type(new_property.type) + "'";
|
||||||
|
}
|
||||||
|
else if (new_property.object_type != old_property.object_type) {
|
||||||
|
m_what = "Target object type for property '" + old_property.name + "' do not match. Old type '" + old_property.object_type + "', new type '" + new_property.object_type + "'";
|
||||||
|
}
|
||||||
|
else if (new_property.is_nullable != old_property.is_nullable) {
|
||||||
|
m_what = "Nullability for property '" + old_property.name + "' has changed from '" + std::to_string(old_property.is_nullable) + "' to '" + std::to_string(new_property.is_nullable) + "'.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangedPrimaryKeyException::ChangedPrimaryKeyException(std::string object_type, std::string old_primary, std::string new_primary) : ObjectSchemaValidationException(object_type), m_old_primary(old_primary), m_new_primary(new_primary)
|
||||||
|
{
|
||||||
|
if (old_primary.size()) {
|
||||||
|
m_what = "Property '" + old_primary + "' is no longer a primary key.";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_what = "Property '" + new_primary + "' has been made a primary key.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidPrimaryKeyException::InvalidPrimaryKeyException(std::string object_type, std::string primary) :
|
||||||
|
ObjectSchemaValidationException(object_type), m_primary_key(primary)
|
||||||
|
{
|
||||||
|
m_what = "Specified primary key property '" + primary + "' does not exist.";
|
||||||
|
}
|
||||||
|
|
||||||
|
DuplicatePrimaryKeysException::DuplicatePrimaryKeysException(std::string object_type) : ObjectSchemaValidationException(object_type)
|
||||||
|
{
|
||||||
|
m_what = "Duplicate primary keys for object '" + object_type + "'.";
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef REALM_OBJECT_STORE_HPP
|
||||||
|
#define REALM_OBJECT_STORE_HPP
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <realm/link_view.hpp>
|
||||||
|
#include <realm/group.hpp>
|
||||||
|
|
||||||
|
#include "object_schema.hpp"
|
||||||
|
|
||||||
|
namespace realm {
|
||||||
|
class ObjectSchemaValidationException;
|
||||||
|
class Schema : public std::map<std::string, ObjectSchema> {
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectStore {
|
||||||
|
public:
|
||||||
|
// Schema version used for uninitialized Realms
|
||||||
|
static const uint64_t NotVersioned;
|
||||||
|
|
||||||
|
// get the last set schema version
|
||||||
|
static uint64_t get_schema_version(Group *group);
|
||||||
|
|
||||||
|
// checks if the schema in the group is at the given version
|
||||||
|
static bool is_schema_at_version(realm::Group *group, uint64_t version);
|
||||||
|
|
||||||
|
// verify a target schema against tables in the given group
|
||||||
|
// updates the column mapping on all ObjectSchema properties
|
||||||
|
// throws if the schema is invalid or does not match tables in the given group
|
||||||
|
static void verify_schema(Group *group, Schema &target_schema, bool allow_missing_tables = false);
|
||||||
|
|
||||||
|
// updates the target_column member for all properties based on the column indexes in the passed in group
|
||||||
|
static void update_column_mapping(Group *group, ObjectSchema &target_schema);
|
||||||
|
|
||||||
|
// determines if you must call update_realm_with_schema for a given realm.
|
||||||
|
// returns true if there is a schema version mismatch, if there tables which still need to be created,
|
||||||
|
// or if file format or other changes/updates need to be made
|
||||||
|
static bool realm_requires_update(Group *group, uint64_t version, Schema &schema);
|
||||||
|
|
||||||
|
// updates a Realm to a given target schema/version creating tables and updating indexes as necessary
|
||||||
|
// returns if any changes were made
|
||||||
|
// passed in schema ar updated with the correct column mapping
|
||||||
|
// optionally runs migration function/lambda if schema is out of date
|
||||||
|
// NOTE: must be performed within a write transaction
|
||||||
|
typedef std::function<void(Group *, Schema &)> MigrationFunction;
|
||||||
|
static bool update_realm_with_schema(Group *group, uint64_t version, Schema &schema, MigrationFunction migration);
|
||||||
|
|
||||||
|
// get a table for an object type
|
||||||
|
static realm::TableRef table_for_object_type(Group *group, StringData object_type);
|
||||||
|
|
||||||
|
// get existing Schema from a group
|
||||||
|
static Schema schema_from_group(Group *group);
|
||||||
|
|
||||||
|
// deletes the table for the given type
|
||||||
|
static void delete_data_for_object(Group *group, const StringData &object_type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// set a new schema version
|
||||||
|
static void set_schema_version(Group *group, uint64_t version);
|
||||||
|
|
||||||
|
// check if the realm already has all metadata tables
|
||||||
|
static bool has_metadata_tables(Group *group);
|
||||||
|
|
||||||
|
// create any metadata tables that don't already exist
|
||||||
|
// must be in write transaction to set
|
||||||
|
// returns true if it actually did anything
|
||||||
|
static bool create_metadata_tables(Group *group);
|
||||||
|
|
||||||
|
// set references to tables on targetSchema and create/update any missing or out-of-date tables
|
||||||
|
// if update existing is true, updates existing tables, otherwise only adds and initializes new tables
|
||||||
|
static bool create_tables(realm::Group *group, Schema &target_schema, bool update_existing);
|
||||||
|
|
||||||
|
// verify a target schema against its table, setting the table_column property on each schema object
|
||||||
|
// updates the column mapping on the target_schema
|
||||||
|
// returns array of validation errors
|
||||||
|
static std::vector<ObjectSchemaValidationException> verify_object_schema(Group *group, ObjectSchema &target_schema, Schema &schema);
|
||||||
|
|
||||||
|
// get primary key property name for object type
|
||||||
|
static StringData get_primary_key_for_object(Group *group, StringData object_type);
|
||||||
|
|
||||||
|
// sets primary key property for object type
|
||||||
|
// must be in write transaction to set
|
||||||
|
static void set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key);
|
||||||
|
|
||||||
|
static TableRef table_for_object_type_create_if_needed(Group *group, const StringData &object_type, bool &created);
|
||||||
|
static std::string table_name_for_object_type(const std::string &class_name);
|
||||||
|
static std::string object_type_for_table_name(const std::string &table_name);
|
||||||
|
|
||||||
|
// check if indexes are up to date - if false you need to call update_realm_with_schema
|
||||||
|
static bool indexes_are_up_to_date(Group *group, Schema &schema);
|
||||||
|
|
||||||
|
// returns if any indexes were changed
|
||||||
|
static bool update_indexes(Group *group, Schema &schema);
|
||||||
|
|
||||||
|
// validates that all primary key properties have unique values
|
||||||
|
static void validate_primary_column_uniqueness(Group *group, Schema &schema);
|
||||||
|
|
||||||
|
friend ObjectSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base exception
|
||||||
|
class ObjectStoreException : public std::exception {
|
||||||
|
public:
|
||||||
|
ObjectStoreException() = default;
|
||||||
|
ObjectStoreException(const std::string &what) : m_what(what) {}
|
||||||
|
virtual const char* what() const noexcept { return m_what.c_str(); }
|
||||||
|
protected:
|
||||||
|
std::string m_what;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Migration exceptions
|
||||||
|
class MigrationException : public ObjectStoreException {};
|
||||||
|
|
||||||
|
class InvalidSchemaVersionException : public MigrationException {
|
||||||
|
public:
|
||||||
|
InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version);
|
||||||
|
uint64_t old_version() { return m_old_version; }
|
||||||
|
uint64_t new_version() { return m_new_version; }
|
||||||
|
private:
|
||||||
|
uint64_t m_old_version, m_new_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DuplicatePrimaryKeyValueException : public MigrationException {
|
||||||
|
public:
|
||||||
|
DuplicatePrimaryKeyValueException(std::string object_type, Property &property);
|
||||||
|
std::string object_type() { return m_object_type; }
|
||||||
|
Property &property() { return m_property; }
|
||||||
|
private:
|
||||||
|
std::string m_object_type;
|
||||||
|
Property m_property;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Schema validation exceptions
|
||||||
|
class SchemaValidationException : public ObjectStoreException {
|
||||||
|
public:
|
||||||
|
SchemaValidationException(std::vector<ObjectSchemaValidationException> errors);
|
||||||
|
std::vector<ObjectSchemaValidationException> &validation_errors() { return m_validation_errors; }
|
||||||
|
private:
|
||||||
|
std::vector<ObjectSchemaValidationException> m_validation_errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectSchemaValidationException : public ObjectStoreException {
|
||||||
|
public:
|
||||||
|
ObjectSchemaValidationException(std::string object_type) : m_object_type(object_type) {}
|
||||||
|
ObjectSchemaValidationException(std::string object_type, std::string message) :
|
||||||
|
m_object_type(object_type) { m_what = message; }
|
||||||
|
std::string object_type() { return m_object_type; }
|
||||||
|
protected:
|
||||||
|
std::string m_object_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectSchemaPropertyException : public ObjectSchemaValidationException {
|
||||||
|
public:
|
||||||
|
ObjectSchemaPropertyException(std::string object_type, Property &property) :
|
||||||
|
ObjectSchemaValidationException(object_type), m_property(property) {}
|
||||||
|
Property &property() { return m_property; }
|
||||||
|
private:
|
||||||
|
Property m_property;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PropertyTypeNotIndexableException : public ObjectSchemaPropertyException {
|
||||||
|
public:
|
||||||
|
PropertyTypeNotIndexableException(std::string object_type, Property &property);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExtraPropertyException : public ObjectSchemaPropertyException {
|
||||||
|
public:
|
||||||
|
ExtraPropertyException(std::string object_type, Property &property);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MissingPropertyException : public ObjectSchemaPropertyException {
|
||||||
|
public:
|
||||||
|
MissingPropertyException(std::string object_type, Property &property);
|
||||||
|
};
|
||||||
|
|
||||||
|
class InvalidNullabilityException : public ObjectSchemaPropertyException {
|
||||||
|
public:
|
||||||
|
InvalidNullabilityException(std::string object_type, Property &property);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MissingObjectTypeException : public ObjectSchemaPropertyException {
|
||||||
|
public:
|
||||||
|
MissingObjectTypeException(std::string object_type, Property &property);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DuplicatePrimaryKeysException : public ObjectSchemaValidationException {
|
||||||
|
public:
|
||||||
|
DuplicatePrimaryKeysException(std::string object_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MismatchedPropertiesException : public ObjectSchemaValidationException {
|
||||||
|
public:
|
||||||
|
MismatchedPropertiesException(std::string object_type, Property &old_property, Property &new_property);
|
||||||
|
Property &old_property() { return m_old_property; }
|
||||||
|
Property &new_property() { return m_new_property; }
|
||||||
|
private:
|
||||||
|
Property m_old_property, m_new_property;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChangedPrimaryKeyException : public ObjectSchemaValidationException {
|
||||||
|
public:
|
||||||
|
ChangedPrimaryKeyException(std::string object_type, std::string old_primary, std::string new_primary);
|
||||||
|
std::string old_primary() { return m_old_primary; }
|
||||||
|
std::string new_primary() { return m_new_primary; }
|
||||||
|
private:
|
||||||
|
std::string m_old_primary, m_new_primary;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InvalidPrimaryKeyException : public ObjectSchemaValidationException {
|
||||||
|
public:
|
||||||
|
InvalidPrimaryKeyException(std::string object_type, std::string primary_key);
|
||||||
|
std::string primary_key() { return m_primary_key; }
|
||||||
|
private:
|
||||||
|
std::string m_primary_key;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* defined(REALM_OBJECT_STORE_HPP) */
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef REALM_PROPERTY_HPP
|
||||||
|
#define REALM_PROPERTY_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace realm {
|
||||||
|
enum PropertyType {
|
||||||
|
/** Integer type: NSInteger, int, long, Int (Swift) */
|
||||||
|
PropertyTypeInt = 0,
|
||||||
|
/** Boolean type: BOOL, bool, Bool (Swift) */
|
||||||
|
PropertyTypeBool = 1,
|
||||||
|
/** Float type: CGFloat (32bit), float, Float (Swift) */
|
||||||
|
PropertyTypeFloat = 9,
|
||||||
|
/** Double type: CGFloat (64bit), double, Double (Swift) */
|
||||||
|
PropertyTypeDouble = 10,
|
||||||
|
/** String type: NSString, String (Swift) */
|
||||||
|
PropertyTypeString = 2,
|
||||||
|
/** Data type: NSData */
|
||||||
|
PropertyTypeData = 4,
|
||||||
|
/** Any type: id, **not supported in Swift** */
|
||||||
|
PropertyTypeAny = 6,
|
||||||
|
/** Date type: NSDate */
|
||||||
|
PropertyTypeDate = 7,
|
||||||
|
/** Object type. See [Realm Models](http://realm.io/docs/cocoa/latest/#models) */
|
||||||
|
PropertyTypeObject = 12,
|
||||||
|
/** Array type. See [Realm Models](http://realm.io/docs/cocoa/latest/#models) */
|
||||||
|
PropertyTypeArray = 13,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Property {
|
||||||
|
public:
|
||||||
|
Property() : object_type(""), is_primary(false), is_indexed(false), is_nullable(false) {}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
PropertyType type;
|
||||||
|
std::string object_type;
|
||||||
|
bool is_primary;
|
||||||
|
bool is_indexed;
|
||||||
|
bool is_nullable;
|
||||||
|
|
||||||
|
size_t table_column;
|
||||||
|
bool requires_index() { return is_primary || is_indexed; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline const char *string_for_property_type(PropertyType type) {
|
||||||
|
switch (type) {
|
||||||
|
case PropertyTypeString:
|
||||||
|
return "string";
|
||||||
|
case PropertyTypeInt:
|
||||||
|
return "int";
|
||||||
|
case PropertyTypeBool:
|
||||||
|
return "bool";
|
||||||
|
case PropertyTypeDate:
|
||||||
|
return "date";
|
||||||
|
case PropertyTypeData:
|
||||||
|
return "data";
|
||||||
|
case PropertyTypeDouble:
|
||||||
|
return "double";
|
||||||
|
case PropertyTypeFloat:
|
||||||
|
return "float";
|
||||||
|
case PropertyTypeAny:
|
||||||
|
return "any";
|
||||||
|
case PropertyTypeObject:
|
||||||
|
return "object";
|
||||||
|
case PropertyTypeArray:
|
||||||
|
return "array";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* REALM_PROPERTY_HPP */
|
|
@ -0,0 +1,399 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 "shared_realm.hpp"
|
||||||
|
#include <realm/commit_log.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace realm;
|
||||||
|
|
||||||
|
RealmCache Realm::s_global_cache;
|
||||||
|
std::mutex Realm::s_init_mutex;
|
||||||
|
|
||||||
|
Realm::Config::Config(const Config& c) : path(c.path), read_only(c.read_only), in_memory(c.in_memory), schema_version(c.schema_version), encryption_key(c.encryption_key), migration_function(c.migration_function)
|
||||||
|
{
|
||||||
|
if (c.schema) {
|
||||||
|
schema = std::make_unique<Schema>(*c.schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Realm::Realm(Config &config) : m_config(config), m_thread_id(std::this_thread::get_id()), m_auto_refresh(true), m_in_transaction(false)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (config.read_only) {
|
||||||
|
m_read_only_group = std::make_unique<Group>(config.path, config.encryption_key.data(), Group::mode_ReadOnly);
|
||||||
|
m_group = m_read_only_group.get();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_history = realm::make_client_history(config.path, config.encryption_key.data());
|
||||||
|
SharedGroup::DurabilityLevel durability = config.in_memory ? SharedGroup::durability_MemOnly :
|
||||||
|
SharedGroup::durability_Full;
|
||||||
|
m_shared_group = std::make_unique<SharedGroup>(*m_history, durability, config.encryption_key.data());
|
||||||
|
m_group = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (util::File::PermissionDenied const& ex) {
|
||||||
|
throw RealmFileException(RealmFileException::Kind::PermissionDenied, "Unable to open a realm at path '" + config.path +
|
||||||
|
"'. Please use a path where your app has " + (config.read_only ? "read" : "read-write") + " permissions.");
|
||||||
|
}
|
||||||
|
catch (util::File::Exists const& ex) {
|
||||||
|
throw RealmFileException(RealmFileException::Kind::Exists, "Unable to open a realm at path '" + config.path + "'");
|
||||||
|
}
|
||||||
|
catch (util::File::AccessError const& ex) {
|
||||||
|
throw RealmFileException(RealmFileException::Kind::AccessError, "Unable to open a realm at path '" + config.path + "'");
|
||||||
|
}
|
||||||
|
catch (IncompatibleLockFile const&) {
|
||||||
|
throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, "Realm file is currently open in another process "
|
||||||
|
"which cannot share access with this process. All processes sharing a single file must be the same architecture.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Realm::~Realm()
|
||||||
|
{
|
||||||
|
s_global_cache.remove(m_config.path, m_thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Group *Realm::read_group()
|
||||||
|
{
|
||||||
|
if (!m_group) {
|
||||||
|
m_group = &const_cast<Group&>(m_shared_group->begin_read());
|
||||||
|
}
|
||||||
|
return m_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedRealm Realm::get_shared_realm(Config &config)
|
||||||
|
{
|
||||||
|
SharedRealm realm = s_global_cache.get_realm(config.path);
|
||||||
|
if (realm) {
|
||||||
|
if (realm->config().read_only != config.read_only) {
|
||||||
|
throw MismatchedConfigException("Realm at path already opened with different read permissions.");
|
||||||
|
}
|
||||||
|
if (realm->config().in_memory != config.in_memory) {
|
||||||
|
throw MismatchedConfigException("Realm at path already opened with different inMemory settings.");
|
||||||
|
}
|
||||||
|
if (realm->config().encryption_key != config.encryption_key) {
|
||||||
|
throw MismatchedConfigException("Realm at path already opened with a different encryption key.");
|
||||||
|
}
|
||||||
|
if (realm->config().schema_version != config.schema_version && config.schema_version != ObjectStore::NotVersioned) {
|
||||||
|
throw MismatchedConfigException("Realm at path already opened with different schema version.");
|
||||||
|
}
|
||||||
|
// FIXME - enable schma comparison
|
||||||
|
/*if (realm->config().schema != config.schema) {
|
||||||
|
throw MismatchedConfigException("Realm at path already opened with different schema");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
realm->m_config.migration_function = config.migration_function;
|
||||||
|
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
realm = SharedRealm(new Realm(config));
|
||||||
|
|
||||||
|
// we want to ensure we are only initializing a single realm at a time
|
||||||
|
std::lock_guard<std::mutex> lock(s_init_mutex);
|
||||||
|
|
||||||
|
uint64_t old_version = ObjectStore::get_schema_version(realm->read_group());
|
||||||
|
if (!realm->m_config.schema) {
|
||||||
|
// get schema from group and skip validation
|
||||||
|
realm->m_config.schema_version = old_version;
|
||||||
|
realm->m_config.schema = std::make_unique<Schema>(ObjectStore::schema_from_group(realm->read_group()));
|
||||||
|
}
|
||||||
|
else if (realm->m_config.read_only) {
|
||||||
|
if (old_version == ObjectStore::NotVersioned) {
|
||||||
|
throw UnitializedRealmException("Can't open an un-initizliazed Realm without a Schema");
|
||||||
|
}
|
||||||
|
ObjectStore::verify_schema(realm->read_group(), *realm->m_config.schema, true);
|
||||||
|
}
|
||||||
|
else if(auto existing = s_global_cache.get_any_realm(realm->config().path)) {
|
||||||
|
// if there is an existing realm at the current path steal its schema/column mapping
|
||||||
|
// FIXME - need to validate that schemas match
|
||||||
|
realm->m_config.schema = std::make_unique<Schema>(*existing->m_config.schema);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// its a non-cached realm so update/migrate if needed
|
||||||
|
realm->update_schema(*realm->m_config.schema, realm->m_config.schema_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_global_cache.cache_realm(realm, realm->m_thread_id);
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Realm::update_schema(Schema &schema, uint64_t version)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
Config old_config(m_config);
|
||||||
|
|
||||||
|
// set new version/schema
|
||||||
|
if (m_config.schema.get() != &schema) {
|
||||||
|
m_config.schema = std::make_unique<Schema>(schema);
|
||||||
|
}
|
||||||
|
m_config.schema_version = version;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!m_config.read_only && ObjectStore::realm_requires_update(read_group(), version, schema)) {
|
||||||
|
// keep old copy to pass to migration function
|
||||||
|
old_config.read_only = true;
|
||||||
|
SharedRealm old_realm = SharedRealm(new Realm(old_config)), updated_realm = shared_from_this();
|
||||||
|
|
||||||
|
// update and migrate
|
||||||
|
begin_transaction();
|
||||||
|
changed = ObjectStore::update_realm_with_schema(read_group(), version, *m_config.schema, [=](__unused Group *group, __unused Schema &target_schema) {
|
||||||
|
m_config.migration_function(old_realm, updated_realm);
|
||||||
|
});
|
||||||
|
commit_transaction();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ObjectStore::verify_schema(read_group(), *m_config.schema, m_config.read_only);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
if (is_in_transaction()) {
|
||||||
|
cancel_transaction();
|
||||||
|
}
|
||||||
|
m_config.schema_version = old_config.schema_version;
|
||||||
|
m_config.schema = std::move(old_config.schema);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_read_write(Realm *realm)
|
||||||
|
{
|
||||||
|
if (realm->config().read_only) {
|
||||||
|
throw InvalidTransactionException("Can't perform transactions on read-only Realms.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Realm::verify_thread()
|
||||||
|
{
|
||||||
|
if (m_thread_id != std::this_thread::get_id()) {
|
||||||
|
throw IncorrectThreadException("Realm accessed from incorrect thread.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Realm::begin_transaction()
|
||||||
|
{
|
||||||
|
check_read_write(this);
|
||||||
|
verify_thread();
|
||||||
|
|
||||||
|
if (m_in_transaction) {
|
||||||
|
throw InvalidTransactionException("The Realm is already in a write transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the upgrade to write will move the transaction forward, announce the change after promoting
|
||||||
|
bool announce = m_shared_group->has_changed();
|
||||||
|
|
||||||
|
// make sure we have a read transaction
|
||||||
|
read_group();
|
||||||
|
|
||||||
|
LangBindHelper::promote_to_write(*m_shared_group, *m_history);
|
||||||
|
m_in_transaction = true;
|
||||||
|
|
||||||
|
if (announce) {
|
||||||
|
send_local_notifications(DidChangeNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Realm::commit_transaction()
|
||||||
|
{
|
||||||
|
check_read_write(this);
|
||||||
|
verify_thread();
|
||||||
|
|
||||||
|
if (!m_in_transaction) {
|
||||||
|
throw InvalidTransactionException("Can't commit a non-existing write transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
LangBindHelper::commit_and_continue_as_read(*m_shared_group);
|
||||||
|
m_in_transaction = false;
|
||||||
|
|
||||||
|
send_external_notifications();
|
||||||
|
send_local_notifications(DidChangeNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Realm::cancel_transaction()
|
||||||
|
{
|
||||||
|
check_read_write(this);
|
||||||
|
verify_thread();
|
||||||
|
|
||||||
|
if (!m_in_transaction) {
|
||||||
|
throw InvalidTransactionException("Can't cancel a non-existing write transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
LangBindHelper::rollback_and_continue_as_read(*m_shared_group, *m_history);
|
||||||
|
m_in_transaction = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Realm::invalidate()
|
||||||
|
{
|
||||||
|
verify_thread();
|
||||||
|
check_read_write(this);
|
||||||
|
|
||||||
|
if (m_in_transaction) {
|
||||||
|
cancel_transaction();
|
||||||
|
}
|
||||||
|
if (!m_group) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_shared_group->end_read();
|
||||||
|
m_group = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Realm::compact()
|
||||||
|
{
|
||||||
|
verify_thread();
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
if (m_in_transaction) {
|
||||||
|
throw InvalidTransactionException("Can't compact a Realm within a write transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &object_schema : *m_config.schema) {
|
||||||
|
ObjectStore::table_for_object_type(read_group(), object_schema.first)->optimize();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_shared_group->end_read();
|
||||||
|
success = m_shared_group->compact();
|
||||||
|
m_shared_group->begin_read();
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Realm::notify()
|
||||||
|
{
|
||||||
|
verify_thread();
|
||||||
|
|
||||||
|
if (m_shared_group->has_changed()) { // Throws
|
||||||
|
if (m_auto_refresh) {
|
||||||
|
if (m_group) {
|
||||||
|
LangBindHelper::advance_read(*m_shared_group, *m_history);
|
||||||
|
}
|
||||||
|
send_local_notifications(DidChangeNotification);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
send_local_notifications(RefreshRequiredNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Realm::send_local_notifications(const std::string &type)
|
||||||
|
{
|
||||||
|
verify_thread();
|
||||||
|
for (NotificationFunction notification : m_notifications) {
|
||||||
|
(*notification)(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Realm::refresh()
|
||||||
|
{
|
||||||
|
verify_thread();
|
||||||
|
check_read_write(this);
|
||||||
|
|
||||||
|
// can't be any new changes if we're in a write transaction
|
||||||
|
if (m_in_transaction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance transaction if database has changed
|
||||||
|
if (!m_shared_group->has_changed()) { // Throws
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_group) {
|
||||||
|
LangBindHelper::advance_read(*m_shared_group, *m_history);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Create the read transaction
|
||||||
|
read_group();
|
||||||
|
}
|
||||||
|
|
||||||
|
send_local_notifications(DidChangeNotification);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedRealm RealmCache::get_realm(const std::string &path, std::thread::id thread_id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
auto path_iter = m_cache.find(path);
|
||||||
|
if (path_iter == m_cache.end()) {
|
||||||
|
return SharedRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto thread_iter = path_iter->second.find(thread_id);
|
||||||
|
if (thread_iter == path_iter->second.end()) {
|
||||||
|
return SharedRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
return thread_iter->second.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedRealm RealmCache::get_any_realm(const std::string &path)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
auto path_iter = m_cache.find(path);
|
||||||
|
if (path_iter == m_cache.end()) {
|
||||||
|
return SharedRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto thread_iter = path_iter->second.begin(); thread_iter != path_iter->second.end(); thread_iter++) {
|
||||||
|
if (auto realm = thread_iter->second.lock()) {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
path_iter->second.erase(thread_iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SharedRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RealmCache::remove(const std::string &path, std::thread::id thread_id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
auto path_iter = m_cache.find(path);
|
||||||
|
if (path_iter == m_cache.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto thread_iter = path_iter->second.find(thread_id);
|
||||||
|
if (thread_iter != path_iter->second.end()) {
|
||||||
|
path_iter->second.erase(thread_iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path_iter->second.size() == 0) {
|
||||||
|
m_cache.erase(path_iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RealmCache::cache_realm(SharedRealm &realm, std::thread::id thread_id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
auto path_iter = m_cache.find(realm->config().path);
|
||||||
|
if (path_iter == m_cache.end()) {
|
||||||
|
m_cache.emplace(realm->config().path, std::map<std::thread::id, WeakRealm>{{thread_id, realm}});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
path_iter->second.emplace(thread_id, realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef REALM_REALM_HPP
|
||||||
|
#define REALM_REALM_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "object_store.hpp"
|
||||||
|
|
||||||
|
namespace realm {
|
||||||
|
class RealmCache;
|
||||||
|
class Realm;
|
||||||
|
typedef std::shared_ptr<Realm> SharedRealm;
|
||||||
|
typedef std::weak_ptr<Realm> WeakRealm;
|
||||||
|
class ClientHistory;
|
||||||
|
|
||||||
|
class Realm : public std::enable_shared_from_this<Realm>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::function<void(SharedRealm old_realm, SharedRealm realm)> MigrationFunction;
|
||||||
|
|
||||||
|
struct Config
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
bool read_only;
|
||||||
|
bool in_memory;
|
||||||
|
StringData encryption_key;
|
||||||
|
|
||||||
|
std::unique_ptr<Schema> schema;
|
||||||
|
uint64_t schema_version;
|
||||||
|
|
||||||
|
MigrationFunction migration_function;
|
||||||
|
|
||||||
|
Config() : read_only(false), in_memory(false), schema_version(ObjectStore::NotVersioned) {};
|
||||||
|
Config(const Config& c);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a cached Realm or create a new one if no cached copies exists
|
||||||
|
// Caching is done by path - mismatches for inMemory and readOnly Config properties
|
||||||
|
// will raise an exception
|
||||||
|
// If schema/schema_version is specified, update_schema is called automatically on the realm
|
||||||
|
// and a migration is performed. If not specified, the schema version and schema are dynamically
|
||||||
|
// read from the the existing Realm.
|
||||||
|
static SharedRealm get_shared_realm(Config &config);
|
||||||
|
|
||||||
|
// Updates a Realm to a given target schema/version creating tables and updating indexes as necessary
|
||||||
|
// Uses the existing migration function on the Config, and the resulting Schema and version with updated
|
||||||
|
// column mappings are set on the realms config upon success.
|
||||||
|
// returns if any changes were made
|
||||||
|
bool update_schema(Schema &schema, uint64_t version);
|
||||||
|
|
||||||
|
const Config &config() const { return m_config; }
|
||||||
|
|
||||||
|
void begin_transaction();
|
||||||
|
void commit_transaction();
|
||||||
|
void cancel_transaction();
|
||||||
|
bool is_in_transaction() { return m_in_transaction; }
|
||||||
|
|
||||||
|
bool refresh();
|
||||||
|
void set_auto_refresh(bool auto_refresh) { m_auto_refresh = auto_refresh; }
|
||||||
|
bool auto_refresh() { return m_auto_refresh; }
|
||||||
|
void notify();
|
||||||
|
|
||||||
|
typedef std::shared_ptr<std::function<void(const std::string)>> NotificationFunction;
|
||||||
|
void add_notification(NotificationFunction ¬ification) { m_notifications.insert(notification); }
|
||||||
|
void remove_notification(NotificationFunction notification) { m_notifications.erase(notification); }
|
||||||
|
void remove_all_notifications() { m_notifications.clear(); }
|
||||||
|
|
||||||
|
void invalidate();
|
||||||
|
bool compact();
|
||||||
|
|
||||||
|
std::thread::id thread_id() const { return m_thread_id; }
|
||||||
|
void verify_thread();
|
||||||
|
|
||||||
|
const std::string RefreshRequiredNotification = "RefreshRequiredNotification";
|
||||||
|
const std::string DidChangeNotification = "DidChangeNotification";
|
||||||
|
|
||||||
|
private:
|
||||||
|
Realm(Config &config);
|
||||||
|
|
||||||
|
Config m_config;
|
||||||
|
std::thread::id m_thread_id;
|
||||||
|
bool m_in_transaction;
|
||||||
|
bool m_auto_refresh;
|
||||||
|
|
||||||
|
std::set<NotificationFunction> m_notifications;
|
||||||
|
void send_local_notifications(const std::string ¬ification);
|
||||||
|
|
||||||
|
typedef std::unique_ptr<std::function<void()>> ExternalNotificationFunction;
|
||||||
|
void send_external_notifications() { if (m_external_notifier) (*m_external_notifier)(); }
|
||||||
|
|
||||||
|
std::unique_ptr<ClientHistory> m_history;
|
||||||
|
std::unique_ptr<SharedGroup> m_shared_group;
|
||||||
|
std::unique_ptr<Group> m_read_only_group;
|
||||||
|
|
||||||
|
Group *m_group;
|
||||||
|
|
||||||
|
static std::mutex s_init_mutex;
|
||||||
|
static RealmCache s_global_cache;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~Realm();
|
||||||
|
ExternalNotificationFunction m_external_notifier;
|
||||||
|
|
||||||
|
// FIXME private
|
||||||
|
Group *read_group();
|
||||||
|
};
|
||||||
|
|
||||||
|
class RealmCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SharedRealm get_realm(const std::string &path, std::thread::id thread_id = std::this_thread::get_id());
|
||||||
|
SharedRealm get_any_realm(const std::string &path);
|
||||||
|
void remove(const std::string &path, std::thread::id thread_id);
|
||||||
|
void cache_realm(SharedRealm &realm, std::thread::id thread_id = std::this_thread::get_id());
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::map<std::thread::id, WeakRealm>> m_cache;
|
||||||
|
std::mutex m_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RealmFileException : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class Kind
|
||||||
|
{
|
||||||
|
/** Thrown for any I/O related exception scenarios when a realm is opened. */
|
||||||
|
AccessError,
|
||||||
|
/** Thrown if the user does not have permission to open or create
|
||||||
|
the specified file in the specified access mode when the realm is opened. */
|
||||||
|
PermissionDenied,
|
||||||
|
/** Thrown if no_create was specified and the file did already exist when the realm is opened. */
|
||||||
|
Exists,
|
||||||
|
/** Thrown if no_create was specified and the file was not found when the realm is opened. */
|
||||||
|
NotFound,
|
||||||
|
/** Thrown if the database file is currently open in another
|
||||||
|
process which cannot share with the current process due to an
|
||||||
|
architecture mismatch. */
|
||||||
|
IncompatibleLockFile,
|
||||||
|
};
|
||||||
|
RealmFileException(Kind kind, std::string message) : std::runtime_error(message), m_kind(kind) {}
|
||||||
|
Kind kind() const { return m_kind; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Kind m_kind;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MismatchedConfigException : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MismatchedConfigException(std::string message) : std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InvalidTransactionException : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InvalidTransactionException(std::string message) : std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class IncorrectThreadException : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IncorrectThreadException(std::string message) : std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnitializedRealmException : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UnitializedRealmException(std::string message) : std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* defined(REALM_REALM_HPP) */
|
Loading…
Reference in New Issue