2015-05-28 23:53:35 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
2015-06-11 17:33:46 +00:00
|
|
|
// Copyright 2015 Realm Inc.
|
2015-05-28 23:53:35 +00:00
|
|
|
//
|
|
|
|
// 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"
|
|
|
|
|
2015-06-10 21:21:43 +00:00
|
|
|
#include <realm/group.hpp>
|
|
|
|
#include <realm/table.hpp>
|
2015-06-05 01:14:53 +00:00
|
|
|
#include <realm/link_view.hpp>
|
|
|
|
#include <realm/table_view.hpp>
|
2015-06-05 20:13:08 +00:00
|
|
|
#include <realm/util/assert.hpp>
|
2015-06-05 01:14:53 +00:00
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
#include <string.h>
|
2015-06-10 21:53:24 +00:00
|
|
|
|
2015-05-28 23:53:35 +00:00
|
|
|
using namespace realm;
|
2015-06-04 00:27:21 +00:00
|
|
|
using namespace std;
|
2015-05-28 23:53:35 +00:00
|
|
|
|
2015-04-20 20:16:30 +00:00
|
|
|
template <typename T>
|
|
|
|
static inline
|
|
|
|
T get_value(TableRef table, size_t row, size_t column);
|
|
|
|
|
|
|
|
template <>
|
|
|
|
StringData get_value(TableRef table, size_t row, size_t column) {
|
|
|
|
return table->get_string(column, row);
|
|
|
|
}
|
|
|
|
|
2015-06-23 21:33:34 +00:00
|
|
|
static inline
|
2015-04-20 20:16:30 +00:00
|
|
|
void set_value(TableRef table, size_t row, size_t column, StringData value) {
|
|
|
|
table->set_string(column, row, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <>
|
|
|
|
BinaryData get_value(TableRef table, size_t row, size_t column) {
|
|
|
|
return table->get_binary(column, row);
|
|
|
|
}
|
|
|
|
|
2015-06-23 21:33:34 +00:00
|
|
|
static inline
|
2015-04-20 20:16:30 +00:00
|
|
|
void set_value(TableRef table, size_t row, size_t column, BinaryData value) {
|
|
|
|
table->set_binary(column, row, value);
|
|
|
|
}
|
|
|
|
|
2015-05-28 23:53:35 +00:00
|
|
|
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;
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
const size_t c_zeroRowIndex = 0;
|
|
|
|
|
|
|
|
const string c_object_table_prefix = "class_";
|
|
|
|
const size_t c_object_table_prefix_length = c_object_table_prefix.length();
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
const uint64_t ObjectStore::NotVersioned = numeric_limits<uint64_t>::max();
|
2015-05-28 23:53:35 +00:00
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
bool ObjectStore::has_metadata_tables(Group *group) {
|
2015-05-28 23:53:35 +00:00
|
|
|
return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName);
|
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
bool ObjectStore::create_metadata_tables(Group *group) {
|
2015-05-28 23:53:35 +00:00
|
|
|
bool changed = false;
|
2015-06-11 17:33:46 +00:00
|
|
|
TableRef table = group->get_or_add_table(c_primaryKeyTableName);
|
2015-05-28 23:53:35 +00:00
|
|
|
if (table->get_column_count() == 0) {
|
2015-06-11 17:33:46 +00:00
|
|
|
table->add_column(type_String, c_primaryKeyObjectClassColumnName);
|
|
|
|
table->add_column(type_String, c_primaryKeyPropertyNameColumnName);
|
2015-05-28 23:53:35 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
table = group->get_or_add_table(c_metadataTableName);
|
|
|
|
if (table->get_column_count() == 0) {
|
2015-06-11 17:33:46 +00:00
|
|
|
table->add_column(type_Int, c_versionColumnName);
|
2015-05-28 23:53:35 +00:00
|
|
|
|
|
|
|
// set initial version
|
|
|
|
table->add_empty_row();
|
2015-06-11 17:33:46 +00:00
|
|
|
table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned);
|
2015-05-28 23:53:35 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
uint64_t ObjectStore::get_schema_version(Group *group) {
|
|
|
|
TableRef table = group->get_table(c_metadataTableName);
|
2015-05-28 23:53:35 +00:00
|
|
|
if (!table || table->get_column_count() == 0) {
|
2015-06-11 17:33:46 +00:00
|
|
|
return ObjectStore::NotVersioned;
|
2015-05-28 23:53:35 +00:00
|
|
|
}
|
2015-06-11 17:33:46 +00:00
|
|
|
return table->get_int(c_versionColumnIndex, c_zeroRowIndex);
|
2015-05-28 23:53:35 +00:00
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
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);
|
2015-05-28 23:53:35 +00:00
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
StringData ObjectStore::get_primary_key_for_object(Group *group, StringData object_type) {
|
|
|
|
TableRef table = group->get_table(c_primaryKeyTableName);
|
2015-05-28 23:53:35 +00:00
|
|
|
if (!table) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
|
2015-06-11 17:33:46 +00:00
|
|
|
if (row == not_found) {
|
2015-05-28 23:53:35 +00:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
return table->get_string(c_primaryKeyPropertyNameColumnIndex, row);
|
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
void ObjectStore::set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key) {
|
|
|
|
TableRef table = group->get_table(c_primaryKeyTableName);
|
2015-05-28 23:53:35 +00:00
|
|
|
|
|
|
|
// get row or create if new object and populate
|
|
|
|
size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
|
2015-06-11 17:33:46 +00:00
|
|
|
if (row == not_found && primary_key.size()) {
|
2015-05-28 23:53:35 +00:00
|
|
|
row = table->add_empty_row();
|
|
|
|
table->set_string(c_primaryKeyObjectClassColumnIndex, row, object_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set if changing, or remove if setting to nil
|
2015-06-10 21:53:24 +00:00
|
|
|
if (primary_key.size() == 0) {
|
2015-06-11 17:33:46 +00:00
|
|
|
if (row != not_found) {
|
2015-06-10 21:53:24 +00:00
|
|
|
table->remove(row);
|
|
|
|
}
|
2015-05-28 23:53:35 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-05 22:52:00 +00:00
|
|
|
string ObjectStore::object_type_for_table_name(const string &table_name) {
|
2015-06-11 17:33:46 +00:00
|
|
|
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);
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
|
2015-06-05 22:52:00 +00:00
|
|
|
string ObjectStore::table_name_for_object_type(const string &object_type) {
|
2015-06-11 17:33:46 +00:00
|
|
|
return c_object_table_prefix + object_type;
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) {
|
2015-06-04 01:04:21 +00:00
|
|
|
return group->get_table(table_name_for_object_type(object_type));
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const StringData &object_type, bool &created) {
|
2015-06-04 01:04:21 +00:00
|
|
|
return group->get_or_add_table(table_name_for_object_type(object_type), &created);
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
2015-06-18 23:04:41 +00:00
|
|
|
std::vector<ObjectStoreException> ObjectStore::validate_object_schema(Group *group, ObjectSchema &target_schema) {
|
2015-06-16 17:07:40 +00:00
|
|
|
vector<ObjectStoreException> exceptions;
|
2015-06-11 17:33:46 +00:00
|
|
|
ObjectSchema table_schema(group, target_schema.name);
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
// check to see if properties are the same
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& current_prop : table_schema.properties) {
|
2015-06-05 02:24:01 +00:00
|
|
|
auto target_prop = target_schema.property_for_name(current_prop.name);
|
2015-06-04 00:27:21 +00:00
|
|
|
|
2015-06-05 02:24:01 +00:00
|
|
|
if (!target_prop) {
|
2015-06-16 17:07:40 +00:00
|
|
|
exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMissingProperty,
|
|
|
|
table_schema.name, current_prop));
|
2015-06-04 00:27:21 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-06-05 02:24:01 +00:00
|
|
|
if (current_prop.type != target_prop->type) {
|
2015-06-16 17:07:40 +00:00
|
|
|
exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedTypes,
|
|
|
|
table_schema.name, current_prop, *target_prop));
|
2015-06-04 00:27:21 +00:00
|
|
|
continue;
|
|
|
|
}
|
2015-06-05 02:24:01 +00:00
|
|
|
if (current_prop.type == PropertyTypeObject || target_prop->type == PropertyTypeArray) {
|
|
|
|
if (current_prop.object_type != target_prop->object_type) {
|
2015-06-16 17:07:40 +00:00
|
|
|
exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedObjectTypes,
|
|
|
|
table_schema.name, current_prop, *target_prop));
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
}
|
2015-04-18 01:12:18 +00:00
|
|
|
if (current_prop.is_nullable != target_prop->is_nullable) {
|
|
|
|
if (current_prop.is_nullable) {
|
|
|
|
validation_errors.push_back("Property '" + current_prop.name + "' is no longer optional.");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
validation_errors.push_back("Property '" + current_prop.name + "' has been made optional.");
|
|
|
|
}
|
|
|
|
}
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
// create new property with aligned column
|
2015-06-05 02:24:01 +00:00
|
|
|
target_prop->table_column = current_prop.table_column;
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
2015-06-16 17:07:40 +00:00
|
|
|
// check for change to primary key
|
|
|
|
if (table_schema.primary_key != target_schema.primary_key) {
|
2015-06-16 23:19:34 +00:00
|
|
|
if (table_schema.primary_key.length()) {
|
|
|
|
exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaChangedPrimaryKey,
|
|
|
|
table_schema.name, table_schema.primary_key));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaNewPrimaryKey,
|
|
|
|
target_schema.name, target_schema.primary_key));
|
|
|
|
}
|
2015-06-16 17:07:40 +00:00
|
|
|
}
|
|
|
|
|
2015-06-04 00:27:21 +00:00
|
|
|
// check for new missing properties
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& target_prop : target_schema.properties) {
|
2015-06-05 02:24:01 +00:00
|
|
|
if (!table_schema.property_for_name(target_prop.name)) {
|
2015-06-16 17:07:40 +00:00
|
|
|
exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaNewProperty,
|
|
|
|
table_schema.name, target_prop));
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-16 17:07:40 +00:00
|
|
|
return exceptions;
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
2015-06-05 20:13:08 +00:00
|
|
|
void ObjectStore::update_column_mapping(Group *group, ObjectSchema &target_schema) {
|
|
|
|
ObjectSchema table_schema(group, target_schema.name);
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& target_prop : target_schema.properties) {
|
2015-06-05 20:13:08 +00:00
|
|
|
auto table_prop = table_schema.property_for_name(target_prop.name);
|
|
|
|
REALM_ASSERT_DEBUG(table_prop);
|
|
|
|
|
|
|
|
target_prop.table_column = table_prop->table_column;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-04 00:27:21 +00:00
|
|
|
static inline bool property_has_changed(Property &p1, Property &p2) {
|
2015-04-16 23:48:07 +00:00
|
|
|
return p1.type != p2.type || p1.name != p2.name || p1.object_type != p2.object_type || p1.is_nullable != p2.is_nullable;
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
2015-04-20 20:16:30 +00:00
|
|
|
static bool property_can_be_migrated_to_nullable(Property &old_property, Property &new_property) {
|
|
|
|
return old_property.type == new_property.type &&
|
|
|
|
!old_property.is_nullable && new_property.is_nullable &&
|
|
|
|
new_property.name == old_property.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
static void copy_property_to_property(Property &old_property, Property &new_property, TableRef table) {
|
|
|
|
size_t old_column = old_property.table_column, new_column = new_property.table_column;
|
|
|
|
size_t count = table->size();
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
T old_value = get_value<T>(table, i, old_column);
|
|
|
|
set_value(table, i, new_column, old_value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-04 00:27:21 +00:00
|
|
|
// 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
|
2015-06-11 17:33:46 +00:00
|
|
|
bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema, bool update_existing) {
|
2015-06-04 01:04:21 +00:00
|
|
|
bool changed = false;
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
// first pass to create missing tables
|
|
|
|
vector<ObjectSchema *> to_update;
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& object_schema : target_schema) {
|
2015-06-04 00:27:21 +00:00
|
|
|
bool created = false;
|
2015-06-04 20:54:07 +00:00
|
|
|
ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created);
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
// we will modify tables for any new objectSchema (table was created) or for all if update_existing is true
|
|
|
|
if (update_existing || created) {
|
2015-06-04 20:54:07 +00:00
|
|
|
to_update.push_back(&object_schema);
|
2015-06-04 00:27:21 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// second pass adds/removes columns for out of date tables
|
2015-06-10 21:53:24 +00:00
|
|
|
for (auto& target_object_schema : to_update) {
|
2015-06-06 01:47:19 +00:00
|
|
|
TableRef table = table_for_object_type(group, target_object_schema->name);
|
2015-06-11 17:33:46 +00:00
|
|
|
ObjectSchema current_schema(group, target_object_schema->name);
|
2015-06-06 01:47:19 +00:00
|
|
|
vector<Property> &target_props = target_object_schema->properties;
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
// add missing columns
|
2015-06-10 21:53:24 +00:00
|
|
|
for (auto& target_prop : target_props) {
|
2015-06-05 02:24:01 +00:00
|
|
|
auto current_prop = current_schema.property_for_name(target_prop.name);
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
// add any new properties (new name or different type)
|
2015-06-05 02:24:01 +00:00
|
|
|
if (!current_prop || property_has_changed(*current_prop, target_prop)) {
|
|
|
|
switch (target_prop.type) {
|
2015-06-04 00:27:21 +00:00
|
|
|
// for objects and arrays, we have to specify target table
|
|
|
|
case PropertyTypeObject:
|
|
|
|
case PropertyTypeArray: {
|
2015-06-11 17:33:46 +00:00
|
|
|
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);
|
2015-06-04 00:27:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
2015-06-18 21:46:37 +00:00
|
|
|
#ifdef REALM_ENABLE_NULL
|
2015-04-16 23:48:07 +00:00
|
|
|
target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name, target_prop.is_nullable);
|
2015-06-18 21:46:37 +00:00
|
|
|
#else
|
|
|
|
target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name);
|
|
|
|
#endif
|
2015-06-04 00:27:21 +00:00
|
|
|
break;
|
|
|
|
}
|
2015-04-20 20:16:30 +00:00
|
|
|
|
|
|
|
if (current_prop && property_can_be_migrated_to_nullable(*current_prop, target_prop)) {
|
|
|
|
switch (target_prop.type) {
|
|
|
|
case PropertyTypeString:
|
|
|
|
copy_property_to_property<StringData>(*current_prop, target_prop, table);
|
|
|
|
break;
|
|
|
|
case PropertyTypeData:
|
|
|
|
copy_property_to_property<BinaryData>(*current_prop, target_prop, table);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
REALM_UNREACHABLE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-04 00:27:21 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove extra columns
|
2015-06-05 02:24:01 +00:00
|
|
|
sort(begin(current_schema.properties), end(current_schema.properties), [](Property &i, Property &j) {
|
2015-06-10 21:21:43 +00:00
|
|
|
return j.table_column < i.table_column;
|
2015-06-05 02:24:01 +00:00
|
|
|
});
|
2015-06-10 21:21:43 +00:00
|
|
|
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)) {
|
2015-06-05 02:24:01 +00:00
|
|
|
table->remove_column(current_prop.table_column);
|
2015-06-04 00:27:21 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update table metadata
|
2015-06-06 01:47:19 +00:00
|
|
|
if (target_object_schema->primary_key.length()) {
|
2015-06-04 00:27:21 +00:00
|
|
|
// if there is a primary key set, check if it is the same as the old key
|
2015-06-10 21:21:43 +00:00
|
|
|
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);
|
2015-06-04 00:27:21 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (current_schema.primary_key.length()) {
|
|
|
|
// there is no primary key, so if there was one nil out
|
2015-06-10 21:21:43 +00:00
|
|
|
set_primary_key_for_object(group, target_object_schema->name, "");
|
2015-06-04 00:27:21 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
bool ObjectStore::is_schema_at_version(Group *group, uint64_t version) {
|
2015-06-04 00:27:21 +00:00
|
|
|
uint64_t old_version = get_schema_version(group);
|
2015-06-11 17:33:46 +00:00
|
|
|
if (old_version > version && old_version != NotVersioned) {
|
2015-06-16 23:19:34 +00:00
|
|
|
throw ObjectStoreException(old_version, version);
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
2015-06-11 17:33:46 +00:00
|
|
|
return old_version != version;
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-06-11 17:33:46 +00:00
|
|
|
bool ObjectStore::update_realm_with_schema(Group *group,
|
2015-06-04 00:27:21 +00:00
|
|
|
uint64_t version,
|
2015-06-04 20:54:07 +00:00
|
|
|
Schema &schema,
|
2015-06-04 00:27:21 +00:00
|
|
|
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
|
2015-06-11 17:33:46 +00:00
|
|
|
bool migrating = is_schema_at_version(group, version);
|
2015-06-04 00:27:21 +00:00
|
|
|
|
|
|
|
// create tables
|
2015-06-06 01:47:19 +00:00
|
|
|
bool changed = create_metadata_tables(group);
|
2015-06-10 21:21:43 +00:00
|
|
|
changed = create_tables(group, schema, migrating) || changed;
|
2015-06-04 00:27:21 +00:00
|
|
|
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& target_schema : schema) {
|
2015-06-04 00:27:21 +00:00
|
|
|
// read-only realms may be missing tables entirely
|
2015-06-06 01:47:19 +00:00
|
|
|
TableRef table = table_for_object_type(group, target_schema.name);
|
2015-06-04 00:27:21 +00:00
|
|
|
if (table) {
|
2015-06-18 23:04:41 +00:00
|
|
|
auto errors = validate_object_schema(group, target_schema);
|
2015-06-04 00:27:21 +00:00
|
|
|
if (errors.size()) {
|
2015-06-16 17:07:40 +00:00
|
|
|
throw ObjectStoreException(errors, target_schema.name);
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-10 21:21:43 +00:00
|
|
|
changed = update_indexes(group, schema) || changed;
|
2015-06-05 00:44:06 +00:00
|
|
|
|
2015-06-04 00:27:21 +00:00
|
|
|
if (!migrating) {
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
// apply the migration block if provided and there's any old data
|
2015-06-11 17:33:46 +00:00
|
|
|
if (get_schema_version(group) != ObjectStore::NotVersioned) {
|
2015-06-06 01:47:19 +00:00
|
|
|
migration(group, schema);
|
2015-06-04 00:27:21 +00:00
|
|
|
}
|
|
|
|
|
2015-06-05 01:14:53 +00:00
|
|
|
validate_primary_column_uniqueness(group, schema);
|
|
|
|
|
2015-06-04 00:27:21 +00:00
|
|
|
set_schema_version(group, version);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-04 02:14:39 +00:00
|
|
|
ObjectStore::Schema ObjectStore::schema_from_group(Group *group) {
|
|
|
|
ObjectStore::Schema schema;
|
2015-06-05 02:24:01 +00:00
|
|
|
for (size_t i = 0; i < group->size(); i++) {
|
2015-06-04 02:14:39 +00:00
|
|
|
string object_type = object_type_for_table_name(group->get_table_name(i));
|
|
|
|
if (object_type.length()) {
|
2015-06-10 21:21:43 +00:00
|
|
|
schema.emplace_back(group, move(object_type));
|
2015-06-04 02:14:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return schema;
|
|
|
|
}
|
2015-06-05 00:44:06 +00:00
|
|
|
|
2015-06-05 19:52:05 +00:00
|
|
|
bool ObjectStore::indexes_are_up_to_date(Group *group, Schema &schema) {
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto &object_schema : schema) {
|
2015-06-05 00:44:06 +00:00
|
|
|
TableRef table = table_for_object_type(group, object_schema.name);
|
|
|
|
if (!table) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-06-05 20:13:08 +00:00
|
|
|
|
|
|
|
update_column_mapping(group, object_schema);
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& property : object_schema.properties) {
|
2015-06-05 01:14:53 +00:00
|
|
|
if (property.requires_index() != table->has_search_index(property.table_column)) {
|
2015-06-05 00:44:06 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ObjectStore::update_indexes(Group *group, Schema &schema) {
|
|
|
|
bool changed = false;
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& object_schema : schema) {
|
2015-06-05 00:44:06 +00:00
|
|
|
TableRef table = table_for_object_type(group, object_schema.name);
|
|
|
|
if (!table) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& property : object_schema.properties) {
|
2015-06-05 01:14:53 +00:00
|
|
|
if (property.requires_index() == table->has_search_index(property.table_column)) {
|
2015-06-05 00:44:06 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
changed = true;
|
2015-06-05 01:14:53 +00:00
|
|
|
if (property.requires_index()) {
|
2015-06-05 00:44:06 +00:00
|
|
|
try {
|
|
|
|
table->add_search_index(property.table_column);
|
|
|
|
}
|
2015-06-11 17:33:46 +00:00
|
|
|
catch (LogicError const&) {
|
2015-06-16 17:07:40 +00:00
|
|
|
throw ObjectStoreException(ObjectStoreException::Kind::RealmPropertyTypeNotIndexable, object_schema.name, property);
|
2015-06-05 00:44:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
table->remove_search_index(property.table_column);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
2015-06-05 01:14:53 +00:00
|
|
|
void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schema) {
|
2015-06-10 21:21:43 +00:00
|
|
|
for (auto& object_schema : schema) {
|
2015-06-05 01:14:53 +00:00
|
|
|
auto primary_prop = object_schema.primary_key_property();
|
2015-06-05 02:24:01 +00:00
|
|
|
if (!primary_prop) {
|
2015-06-05 01:14:53 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-06-06 01:47:19 +00:00
|
|
|
TableRef table = table_for_object_type(group, object_schema.name);
|
2015-06-05 01:14:53 +00:00
|
|
|
if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) {
|
2015-06-16 17:07:40 +00:00
|
|
|
throw ObjectStoreException(ObjectStoreException::Kind::RealmDuplicatePrimaryKeyValue, object_schema.name, *primary_prop);
|
2015-06-05 01:14:53 +00:00
|
|
|
}
|
|
|
|
}
|
2015-06-06 01:47:19 +00:00
|
|
|
}
|
2015-06-11 19:17:55 +00:00
|
|
|
|
|
|
|
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, "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|