From 84d50446b59ea7b4255d0358681f1cf372423aae Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 28 May 2015 16:53:35 -0700 Subject: [PATCH 01/40] refactor out schema version code to c++ --- object_store.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++++++ object_store.hpp | 54 ++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 object_store.cpp create mode 100644 object_store.hpp diff --git a/object_store.cpp b/object_store.cpp new file mode 100644 index 00000000..517085f2 --- /dev/null +++ b/object_store.cpp @@ -0,0 +1,104 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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" + +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 uint64_t ObjectStore::NotVersioned = std::numeric_limits::max(); + +bool ObjectStore::has_metadata_tables(realm::Group *group) { + return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName); +} + +bool ObjectStore::create_metadata_tables(realm::Group *group) { + bool changed = false; + realm::TableRef table = group->get_or_add_table(c_primaryKeyTableName); + if (table->get_column_count() == 0) { + table->add_column(realm::type_String, c_primaryKeyObjectClassColumnName); + table->add_column(realm::type_String, c_primaryKeyPropertyNameColumnName); + changed = true; + } + + table = group->get_or_add_table(c_metadataTableName); + if (table->get_column_count() == 0) { + table->add_column(realm::type_Int, c_versionColumnName); + + // set initial version + table->add_empty_row(); + table->set_int(c_versionColumnIndex, 0, realm::ObjectStore::NotVersioned); + changed = true; + } + + return changed; +} + +uint64_t ObjectStore::get_schema_version(realm::Group *group) { + realm::TableRef table = group->get_table(c_metadataTableName); + if (!table || table->get_column_count() == 0) { + return realm::ObjectStore::NotVersioned; + } + return table->get_int(c_versionColumnIndex, 0); +} + +void ObjectStore::set_schema_version(realm::Group *group, uint64_t version) { + realm::TableRef table = group->get_or_add_table(c_metadataTableName); + table->set_int(c_versionColumnIndex, 0, version); +} + +std::string ObjectStore::get_primary_key_for_object(realm::Group *group, std::string object_type) { + realm::TableRef table = group->get_table(c_primaryKeyTableName); + if (!table) { + return ""; + } + size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type); + if (row == realm::not_found) { + return ""; + } + return table->get_string(c_primaryKeyPropertyNameColumnIndex, row); +} + +void ObjectStore::set_primary_key_for_object(realm::Group *group, std::string object_type, std::string primary_key) { + realm::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 == realm::not_found && primary_key.length()) { + 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.length() == 0 && row != realm::not_found) { + table->remove(row); + } + else { + table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key); + } +} + diff --git a/object_store.hpp b/object_store.hpp new file mode 100644 index 00000000..483fb0aa --- /dev/null +++ b/object_store.hpp @@ -0,0 +1,54 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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__ +#define __Realm__object_store__ + +#include + +namespace realm { + class ObjectStore { + public: + // Schema version used for uninitialized Realms + static const uint64_t NotVersioned; + + // check if the realm already has all metadata tables + static bool has_metadata_tables(realm::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(realm::Group *group); + + // get the last set schema version + static uint64_t get_schema_version(realm::Group *group); + + // set a new schema version + static void set_schema_version(realm::Group *group, uint64_t version); + + // get primary key property name for object type + static std::string get_primary_key_for_object(realm::Group *group, std::string object_type); + + // sets primary key property for object type + // must be in write transaction to set + static void set_primary_key_for_object(realm::Group *group, std::string object_type, std::string primary_key); + }; +} + +#endif /* defined(__Realm__object_store__) */ + From 4994428e63c08cacd9f6fd00adcb9f46f59ff189 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 28 May 2015 17:11:54 -0700 Subject: [PATCH 02/40] std::String -> StringData, fix header include identifier --- object_store.cpp | 8 ++++---- object_store.hpp | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 517085f2..f4ea0f30 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -71,7 +71,7 @@ void ObjectStore::set_schema_version(realm::Group *group, uint64_t version) { table->set_int(c_versionColumnIndex, 0, version); } -std::string ObjectStore::get_primary_key_for_object(realm::Group *group, std::string object_type) { +StringData ObjectStore::get_primary_key_for_object(realm::Group *group, StringData object_type) { realm::TableRef table = group->get_table(c_primaryKeyTableName); if (!table) { return ""; @@ -83,18 +83,18 @@ std::string ObjectStore::get_primary_key_for_object(realm::Group *group, std::st return table->get_string(c_primaryKeyPropertyNameColumnIndex, row); } -void ObjectStore::set_primary_key_for_object(realm::Group *group, std::string object_type, std::string primary_key) { +void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData object_type, StringData primary_key) { realm::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 == realm::not_found && primary_key.length()) { + if (row == realm::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.length() == 0 && row != realm::not_found) { + if (primary_key.size() == 0 && row != realm::not_found) { table->remove(row); } else { diff --git a/object_store.hpp b/object_store.hpp index 483fb0aa..0f71dc27 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -16,8 +16,8 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef __Realm__object_store__ -#define __Realm__object_store__ +#ifndef __realm__object_store__ +#define __realm__object_store__ #include @@ -42,13 +42,13 @@ namespace realm { static void set_schema_version(realm::Group *group, uint64_t version); // get primary key property name for object type - static std::string get_primary_key_for_object(realm::Group *group, std::string object_type); + static StringData get_primary_key_for_object(realm::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(realm::Group *group, std::string object_type, std::string primary_key); + static void set_primary_key_for_object(realm::Group *group, StringData object_type, StringData primary_key); }; } -#endif /* defined(__Realm__object_store__) */ +#endif /* defined(__realm__object_store__) */ From a54d2216f92f89a7641ef6024f7ba25794092141 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 3 Jun 2015 17:27:21 -0700 Subject: [PATCH 03/40] refact cocoa to use c++ table creation apis --- object_schema.cpp | 82 +++++++++++++++++++ object_schema.hpp | 49 +++++++++++ object_store.cpp | 204 +++++++++++++++++++++++++++++++++++++++++++++- object_store.hpp | 62 ++++++++++++-- property.cpp | 21 +++++ property.hpp | 85 +++++++++++++++++++ 6 files changed, 496 insertions(+), 7 deletions(-) create mode 100644 object_schema.cpp create mode 100644 object_schema.hpp create mode 100644 property.cpp create mode 100644 property.hpp diff --git a/object_schema.cpp b/object_schema.cpp new file mode 100644 index 00000000..12d61209 --- /dev/null +++ b/object_schema.cpp @@ -0,0 +1,82 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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" + +using namespace realm; +using namespace std; + +ObjectSchema::ObjectSchema(realm::Group *group, std::string name) : name(name) { + TableRef table = ObjectStore::table_for_object_type(group, name); + size_t count = table->get_column_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.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::class_for_table_name(linkTable->get_name().data()); + } + else { + property.object_type = ""; + } + properties.push_back(property); + } + + primary_key = realm::ObjectStore::get_primary_key_for_object(group, name); + if (primary_key.length()) { + auto primary_key_iter = primary_key_property(); + if (primary_key_iter == properties.end()) { + std::vector errors; + errors.push_back("No property matching primary key '" + primary_key + "'"); + throw ObjectStoreValidationException(errors, name); + } + primary_key_iter->is_primary = true; + } +} + +std::vector::iterator ObjectSchema::property_for_name(std::string name) { + auto iter = properties.begin(); + while (iter < properties.end()) { + if (iter->name == name) { + return iter; + } + iter++; + } + return iter; +} + +std::vector ObjectSchema::object_schema_from_group(realm::Group *group) { + // generate object schema and class mapping for all tables in the realm + unsigned long numTables = group->size(); + vector object_schema; + + for (unsigned long i = 0; i < numTables; i++) { + std::string name = ObjectStore::class_for_table_name(group->get_table_name(i).data()); + if (name.length()) { + object_schema.push_back(ObjectSchema(group, name)); + } + } + + return object_schema; +} diff --git a/object_schema.hpp b/object_schema.hpp new file mode 100644 index 00000000..7d962018 --- /dev/null +++ b/object_schema.hpp @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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__ +#define __realm__object_schema__ + +#include +#include + +#include "property.hpp" +#include + +namespace realm { + class ObjectSchema { + public: + ObjectSchema() {} + ObjectSchema(Group *group, std::string name); + + static std::vector object_schema_from_group(Group *group); + + std::string name; + std::vector properties; + std::string primary_key; + + std::vector::iterator property_for_name(std::string name); + std::vector::iterator primary_key_property() { + return property_for_name(primary_key); + } + }; + + typedef std::shared_ptr ObjectSchemaRef; +} + +#endif /* defined(__realm__object_schema__) */ diff --git a/object_store.cpp b/object_store.cpp index f4ea0f30..7fc969d1 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -19,6 +19,7 @@ #include "object_store.hpp" using namespace realm; +using namespace std; const char * const c_metadataTableName = "metadata"; const char * const c_versionColumnName = "version"; @@ -30,7 +31,9 @@ const size_t c_primaryKeyObjectClassColumnIndex = 0; const char * const c_primaryKeyPropertyNameColumnName = "pk_property"; const size_t c_primaryKeyPropertyNameColumnIndex = 1; -const uint64_t ObjectStore::NotVersioned = std::numeric_limits::max(); +const string c_object_table_name_prefix = "class_"; + +const uint64_t ObjectStore::NotVersioned = numeric_limits::max(); bool ObjectStore::has_metadata_tables(realm::Group *group) { return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName); @@ -102,3 +105,202 @@ void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData obj } } +string ObjectStore::class_for_table_name(string table_name) { + if (table_name.compare(0, 6, c_object_table_name_prefix) == 0) { + return table_name.substr(6, table_name.length()-6); + } + return string(); +} + +string ObjectStore::table_name_for_class(string class_name) { + return c_object_table_name_prefix + class_name; +} + +realm::TableRef ObjectStore::table_for_object_type(realm::Group *group, StringData object_type) { + return group->get_table(table_name_for_class(object_type)); +} + +realm::TableRef ObjectStore::table_for_object_type_create_if_needed(realm::Group *group, StringData object_type, bool &created) { + return group->get_or_add_table(table_name_for_class(object_type), &created); +} + +std::vector ObjectStore::validate_and_update_column_mapping(realm::Group *group, ObjectSchema &target_schema) { + vector validation_errors; + ObjectSchema table_schema(group, target_schema.name); + + // check to see if properties are the same + for (auto current_prop = table_schema.properties.begin(); current_prop != table_schema.properties.end(); current_prop++) { + auto target_prop = target_schema.property_for_name(current_prop->name); + + if (target_prop == target_schema.properties.end()) { + validation_errors.push_back("Property '" + current_prop->name + "' is missing from latest object model."); + continue; + } + + if (current_prop->type != target_prop->type) { + validation_errors.push_back("Property types for '" + target_prop->name + "' property do not match. " + + "Old type '" + string_for_property_type(current_prop->type) + + "', new type '" + string_for_property_type(target_prop->type) + "'"); + continue; + } + if (current_prop->type == PropertyTypeObject || target_prop->type == PropertyTypeArray) { + if (current_prop->object_type != target_prop->object_type) { + validation_errors.push_back("Target object type for property '" + current_prop->name + "' does not match. " + + "Old type '" + current_prop->object_type + + "', new type '" + target_prop->object_type + "'."); + } + } + if (current_prop->is_primary != target_prop->is_primary) { + if (current_prop->is_primary) { + validation_errors.push_back("Property '" + current_prop->name + "' is no longer a primary key."); + } + else { + validation_errors.push_back("Property '" + current_prop->name + "' has been made a primary key."); + } + } + + // create new property with aligned column + target_prop->table_column = current_prop->table_column; + } + + // check for new missing properties + for (auto target_iter = target_schema.properties.begin(); target_iter != target_schema.properties.end(); target_iter++) { + if (table_schema.property_for_name(target_iter->name) == table_schema.properties.end()) { + validation_errors.push_back("Property '" + target_iter->name + "' has been added to latest object model."); + } + } + + return validation_errors; +} + +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; +} + +// 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 +static inline bool create_tables(realm::Group *group, ObjectStore::Schema target_schema, bool update_existing) { + // create metadata tables if neded + bool changed = realm::ObjectStore::create_metadata_tables(group); + + // first pass to create missing tables + vector to_update; + for (size_t i = 0; i < target_schema.size(); i++) { + ObjectSchema *object_schema = target_schema[i].get(); + bool created = false; + ObjectStore::table_for_object_type_create_if_needed(group, object_schema->name, 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); + changed = true; + } + } + + // second pass adds/removes columns for out of date tables + for (size_t i = 0; i < to_update.size(); i++) { + ObjectSchema *target_schema = to_update[i]; + TableRef table = ObjectStore::table_for_object_type(group, target_schema->name); + + ObjectSchema current_schema(group, target_schema->name); + vector &target_props = target_schema->properties; + + // add missing columns + for (auto target_prop = target_props.begin(); target_prop < target_props.end(); target_prop++) { + auto current_prop = current_schema.property_for_name(target_prop->name); + + // add any new properties (new name or different type) + if (current_prop == current_schema.properties.end() || 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: { + realm::TableRef link_table = ObjectStore::table_for_object_type(group, target_prop->object_type); + target_prop->table_column = table->add_column_link(realm::DataType(target_prop->type), target_prop->name, *link_table); + break; + } + default: + target_prop->table_column = table->add_column(realm::DataType(target_prop->type), target_prop->name); + break; + } + changed = true; + } + } + + // remove extra columns + vector reverse_props = current_schema.properties; + std::sort(reverse_props.begin(), reverse_props.end(), [](Property &i, Property &j){ return (j.table_column < i.table_column); }); + for (auto iter = reverse_props.begin(); iter != reverse_props.end(); iter++) { + auto target_prop_iter = target_schema->property_for_name(iter->name); + if (target_prop_iter == target_props.end() || property_has_changed(*iter, *target_prop_iter)) { + table->remove_column(iter->table_column); + changed = true; + } + } + + // update table metadata + if (target_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.length() || current_schema.primary_key != target_schema->primary_key) { + realm::ObjectStore::set_primary_key_for_object(group, target_schema->name, target_schema->primary_key); + changed = true; + } + } + else if (current_schema.primary_key.length()) { + // there is no primary key, so if there was one nil out + realm::ObjectStore::set_primary_key_for_object(group, target_schema->name, ""); + changed = true; + } + } + + return changed; +} + +bool ObjectStore::is_migration_required(realm::Group *group, uint64_t new_version) { + uint64_t old_version = get_schema_version(group); + if (old_version > new_version && old_version != realm::ObjectStore::NotVersioned) { + throw ObjectStoreException(ObjectStoreException::RealmVersionGreaterThanSchemaVersion); + } + return old_version != new_version; +} + + +bool ObjectStore::update_realm_with_schema(realm::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_migration_required(group, version); + + // create tables + bool changed = create_tables(group, schema, migrating); + for (size_t i = 0; i < schema.size(); i++) { + ObjectSchema *target_schema = schema[i].get(); + TableRef table = table_for_object_type(group, target_schema->name); + + // read-only realms may be missing tables entirely + if (table) { + auto errors = validate_and_update_column_mapping(group, *target_schema); + if (errors.size()) { + throw ObjectStoreValidationException(errors, target_schema->name); + } + } + } + + if (!migrating) { + return changed; + } + + // apply the migration block if provided and there's any old data + // to be migrated + if (get_schema_version(group) != realm::ObjectStore::NotVersioned) { + migration(); + } + + set_schema_version(group, version); + return true; +} + diff --git a/object_store.hpp b/object_store.hpp index 0f71dc27..19fbe772 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -20,6 +20,7 @@ #define __realm__object_store__ #include +#include "object_schema.hpp" namespace realm { class ObjectStore { @@ -28,25 +29,74 @@ namespace realm { static const uint64_t NotVersioned; // check if the realm already has all metadata tables - static bool has_metadata_tables(realm::Group *group); + 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(realm::Group *group); + static bool create_metadata_tables(Group *group); // get the last set schema version - static uint64_t get_schema_version(realm::Group *group); + static uint64_t get_schema_version(Group *group); // set a new schema version - static void set_schema_version(realm::Group *group, uint64_t version); + static void set_schema_version(Group *group, uint64_t version); // get primary key property name for object type - static StringData get_primary_key_for_object(realm::Group *group, StringData 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(realm::Group *group, StringData object_type, StringData primary_key); + static void set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key); + + // verify a target schema against its table, setting the table_column property on each schema object + // returns array of validation errors + static std::vector validate_and_update_column_mapping(Group *group, ObjectSchema &target_schema); + + // get the table used to store object of objectClass + static std::string table_name_for_class(std::string class_name); + static std::string class_for_table_name(std::string table_name); + static realm::TableRef table_for_object_type(Group *group, StringData object_type); + static realm::TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created); + + static bool is_migration_required(realm::Group *group, uint64_t new_version); + + // updates a Realm to a given target schema/version creating tables 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 MigrationFunction; + typedef std::vector Schema; + static bool update_realm_with_schema(Group *group, + uint64_t version, + Schema schema, + MigrationFunction migration); + }; + + class ObjectStoreException : public std::exception { + public: + enum Kind { + // thrown when calling update_realm_to_schema and the realm version is greater than the given version + RealmVersionGreaterThanSchemaVersion, + }; + ObjectStoreException(Kind kind) : m_kind(kind) {} + ObjectStoreException::Kind kind() { return m_kind; } + + private: + Kind m_kind; + }; + + class ObjectStoreValidationException : public std::exception { + public: + ObjectStoreValidationException(std::vector validation_errors, std::string object_type) : + m_validation_errors(validation_errors), m_object_type(object_type) {} + std::vector validation_errors() { return m_validation_errors; } + std::string object_type() { return m_object_type; } + + private: + std::vector m_validation_errors; + std::string m_object_type; }; } diff --git a/property.cpp b/property.cpp new file mode 100644 index 00000000..44dfa8fa --- /dev/null +++ b/property.cpp @@ -0,0 +1,21 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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 "property.hpp" + +using namespace realm; diff --git a/property.hpp b/property.hpp new file mode 100644 index 00000000..a9a1d145 --- /dev/null +++ b/property.hpp @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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__ +#define __realm__property__ + +#include + +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, + }; + + class Property { + public: + std::string name; + PropertyType type; + std::string object_type; + bool is_primary; + bool is_indexed; + + size_t table_column; + }; + + 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 /* defined__realm__property__ */ From 6e9d9bb7934b287af93639f502ca250d0a8bd058 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 3 Jun 2015 18:04:21 -0700 Subject: [PATCH 04/40] remove duplicte code and privitize methods --- object_schema.cpp | 4 ++-- object_store.cpp | 21 +++++++++--------- object_store.hpp | 55 +++++++++++++++++++++++++++-------------------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index 12d61209..f1b03d29 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -35,7 +35,7 @@ ObjectSchema::ObjectSchema(realm::Group *group, std::string name) : name(name) { 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::class_for_table_name(linkTable->get_name().data()); + property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data()); } else { property.object_type = ""; @@ -72,7 +72,7 @@ std::vector ObjectSchema::object_schema_from_group(realm::Group *g vector object_schema; for (unsigned long i = 0; i < numTables; i++) { - std::string name = ObjectStore::class_for_table_name(group->get_table_name(i).data()); + std::string name = ObjectStore::object_type_for_table_name(group->get_table_name(i).data()); if (name.length()) { object_schema.push_back(ObjectSchema(group, name)); } diff --git a/object_store.cpp b/object_store.cpp index 7fc969d1..f7a46aaa 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -105,26 +105,26 @@ void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData obj } } -string ObjectStore::class_for_table_name(string table_name) { +string ObjectStore::object_type_for_table_name(string table_name) { if (table_name.compare(0, 6, c_object_table_name_prefix) == 0) { return table_name.substr(6, table_name.length()-6); } return string(); } -string ObjectStore::table_name_for_class(string class_name) { - return c_object_table_name_prefix + class_name; +string ObjectStore::table_name_for_object_type(string object_type) { + return c_object_table_name_prefix + object_type; } realm::TableRef ObjectStore::table_for_object_type(realm::Group *group, StringData object_type) { - return group->get_table(table_name_for_class(object_type)); + return group->get_table(table_name_for_object_type(object_type)); } realm::TableRef ObjectStore::table_for_object_type_create_if_needed(realm::Group *group, StringData object_type, bool &created) { - return group->get_or_add_table(table_name_for_class(object_type), &created); + return group->get_or_add_table(table_name_for_object_type(object_type), &created); } -std::vector ObjectStore::validate_and_update_column_mapping(realm::Group *group, ObjectSchema &target_schema) { +std::vector ObjectStore::validate_schema_and_update_column_mapping(realm::Group *group, ObjectSchema &target_schema) { vector validation_errors; ObjectSchema table_schema(group, target_schema.name); @@ -180,9 +180,8 @@ static inline bool property_has_changed(Property &p1, Property &p2) { // 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 -static inline bool create_tables(realm::Group *group, ObjectStore::Schema target_schema, bool update_existing) { - // create metadata tables if neded - bool changed = realm::ObjectStore::create_metadata_tables(group); +bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema target_schema, bool update_existing) { + bool changed = false; // first pass to create missing tables vector to_update; @@ -276,14 +275,14 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, bool migrating = is_migration_required(group, version); // create tables - bool changed = create_tables(group, schema, migrating); + bool changed = create_metadata_tables(group) | create_tables(group, schema, migrating); for (size_t i = 0; i < schema.size(); i++) { ObjectSchema *target_schema = schema[i].get(); TableRef table = table_for_object_type(group, target_schema->name); // read-only realms may be missing tables entirely if (table) { - auto errors = validate_and_update_column_mapping(group, *target_schema); + auto errors = validate_schema_and_update_column_mapping(group, *target_schema); if (errors.size()) { throw ObjectStoreValidationException(errors, target_schema->name); } diff --git a/object_store.hpp b/object_store.hpp index 19fbe772..a2cf38b6 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -28,38 +28,18 @@ namespace realm { // Schema version used for uninitialized Realms static const uint64_t NotVersioned; - // 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); - // get the last set schema version static uint64_t get_schema_version(Group *group); // set a new schema version static void set_schema_version(Group *group, uint64_t version); - // 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); + // checks if a migration is required for a given schema version + static bool is_migration_required(realm::Group *group, uint64_t new_version); // verify a target schema against its table, setting the table_column property on each schema object // returns array of validation errors - static std::vector validate_and_update_column_mapping(Group *group, ObjectSchema &target_schema); - - // get the table used to store object of objectClass - static std::string table_name_for_class(std::string class_name); - static std::string class_for_table_name(std::string table_name); - static realm::TableRef table_for_object_type(Group *group, StringData object_type); - static realm::TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created); - - static bool is_migration_required(realm::Group *group, uint64_t new_version); + static std::vector validate_schema_and_update_column_mapping(Group *group, ObjectSchema &target_schema); // updates a Realm to a given target schema/version creating tables as necessary // returns if any changes were made @@ -72,6 +52,35 @@ namespace realm { 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); + + private: + // 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 validates existing tables + static bool create_tables(realm::Group *group, ObjectStore::Schema target_schema, bool update_existing); + + // 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 realm::TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created); + static std::string table_name_for_object_type(std::string class_name); + static std::string object_type_for_table_name(std::string table_name); + + friend ObjectSchema; }; class ObjectStoreException : public std::exception { From 479179716d6f3ecae1f9dd30a871fe5ade769e28 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 3 Jun 2015 18:09:14 -0700 Subject: [PATCH 05/40] comment --- object_store.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/object_store.hpp b/object_store.hpp index a2cf38b6..e125e905 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -32,6 +32,7 @@ namespace realm { static uint64_t get_schema_version(Group *group); // set a new schema version + // FIXME - should be private (set through update_realm_with_schema) static void set_schema_version(Group *group, uint64_t version); // checks if a migration is required for a given schema version From 4b82701a9eae41ad11861e7d0456299417b3cc94 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 3 Jun 2015 18:34:50 -0700 Subject: [PATCH 06/40] make set_schema_version private --- object_store.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/object_store.hpp b/object_store.hpp index e125e905..473bc0f0 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -31,10 +31,6 @@ namespace realm { // get the last set schema version static uint64_t get_schema_version(Group *group); - // set a new schema version - // FIXME - should be private (set through update_realm_with_schema) - static void set_schema_version(Group *group, uint64_t version); - // checks if a migration is required for a given schema version static bool is_migration_required(realm::Group *group, uint64_t new_version); @@ -58,6 +54,9 @@ namespace realm { static realm::TableRef table_for_object_type(Group *group, 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); From a82805548eae5201e837580b0b22054672911143 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 3 Jun 2015 19:14:39 -0700 Subject: [PATCH 07/40] remove logic for table name determination from cocoa --- object_store.cpp | 10 ++++++++++ object_store.hpp | 3 +++ 2 files changed, 13 insertions(+) diff --git a/object_store.cpp b/object_store.cpp index f7a46aaa..c825f621 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -303,3 +303,13 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, return true; } +ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { + ObjectStore::Schema schema; + for (unsigned long i = 0; i < group->size(); i++) { + string object_type = object_type_for_table_name(group->get_table_name(i)); + if (object_type.length()) { + schema.push_back(ObjectSchemaRef(new ObjectSchema(group, object_type))); + } + } + return schema; +} diff --git a/object_store.hpp b/object_store.hpp index 473bc0f0..5e8e1b43 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -53,6 +53,9 @@ namespace realm { // 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); + private: // set a new schema version static void set_schema_version(Group *group, uint64_t version); From 85047bb96dfe620c3461f821f568cc1eb030b487 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 4 Jun 2015 13:54:07 -0700 Subject: [PATCH 08/40] use references instead of shared_ptr --- object_schema.hpp | 2 -- object_store.cpp | 20 ++++++++++---------- object_store.hpp | 6 +++--- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/object_schema.hpp b/object_schema.hpp index 7d962018..3fa1f1b8 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -42,8 +42,6 @@ namespace realm { return property_for_name(primary_key); } }; - - typedef std::shared_ptr ObjectSchemaRef; } #endif /* defined(__realm__object_schema__) */ diff --git a/object_store.cpp b/object_store.cpp index c825f621..c905a643 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -180,19 +180,19 @@ static inline bool property_has_changed(Property &p1, Property &p2) { // 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(realm::Group *group, ObjectStore::Schema target_schema, bool update_existing) { +bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target_schema, bool update_existing) { bool changed = false; // first pass to create missing tables vector to_update; for (size_t i = 0; i < target_schema.size(); i++) { - ObjectSchema *object_schema = target_schema[i].get(); + ObjectSchema &object_schema = target_schema[i]; bool created = false; - ObjectStore::table_for_object_type_create_if_needed(group, object_schema->name, created); + ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, 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); + to_update.push_back(&object_schema); changed = true; } } @@ -267,7 +267,7 @@ bool ObjectStore::is_migration_required(realm::Group *group, uint64_t new_versio bool ObjectStore::update_realm_with_schema(realm::Group *group, uint64_t version, - Schema schema, + 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 @@ -277,14 +277,14 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, // create tables bool changed = create_metadata_tables(group) | create_tables(group, schema, migrating); for (size_t i = 0; i < schema.size(); i++) { - ObjectSchema *target_schema = schema[i].get(); - TableRef table = table_for_object_type(group, target_schema->name); + ObjectSchema &target_schema = schema[i]; + TableRef table = table_for_object_type(group, target_schema.name); // read-only realms may be missing tables entirely if (table) { - auto errors = validate_schema_and_update_column_mapping(group, *target_schema); + auto errors = validate_schema_and_update_column_mapping(group, target_schema); if (errors.size()) { - throw ObjectStoreValidationException(errors, target_schema->name); + throw ObjectStoreValidationException(errors, target_schema.name); } } } @@ -308,7 +308,7 @@ ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { for (unsigned long i = 0; i < group->size(); i++) { string object_type = object_type_for_table_name(group->get_table_name(i)); if (object_type.length()) { - schema.push_back(ObjectSchemaRef(new ObjectSchema(group, object_type))); + schema.push_back(ObjectSchema(group, object_type)); } } return schema; diff --git a/object_store.hpp b/object_store.hpp index 5e8e1b43..446f71c3 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -44,10 +44,10 @@ namespace realm { // optionally runs migration function/lambda if schema is out of date // NOTE: must be performed within a write transaction typedef std::function MigrationFunction; - typedef std::vector Schema; + typedef std::vector Schema; static bool update_realm_with_schema(Group *group, uint64_t version, - Schema schema, + Schema &schema, MigrationFunction migration); // get a table for an object type @@ -70,7 +70,7 @@ namespace realm { // 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 - static bool create_tables(realm::Group *group, ObjectStore::Schema target_schema, bool update_existing); + static bool create_tables(realm::Group *group, ObjectStore::Schema &target_schema, bool update_existing); // get primary key property name for object type static StringData get_primary_key_for_object(Group *group, StringData object_type); From 1ccf9f619218d50c5ab5eb9b15c436c9c570f6e6 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 4 Jun 2015 17:44:06 -0700 Subject: [PATCH 09/40] update table indexes based on schema changes --- object_store.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ object_store.hpp | 15 ++++++++++++-- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index c905a643..7295896d 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -289,6 +289,8 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, } } + changed = changed | update_indexes(group, schema); + if (!migrating) { return changed; } @@ -313,3 +315,52 @@ ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { } return schema; } + +bool ObjectStore::are_indexes_up_to_date(Group *group, Schema &schema) { + for (auto &object_schema:schema) { + TableRef table = table_for_object_type(group, object_schema.name); + if (!table) { + continue; + } + + validate_schema_and_update_column_mapping(group, object_schema); // FIXME we just need the column mapping + for (auto &property:object_schema.properties) { + if (property.is_indexed != 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.name); + if (!table) { + continue; + } + + for (auto &property:object_schema.properties) { + if (property.is_indexed == table->has_search_index(property.table_column)) { + continue; + } + + changed = true; + if (property.is_indexed) { + try { + table->add_search_index(property.table_column); + } + catch (realm::LogicError const&) { + throw ObjectStoreException(ObjectStoreException::RealmPropertyTypeNotIndexable, + {{"object_type", object_schema.name}, {"property_name", property.name}, {"property_type", string_for_property_type(property.type)}}); + } + } + else { + table->remove_search_index(property.table_column); + } + } + } + return changed; +} + diff --git a/object_store.hpp b/object_store.hpp index 446f71c3..336108be 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -38,7 +38,7 @@ namespace realm { // returns array of validation errors static std::vector validate_schema_and_update_column_mapping(Group *group, ObjectSchema &target_schema); - // updates a Realm to a given target schema/version creating tables as necessary + // 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 @@ -56,6 +56,9 @@ namespace realm { // get existing Schema from a group static Schema schema_from_group(Group *group); + // check if indexes are up to date - if false you need to call update_realm_with_schema + static bool are_indexes_up_to_date(Group *group, Schema &schema); + private: // set a new schema version static void set_schema_version(Group *group, uint64_t version); @@ -83,6 +86,9 @@ namespace realm { static std::string table_name_for_object_type(std::string class_name); static std::string object_type_for_table_name(std::string table_name); + // returns if any indexes were changed + static bool update_indexes(Group *group, Schema &schema); + friend ObjectSchema; }; @@ -91,12 +97,17 @@ namespace realm { enum Kind { // thrown when calling update_realm_to_schema and the realm version is greater than the given version RealmVersionGreaterThanSchemaVersion, + RealmPropertyTypeNotIndexable, // object_type, property_name, property_type }; - ObjectStoreException(Kind kind) : m_kind(kind) {} + + typedef std::map Dict; + ObjectStoreException(Kind kind, Dict dict = Dict()) : m_kind(kind), m_dict(dict) {} ObjectStoreException::Kind kind() { return m_kind; } + ObjectStoreException::Dict &dict() { return m_dict; } private: Kind m_kind; + Dict m_dict; }; class ObjectStoreValidationException : public std::exception { From 3c9953f4c628e6d33266c20ed694904ef8d0aef6 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 4 Jun 2015 18:14:53 -0700 Subject: [PATCH 10/40] validate primary keys after migrations --- object_store.cpp | 27 ++++++++++++++++++++++----- object_store.hpp | 4 ++++ property.hpp | 1 + 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 7295896d..8fac601a 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -18,6 +18,9 @@ #include "object_store.hpp" +#include +#include + using namespace realm; using namespace std; @@ -289,18 +292,19 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, } } - changed = changed | update_indexes(group, schema); + changed = update_indexes(group, schema) | changed; if (!migrating) { return changed; } // apply the migration block if provided and there's any old data - // to be migrated if (get_schema_version(group) != realm::ObjectStore::NotVersioned) { migration(); } + validate_primary_column_uniqueness(group, schema); + set_schema_version(group, version); return true; } @@ -325,7 +329,7 @@ bool ObjectStore::are_indexes_up_to_date(Group *group, Schema &schema) { validate_schema_and_update_column_mapping(group, object_schema); // FIXME we just need the column mapping for (auto &property:object_schema.properties) { - if (property.is_indexed != table->has_search_index(property.table_column)) { + if (property.requires_index() != table->has_search_index(property.table_column)) { return false; } } @@ -342,12 +346,12 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { } for (auto &property:object_schema.properties) { - if (property.is_indexed == table->has_search_index(property.table_column)) { + if (property.requires_index() == table->has_search_index(property.table_column)) { continue; } changed = true; - if (property.is_indexed) { + if (property.requires_index()) { try { table->add_search_index(property.table_column); } @@ -364,3 +368,16 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { return changed; } +void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schema) { + for (auto &object_schema:schema) { + auto primary_prop = object_schema.primary_key_property(); + if (primary_prop == object_schema.properties.end()) { + continue; + } + + realm::TableRef table = table_for_object_type(group, object_schema.name); + if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) { + throw ObjectStoreException(ObjectStoreException::RealmDuplicatePrimaryKeyValue, {{"object_type", object_schema.name}, {"property_name", primary_prop->name}}); + } + } +} \ No newline at end of file diff --git a/object_store.hpp b/object_store.hpp index 336108be..153bccf8 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -89,6 +89,9 @@ namespace realm { // 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; }; @@ -98,6 +101,7 @@ namespace realm { // thrown when calling update_realm_to_schema and the realm version is greater than the given version RealmVersionGreaterThanSchemaVersion, RealmPropertyTypeNotIndexable, // object_type, property_name, property_type + RealmDuplicatePrimaryKeyValue, // object_type, property_name }; typedef std::map Dict; diff --git a/property.hpp b/property.hpp index a9a1d145..3a47fa34 100644 --- a/property.hpp +++ b/property.hpp @@ -54,6 +54,7 @@ namespace realm { bool is_indexed; size_t table_column; + bool requires_index() { return is_primary | is_indexed; } }; static inline const char *string_for_property_type(PropertyType type) { From 3453515601004ce880aa7cb8173064b27aa8c565 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 4 Jun 2015 19:24:01 -0700 Subject: [PATCH 11/40] use foreach/return pointers for optional properties --- object_schema.cpp | 14 +++---- object_schema.hpp | 4 +- object_store.cpp | 93 ++++++++++++++++++++++++----------------------- object_store.hpp | 5 ++- 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index f1b03d29..643fdfce 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -46,7 +46,7 @@ ObjectSchema::ObjectSchema(realm::Group *group, std::string name) : name(name) { primary_key = realm::ObjectStore::get_primary_key_for_object(group, name); if (primary_key.length()) { auto primary_key_iter = primary_key_property(); - if (primary_key_iter == properties.end()) { + if (!primary_key_iter) { std::vector errors; errors.push_back("No property matching primary key '" + primary_key + "'"); throw ObjectStoreValidationException(errors, name); @@ -55,15 +55,13 @@ ObjectSchema::ObjectSchema(realm::Group *group, std::string name) : name(name) { } } -std::vector::iterator ObjectSchema::property_for_name(std::string name) { - auto iter = properties.begin(); - while (iter < properties.end()) { - if (iter->name == name) { - return iter; +Property *ObjectSchema::property_for_name(std::string name) { + for (auto& prop:properties) { + if (prop.name == name) { + return ∝ } - iter++; } - return iter; + return nullptr; } std::vector ObjectSchema::object_schema_from_group(realm::Group *group) { diff --git a/object_schema.hpp b/object_schema.hpp index 3fa1f1b8..0df5c524 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -37,8 +37,8 @@ namespace realm { std::vector properties; std::string primary_key; - std::vector::iterator property_for_name(std::string name); - std::vector::iterator primary_key_property() { + Property *property_for_name(std::string name); + Property *primary_key_property() { return property_for_name(primary_key); } }; diff --git a/object_store.cpp b/object_store.cpp index 8fac601a..48071318 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -132,44 +132,44 @@ std::vector ObjectStore::validate_schema_and_update_column_mapping( ObjectSchema table_schema(group, target_schema.name); // check to see if properties are the same - for (auto current_prop = table_schema.properties.begin(); current_prop != table_schema.properties.end(); current_prop++) { - auto target_prop = target_schema.property_for_name(current_prop->name); + for (auto& current_prop:table_schema.properties) { + auto target_prop = target_schema.property_for_name(current_prop.name); - if (target_prop == target_schema.properties.end()) { - validation_errors.push_back("Property '" + current_prop->name + "' is missing from latest object model."); + if (!target_prop) { + validation_errors.push_back("Property '" + current_prop.name + "' is missing from latest object model."); continue; } - if (current_prop->type != target_prop->type) { + if (current_prop.type != target_prop->type) { validation_errors.push_back("Property types for '" + target_prop->name + "' property do not match. " + - "Old type '" + string_for_property_type(current_prop->type) + + "Old type '" + string_for_property_type(current_prop.type) + "', new type '" + string_for_property_type(target_prop->type) + "'"); continue; } - if (current_prop->type == PropertyTypeObject || target_prop->type == PropertyTypeArray) { - if (current_prop->object_type != target_prop->object_type) { - validation_errors.push_back("Target object type for property '" + current_prop->name + "' does not match. " + - "Old type '" + current_prop->object_type + + if (current_prop.type == PropertyTypeObject || target_prop->type == PropertyTypeArray) { + if (current_prop.object_type != target_prop->object_type) { + validation_errors.push_back("Target object type for property '" + current_prop.name + "' does not match. " + + "Old type '" + current_prop.object_type + "', new type '" + target_prop->object_type + "'."); } } - if (current_prop->is_primary != target_prop->is_primary) { - if (current_prop->is_primary) { - validation_errors.push_back("Property '" + current_prop->name + "' is no longer a primary key."); + if (current_prop.is_primary != target_prop->is_primary) { + if (current_prop.is_primary) { + validation_errors.push_back("Property '" + current_prop.name + "' is no longer a primary key."); } else { - validation_errors.push_back("Property '" + current_prop->name + "' has been made a primary key."); + validation_errors.push_back("Property '" + current_prop.name + "' has been made a primary key."); } } // create new property with aligned column - target_prop->table_column = current_prop->table_column; + target_prop->table_column = current_prop.table_column; } // check for new missing properties - for (auto target_iter = target_schema.properties.begin(); target_iter != target_schema.properties.end(); target_iter++) { - if (table_schema.property_for_name(target_iter->name) == table_schema.properties.end()) { - validation_errors.push_back("Property '" + target_iter->name + "' has been added to latest object model."); + for (auto& target_prop:target_schema.properties) { + if (!table_schema.property_for_name(target_prop.name)) { + validation_errors.push_back("Property '" + target_prop.name + "' has been added to latest object model."); } } @@ -188,8 +188,7 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target // first pass to create missing tables vector to_update; - for (size_t i = 0; i < target_schema.size(); i++) { - ObjectSchema &object_schema = target_schema[i]; + for (auto& object_schema:target_schema) { bool created = false; ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); @@ -201,29 +200,28 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target } // second pass adds/removes columns for out of date tables - for (size_t i = 0; i < to_update.size(); i++) { - ObjectSchema *target_schema = to_update[i]; + for (auto target_schema:to_update) { TableRef table = ObjectStore::table_for_object_type(group, target_schema->name); ObjectSchema current_schema(group, target_schema->name); vector &target_props = target_schema->properties; // add missing columns - for (auto target_prop = target_props.begin(); target_prop < target_props.end(); target_prop++) { - auto current_prop = current_schema.property_for_name(target_prop->name); + 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 == current_schema.properties.end() || property_has_changed(*current_prop, *target_prop)) { - switch (target_prop->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: { - realm::TableRef link_table = ObjectStore::table_for_object_type(group, target_prop->object_type); - target_prop->table_column = table->add_column_link(realm::DataType(target_prop->type), target_prop->name, *link_table); + realm::TableRef link_table = ObjectStore::table_for_object_type(group, target_prop.object_type); + target_prop.table_column = table->add_column_link(realm::DataType(target_prop.type), target_prop.name, *link_table); break; } default: - target_prop->table_column = table->add_column(realm::DataType(target_prop->type), target_prop->name); + target_prop.table_column = table->add_column(realm::DataType(target_prop.type), target_prop.name); break; } changed = true; @@ -231,12 +229,13 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target } // remove extra columns - vector reverse_props = current_schema.properties; - std::sort(reverse_props.begin(), reverse_props.end(), [](Property &i, Property &j){ return (j.table_column < i.table_column); }); - for (auto iter = reverse_props.begin(); iter != reverse_props.end(); iter++) { - auto target_prop_iter = target_schema->property_for_name(iter->name); - if (target_prop_iter == target_props.end() || property_has_changed(*iter, *target_prop_iter)) { - table->remove_column(iter->table_column); + 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_iter = target_schema->property_for_name(current_prop.name); + if (!target_prop_iter || property_has_changed(current_prop, *target_prop_iter)) { + table->remove_column(current_prop.table_column); changed = true; } } @@ -279,8 +278,7 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, // create tables bool changed = create_metadata_tables(group) | create_tables(group, schema, migrating); - for (size_t i = 0; i < schema.size(); i++) { - ObjectSchema &target_schema = schema[i]; + for (auto& target_schema:schema) { TableRef table = table_for_object_type(group, target_schema.name); // read-only realms may be missing tables entirely @@ -311,7 +309,7 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { ObjectStore::Schema schema; - for (unsigned long i = 0; i < group->size(); i++) { + for (size_t i = 0; i < group->size(); i++) { string object_type = object_type_for_table_name(group->get_table_name(i)); if (object_type.length()) { schema.push_back(ObjectSchema(group, object_type)); @@ -321,14 +319,14 @@ ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { } bool ObjectStore::are_indexes_up_to_date(Group *group, Schema &schema) { - for (auto &object_schema:schema) { + for (auto& object_schema:schema) { TableRef table = table_for_object_type(group, object_schema.name); if (!table) { continue; } validate_schema_and_update_column_mapping(group, object_schema); // FIXME we just need the column mapping - for (auto &property:object_schema.properties) { + for (auto& property:object_schema.properties) { if (property.requires_index() != table->has_search_index(property.table_column)) { return false; } @@ -339,13 +337,13 @@ bool ObjectStore::are_indexes_up_to_date(Group *group, Schema &schema) { bool ObjectStore::update_indexes(Group *group, Schema &schema) { bool changed = false; - for (auto &object_schema:schema) { + for (auto& object_schema:schema) { TableRef table = table_for_object_type(group, object_schema.name); if (!table) { continue; } - for (auto &property:object_schema.properties) { + for (auto& property:object_schema.properties) { if (property.requires_index() == table->has_search_index(property.table_column)) { continue; } @@ -356,8 +354,11 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { table->add_search_index(property.table_column); } catch (realm::LogicError const&) { - throw ObjectStoreException(ObjectStoreException::RealmPropertyTypeNotIndexable, - {{"object_type", object_schema.name}, {"property_name", property.name}, {"property_type", string_for_property_type(property.type)}}); + throw ObjectStoreException(ObjectStoreException::RealmPropertyTypeNotIndexable, { + {"object_type", object_schema.name}, + {"property_name", property.name}, + {"property_type", string_for_property_type(property.type)} + }); } } else { @@ -369,9 +370,9 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { } void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schema) { - for (auto &object_schema:schema) { + for (auto& object_schema:schema) { auto primary_prop = object_schema.primary_key_property(); - if (primary_prop == object_schema.properties.end()) { + if (!primary_prop) { continue; } diff --git a/object_store.hpp b/object_store.hpp index 153bccf8..7254b7a8 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -82,7 +82,7 @@ namespace realm { // must be in write transaction to set static void set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key); - static realm::TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created); + static TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created); static std::string table_name_for_object_type(std::string class_name); static std::string object_type_for_table_name(std::string table_name); @@ -103,9 +103,10 @@ namespace realm { RealmPropertyTypeNotIndexable, // object_type, property_name, property_type RealmDuplicatePrimaryKeyValue, // object_type, property_name }; - typedef std::map Dict; + ObjectStoreException(Kind kind, Dict dict = Dict()) : m_kind(kind), m_dict(dict) {} + ObjectStoreException::Kind kind() { return m_kind; } ObjectStoreException::Dict &dict() { return m_dict; } From b5372a40e97ffc6f7226cb9342283e64ddb63d55 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 5 Jun 2015 12:52:05 -0700 Subject: [PATCH 12/40] error handling cleanup --- object_store.cpp | 4 ++-- object_store.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 48071318..9ecf0b5c 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -318,8 +318,8 @@ ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { return schema; } -bool ObjectStore::are_indexes_up_to_date(Group *group, Schema &schema) { - for (auto& object_schema: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.name); if (!table) { continue; diff --git a/object_store.hpp b/object_store.hpp index 7254b7a8..f190d501 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -57,7 +57,7 @@ namespace realm { static Schema schema_from_group(Group *group); // check if indexes are up to date - if false you need to call update_realm_with_schema - static bool are_indexes_up_to_date(Group *group, Schema &schema); + static bool indexes_are_up_to_date(Group *group, Schema &schema); private: // set a new schema version From 094192a86984d4018b209668d9b8dd547a3db1a8 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 5 Jun 2015 13:13:08 -0700 Subject: [PATCH 13/40] don't validate when we only need an updated column mapping --- object_store.cpp | 19 +++++++++++++++---- object_store.hpp | 11 ++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 9ecf0b5c..fd20858a 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -20,6 +20,7 @@ #include #include +#include using namespace realm; using namespace std; @@ -127,7 +128,7 @@ realm::TableRef ObjectStore::table_for_object_type_create_if_needed(realm::Group return group->get_or_add_table(table_name_for_object_type(object_type), &created); } -std::vector ObjectStore::validate_schema_and_update_column_mapping(realm::Group *group, ObjectSchema &target_schema) { +std::vector ObjectStore::validate_schema(realm::Group *group, ObjectSchema &target_schema) { vector validation_errors; ObjectSchema table_schema(group, target_schema.name); @@ -176,6 +177,16 @@ std::vector ObjectStore::validate_schema_and_update_column_mapping( return validation_errors; } +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; + } +} + 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; } @@ -283,7 +294,7 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, // read-only realms may be missing tables entirely if (table) { - auto errors = validate_schema_and_update_column_mapping(group, target_schema); + auto errors = validate_schema(group, target_schema); if (errors.size()) { throw ObjectStoreValidationException(errors, target_schema.name); } @@ -324,8 +335,8 @@ bool ObjectStore::indexes_are_up_to_date(Group *group, Schema &schema) { if (!table) { continue; } - - validate_schema_and_update_column_mapping(group, object_schema); // FIXME we just need the column mapping + + update_column_mapping(group, object_schema); for (auto& property:object_schema.properties) { if (property.requires_index() != table->has_search_index(property.table_column)) { return false; diff --git a/object_store.hpp b/object_store.hpp index f190d501..f342d4a9 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -35,8 +35,12 @@ namespace realm { static bool is_migration_required(realm::Group *group, uint64_t new_version); // 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 validate_schema_and_update_column_mapping(Group *group, ObjectSchema &target_schema); + static std::vector validate_schema(Group *group, ObjectSchema &target_schema); + + // 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); // updates a Realm to a given target schema/version creating tables and updating indexes as necessary // returns if any changes were made @@ -45,10 +49,7 @@ namespace realm { // NOTE: must be performed within a write transaction typedef std::function MigrationFunction; typedef std::vector Schema; - static bool update_realm_with_schema(Group *group, - uint64_t version, - Schema &schema, - MigrationFunction migration); + 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); From 3c6ecf6b65d055f83f01cf0af3ed54b3ee03c5c5 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 5 Jun 2015 14:06:19 -0700 Subject: [PATCH 14/40] remove duplicate code --- object_schema.cpp | 15 --------------- object_schema.hpp | 2 -- 2 files changed, 17 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index 643fdfce..adf5bc94 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -63,18 +63,3 @@ Property *ObjectSchema::property_for_name(std::string name) { } return nullptr; } - -std::vector ObjectSchema::object_schema_from_group(realm::Group *group) { - // generate object schema and class mapping for all tables in the realm - unsigned long numTables = group->size(); - vector object_schema; - - for (unsigned long i = 0; i < numTables; i++) { - std::string name = ObjectStore::object_type_for_table_name(group->get_table_name(i).data()); - if (name.length()) { - object_schema.push_back(ObjectSchema(group, name)); - } - } - - return object_schema; -} diff --git a/object_schema.hpp b/object_schema.hpp index 0df5c524..23918117 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -31,8 +31,6 @@ namespace realm { ObjectSchema() {} ObjectSchema(Group *group, std::string name); - static std::vector object_schema_from_group(Group *group); - std::string name; std::vector properties; std::string primary_key; From 73b1dd549b54a7b592fc13aab338cbcb7189a542 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 5 Jun 2015 15:52:00 -0700 Subject: [PATCH 15/40] make all string arguments const references --- object_schema.cpp | 4 ++-- object_schema.hpp | 4 ++-- object_store.cpp | 6 +++--- object_store.hpp | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index adf5bc94..e4cbc195 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -22,7 +22,7 @@ using namespace realm; using namespace std; -ObjectSchema::ObjectSchema(realm::Group *group, std::string name) : name(name) { +ObjectSchema::ObjectSchema(realm::Group *group, const std::string &name) : name(name) { TableRef table = ObjectStore::table_for_object_type(group, name); size_t count = table->get_column_count(); for (size_t col = 0; col < count; col++) { @@ -55,7 +55,7 @@ ObjectSchema::ObjectSchema(realm::Group *group, std::string name) : name(name) { } } -Property *ObjectSchema::property_for_name(std::string name) { +Property *ObjectSchema::property_for_name(const std::string &name) { for (auto& prop:properties) { if (prop.name == name) { return ∝ diff --git a/object_schema.hpp b/object_schema.hpp index 23918117..54ee2a14 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -29,13 +29,13 @@ namespace realm { class ObjectSchema { public: ObjectSchema() {} - ObjectSchema(Group *group, std::string name); + ObjectSchema(Group *group, const std::string &name); std::string name; std::vector properties; std::string primary_key; - Property *property_for_name(std::string name); + Property *property_for_name(const std::string &name); Property *primary_key_property() { return property_for_name(primary_key); } diff --git a/object_store.cpp b/object_store.cpp index fd20858a..b8021c33 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -109,14 +109,14 @@ void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData obj } } -string ObjectStore::object_type_for_table_name(string table_name) { +string ObjectStore::object_type_for_table_name(const string &table_name) { if (table_name.compare(0, 6, c_object_table_name_prefix) == 0) { return table_name.substr(6, table_name.length()-6); } return string(); } -string ObjectStore::table_name_for_object_type(string object_type) { +string ObjectStore::table_name_for_object_type(const string &object_type) { return c_object_table_name_prefix + object_type; } @@ -124,7 +124,7 @@ realm::TableRef ObjectStore::table_for_object_type(realm::Group *group, StringDa return group->get_table(table_name_for_object_type(object_type)); } -realm::TableRef ObjectStore::table_for_object_type_create_if_needed(realm::Group *group, StringData object_type, bool &created) { +realm::TableRef ObjectStore::table_for_object_type_create_if_needed(realm::Group *group, const StringData &object_type, bool &created) { return group->get_or_add_table(table_name_for_object_type(object_type), &created); } diff --git a/object_store.hpp b/object_store.hpp index f342d4a9..f067c605 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -83,9 +83,9 @@ namespace realm { // 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, StringData object_type, bool &created); - static std::string table_name_for_object_type(std::string class_name); - static std::string object_type_for_table_name(std::string table_name); + 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); // returns if any indexes were changed static bool update_indexes(Group *group, Schema &schema); From 9f124ab37e6136ea0d11fb898ace40322a767e8a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 5 Jun 2015 18:47:19 -0700 Subject: [PATCH 16/40] don't verify when not told to, reuse tables in cases it isn't inconvinient --- object_schema.cpp | 10 ++++++++-- object_schema.hpp | 6 +++++- object_store.cpp | 38 +++++++++++++++++++------------------- object_store.hpp | 5 +++-- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index e4cbc195..ca18d17e 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -22,8 +22,13 @@ using namespace realm; using namespace std; -ObjectSchema::ObjectSchema(realm::Group *group, const std::string &name) : name(name) { - TableRef table = ObjectStore::table_for_object_type(group, name); +ObjectSchema::ObjectSchema(Group *group, const std::string &name, Table *table) : name(name) { + TableRef tableRef; + if (!table) { + tableRef = ObjectStore::table_for_object_type(group, name); + table = tableRef.get(); + } + size_t count = table->get_column_count(); for (size_t col = 0; col < count; col++) { Property property; @@ -63,3 +68,4 @@ Property *ObjectSchema::property_for_name(const std::string &name) { } return nullptr; } + diff --git a/object_schema.hpp b/object_schema.hpp index 54ee2a14..485ceb09 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -24,12 +24,16 @@ #include "property.hpp" #include +#include namespace realm { class ObjectSchema { public: ObjectSchema() {} - ObjectSchema(Group *group, const std::string &name); + + // 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, Table *table = nullptr); std::string name; std::vector properties; diff --git a/object_store.cpp b/object_store.cpp index b8021c33..4c08f406 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -128,9 +128,9 @@ realm::TableRef ObjectStore::table_for_object_type_create_if_needed(realm::Group return group->get_or_add_table(table_name_for_object_type(object_type), &created); } -std::vector ObjectStore::validate_schema(realm::Group *group, ObjectSchema &target_schema) { +std::vector ObjectStore::validate_schema(realm::Group *group, ObjectSchema &target_schema, Table *cached_table) { vector validation_errors; - ObjectSchema table_schema(group, target_schema.name); + ObjectSchema table_schema(group, target_schema.name, cached_table); // check to see if properties are the same for (auto& current_prop:table_schema.properties) { @@ -211,11 +211,10 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target } // second pass adds/removes columns for out of date tables - for (auto target_schema:to_update) { - TableRef table = ObjectStore::table_for_object_type(group, target_schema->name); - - ObjectSchema current_schema(group, target_schema->name); - vector &target_props = target_schema->properties; + 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, table.get()); + vector &target_props = target_object_schema->properties; // add missing columns for (auto target_prop:target_props) { @@ -244,7 +243,7 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target return (j.table_column < i.table_column); }); for (auto& current_prop:current_schema.properties) { - auto target_prop_iter = target_schema->property_for_name(current_prop.name); + auto target_prop_iter = target_object_schema->property_for_name(current_prop.name); if (!target_prop_iter || property_has_changed(current_prop, *target_prop_iter)) { table->remove_column(current_prop.table_column); changed = true; @@ -252,16 +251,16 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target } // update table metadata - if (target_schema->primary_key.length()) { + 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.length() || current_schema.primary_key != target_schema->primary_key) { - realm::ObjectStore::set_primary_key_for_object(group, target_schema->name, target_schema->primary_key); + if (!current_schema.primary_key.length() || current_schema.primary_key != target_object_schema->primary_key) { + realm::ObjectStore::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 - realm::ObjectStore::set_primary_key_for_object(group, target_schema->name, ""); + realm::ObjectStore::set_primary_key_for_object(group, target_object_schema->name, ""); changed = true; } } @@ -288,13 +287,14 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, bool migrating = is_migration_required(group, version); // create tables - bool changed = create_metadata_tables(group) | create_tables(group, schema, migrating); - for (auto& target_schema:schema) { - TableRef table = table_for_object_type(group, target_schema.name); + bool changed = create_metadata_tables(group); + changed = create_tables(group, schema, migrating) | changed; + for (auto& target_schema:schema) { // read-only realms may be missing tables entirely + TableRef table = table_for_object_type(group, target_schema.name); if (table) { - auto errors = validate_schema(group, target_schema); + auto errors = validate_schema(group, target_schema, table.get()); if (errors.size()) { throw ObjectStoreValidationException(errors, target_schema.name); } @@ -309,7 +309,7 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, // apply the migration block if provided and there's any old data if (get_schema_version(group) != realm::ObjectStore::NotVersioned) { - migration(); + migration(group, schema); } validate_primary_column_uniqueness(group, schema); @@ -387,9 +387,9 @@ void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schem continue; } - realm::TableRef table = table_for_object_type(group, object_schema.name); + TableRef table = table_for_object_type(group, object_schema.name); if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) { throw ObjectStoreException(ObjectStoreException::RealmDuplicatePrimaryKeyValue, {{"object_type", object_schema.name}, {"property_name", primary_prop->name}}); } } -} \ No newline at end of file +} diff --git a/object_store.hpp b/object_store.hpp index f067c605..88722b31 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -36,8 +36,9 @@ namespace realm { // verify a target schema against its table, setting the table_column property on each schema object // updates the column mapping on the target_schema + // if no table is provided it is fetched from the group // returns array of validation errors - static std::vector validate_schema(Group *group, ObjectSchema &target_schema); + static std::vector validate_schema(Group *group, ObjectSchema &target_schema, Table *cached_table = nullptr); // 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); @@ -47,8 +48,8 @@ namespace realm { // 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 MigrationFunction; typedef std::vector Schema; + typedef std::function MigrationFunction; static bool update_realm_with_schema(Group *group, uint64_t version, Schema &schema, MigrationFunction migration); // get a table for an object type From 0e81927e780b2cf263eece5828f5b00605da53e5 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 10 Jun 2015 14:21:43 -0700 Subject: [PATCH 17/40] fixes for pr comments --- object_schema.cpp | 19 +++++++++--------- object_schema.hpp | 11 ++++++----- object_store.cpp | 50 ++++++++++++++++++++++++----------------------- object_store.hpp | 21 ++++++++++++++------ property.cpp | 21 -------------------- property.hpp | 4 ++-- 6 files changed, 58 insertions(+), 68 deletions(-) delete mode 100644 property.cpp diff --git a/object_schema.cpp b/object_schema.cpp index ca18d17e..2e74b17f 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -19,6 +19,9 @@ #include "object_schema.hpp" #include "object_store.hpp" +#include +#include + using namespace realm; using namespace std; @@ -30,6 +33,7 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name, Table *table) } 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(); @@ -42,21 +46,16 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name, Table *table) realm::TableRef linkTable = table->get_link_target(col); property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data()); } - else { - property.object_type = ""; - } - properties.push_back(property); + properties.push_back(move(property)); } primary_key = realm::ObjectStore::get_primary_key_for_object(group, name); if (primary_key.length()) { - auto primary_key_iter = primary_key_property(); - if (!primary_key_iter) { - std::vector errors; - errors.push_back("No property matching primary key '" + primary_key + "'"); - throw ObjectStoreValidationException(errors, name); + auto primary_key_prop = primary_key_property(); + if (!primary_key_prop) { + throw ObjectStoreValidationException({"No property matching primary key '" + primary_key + "'"}, name); } - primary_key_iter->is_primary = true; + primary_key_prop->is_primary = true; } } diff --git a/object_schema.hpp b/object_schema.hpp index 485ceb09..1bd109fb 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -16,17 +16,18 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef __realm__object_schema__ -#define __realm__object_schema__ +#ifndef REALM_OBJECT_SCHEMA_HPP +#define REALM_OBJECT_SCHEMA_HPP #include #include #include "property.hpp" -#include -#include namespace realm { + class Group; + class Table; + class ObjectSchema { public: ObjectSchema() {} @@ -46,4 +47,4 @@ namespace realm { }; } -#endif /* defined(__realm__object_schema__) */ +#endif /* defined(REALM_OBJECT_SCHEMA_HPP) */ diff --git a/object_store.cpp b/object_store.cpp index 4c08f406..c875c535 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -18,6 +18,8 @@ #include "object_store.hpp" +#include +#include #include #include #include @@ -110,7 +112,7 @@ void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData obj } string ObjectStore::object_type_for_table_name(const string &table_name) { - if (table_name.compare(0, 6, c_object_table_name_prefix) == 0) { + if (table_name.size() >= 6 && table_name.compare(0, 6, c_object_table_name_prefix) == 0) { return table_name.substr(6, table_name.length()-6); } return string(); @@ -133,7 +135,7 @@ std::vector ObjectStore::validate_schema(realm::Group *group, Objec ObjectSchema table_schema(group, target_schema.name, cached_table); // check to see if properties are the same - for (auto& current_prop:table_schema.properties) { + for (auto& current_prop : table_schema.properties) { auto target_prop = target_schema.property_for_name(current_prop.name); if (!target_prop) { @@ -168,7 +170,7 @@ std::vector ObjectStore::validate_schema(realm::Group *group, Objec } // check for new missing properties - for (auto& target_prop:target_schema.properties) { + for (auto& target_prop : target_schema.properties) { if (!table_schema.property_for_name(target_prop.name)) { validation_errors.push_back("Property '" + target_prop.name + "' has been added to latest object model."); } @@ -179,7 +181,7 @@ std::vector ObjectStore::validate_schema(realm::Group *group, Objec void ObjectStore::update_column_mapping(Group *group, ObjectSchema &target_schema) { ObjectSchema table_schema(group, target_schema.name); - for (auto& target_prop:target_schema.properties) { + for (auto& target_prop : target_schema.properties) { auto table_prop = table_schema.property_for_name(target_prop.name); REALM_ASSERT_DEBUG(table_prop); @@ -199,7 +201,7 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target // first pass to create missing tables vector to_update; - for (auto& object_schema:target_schema) { + for (auto& object_schema : target_schema) { bool created = false; ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); @@ -211,13 +213,13 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target } // second pass adds/removes columns for out of date tables - for (auto target_object_schema:to_update) { + 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, table.get()); vector &target_props = target_object_schema->properties; // add missing columns - for (auto target_prop:target_props) { + 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) @@ -240,11 +242,11 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target // remove extra columns sort(begin(current_schema.properties), end(current_schema.properties), [](Property &i, Property &j) { - return (j.table_column < i.table_column); + return j.table_column < i.table_column; }); - for (auto& current_prop:current_schema.properties) { - auto target_prop_iter = target_object_schema->property_for_name(current_prop.name); - if (!target_prop_iter || property_has_changed(current_prop, *target_prop_iter)) { + 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; } @@ -253,14 +255,14 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target // 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.length() || current_schema.primary_key != target_object_schema->primary_key) { - realm::ObjectStore::set_primary_key_for_object(group, target_object_schema->name, target_object_schema->primary_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 - realm::ObjectStore::set_primary_key_for_object(group, target_object_schema->name, ""); + set_primary_key_for_object(group, target_object_schema->name, ""); changed = true; } } @@ -270,7 +272,7 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target bool ObjectStore::is_migration_required(realm::Group *group, uint64_t new_version) { uint64_t old_version = get_schema_version(group); - if (old_version > new_version && old_version != realm::ObjectStore::NotVersioned) { + if (old_version > new_version && old_version != NotVersioned) { throw ObjectStoreException(ObjectStoreException::RealmVersionGreaterThanSchemaVersion); } return old_version != new_version; @@ -288,9 +290,9 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, // create tables bool changed = create_metadata_tables(group); - changed = create_tables(group, schema, migrating) | changed; + changed = create_tables(group, schema, migrating) || changed; - for (auto& target_schema:schema) { + for (auto& target_schema : schema) { // read-only realms may be missing tables entirely TableRef table = table_for_object_type(group, target_schema.name); if (table) { @@ -301,7 +303,7 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, } } - changed = update_indexes(group, schema) | changed; + changed = update_indexes(group, schema) || changed; if (!migrating) { return changed; @@ -323,21 +325,21 @@ ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { for (size_t i = 0; i < group->size(); i++) { string object_type = object_type_for_table_name(group->get_table_name(i)); if (object_type.length()) { - schema.push_back(ObjectSchema(group, object_type)); + schema.emplace_back(group, move(object_type)); } } return schema; } bool ObjectStore::indexes_are_up_to_date(Group *group, Schema &schema) { - for (auto &object_schema:schema) { + for (auto &object_schema : schema) { TableRef table = table_for_object_type(group, object_schema.name); if (!table) { continue; } update_column_mapping(group, object_schema); - for (auto& property:object_schema.properties) { + for (auto& property : object_schema.properties) { if (property.requires_index() != table->has_search_index(property.table_column)) { return false; } @@ -348,13 +350,13 @@ bool ObjectStore::indexes_are_up_to_date(Group *group, Schema &schema) { bool ObjectStore::update_indexes(Group *group, Schema &schema) { bool changed = false; - for (auto& object_schema:schema) { + for (auto& object_schema : schema) { TableRef table = table_for_object_type(group, object_schema.name); if (!table) { continue; } - for (auto& property:object_schema.properties) { + for (auto& property : object_schema.properties) { if (property.requires_index() == table->has_search_index(property.table_column)) { continue; } @@ -381,7 +383,7 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { } void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schema) { - for (auto& object_schema:schema) { + for (auto& object_schema : schema) { auto primary_prop = object_schema.primary_key_property(); if (!primary_prop) { continue; diff --git a/object_store.hpp b/object_store.hpp index 88722b31..72467a54 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -16,13 +16,22 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef __realm__object_store__ -#define __realm__object_store__ +#ifndef REALM_OBJECT_STORE_HPP +#define REALM_OBJECT_STORE_HPP + +#include +#include +#include -#include #include "object_schema.hpp" namespace realm { + class Group; + class StringData; + class Table; + template class BasicTableRef; + typedef BasicTableRef TableRef; + class ObjectStore { public: // Schema version used for uninitialized Realms @@ -106,11 +115,11 @@ namespace realm { RealmDuplicatePrimaryKeyValue, // object_type, property_name }; typedef std::map Dict; - + ObjectStoreException(Kind kind, Dict dict = Dict()) : m_kind(kind), m_dict(dict) {} ObjectStoreException::Kind kind() { return m_kind; } - ObjectStoreException::Dict &dict() { return m_dict; } + const ObjectStoreException::Dict &dict() { return m_dict; } private: Kind m_kind; @@ -130,5 +139,5 @@ namespace realm { }; } -#endif /* defined(__realm__object_store__) */ +#endif /* defined(REALM_OBJECT_STORE_HPP) */ diff --git a/property.cpp b/property.cpp deleted file mode 100644 index 44dfa8fa..00000000 --- a/property.cpp +++ /dev/null @@ -1,21 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2014 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 "property.hpp" - -using namespace realm; diff --git a/property.hpp b/property.hpp index 3a47fa34..0a368684 100644 --- a/property.hpp +++ b/property.hpp @@ -45,7 +45,7 @@ namespace realm { PropertyTypeArray = 13, }; - class Property { + struct Property { public: std::string name; PropertyType type; @@ -54,7 +54,7 @@ namespace realm { bool is_indexed; size_t table_column; - bool requires_index() { return is_primary | is_indexed; } + bool requires_index() { return is_primary || is_indexed; } }; static inline const char *string_for_property_type(PropertyType type) { From 4a87bc6505cb7b88c5a33de0db65ba528809446a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 10 Jun 2015 14:39:01 -0700 Subject: [PATCH 18/40] make stuff const and use move --- object_store.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/object_store.hpp b/object_store.hpp index 72467a54..f60d1f55 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -118,8 +118,8 @@ namespace realm { ObjectStoreException(Kind kind, Dict dict = Dict()) : m_kind(kind), m_dict(dict) {} - ObjectStoreException::Kind kind() { return m_kind; } - const ObjectStoreException::Dict &dict() { return m_dict; } + ObjectStoreException::Kind kind() const { return m_kind; } + const ObjectStoreException::Dict &dict() const { return m_dict; } private: Kind m_kind; @@ -130,8 +130,8 @@ namespace realm { public: ObjectStoreValidationException(std::vector validation_errors, std::string object_type) : m_validation_errors(validation_errors), m_object_type(object_type) {} - std::vector validation_errors() { return m_validation_errors; } - std::string object_type() { return m_object_type; } + const std::vector &validation_errors() const { return m_validation_errors; } + std::string object_type() const { return m_object_type; } private: std::vector m_validation_errors; From b3b3136f9af222bb634d83585eaf477d2fab034e Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 10 Jun 2015 14:53:24 -0700 Subject: [PATCH 19/40] clear primary key for deleted objects --- object_store.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index c875c535..1e8f0025 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -24,6 +24,8 @@ #include #include +#include + using namespace realm; using namespace std; @@ -103,8 +105,10 @@ void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData obj } // set if changing, or remove if setting to nil - if (primary_key.size() == 0 && row != realm::not_found) { - table->remove(row); + if (primary_key.size() == 0) { + if (row != realm::not_found) { + table->remove(row); + } } else { table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key); @@ -201,6 +205,7 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target // first pass to create missing tables vector to_update; + set target_type_names; for (auto& object_schema : target_schema) { bool created = false; ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); @@ -210,16 +215,19 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target to_update.push_back(&object_schema); changed = true; } + + // keep track of names to figure out what tables need deletion + target_type_names.insert(object_schema.name); } // second pass adds/removes columns for out of date tables - for (auto target_object_schema : to_update) { + 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, table.get()); vector &target_props = target_object_schema->properties; // add missing columns - for (auto target_prop : target_props) { + 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) @@ -267,6 +275,18 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target } } + // remove primary key entries for deleted types + // FIXME - delete actual tables once we have proper testing + ObjectStore::Schema schema; + for (int i = (int)group->size() - 1; i >= 0 ; i--) { + string object_type = object_type_for_table_name(group->get_table_name(i)); + if (object_type.length()) { + if (target_type_names.find(object_type) == target_type_names.end()) { + set_primary_key_for_object(group, object_type, ""); + } + } + } + return changed; } From f972ab427832367f655005534468c2c115b8b5be Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 10 Jun 2015 15:45:29 -0700 Subject: [PATCH 20/40] move exception classes to their own file - store exception messages and implmenet what() --- object_store.cpp | 7 ++-- object_store.hpp | 33 +------------------ object_store_exceptions.cpp | 45 +++++++++++++++++++++++++ object_store_exceptions.hpp | 66 +++++++++++++++++++++++++++++++++++++ property.hpp | 6 ++-- 5 files changed, 119 insertions(+), 38 deletions(-) create mode 100644 object_store_exceptions.cpp create mode 100644 object_store_exceptions.hpp diff --git a/object_store.cpp b/object_store.cpp index 1e8f0025..18160e08 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -293,7 +293,7 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target bool ObjectStore::is_migration_required(realm::Group *group, uint64_t new_version) { uint64_t old_version = get_schema_version(group); if (old_version > new_version && old_version != NotVersioned) { - throw ObjectStoreException(ObjectStoreException::RealmVersionGreaterThanSchemaVersion); + throw ObjectStoreException(ObjectStoreException::Kind::RealmVersionGreaterThanSchemaVersion); } return old_version != new_version; } @@ -387,7 +387,7 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { table->add_search_index(property.table_column); } catch (realm::LogicError const&) { - throw ObjectStoreException(ObjectStoreException::RealmPropertyTypeNotIndexable, { + throw ObjectStoreException(ObjectStoreException::Kind::RealmPropertyTypeNotIndexable, { {"object_type", object_schema.name}, {"property_name", property.name}, {"property_type", string_for_property_type(property.type)} @@ -411,7 +411,8 @@ void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schem TableRef table = table_for_object_type(group, object_schema.name); if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) { - throw ObjectStoreException(ObjectStoreException::RealmDuplicatePrimaryKeyValue, {{"object_type", object_schema.name}, {"property_name", primary_prop->name}}); + throw ObjectStoreException(ObjectStoreException::Kind::RealmDuplicatePrimaryKeyValue, + {{"object_type", object_schema.name}, {"property_name", primary_prop->name}}); } } } diff --git a/object_store.hpp b/object_store.hpp index f60d1f55..278ed39c 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -24,6 +24,7 @@ #include #include "object_schema.hpp" +#include "object_store_exceptions.hpp" namespace realm { class Group; @@ -105,38 +106,6 @@ namespace realm { friend ObjectSchema; }; - - class ObjectStoreException : public std::exception { - public: - enum Kind { - // thrown when calling update_realm_to_schema and the realm version is greater than the given version - RealmVersionGreaterThanSchemaVersion, - RealmPropertyTypeNotIndexable, // object_type, property_name, property_type - RealmDuplicatePrimaryKeyValue, // object_type, property_name - }; - typedef std::map Dict; - - ObjectStoreException(Kind kind, Dict dict = Dict()) : m_kind(kind), m_dict(dict) {} - - ObjectStoreException::Kind kind() const { return m_kind; } - const ObjectStoreException::Dict &dict() const { return m_dict; } - - private: - Kind m_kind; - Dict m_dict; - }; - - class ObjectStoreValidationException : public std::exception { - public: - ObjectStoreValidationException(std::vector validation_errors, std::string object_type) : - m_validation_errors(validation_errors), m_object_type(object_type) {} - const std::vector &validation_errors() const { return m_validation_errors; } - std::string object_type() const { return m_object_type; } - - private: - std::vector m_validation_errors; - std::string m_object_type; - }; } #endif /* defined(REALM_OBJECT_STORE_HPP) */ diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp new file mode 100644 index 00000000..67c4b37c --- /dev/null +++ b/object_store_exceptions.cpp @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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_exceptions.hpp" + +using namespace realm; +using namespace std; + +ObjectStoreException::ObjectStoreException(Kind kind, Dict dict) : m_kind(kind), m_dict(dict) { + switch (m_kind) { + case Kind::RealmVersionGreaterThanSchemaVersion: + m_what = "Schema version less than last set version in Realm"; + break; + case Kind::RealmPropertyTypeNotIndexable: + m_what = "Can't index property '" + m_dict.at("object_type") + "." + m_dict.at("property_name") + "': " + + "indexing properties of type '" + m_dict.at("property_type") + "' is currently not supported"; + break; + case Kind::RealmDuplicatePrimaryKeyValue: + m_what = "Primary key property '" + m_dict["property_name"] + "' has duplicate values after migration."; + break; + } +} + +ObjectStoreValidationException::ObjectStoreValidationException(std::vector validation_errors, std::string object_type) : + m_validation_errors(validation_errors), m_object_type(object_type) { + m_what = "Migration is required for object type '" + m_object_type + "' due to the following errors:"; + for (auto error : m_validation_errors) { + m_what += "\n- " + error; + } +} diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp new file mode 100644 index 00000000..7f6cc2e8 --- /dev/null +++ b/object_store_exceptions.hpp @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2014 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_EXCEPTIONS_HPP +#define REALM_OBJECT_STORE_EXCEPTIONS_HPP + +#include +#include +#include + +namespace realm { + + class ObjectStoreException : public std::exception { + public: + enum class Kind { + // thrown when calling update_realm_to_schema and the realm version is greater than the given version + RealmVersionGreaterThanSchemaVersion, + RealmPropertyTypeNotIndexable, // object_type, property_name, property_type + RealmDuplicatePrimaryKeyValue, // object_type, property_name + }; + typedef std::map Dict; + + ObjectStoreException(Kind kind, Dict dict = Dict()); + + ObjectStoreException::Kind kind() const { return m_kind; } + const ObjectStoreException::Dict &dict() const { return m_dict; } + + const char *what() const noexcept override { return m_what.c_str(); } + + private: + Kind m_kind; + Dict m_dict; + std::string m_what; + }; + + class ObjectStoreValidationException : public std::exception { + public: + ObjectStoreValidationException(std::vector validation_errors, std::string object_type); + + const std::vector &validation_errors() const { return m_validation_errors; } + std::string object_type() const { return m_object_type; } + const char *what() const noexcept override { return m_what.c_str(); } + + private: + std::vector m_validation_errors; + std::string m_object_type; + std::string m_what; + }; +} + +#endif /* defined(REALM_OBJECT_STORE_EXCEPTIONS_HPP) */ diff --git a/property.hpp b/property.hpp index 0a368684..9679034a 100644 --- a/property.hpp +++ b/property.hpp @@ -16,8 +16,8 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef __realm__property__ -#define __realm__property__ +#ifndef REALM_PROPERTY_HPP +#define REALM_PROPERTY_HPP #include @@ -83,4 +83,4 @@ namespace realm { } } -#endif /* defined__realm__property__ */ +#endif /* REALM_PROPERTY_HPP */ From b3bee56f382dc97f2d39cca73714520c6db7a2d7 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 11 Jun 2015 10:33:46 -0700 Subject: [PATCH 21/40] pr feedback --- object_schema.cpp | 11 ++--- object_schema.hpp | 5 +- object_store.cpp | 93 +++++++++++++++++++------------------ object_store.hpp | 8 ++-- object_store_exceptions.cpp | 4 +- object_store_exceptions.hpp | 4 +- property.hpp | 2 +- 7 files changed, 64 insertions(+), 63 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index 2e74b17f..2b29fc41 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2014 Realm Inc. +// 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. @@ -25,12 +25,9 @@ using namespace realm; using namespace std; -ObjectSchema::ObjectSchema(Group *group, const std::string &name, Table *table) : name(name) { - TableRef tableRef; - if (!table) { - tableRef = ObjectStore::table_for_object_type(group, name); - table = tableRef.get(); - } +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); diff --git a/object_schema.hpp b/object_schema.hpp index 1bd109fb..56a14d17 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2014 Realm Inc. +// 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. @@ -26,7 +26,6 @@ namespace realm { class Group; - class Table; class ObjectSchema { public: @@ -34,7 +33,7 @@ namespace realm { // 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, Table *table = nullptr); + ObjectSchema(Group *group, const std::string &name); std::string name; std::vector properties; diff --git a/object_store.cpp b/object_store.cpp index 18160e08..dd7e43e1 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2014 Realm Inc. +// 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. @@ -25,6 +25,7 @@ #include #include +#include using namespace realm; using namespace std; @@ -39,74 +40,77 @@ const size_t c_primaryKeyObjectClassColumnIndex = 0; const char * const c_primaryKeyPropertyNameColumnName = "pk_property"; const size_t c_primaryKeyPropertyNameColumnIndex = 1; -const string c_object_table_name_prefix = "class_"; +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(); const uint64_t ObjectStore::NotVersioned = numeric_limits::max(); -bool ObjectStore::has_metadata_tables(realm::Group *group) { +bool ObjectStore::has_metadata_tables(Group *group) { return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName); } -bool ObjectStore::create_metadata_tables(realm::Group *group) { +bool ObjectStore::create_metadata_tables(Group *group) { bool changed = false; - realm::TableRef table = group->get_or_add_table(c_primaryKeyTableName); + TableRef table = group->get_or_add_table(c_primaryKeyTableName); if (table->get_column_count() == 0) { - table->add_column(realm::type_String, c_primaryKeyObjectClassColumnName); - table->add_column(realm::type_String, c_primaryKeyPropertyNameColumnName); + 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(realm::type_Int, c_versionColumnName); + table->add_column(type_Int, c_versionColumnName); // set initial version table->add_empty_row(); - table->set_int(c_versionColumnIndex, 0, realm::ObjectStore::NotVersioned); + table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned); changed = true; } return changed; } -uint64_t ObjectStore::get_schema_version(realm::Group *group) { - realm::TableRef table = group->get_table(c_metadataTableName); +uint64_t ObjectStore::get_schema_version(Group *group) { + TableRef table = group->get_table(c_metadataTableName); if (!table || table->get_column_count() == 0) { - return realm::ObjectStore::NotVersioned; + return ObjectStore::NotVersioned; } - return table->get_int(c_versionColumnIndex, 0); + return table->get_int(c_versionColumnIndex, c_zeroRowIndex); } -void ObjectStore::set_schema_version(realm::Group *group, uint64_t version) { - realm::TableRef table = group->get_or_add_table(c_metadataTableName); - table->set_int(c_versionColumnIndex, 0, version); +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(realm::Group *group, StringData object_type) { - realm::TableRef table = group->get_table(c_primaryKeyTableName); +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 == realm::not_found) { + if (row == not_found) { return ""; } return table->get_string(c_primaryKeyPropertyNameColumnIndex, row); } -void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData object_type, StringData primary_key) { - realm::TableRef table = group->get_table(c_primaryKeyTableName); +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 == realm::not_found && primary_key.size()) { + 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 != realm::not_found) { + if (row != not_found) { table->remove(row); } } @@ -116,27 +120,27 @@ void ObjectStore::set_primary_key_for_object(realm::Group *group, StringData obj } string ObjectStore::object_type_for_table_name(const string &table_name) { - if (table_name.size() >= 6 && table_name.compare(0, 6, c_object_table_name_prefix) == 0) { - return table_name.substr(6, table_name.length()-6); + 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 string(); } string ObjectStore::table_name_for_object_type(const string &object_type) { - return c_object_table_name_prefix + object_type; + return c_object_table_prefix + object_type; } -realm::TableRef ObjectStore::table_for_object_type(realm::Group *group, StringData object_type) { +TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) { return group->get_table(table_name_for_object_type(object_type)); } -realm::TableRef ObjectStore::table_for_object_type_create_if_needed(realm::Group *group, const StringData &object_type, bool &created) { +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); } -std::vector ObjectStore::validate_schema(realm::Group *group, ObjectSchema &target_schema, Table *cached_table) { +std::vector ObjectStore::validate_schema(Group *group, ObjectSchema &target_schema) { vector validation_errors; - ObjectSchema table_schema(group, target_schema.name, cached_table); + ObjectSchema table_schema(group, target_schema.name); // check to see if properties are the same for (auto& current_prop : table_schema.properties) { @@ -200,7 +204,7 @@ static inline bool property_has_changed(Property &p1, Property &p2) { // 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(realm::Group *group, ObjectStore::Schema &target_schema, bool update_existing) { +bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema, bool update_existing) { bool changed = false; // first pass to create missing tables @@ -223,7 +227,7 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target // 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, table.get()); + ObjectSchema current_schema(group, target_object_schema->name); vector &target_props = target_object_schema->properties; // add missing columns @@ -236,12 +240,12 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target // for objects and arrays, we have to specify target table case PropertyTypeObject: case PropertyTypeArray: { - realm::TableRef link_table = ObjectStore::table_for_object_type(group, target_prop.object_type); - target_prop.table_column = table->add_column_link(realm::DataType(target_prop.type), target_prop.name, *link_table); + 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(realm::DataType(target_prop.type), target_prop.name); + target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name); break; } changed = true; @@ -290,23 +294,24 @@ bool ObjectStore::create_tables(realm::Group *group, ObjectStore::Schema &target return changed; } -bool ObjectStore::is_migration_required(realm::Group *group, uint64_t new_version) { +bool ObjectStore::is_schema_at_version(Group *group, uint64_t version) { uint64_t old_version = get_schema_version(group); - if (old_version > new_version && old_version != NotVersioned) { - throw ObjectStoreException(ObjectStoreException::Kind::RealmVersionGreaterThanSchemaVersion); + if (old_version > version && old_version != NotVersioned) { + throw ObjectStoreException(ObjectStoreException::Kind::RealmVersionGreaterThanSchemaVersion, + {{"old_version", to_string(old_version)}, {"new_version", to_string(version)}}); } - return old_version != new_version; + return old_version != version; } -bool ObjectStore::update_realm_with_schema(realm::Group *group, +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_migration_required(group, version); + bool migrating = is_schema_at_version(group, version); // create tables bool changed = create_metadata_tables(group); @@ -316,7 +321,7 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, // read-only realms may be missing tables entirely TableRef table = table_for_object_type(group, target_schema.name); if (table) { - auto errors = validate_schema(group, target_schema, table.get()); + auto errors = validate_schema(group, target_schema); if (errors.size()) { throw ObjectStoreValidationException(errors, target_schema.name); } @@ -330,7 +335,7 @@ bool ObjectStore::update_realm_with_schema(realm::Group *group, } // apply the migration block if provided and there's any old data - if (get_schema_version(group) != realm::ObjectStore::NotVersioned) { + if (get_schema_version(group) != ObjectStore::NotVersioned) { migration(group, schema); } @@ -386,7 +391,7 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { try { table->add_search_index(property.table_column); } - catch (realm::LogicError const&) { + catch (LogicError const&) { throw ObjectStoreException(ObjectStoreException::Kind::RealmPropertyTypeNotIndexable, { {"object_type", object_schema.name}, {"property_name", property.name}, diff --git a/object_store.hpp b/object_store.hpp index 278ed39c..0c5f547e 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2014 Realm Inc. +// 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. @@ -41,14 +41,14 @@ namespace realm { // get the last set schema version static uint64_t get_schema_version(Group *group); - // checks if a migration is required for a given schema version - static bool is_migration_required(realm::Group *group, uint64_t new_version); + // 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 its table, setting the table_column property on each schema object // updates the column mapping on the target_schema // if no table is provided it is fetched from the group // returns array of validation errors - static std::vector validate_schema(Group *group, ObjectSchema &target_schema, Table *cached_table = nullptr); + static std::vector validate_schema(Group *group, ObjectSchema &target_schema); // 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); diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp index 67c4b37c..486ff124 100644 --- a/object_store_exceptions.cpp +++ b/object_store_exceptions.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2014 Realm Inc. +// 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. @@ -24,7 +24,7 @@ using namespace std; ObjectStoreException::ObjectStoreException(Kind kind, Dict dict) : m_kind(kind), m_dict(dict) { switch (m_kind) { case Kind::RealmVersionGreaterThanSchemaVersion: - m_what = "Schema version less than last set version in Realm"; + m_what = "Provided schema version " + m_dict.at("old_version") + " is less than last set version " + m_dict.at("new_version") + "."; break; case Kind::RealmPropertyTypeNotIndexable: m_what = "Can't index property '" + m_dict.at("object_type") + "." + m_dict.at("property_name") + "': " + diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp index 7f6cc2e8..268fbf0a 100644 --- a/object_store_exceptions.hpp +++ b/object_store_exceptions.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2014 Realm Inc. +// 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. @@ -29,7 +29,7 @@ namespace realm { public: enum class Kind { // thrown when calling update_realm_to_schema and the realm version is greater than the given version - RealmVersionGreaterThanSchemaVersion, + RealmVersionGreaterThanSchemaVersion, // old_version, new_version RealmPropertyTypeNotIndexable, // object_type, property_name, property_type RealmDuplicatePrimaryKeyValue, // object_type, property_name }; diff --git a/property.hpp b/property.hpp index 9679034a..156310ee 100644 --- a/property.hpp +++ b/property.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2014 Realm Inc. +// 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. From 107c2de9b6f72e381936f41f335c4cbf2d15356c Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 11 Jun 2015 12:17:55 -0700 Subject: [PATCH 22/40] add api to delete a table and remove the pk for an object type --- object_store.cpp | 27 +++++++++------------------ object_store.hpp | 5 ++++- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index dd7e43e1..65a47f3a 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -24,7 +24,6 @@ #include #include -#include #include using namespace realm; @@ -209,7 +208,6 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema // first pass to create missing tables vector to_update; - set target_type_names; for (auto& object_schema : target_schema) { bool created = false; ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); @@ -219,9 +217,6 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema to_update.push_back(&object_schema); changed = true; } - - // keep track of names to figure out what tables need deletion - target_type_names.insert(object_schema.name); } // second pass adds/removes columns for out of date tables @@ -278,19 +273,6 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema changed = true; } } - - // remove primary key entries for deleted types - // FIXME - delete actual tables once we have proper testing - ObjectStore::Schema schema; - for (int i = (int)group->size() - 1; i >= 0 ; i--) { - string object_type = object_type_for_table_name(group->get_table_name(i)); - if (object_type.length()) { - if (target_type_names.find(object_type) == target_type_names.end()) { - set_primary_key_for_object(group, object_type, ""); - } - } - } - return changed; } @@ -421,3 +403,12 @@ void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schem } } } + +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, ""); + } +} + diff --git a/object_store.hpp b/object_store.hpp index 0c5f547e..5d1967a7 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -71,6 +71,9 @@ namespace realm { // 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); + // 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); @@ -84,7 +87,7 @@ namespace realm { 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 validates existing tables + // if update existing is true, updates existing tables, otherwise only adds and initializes new tables static bool create_tables(realm::Group *group, ObjectStore::Schema &target_schema, bool update_existing); // get primary key property name for object type From 80b1642d327dd63eb336600ce1d334dac5478131 Mon Sep 17 00:00:00 2001 From: "Samuel E. Giddins" Date: Thu, 16 Apr 2015 16:48:07 -0700 Subject: [PATCH 23/40] Add support for nullable string columns, and make NSString properties nullable by default --- object_schema.cpp | 1 + object_store.cpp | 4 ++-- property.hpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index 2b29fc41..a0cc3429 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -37,6 +37,7 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { 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 diff --git a/object_store.cpp b/object_store.cpp index 65a47f3a..e5261694 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -197,7 +197,7 @@ void ObjectStore::update_column_mapping(Group *group, ObjectSchema &target_schem } 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; + return p1.type != p2.type || p1.name != p2.name || p1.object_type != p2.object_type || p1.is_nullable != p2.is_nullable; } // set references to tables on targetSchema and create/update any missing or out-of-date tables @@ -240,7 +240,7 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema break; } default: - target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name); + target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name, target_prop.is_nullable); break; } changed = true; diff --git a/property.hpp b/property.hpp index 156310ee..a24e42f2 100644 --- a/property.hpp +++ b/property.hpp @@ -52,6 +52,7 @@ namespace realm { 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; } From e19fcfd67bcff0689962b9eac9b0099996860680 Mon Sep 17 00:00:00 2001 From: "Samuel E. Giddins" Date: Fri, 17 Apr 2015 18:12:18 -0700 Subject: [PATCH 24/40] [RLMObjectStore] Require a migration when changing the nullability of a column --- object_store.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/object_store.cpp b/object_store.cpp index e5261694..7e4c0490 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -171,6 +171,14 @@ std::vector ObjectStore::validate_schema(Group *group, ObjectSchema validation_errors.push_back("Property '" + current_prop.name + "' has been made a primary key."); } } + 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."); + } + } // create new property with aligned column target_prop->table_column = current_prop.table_column; From c90f3e02109a5e9c276a10c6d1f1446fbc6c03a7 Mon Sep 17 00:00:00 2001 From: "Samuel E. Giddins" Date: Mon, 20 Apr 2015 13:16:30 -0700 Subject: [PATCH 25/40] [RLMObjectStore] Automatically migrate required properties to be optional --- object_store.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/object_store.cpp b/object_store.cpp index 7e4c0490..6d605138 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -29,6 +29,34 @@ using namespace realm; using namespace std; +template +static inline +T get_value(TableRef table, size_t row, size_t column); + +template +static inline +void set_value(TableRef table, size_t row, size_t column, T value); + +template <> +StringData get_value(TableRef table, size_t row, size_t column) { + return table->get_string(column, row); +} + +template <> +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); +} + +template <> +void set_value(TableRef table, size_t row, size_t column, BinaryData value) { + table->set_binary(column, row, value); +} + const char * const c_metadataTableName = "metadata"; const char * const c_versionColumnName = "version"; const size_t c_versionColumnIndex = 0; @@ -208,6 +236,22 @@ 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; } +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 +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(table, i, old_column); + set_value(table, i, new_column, old_value); + } +} + // 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 @@ -251,6 +295,20 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name, target_prop.is_nullable); break; } + + if (current_prop && property_can_be_migrated_to_nullable(*current_prop, target_prop)) { + switch (target_prop.type) { + case PropertyTypeString: + copy_property_to_property(*current_prop, target_prop, table); + break; + case PropertyTypeData: + copy_property_to_property(*current_prop, target_prop, table); + break; + default: + REALM_UNREACHABLE(); + } + } + changed = true; } } From c0da7c76e1b2fbe0f5bc24d4ea63e1de050ce7d1 Mon Sep 17 00:00:00 2001 From: "Samuel E. Giddins" Date: Thu, 18 Jun 2015 14:46:37 -0700 Subject: [PATCH 26/40] Feature-flag null binary and string support -DREALM_ENABLE_NULL to enable --- object_schema.cpp | 4 ++++ object_store.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/object_schema.cpp b/object_schema.cpp index a0cc3429..e25ab009 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -37,7 +37,11 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { property.type = (PropertyType)table->get_column_type(col); property.is_indexed = table->has_search_index(col); property.is_primary = false; +#ifdef REALM_ENABLE_NULL property.is_nullable = table->is_nullable(col) || property.type == PropertyTypeObject; +#else + property.is_nullable = property.type == PropertyTypeObject; +#endif property.table_column = col; if (property.type == PropertyTypeObject || property.type == PropertyTypeArray) { // set link type for objects and arrays diff --git a/object_store.cpp b/object_store.cpp index 6d605138..f0e6b5dd 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -292,7 +292,11 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema break; } default: +#ifdef REALM_ENABLE_NULL target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name, target_prop.is_nullable); +#else + target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name); +#endif break; } From a29037b47f496aa6b812d935deb31384dac28ceb Mon Sep 17 00:00:00 2001 From: "Samuel E. Giddins" Date: Tue, 23 Jun 2015 14:33:34 -0700 Subject: [PATCH 27/40] [ObjectStore] No need to template set_value, it can be overloaded --- object_store.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index f0e6b5dd..1090bd3e 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -33,16 +33,12 @@ template static inline T get_value(TableRef table, size_t row, size_t column); -template -static inline -void set_value(TableRef table, size_t row, size_t column, T value); - template <> StringData get_value(TableRef table, size_t row, size_t column) { return table->get_string(column, row); } -template <> +static inline void set_value(TableRef table, size_t row, size_t column, StringData value) { table->set_string(column, row, value); } @@ -52,7 +48,7 @@ BinaryData get_value(TableRef table, size_t row, size_t column) { return table->get_binary(column, row); } -template <> +static inline void set_value(TableRef table, size_t row, size_t column, BinaryData value) { table->set_binary(column, row, value); } From c3e82a58ae76a0539020157fa26dd6241339c5b4 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 16 Jun 2015 10:07:40 -0700 Subject: [PATCH 28/40] use enum for dict/info keys, support per platform/language exception messages --- object_schema.cpp | 5 ++- object_store.cpp | 55 ++++++++++++------------ object_store.hpp | 2 +- object_store_exceptions.cpp | 85 ++++++++++++++++++++++++++++++++----- object_store_exceptions.hpp | 61 +++++++++++++++++--------- 5 files changed, 146 insertions(+), 62 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index e25ab009..4f0eef52 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -55,7 +55,10 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { if (primary_key.length()) { auto primary_key_prop = primary_key_property(); if (!primary_key_prop) { - throw ObjectStoreValidationException({"No property matching primary key '" + primary_key + "'"}, name); + throw ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedPrimaryKey, { + {ObjectStoreException::InfoKey::ObjectType, name}, + {ObjectStoreException::InfoKey::PrimaryKey, ""}, + {ObjectStoreException::InfoKey::OldPrimaryKey, primary_key}}); } primary_key_prop->is_primary = true; } diff --git a/object_store.cpp b/object_store.cpp index 1090bd3e..233923d7 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -161,8 +161,8 @@ TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const return group->get_or_add_table(table_name_for_object_type(object_type), &created); } -std::vector ObjectStore::validate_schema(Group *group, ObjectSchema &target_schema) { - vector validation_errors; +std::vector ObjectStore::validate_schema(Group *group, ObjectSchema &target_schema) { + vector exceptions; ObjectSchema table_schema(group, target_schema.name); // check to see if properties are the same @@ -170,29 +170,20 @@ std::vector ObjectStore::validate_schema(Group *group, ObjectSchema auto target_prop = target_schema.property_for_name(current_prop.name); if (!target_prop) { - validation_errors.push_back("Property '" + current_prop.name + "' is missing from latest object model."); + exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMissingProperty, + table_schema.name, current_prop)); continue; } if (current_prop.type != target_prop->type) { - validation_errors.push_back("Property types for '" + target_prop->name + "' property do not match. " + - "Old type '" + string_for_property_type(current_prop.type) + - "', new type '" + string_for_property_type(target_prop->type) + "'"); + exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedTypes, + table_schema.name, current_prop, *target_prop)); continue; } if (current_prop.type == PropertyTypeObject || target_prop->type == PropertyTypeArray) { if (current_prop.object_type != target_prop->object_type) { - validation_errors.push_back("Target object type for property '" + current_prop.name + "' does not match. " + - "Old type '" + current_prop.object_type + - "', new type '" + target_prop->object_type + "'."); - } - } - if (current_prop.is_primary != target_prop->is_primary) { - if (current_prop.is_primary) { - validation_errors.push_back("Property '" + current_prop.name + "' is no longer a primary key."); - } - else { - validation_errors.push_back("Property '" + current_prop.name + "' has been made a primary key."); + exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedObjectTypes, + table_schema.name, current_prop, *target_prop)); } } if (current_prop.is_nullable != target_prop->is_nullable) { @@ -208,14 +199,24 @@ std::vector ObjectStore::validate_schema(Group *group, ObjectSchema 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(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedPrimaryKey, { + {ObjectStoreException::InfoKey::ObjectType, target_schema.name}, + {ObjectStoreException::InfoKey::PrimaryKey, target_schema.primary_key}, + {ObjectStoreException::InfoKey::OldPrimaryKey, table_schema.primary_key}, + })); + } + // check for new missing properties for (auto& target_prop : target_schema.properties) { if (!table_schema.property_for_name(target_prop.name)) { - validation_errors.push_back("Property '" + target_prop.name + "' has been added to latest object model."); + exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaNewProperty, + table_schema.name, target_prop)); } } - return validation_errors; + return exceptions; } void ObjectStore::update_column_mapping(Group *group, ObjectSchema &target_schema) { @@ -345,8 +346,9 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema 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 ObjectStoreException(ObjectStoreException::Kind::RealmVersionGreaterThanSchemaVersion, - {{"old_version", to_string(old_version)}, {"new_version", to_string(version)}}); + throw ObjectStoreException(ObjectStoreException::Kind::RealmVersionGreaterThanSchemaVersion, { + {ObjectStoreException::InfoKey::OldVersion, to_string(old_version)}, + {ObjectStoreException::InfoKey::NewVersion, to_string(version)}}); } return old_version != version; } @@ -371,7 +373,7 @@ bool ObjectStore::update_realm_with_schema(Group *group, if (table) { auto errors = validate_schema(group, target_schema); if (errors.size()) { - throw ObjectStoreValidationException(errors, target_schema.name); + throw ObjectStoreException(errors, target_schema.name); } } } @@ -440,11 +442,7 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { table->add_search_index(property.table_column); } catch (LogicError const&) { - throw ObjectStoreException(ObjectStoreException::Kind::RealmPropertyTypeNotIndexable, { - {"object_type", object_schema.name}, - {"property_name", property.name}, - {"property_type", string_for_property_type(property.type)} - }); + throw ObjectStoreException(ObjectStoreException::Kind::RealmPropertyTypeNotIndexable, object_schema.name, property); } } else { @@ -464,8 +462,7 @@ void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schem TableRef table = table_for_object_type(group, object_schema.name); if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) { - throw ObjectStoreException(ObjectStoreException::Kind::RealmDuplicatePrimaryKeyValue, - {{"object_type", object_schema.name}, {"property_name", primary_prop->name}}); + throw ObjectStoreException(ObjectStoreException::Kind::RealmDuplicatePrimaryKeyValue, object_schema.name, *primary_prop); } } } diff --git a/object_store.hpp b/object_store.hpp index 5d1967a7..c1e581be 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -48,7 +48,7 @@ namespace realm { // updates the column mapping on the target_schema // if no table is provided it is fetched from the group // returns array of validation errors - static std::vector validate_schema(Group *group, ObjectSchema &target_schema); + static std::vector validate_schema(Group *group, ObjectSchema &target_schema); // 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); diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp index 486ff124..e93376d4 100644 --- a/object_store_exceptions.cpp +++ b/object_store_exceptions.cpp @@ -17,29 +17,92 @@ //////////////////////////////////////////////////////////////////////////// #include "object_store_exceptions.hpp" +#include "property.hpp" + +#include using namespace realm; using namespace std; -ObjectStoreException::ObjectStoreException(Kind kind, Dict dict) : m_kind(kind), m_dict(dict) { +ObjectStoreException::CustomWhat ObjectStoreException::s_custom_what = nullptr; + +ObjectStoreException::ObjectStoreException(Kind kind, Info info) : m_kind(kind), m_info(info) { + set_what(); +} + +ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop) : m_kind(kind) { + m_info[InfoKey::ObjectType] = object_type; + m_info[InfoKey::PropertyName] = prop.name; + m_info[InfoKey::PropertyType] = string_for_property_type(prop.type); + m_info[InfoKey::PropertyObjectType] = prop.object_type; + set_what(); +} + +ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop, const Property &oldProp) : + m_kind(kind) { + m_info[InfoKey::ObjectType] = object_type; + m_info[InfoKey::PropertyName] = prop.name; + m_info[InfoKey::PropertyType] = string_for_property_type(prop.type); + m_info[InfoKey::OldPropertyType] = string_for_property_type(oldProp.type); + m_info[InfoKey::PropertyObjectType] = prop.object_type; + m_info[InfoKey::OldPropertyObjectType] = oldProp.object_type; + set_what(); +} + +void ObjectStoreException::set_what() { + if (s_custom_what) { + string custom = s_custom_what(*this); + if (custom.length()) { + m_what = custom; + return; + } + } + switch (m_kind) { case Kind::RealmVersionGreaterThanSchemaVersion: - m_what = "Provided schema version " + m_dict.at("old_version") + " is less than last set version " + m_dict.at("new_version") + "."; + m_what = "Provided schema version " + m_info[InfoKey::OldVersion] + + " is less than last set version " + m_info[InfoKey::NewVersion] + "."; break; case Kind::RealmPropertyTypeNotIndexable: - m_what = "Can't index property '" + m_dict.at("object_type") + "." + m_dict.at("property_name") + "': " + - "indexing properties of type '" + m_dict.at("property_type") + "' is currently not supported"; + m_what = "Can't index property '" + m_info[InfoKey::ObjectType] + "." + m_info[InfoKey::PropertyName] + "': " + + "indexing properties of type '" + m_info[InfoKey::PropertyType] + "' is currently not supported"; break; case Kind::RealmDuplicatePrimaryKeyValue: - m_what = "Primary key property '" + m_dict["property_name"] + "' has duplicate values after migration."; + m_what = "Primary key property '" + m_info[InfoKey::PropertyType] + "' has duplicate values after migration."; + break; + case Kind::ObjectSchemaMissingProperty: + m_what = "Property '" + m_info[InfoKey::PropertyName] + "' is missing from latest object model."; + break; + case Kind::ObjectSchemaNewProperty: + m_what = "Property '" + m_info[InfoKey::PropertyName] + "' has been added to latest object model."; + break; + case Kind::ObjectSchemaMismatchedTypes: + m_what = "Property types for '" + m_info[InfoKey::PropertyName] + "' property do not match. " + + "Old type '" + m_info[InfoKey::OldPropertyType] + "', new type '" + m_info[InfoKey::PropertyType] + "'"; + break; + case Kind::ObjectSchemaMismatchedObjectTypes: + m_what = "Target object type for property '" + m_info[InfoKey::PropertyName] + "' does not match. " + + "Old type '" + m_info[InfoKey::OldPropertyObjectType] + "', new type '" + m_info[InfoKey::PropertyObjectType] + "'."; + break; + case Kind::ObjectSchemaMismatchedPrimaryKey: + if (!m_info[InfoKey::PrimaryKey].length()) { + m_what = "Property '" + m_info[InfoKey::OldPrimaryKey] + "' is no longer a primary key."; + } + else { + m_what = "Property '" + m_info[InfoKey::PrimaryKey] + "' has been made a primary key."; + } + break; + case Kind::ObjectStoreValidationFailure: + m_what = "Migration is required for object type '" + info().at(InfoKey::ObjectType) + "' due to the following errors:"; + for (auto error : m_validation_errors) { + m_what += string("\n- ") + error.what(); + } break; } } -ObjectStoreValidationException::ObjectStoreValidationException(std::vector validation_errors, std::string object_type) : - m_validation_errors(validation_errors), m_object_type(object_type) { - m_what = "Migration is required for object type '" + m_object_type + "' due to the following errors:"; - for (auto error : m_validation_errors) { - m_what += "\n- " + error; - } +ObjectStoreException::ObjectStoreException(vector validation_errors, const string &object_type) : + m_validation_errors(validation_errors), m_kind(Kind::ObjectStoreValidationFailure), m_info({{InfoKey::ObjectType, object_type}}) { + set_what(); } + diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp index 268fbf0a..12f3f732 100644 --- a/object_store_exceptions.hpp +++ b/object_store_exceptions.hpp @@ -24,42 +24,63 @@ #include namespace realm { + class Property; class ObjectStoreException : public std::exception { public: enum class Kind { // thrown when calling update_realm_to_schema and the realm version is greater than the given version - RealmVersionGreaterThanSchemaVersion, // old_version, new_version - RealmPropertyTypeNotIndexable, // object_type, property_name, property_type - RealmDuplicatePrimaryKeyValue, // object_type, property_name + RealmVersionGreaterThanSchemaVersion, // OldVersion, NewVersion + RealmPropertyTypeNotIndexable, // ObjectType, PropertyName, PropertyType + RealmDuplicatePrimaryKeyValue, // ObjectType, PropertyName, PropertyType + ObjectSchemaMissingProperty, // ObjectType, PropertyName, PropertyType + ObjectSchemaNewProperty, // ObjectType, PropertyName, PropertyType + ObjectSchemaMismatchedTypes, // ObjectType, PropertyName, PropertyType, OldPropertyType + ObjectSchemaMismatchedObjectTypes, // ObjectType, PropertyName, PropertyType, ObjectType, OldObjectType + ObjectSchemaMismatchedPrimaryKey, // ObjectType, PrimaryKey, OldPrimaryKey + ObjectStoreValidationFailure, // ObjectType, vector }; - typedef std::map Dict; - ObjectStoreException(Kind kind, Dict dict = Dict()); + enum class InfoKey { + OldVersion, + NewVersion, + ObjectType, + PropertyName, + PropertyType, + OldPropertyType, + PropertyObjectType, + OldPropertyObjectType, + PrimaryKey, + OldPrimaryKey, + }; + typedef std::map Info; + + ObjectStoreException(Kind kind, Info info = Info()); + ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop); + ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop, const Property &oldProp); + + // ObjectStoreValidationFailure + ObjectStoreException(std::vector validation_errors, const std::string &object_type); ObjectStoreException::Kind kind() const { return m_kind; } - const ObjectStoreException::Dict &dict() const { return m_dict; } + const ObjectStoreException::Info &info() const { return m_info; } + const std::vector &validation_errors() { return m_validation_errors; } const char *what() const noexcept override { return m_what.c_str(); } + // implement CustomWhat to customize exception messages per platform/language + typedef std::string (*CustomWhat)(ObjectStoreException &); + static void set_custom_what(CustomWhat message_generator) { s_custom_what = message_generator; } + private: Kind m_kind; - Dict m_dict; + Info m_info; + std::vector m_validation_errors; + std::string m_what; - }; + void set_what(); - class ObjectStoreValidationException : public std::exception { - public: - ObjectStoreValidationException(std::vector validation_errors, std::string object_type); - - const std::vector &validation_errors() const { return m_validation_errors; } - std::string object_type() const { return m_object_type; } - const char *what() const noexcept override { return m_what.c_str(); } - - private: - std::vector m_validation_errors; - std::string m_object_type; - std::string m_what; + static CustomWhat s_custom_what; }; } From 3874860d50705011bb8b4b2b825f79437f9162cd Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 16 Jun 2015 10:36:40 -0700 Subject: [PATCH 29/40] allow replacement of work 'property' in default exception messages --- object_store_exceptions.cpp | 28 +++++++++++++++++++--------- object_store_exceptions.hpp | 5 +++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp index e93376d4..0178f4dc 100644 --- a/object_store_exceptions.cpp +++ b/object_store_exceptions.cpp @@ -25,6 +25,8 @@ using namespace realm; using namespace std; ObjectStoreException::CustomWhat ObjectStoreException::s_custom_what = nullptr; +string ObjectStoreException::s_property_string = "property"; +string ObjectStoreException::s_property_string_upper = "Property"; ObjectStoreException::ObjectStoreException(Kind kind, Info info) : m_kind(kind), m_info(info) { set_what(); @@ -64,32 +66,32 @@ void ObjectStoreException::set_what() { " is less than last set version " + m_info[InfoKey::NewVersion] + "."; break; case Kind::RealmPropertyTypeNotIndexable: - m_what = "Can't index property '" + m_info[InfoKey::ObjectType] + "." + m_info[InfoKey::PropertyName] + "': " + - "indexing properties of type '" + m_info[InfoKey::PropertyType] + "' is currently not supported"; + m_what = "Can't index " + s_property_string + " '" + m_info[InfoKey::ObjectType] + "." + m_info[InfoKey::PropertyName] + "': " + + "indexing a " + s_property_string + " of type '" + m_info[InfoKey::PropertyType] + "' is currently not supported"; break; case Kind::RealmDuplicatePrimaryKeyValue: - m_what = "Primary key property '" + m_info[InfoKey::PropertyType] + "' has duplicate values after migration."; + m_what = "Primary key " + s_property_string + " '" + m_info[InfoKey::PropertyType] + "' has duplicate values after migration."; break; case Kind::ObjectSchemaMissingProperty: - m_what = "Property '" + m_info[InfoKey::PropertyName] + "' is missing from latest object model."; + m_what = s_property_string_upper + " '" + m_info[InfoKey::PropertyName] + "' is missing from latest object model."; break; case Kind::ObjectSchemaNewProperty: - m_what = "Property '" + m_info[InfoKey::PropertyName] + "' has been added to latest object model."; + m_what = s_property_string_upper + " '" + m_info[InfoKey::PropertyName] + "' has been added to latest object model."; break; case Kind::ObjectSchemaMismatchedTypes: - m_what = "Property types for '" + m_info[InfoKey::PropertyName] + "' property do not match. " + + m_what = s_property_string_upper + " types for '" + m_info[InfoKey::PropertyName] + "' " + s_property_string + " do not match. " + "Old type '" + m_info[InfoKey::OldPropertyType] + "', new type '" + m_info[InfoKey::PropertyType] + "'"; break; case Kind::ObjectSchemaMismatchedObjectTypes: - m_what = "Target object type for property '" + m_info[InfoKey::PropertyName] + "' does not match. " + + m_what = "Target object type for " + s_property_string + " '" + m_info[InfoKey::PropertyName] + "' does not match. " + "Old type '" + m_info[InfoKey::OldPropertyObjectType] + "', new type '" + m_info[InfoKey::PropertyObjectType] + "'."; break; case Kind::ObjectSchemaMismatchedPrimaryKey: if (!m_info[InfoKey::PrimaryKey].length()) { - m_what = "Property '" + m_info[InfoKey::OldPrimaryKey] + "' is no longer a primary key."; + m_what = s_property_string_upper + " '" + m_info[InfoKey::OldPrimaryKey] + "' is no longer a primary key."; } else { - m_what = "Property '" + m_info[InfoKey::PrimaryKey] + "' has been made a primary key."; + m_what = s_property_string_upper + " '" + m_info[InfoKey::PrimaryKey] + "' has been made a primary key."; } break; case Kind::ObjectStoreValidationFailure: @@ -106,3 +108,11 @@ ObjectStoreException::ObjectStoreException(vector validati set_what(); } +void ObjectStoreException::set_property_string(std::string property_string) { + s_property_string = s_property_string_upper = property_string; + s_property_string[0] = tolower(s_property_string[0]); + s_property_string_upper[0] = toupper(s_property_string_upper[0]); + +} + + diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp index 12f3f732..af41e31f 100644 --- a/object_store_exceptions.hpp +++ b/object_store_exceptions.hpp @@ -72,6 +72,9 @@ namespace realm { typedef std::string (*CustomWhat)(ObjectStoreException &); static void set_custom_what(CustomWhat message_generator) { s_custom_what = message_generator; } + // set the string used in defualt messages to represent the property object - defaults to 'property' + static void set_property_string(std::string property_string); + private: Kind m_kind; Info m_info; @@ -81,6 +84,8 @@ namespace realm { void set_what(); static CustomWhat s_custom_what; + static std::string s_property_string; + static std::string s_property_string_upper; }; } From 26f1a0a4c88b8c87ddc63a43c30554992ab866b2 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 16 Jun 2015 16:19:34 -0700 Subject: [PATCH 30/40] support exception format strings --- object_schema.cpp | 5 +- object_store.cpp | 17 ++-- object_store_exceptions.cpp | 159 +++++++++++++++++++----------------- object_store_exceptions.hpp | 58 +++++++------ 4 files changed, 127 insertions(+), 112 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index 4f0eef52..d31c15a6 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -55,10 +55,7 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { if (primary_key.length()) { auto primary_key_prop = primary_key_property(); if (!primary_key_prop) { - throw ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedPrimaryKey, { - {ObjectStoreException::InfoKey::ObjectType, name}, - {ObjectStoreException::InfoKey::PrimaryKey, ""}, - {ObjectStoreException::InfoKey::OldPrimaryKey, primary_key}}); + throw ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaChangedPrimaryKey, name, primary_key); } primary_key_prop->is_primary = true; } diff --git a/object_store.cpp b/object_store.cpp index 233923d7..3622c31b 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -201,11 +201,14 @@ std::vector ObjectStore::validate_schema(Group *group, Obj // check for change to primary key if (table_schema.primary_key != target_schema.primary_key) { - exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedPrimaryKey, { - {ObjectStoreException::InfoKey::ObjectType, target_schema.name}, - {ObjectStoreException::InfoKey::PrimaryKey, target_schema.primary_key}, - {ObjectStoreException::InfoKey::OldPrimaryKey, table_schema.primary_key}, - })); + 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)); + } } // check for new missing properties @@ -346,9 +349,7 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema 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 ObjectStoreException(ObjectStoreException::Kind::RealmVersionGreaterThanSchemaVersion, { - {ObjectStoreException::InfoKey::OldVersion, to_string(old_version)}, - {ObjectStoreException::InfoKey::NewVersion, to_string(version)}}); + throw ObjectStoreException(old_version, version); } return old_version != version; } diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp index 0178f4dc..1482060a 100644 --- a/object_store_exceptions.cpp +++ b/object_store_exceptions.cpp @@ -20,99 +20,108 @@ #include "property.hpp" #include +#include using namespace realm; using namespace std; -ObjectStoreException::CustomWhat ObjectStoreException::s_custom_what = nullptr; -string ObjectStoreException::s_property_string = "property"; -string ObjectStoreException::s_property_string_upper = "Property"; - -ObjectStoreException::ObjectStoreException(Kind kind, Info info) : m_kind(kind), m_info(info) { - set_what(); -} +ObjectStoreException::ObjectStoreException(Kind kind, Info info) : m_kind(kind), m_info(info), m_what(generate_what()) {} ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop) : m_kind(kind) { - m_info[InfoKey::ObjectType] = object_type; - m_info[InfoKey::PropertyName] = prop.name; - m_info[InfoKey::PropertyType] = string_for_property_type(prop.type); - m_info[InfoKey::PropertyObjectType] = prop.object_type; - set_what(); + m_info[InfoKeyObjectType] = object_type; + m_info[InfoKeyPropertyName] = prop.name; + m_info[InfoKeyPropertyType] = string_for_property_type(prop.type); + m_info[InfoKeyPropertyObjectType] = prop.object_type; + m_what = generate_what(); } ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop, const Property &oldProp) : m_kind(kind) { - m_info[InfoKey::ObjectType] = object_type; - m_info[InfoKey::PropertyName] = prop.name; - m_info[InfoKey::PropertyType] = string_for_property_type(prop.type); - m_info[InfoKey::OldPropertyType] = string_for_property_type(oldProp.type); - m_info[InfoKey::PropertyObjectType] = prop.object_type; - m_info[InfoKey::OldPropertyObjectType] = oldProp.object_type; - set_what(); + m_info[InfoKeyObjectType] = object_type; + m_info[InfoKeyPropertyName] = prop.name; + m_info[InfoKeyPropertyType] = string_for_property_type(prop.type); + m_info[InfoKeyOldPropertyType] = string_for_property_type(oldProp.type); + m_info[InfoKeyPropertyObjectType] = prop.object_type; + m_info[InfoKeyOldPropertyObjectType] = oldProp.object_type; + m_what = generate_what(); } -void ObjectStoreException::set_what() { - if (s_custom_what) { - string custom = s_custom_what(*this); - if (custom.length()) { - m_what = custom; - return; - } - } +ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const std::string primary_key) : m_kind(kind) { + m_info[InfoKeyObjectType] = object_type; + m_info[InfoKeyPrimaryKey] = primary_key; + m_what = generate_what(); +} - switch (m_kind) { - case Kind::RealmVersionGreaterThanSchemaVersion: - m_what = "Provided schema version " + m_info[InfoKey::OldVersion] + - " is less than last set version " + m_info[InfoKey::NewVersion] + "."; - break; - case Kind::RealmPropertyTypeNotIndexable: - m_what = "Can't index " + s_property_string + " '" + m_info[InfoKey::ObjectType] + "." + m_info[InfoKey::PropertyName] + "': " + - "indexing a " + s_property_string + " of type '" + m_info[InfoKey::PropertyType] + "' is currently not supported"; - break; - case Kind::RealmDuplicatePrimaryKeyValue: - m_what = "Primary key " + s_property_string + " '" + m_info[InfoKey::PropertyType] + "' has duplicate values after migration."; - break; - case Kind::ObjectSchemaMissingProperty: - m_what = s_property_string_upper + " '" + m_info[InfoKey::PropertyName] + "' is missing from latest object model."; - break; - case Kind::ObjectSchemaNewProperty: - m_what = s_property_string_upper + " '" + m_info[InfoKey::PropertyName] + "' has been added to latest object model."; - break; - case Kind::ObjectSchemaMismatchedTypes: - m_what = s_property_string_upper + " types for '" + m_info[InfoKey::PropertyName] + "' " + s_property_string + " do not match. " + - "Old type '" + m_info[InfoKey::OldPropertyType] + "', new type '" + m_info[InfoKey::PropertyType] + "'"; - break; - case Kind::ObjectSchemaMismatchedObjectTypes: - m_what = "Target object type for " + s_property_string + " '" + m_info[InfoKey::PropertyName] + "' does not match. " + - "Old type '" + m_info[InfoKey::OldPropertyObjectType] + "', new type '" + m_info[InfoKey::PropertyObjectType] + "'."; - break; - case Kind::ObjectSchemaMismatchedPrimaryKey: - if (!m_info[InfoKey::PrimaryKey].length()) { - m_what = s_property_string_upper + " '" + m_info[InfoKey::OldPrimaryKey] + "' is no longer a primary key."; - } - else { - m_what = s_property_string_upper + " '" + m_info[InfoKey::PrimaryKey] + "' has been made a primary key."; - } - break; - case Kind::ObjectStoreValidationFailure: - m_what = "Migration is required for object type '" + info().at(InfoKey::ObjectType) + "' due to the following errors:"; - for (auto error : m_validation_errors) { - m_what += string("\n- ") + error.what(); - } - break; - } +ObjectStoreException::ObjectStoreException(uint64_t old_version, uint64_t new_version) : m_kind(Kind::RealmVersionGreaterThanSchemaVersion) { + m_info[InfoKeyOldVersion] = to_string(old_version); + m_info[InfoKeyNewVersion] = to_string(new_version); + m_what = generate_what(); } ObjectStoreException::ObjectStoreException(vector validation_errors, const string &object_type) : - m_validation_errors(validation_errors), m_kind(Kind::ObjectStoreValidationFailure), m_info({{InfoKey::ObjectType, object_type}}) { - set_what(); + m_validation_errors(validation_errors), + m_kind(Kind::ObjectStoreValidationFailure), + m_info({{InfoKeyObjectType, object_type}}), + m_what(generate_what()) { } -void ObjectStoreException::set_property_string(std::string property_string) { - s_property_string = s_property_string_upper = property_string; - s_property_string[0] = tolower(s_property_string[0]); - s_property_string_upper[0] = toupper(s_property_string_upper[0]); - +string ObjectStoreException::generate_what() const { + auto format_string = s_custom_format_strings.find(m_kind); + if (format_string != s_custom_format_strings.end()) { + return populate_format_string(format_string->second); + } + return populate_format_string(s_default_format_strings.at(m_kind)); } +string ObjectStoreException::validation_errors_string() const { + string errors_string; + for (auto error : m_validation_errors) { + errors_string += string("\n- ") + error.what(); + } + return errors_string; +} + +std::string ObjectStoreException::populate_format_string(const std::string & format_string) const { + string out_string, current(format_string); + smatch sm; + regex re("\\{(\\w+)\\}"); + while(regex_search(current, sm, re)) { + out_string += sm.prefix(); + const string &key = sm[1]; + if (key == "ValidationString") { + out_string += validation_errors_string(); + } + else { + out_string += m_info.at(key); + } + current = sm.suffix(); + } + out_string += current; + return out_string; +} + +ObjectStoreException::FormatStrings ObjectStoreException::s_custom_format_strings; +const ObjectStoreException::FormatStrings ObjectStoreException::s_default_format_strings = { + {Kind::RealmVersionGreaterThanSchemaVersion, + "Provided schema version {InfoKeyOldVersion} is less than last set version {InfoKeyNewVersion}."}, + {Kind::RealmPropertyTypeNotIndexable, + "Can't index property {InfoKeyObjectType}.{InfoKeyPropertyName}: indexing a property of type '{InfoKeyPropertyType}' is currently not supported"}, + {Kind::RealmDuplicatePrimaryKeyValue, + "Primary key property '{InfoKeyPropertyType}' has duplicate values after migration."}, + {Kind::ObjectSchemaMissingProperty, + "Property '{InfoKeyPropertyName}' is missing from latest object model."}, + {Kind::ObjectSchemaNewProperty, + "Property '{InfoKeyPropertyName}' has been added to latest object model."}, + {Kind::ObjectSchemaMismatchedTypes, + "Property types for '{InfoKeyPropertyName}' property do not match. Old type '{InfoKeyOldPropertyType}', new type '{InfoKeyPropertyType}'"}, + {Kind::ObjectSchemaMismatchedObjectTypes, + "Target object type for property '{InfoKeyPropertyName}' does not match. Old type '{InfoKeyOldPropertyObjectType}', new type '{InfoKeyPropertyObjectType}'."}, + {Kind::ObjectSchemaChangedPrimaryKey, + "Property '{InfoKeyPrimaryKey}' is no longer a primary key."}, + {Kind::ObjectSchemaNewPrimaryKey, + "Property '{InfoKeyPrimaryKey}' has been made a primary key."}, + {Kind::ObjectStoreValidationFailure, + "Migration is required for object type '{InfoKeyObjectType}' due to the following errors: {ValidationErrors}"} +}; + diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp index af41e31f..098fea30 100644 --- a/object_store_exceptions.hpp +++ b/object_store_exceptions.hpp @@ -23,13 +23,14 @@ #include #include +#define INFO_KEY(key) InfoKey InfoKey##key = "InfoKey" #key; + namespace realm { class Property; class ObjectStoreException : public std::exception { public: enum class Kind { - // thrown when calling update_realm_to_schema and the realm version is greater than the given version RealmVersionGreaterThanSchemaVersion, // OldVersion, NewVersion RealmPropertyTypeNotIndexable, // ObjectType, PropertyName, PropertyType RealmDuplicatePrimaryKeyValue, // ObjectType, PropertyName, PropertyType @@ -37,55 +38,62 @@ namespace realm { ObjectSchemaNewProperty, // ObjectType, PropertyName, PropertyType ObjectSchemaMismatchedTypes, // ObjectType, PropertyName, PropertyType, OldPropertyType ObjectSchemaMismatchedObjectTypes, // ObjectType, PropertyName, PropertyType, ObjectType, OldObjectType - ObjectSchemaMismatchedPrimaryKey, // ObjectType, PrimaryKey, OldPrimaryKey + ObjectSchemaChangedPrimaryKey, // ObjectType, PrimaryKey + ObjectSchemaNewPrimaryKey, // ObjectType, PrimaryKey ObjectStoreValidationFailure, // ObjectType, vector }; - enum class InfoKey { - OldVersion, - NewVersion, - ObjectType, - PropertyName, - PropertyType, - OldPropertyType, - PropertyObjectType, - OldPropertyObjectType, - PrimaryKey, - OldPrimaryKey, - }; + typedef const std::string InfoKey; typedef std::map Info; - ObjectStoreException(Kind kind, Info info = Info()); ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop); + + // ObjectSchemaMismatchedTypes, ObjectSchemaMismatchedObjectTypes ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop, const Property &oldProp); + // ObjectSchemaChangedPrimaryKey, ObjectSchemaNewPrimaryKey + ObjectStoreException(Kind kind, const std::string &object_type, const std::string primary_key); + + // RealmVersionGreaterThanSchemaVersion + ObjectStoreException(uint64_t old_version, uint64_t new_version); + // ObjectStoreValidationFailure ObjectStoreException(std::vector validation_errors, const std::string &object_type); ObjectStoreException::Kind kind() const { return m_kind; } const ObjectStoreException::Info &info() const { return m_info; } - const std::vector &validation_errors() { return m_validation_errors; } const char *what() const noexcept override { return m_what.c_str(); } // implement CustomWhat to customize exception messages per platform/language - typedef std::string (*CustomWhat)(ObjectStoreException &); - static void set_custom_what(CustomWhat message_generator) { s_custom_what = message_generator; } - - // set the string used in defualt messages to represent the property object - defaults to 'property' - static void set_property_string(std::string property_string); + typedef std::map FormatStrings; + static void set_custom_format_strings(FormatStrings custom_format_strings) { s_custom_format_strings = custom_format_strings; } private: + ObjectStoreException(Kind kind, Info info = Info()); + Kind m_kind; Info m_info; std::vector m_validation_errors; std::string m_what; - void set_what(); + std::string generate_what() const; + std::string validation_errors_string() const; + std::string populate_format_string(const std::string &format_string) const; - static CustomWhat s_custom_what; - static std::string s_property_string; - static std::string s_property_string_upper; + static const FormatStrings s_default_format_strings; + static FormatStrings s_custom_format_strings; + + public: + INFO_KEY(OldVersion); + INFO_KEY(NewVersion); + INFO_KEY(ObjectType); + INFO_KEY(PropertyName); + INFO_KEY(PropertyType); + INFO_KEY(OldPropertyType); + INFO_KEY(PropertyObjectType); + INFO_KEY(OldPropertyObjectType); + INFO_KEY(PrimaryKey); }; } From 68296d04b7dbce95da86e69f0b17a84803f179db Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 16 Jun 2015 17:08:07 -0700 Subject: [PATCH 31/40] undef INFO_KEY macro after use --- object_store_exceptions.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp index 098fea30..ae6e7a27 100644 --- a/object_store_exceptions.hpp +++ b/object_store_exceptions.hpp @@ -23,8 +23,6 @@ #include #include -#define INFO_KEY(key) InfoKey InfoKey##key = "InfoKey" #key; - namespace realm { class Property; @@ -85,6 +83,7 @@ namespace realm { static FormatStrings s_custom_format_strings; public: + #define INFO_KEY(key) InfoKey InfoKey##key = "InfoKey" #key; INFO_KEY(OldVersion); INFO_KEY(NewVersion); INFO_KEY(ObjectType); @@ -94,6 +93,7 @@ namespace realm { INFO_KEY(PropertyObjectType); INFO_KEY(OldPropertyObjectType); INFO_KEY(PrimaryKey); + #undef INFO_KEY }; } From 60700ba121639f9e6f020aeab4f234ac540ff1d5 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 18 Jun 2015 16:04:41 -0700 Subject: [PATCH 32/40] first pass refactor of RLMRealm to c++ --- object_store.cpp | 4 +- object_store.hpp | 2 +- shared_realm.cpp | 341 +++++++++++++++++++++++++++++++++++++++++++++++ shared_realm.hpp | 159 ++++++++++++++++++++++ 4 files changed, 503 insertions(+), 3 deletions(-) create mode 100644 shared_realm.cpp create mode 100644 shared_realm.hpp diff --git a/object_store.cpp b/object_store.cpp index 3622c31b..ac883aa2 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -161,7 +161,7 @@ TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const return group->get_or_add_table(table_name_for_object_type(object_type), &created); } -std::vector ObjectStore::validate_schema(Group *group, ObjectSchema &target_schema) { +std::vector ObjectStore::validate_object_schema(Group *group, ObjectSchema &target_schema) { vector exceptions; ObjectSchema table_schema(group, target_schema.name); @@ -372,7 +372,7 @@ bool ObjectStore::update_realm_with_schema(Group *group, // read-only realms may be missing tables entirely TableRef table = table_for_object_type(group, target_schema.name); if (table) { - auto errors = validate_schema(group, target_schema); + auto errors = validate_object_schema(group, target_schema); if (errors.size()) { throw ObjectStoreException(errors, target_schema.name); } diff --git a/object_store.hpp b/object_store.hpp index c1e581be..48ae4a31 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -48,7 +48,7 @@ namespace realm { // updates the column mapping on the target_schema // if no table is provided it is fetched from the group // returns array of validation errors - static std::vector validate_schema(Group *group, ObjectSchema &target_schema); + static std::vector validate_object_schema(Group *group, ObjectSchema &target_schema); // 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); diff --git a/shared_realm.cpp b/shared_realm.cpp new file mode 100644 index 00000000..ca984d85 --- /dev/null +++ b/shared_realm.cpp @@ -0,0 +1,341 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 + +using namespace std; +using namespace realm; + +RealmCache Realm::s_global_cache; +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(*c.schema); + } +} + +Realm::Realm(Config &config) : m_config(config), m_thread_id(this_thread::get_id()), m_auto_refresh(true), m_in_transaction(false) +{ + try { + if (config.read_only) { + m_read_only_group = make_unique(config.path, config.encryption_key.data(), Group::mode_ReadOnly); + m_group = m_read_only_group.get(); + } + else { + m_replication = realm::makeWriteLogCollector(config.path, false, config.encryption_key.data()); + SharedGroup::DurabilityLevel durability = config.in_memory ? SharedGroup::durability_MemOnly : + SharedGroup::durability_Full; + m_shared_group = make_unique(*m_replication, durability, config.encryption_key.data()); + m_group = nullptr; + } + } + catch (util::File::PermissionDenied const& ex) { + throw RealmException(RealmException::Kind::FilePermissionDenied, "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 RealmException(RealmException::Kind::FileExists, "Unable to open a realm at path '" + config.path + "'"); + } + catch (util::File::AccessError const& ex) { + throw RealmException(RealmException::Kind::FileAccessError, "Unable to open a realm at path '" + config.path + "'"); + } + catch (IncompatibleLockFile const&) { + throw RealmException(RealmException::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."); + } +} + +Group *Realm::read_group() { + if (!m_group) { + m_group = &const_cast(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 RealmException(RealmException::Kind::MismatchedConfig, "Realm at path already opened with different read permissions"); + } + if (realm->config().in_memory != config.in_memory) { + throw RealmException(RealmException::Kind::MismatchedConfig, "Realm at path already opened with different inMemory settings"); + } + return realm; + } + + realm = make_shared(config); + + // we want to ensure we are only initializing a single realm at a time + lock_guard lock(s_init_mutex); + + if (!config.schema) { + // get schema from group and skip validation + realm->m_config.schema = make_unique(ObjectStore::schema_from_group(realm->read_group())); + } + else if (config.read_only) { + // for read-only validate all existing tables + for (auto &object_schema : *realm->m_config.schema) { + if (ObjectStore::table_for_object_type(realm->read_group(), object_schema.name)) { + ObjectStore::validate_object_schema(realm->read_group(), object_schema); + } + } + } + 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 = make_unique(*existing->m_config.schema); + } + else { + // its a new realm so update/migrate if needed + ObjectStore::update_realm_with_schema(realm->read_group(), config.schema_version, *realm->m_config.schema, realm->m_config.migration_function); + } + + s_global_cache.cache_realm(realm, realm->m_thread_id); + return realm; +} + +static void check_read_write(Realm *realm) { + if (realm->config().read_only) { + throw RealmException(RealmException::Kind::InvalidTransaction, "Can't perform transactions on read-only Realms."); + } +} + +void Realm::verify_thread() { + if (m_thread_id != this_thread::get_id()) { + throw RealmException(RealmException::Kind::IncorrectThread, "Realm accessed from incorrect thread."); + } +} + +void Realm::begin_transaction() +{ + check_read_write(this); + verify_thread(); + + if (m_in_transaction) { + throw RealmException(RealmException::Kind::InvalidTransaction, "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_in_transaction = true; + + if (announce) { + send_local_notifications(DidChangeNotification); + } +} + +void Realm::commit_transaction() +{ + check_read_write(this); + verify_thread(); + + if (!m_in_transaction) { + throw RealmException(RealmException::Kind::InvalidTransaction, "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 RealmException(RealmException::Kind::InvalidTransaction, "Can't cancel a non-existing write transaction"); + } + + LangBindHelper::rollback_and_continue_as_read(*m_shared_group); + 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 RealmException(RealmException::Kind::InvalidTransaction, "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.name)->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); + } + send_local_notifications(DidChangeNotification); + } + else { + send_local_notifications(RefreshRequiredNotification); + } + } +} + + +void Realm::send_local_notifications(const 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); + } + 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) +{ + lock_guard 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) +{ + lock_guard 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) +{ + lock_guard 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) +{ + lock_guard lock(m_mutex); + + auto path_iter = m_cache.find(realm->config().path); + if (path_iter == m_cache.end()) { + m_cache.emplace(realm->config().path, map{{thread_id, realm}}); + } + else { + path_iter->second.emplace(thread_id, realm); + } +} + + diff --git a/shared_realm.hpp b/shared_realm.hpp new file mode 100644 index 00000000..88a84a0e --- /dev/null +++ b/shared_realm.hpp @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 +#include +#include +#include +#include +#include +#include "object_store.hpp" +#include + +namespace realm { + class RealmCache; + class Realm; + typedef std::shared_ptr SharedRealm; + typedef std::weak_ptr WeakRealm; + + class Realm + { + public: + struct Config + { + std::string path; + bool read_only; + bool in_memory; + StringData encryption_key; + + std::unique_ptr schema; + uint64_t schema_version; + ObjectStore::MigrationFunction migration_function; + + Config() = default; + Config(const Config& c); + }; + + Realm(Config &config); + Realm(const Realm& r) = delete; + + static SharedRealm get_shared_realm(Config &config); + + 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> NotificationFunction; + void add_notification(NotificationFunction ¬ification) { m_notifications.insert(notification); } + void remove_notification(NotificationFunction notification) { m_notifications.erase(notification); } + + 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: + Config m_config; + std::thread::id m_thread_id; + bool m_in_transaction; + bool m_auto_refresh; + + std::set m_notifications; + void send_local_notifications(const std::string ¬ification); + + typedef std::unique_ptr> ExternalNotificationFunction; + void send_external_notifications() { if (m_external_notifier) (*m_external_notifier)(); } + + std::unique_ptr m_replication; + std::unique_ptr m_shared_group; + std::unique_ptr m_read_only_group; + + Group *m_group; + + public: // FIXME private + Group *read_group(); + + ExternalNotificationFunction m_external_notifier; + + static std::mutex s_init_mutex; + static RealmCache s_global_cache; + }; + + class RealmException : public std::exception + { + public: + enum class Kind + { + /** Options specified in the config do not match other Realm instances opened on the same thread */ + MismatchedConfig, + /** Thrown for any I/O related exception scenarios when a realm is opened. */ + FileAccessError, + /** 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. */ + FilePermissionDenied, + /** Thrown if no_create was specified and the file did already exist when the realm is opened. */ + FileExists, + /** Thrown if no_create was specified and the file was not found when the realm is opened. */ + FileNotFound, + /** Thrown if the database file is currently open in another + process which cannot share with the current process due to an + architecture mismatch. */ + IncompatibleLockFile, + InvalidTransaction, + IncorrectThread, + }; + RealmException(Kind kind, std::string message) : m_kind(kind), m_what(message) {} + + virtual const char *what() noexcept { return m_what.c_str(); } + Kind kind() const { return m_kind; } + + private: + Kind m_kind; + std::string m_what; + }; + + 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> m_cache; + std::mutex m_mutex; + }; +} + +#endif /* defined(REALM_REALM_HPP) */ From 2a0a5d234f2e93236632aecc52d81edbc5b85da4 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 22 Jun 2015 10:32:31 -0700 Subject: [PATCH 33/40] run migrations and schema update through new Realm apis --- object_store.cpp | 4 ++-- object_store_exceptions.cpp | 9 ++++----- shared_realm.cpp | 35 ++++++++++++++++++++++++++++++----- shared_realm.hpp | 21 ++++++++++++++++++--- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index ac883aa2..8a5532f4 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -351,7 +351,7 @@ bool ObjectStore::is_schema_at_version(Group *group, uint64_t version) { if (old_version > version && old_version != NotVersioned) { throw ObjectStoreException(old_version, version); } - return old_version != version; + return old_version == version; } @@ -362,7 +362,7 @@ bool ObjectStore::update_realm_with_schema(Group *group, // 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); + bool migrating = !is_schema_at_version(group, version); // create tables bool changed = create_metadata_tables(group); diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp index 1482060a..564bead8 100644 --- a/object_store_exceptions.cpp +++ b/object_store_exceptions.cpp @@ -59,10 +59,9 @@ ObjectStoreException::ObjectStoreException(uint64_t old_version, uint64_t new_ve } ObjectStoreException::ObjectStoreException(vector validation_errors, const string &object_type) : - m_validation_errors(validation_errors), - m_kind(Kind::ObjectStoreValidationFailure), - m_info({{InfoKeyObjectType, object_type}}), - m_what(generate_what()) { + m_validation_errors(validation_errors), m_kind(Kind::ObjectStoreValidationFailure) { + m_info[InfoKeyObjectType] = object_type; + m_what = generate_what(); } string ObjectStoreException::generate_what() const { @@ -88,7 +87,7 @@ std::string ObjectStoreException::populate_format_string(const std::string & for while(regex_search(current, sm, re)) { out_string += sm.prefix(); const string &key = sm[1]; - if (key == "ValidationString") { + if (key == "ValidationErrors") { out_string += validation_errors_string(); } else { diff --git a/shared_realm.cpp b/shared_realm.cpp index ca984d85..1a8b3f97 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -84,13 +84,14 @@ SharedRealm Realm::get_shared_realm(Config &config) return realm; } - realm = make_shared(config); + realm = SharedRealm(new Realm(config)); // we want to ensure we are only initializing a single realm at a time lock_guard lock(s_init_mutex); if (!config.schema) { // get schema from group and skip validation + realm->m_config.schema_version = ObjectStore::get_schema_version(realm->read_group()); realm->m_config.schema = make_unique(ObjectStore::schema_from_group(realm->read_group())); } else if (config.read_only) { @@ -107,21 +108,45 @@ SharedRealm Realm::get_shared_realm(Config &config) realm->m_config.schema = make_unique(*existing->m_config.schema); } else { - // its a new realm so update/migrate if needed - ObjectStore::update_realm_with_schema(realm->read_group(), config.schema_version, *realm->m_config.schema, realm->m_config.migration_function); + // its a non-cached realm so update/migrate if needed + realm->update_schema(*realm->m_config.schema, config.schema_version); } s_global_cache.cache_realm(realm, realm->m_thread_id); return realm; } -static void check_read_write(Realm *realm) { +bool Realm::update_schema(ObjectStore::Schema &schema, uint64_t version) +{ + bool changed = false; + try { + begin_transaction(); + changed = ObjectStore::update_realm_with_schema(read_group(), version, schema, m_config.migration_function); + commit_transaction(); + m_config.schema_version = version; + if (m_config.schema.get() != &schema) { + m_config.schema = make_unique(schema); + } + } + catch (...) { + if (is_in_transaction()) { + cancel_transaction(); + } + throw; + } + + return changed; +} + +static void check_read_write(Realm *realm) +{ if (realm->config().read_only) { throw RealmException(RealmException::Kind::InvalidTransaction, "Can't perform transactions on read-only Realms."); } } -void Realm::verify_thread() { +void Realm::verify_thread() +{ if (m_thread_id != this_thread::get_id()) { throw RealmException(RealmException::Kind::IncorrectThread, "Realm accessed from incorrect thread."); } diff --git a/shared_realm.hpp b/shared_realm.hpp index 88a84a0e..4fc501b9 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -52,11 +52,20 @@ namespace realm { Config(const Config& c); }; - Realm(Config &config); - Realm(const Realm& r) = delete; - + // 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(ObjectStore::Schema &schema, uint64_t version); + const Config &config() const { return m_config; } void begin_transaction(); @@ -83,6 +92,9 @@ namespace realm { const std::string DidChangeNotification = "DidChangeNotification"; private: + Realm(Config &config); + //Realm(const Realm& r) = delete; + Config m_config; std::thread::id m_thread_id; bool m_in_transaction; @@ -131,6 +143,9 @@ namespace realm { IncompatibleLockFile, InvalidTransaction, IncorrectThread, + /** Thrown when trying to open an unitialized Realm without a target schema or with a mismatching + schema version **/ + InvalidSchemaVersion }; RealmException(Kind kind, std::string message) : m_kind(kind), m_what(message) {} From d8e9d36c886a0a1f85da0d9f977666af23c59b51 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 20 Jul 2015 11:53:21 -0700 Subject: [PATCH 34/40] rebase fixes --- object_store.cpp | 4 ++-- object_store_exceptions.cpp | 6 ++++-- object_store_exceptions.hpp | 2 ++ shared_realm.cpp | 12 ++++++------ shared_realm.hpp | 3 ++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 8a5532f4..b27748d3 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -188,10 +188,10 @@ std::vector ObjectStore::validate_object_schema(Group *gro } 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."); + exceptions.emplace_back(ObjectStoreException::Kind::ObjectSchemaChangedOptionalProperty, table_schema.name, current_prop, *target_prop); } else { - validation_errors.push_back("Property '" + current_prop.name + "' has been made optional."); + exceptions.emplace_back(ObjectStoreException::Kind::ObjectSchemaNewOptionalProperty, table_schema.name, current_prop, *target_prop); } } diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp index 564bead8..1c46d341 100644 --- a/object_store_exceptions.cpp +++ b/object_store_exceptions.cpp @@ -119,8 +119,10 @@ const ObjectStoreException::FormatStrings ObjectStoreException::s_default_format "Property '{InfoKeyPrimaryKey}' is no longer a primary key."}, {Kind::ObjectSchemaNewPrimaryKey, "Property '{InfoKeyPrimaryKey}' has been made a primary key."}, + {Kind::ObjectSchemaChangedOptionalProperty, + "Property '{InfoKeyPrimaryKey}' is no longer optional."}, + {Kind::ObjectSchemaNewOptionalProperty, + "Property '{InfoKeyPrimaryKey}' has been made optional."}, {Kind::ObjectStoreValidationFailure, "Migration is required for object type '{InfoKeyObjectType}' due to the following errors: {ValidationErrors}"} }; - - diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp index ae6e7a27..b0236f8a 100644 --- a/object_store_exceptions.hpp +++ b/object_store_exceptions.hpp @@ -38,6 +38,8 @@ namespace realm { ObjectSchemaMismatchedObjectTypes, // ObjectType, PropertyName, PropertyType, ObjectType, OldObjectType ObjectSchemaChangedPrimaryKey, // ObjectType, PrimaryKey ObjectSchemaNewPrimaryKey, // ObjectType, PrimaryKey + ObjectSchemaChangedOptionalProperty, + ObjectSchemaNewOptionalProperty, ObjectStoreValidationFailure, // ObjectType, vector }; diff --git a/shared_realm.cpp b/shared_realm.cpp index 1a8b3f97..5dd46bdc 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -41,10 +41,10 @@ Realm::Realm(Config &config) : m_config(config), m_thread_id(this_thread::get_id m_group = m_read_only_group.get(); } else { - m_replication = realm::makeWriteLogCollector(config.path, false, config.encryption_key.data()); + 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 = make_unique(*m_replication, durability, config.encryption_key.data()); + m_shared_group = make_unique(*m_history, durability, config.encryption_key.data()); m_group = nullptr; } } @@ -167,7 +167,7 @@ void Realm::begin_transaction() // make sure we have a read transaction read_group(); - LangBindHelper::promote_to_write(*m_shared_group); + LangBindHelper::promote_to_write(*m_shared_group, *m_history); m_in_transaction = true; if (announce) { @@ -200,7 +200,7 @@ void Realm::cancel_transaction() throw RealmException(RealmException::Kind::InvalidTransaction, "Can't cancel a non-existing write transaction"); } - LangBindHelper::rollback_and_continue_as_read(*m_shared_group); + LangBindHelper::rollback_and_continue_as_read(*m_shared_group, *m_history); m_in_transaction = false; } @@ -248,7 +248,7 @@ void Realm::notify() if (m_shared_group->has_changed()) { // Throws if (m_auto_refresh) { if (m_group) { - LangBindHelper::advance_read(*m_shared_group); + LangBindHelper::advance_read(*m_shared_group, *m_history); } send_local_notifications(DidChangeNotification); } @@ -284,7 +284,7 @@ bool Realm::refresh() } if (m_group) { - LangBindHelper::advance_read(*m_shared_group); + LangBindHelper::advance_read(*m_shared_group, *m_history); } else { // Create the read transaction diff --git a/shared_realm.hpp b/shared_realm.hpp index 4fc501b9..7aa5aa2d 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -33,6 +33,7 @@ namespace realm { class Realm; typedef std::shared_ptr SharedRealm; typedef std::weak_ptr WeakRealm; + class ClientHistory; class Realm { @@ -106,7 +107,7 @@ namespace realm { typedef std::unique_ptr> ExternalNotificationFunction; void send_external_notifications() { if (m_external_notifier) (*m_external_notifier)(); } - std::unique_ptr m_replication; + std::unique_ptr m_history; std::unique_ptr m_shared_group; std::unique_ptr m_read_only_group; From eb2b079e2a138a48c8a9f021c95b01e6492263a0 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 20 Jul 2015 16:11:15 -0700 Subject: [PATCH 35/40] pr fixes --- object_schema.cpp | 3 +-- object_store.cpp | 19 +++++++++---------- object_store_exceptions.cpp | 23 +++++++++++------------ object_store_exceptions.hpp | 4 ++-- shared_realm.cpp | 31 +++++++++++++++---------------- 5 files changed, 38 insertions(+), 42 deletions(-) diff --git a/object_schema.cpp b/object_schema.cpp index d31c15a6..b7fe7991 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -23,7 +23,6 @@ #include using namespace realm; -using namespace std; ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { TableRef tableRef = ObjectStore::table_for_object_type(group, name); @@ -48,7 +47,7 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { realm::TableRef linkTable = table->get_link_target(col); property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data()); } - properties.push_back(move(property)); + properties.push_back(std::move(property)); } primary_key = realm::ObjectStore::get_primary_key_for_object(group, name); diff --git a/object_store.cpp b/object_store.cpp index b27748d3..9fda06e7 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -27,7 +27,6 @@ #include using namespace realm; -using namespace std; template static inline @@ -65,10 +64,10 @@ const size_t c_primaryKeyPropertyNameColumnIndex = 1; const size_t c_zeroRowIndex = 0; -const string c_object_table_prefix = "class_"; +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 = numeric_limits::max(); +const uint64_t ObjectStore::NotVersioned = std::numeric_limits::max(); bool ObjectStore::has_metadata_tables(Group *group) { return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName); @@ -142,14 +141,14 @@ void ObjectStore::set_primary_key_for_object(Group *group, StringData object_typ } } -string ObjectStore::object_type_for_table_name(const string &table_name) { +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 string(); + return std::string(); } -string ObjectStore::table_name_for_object_type(const string &object_type) { +std::string ObjectStore::table_name_for_object_type(const std::string &object_type) { return c_object_table_prefix + object_type; } @@ -162,7 +161,7 @@ TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const } std::vector ObjectStore::validate_object_schema(Group *group, ObjectSchema &target_schema) { - vector exceptions; + std::vector exceptions; ObjectSchema table_schema(group, target_schema.name); // check to see if properties are the same @@ -259,7 +258,7 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema bool changed = false; // first pass to create missing tables - vector to_update; + std::vector to_update; for (auto& object_schema : target_schema) { bool created = false; ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); @@ -275,7 +274,7 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema 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); - vector &target_props = target_object_schema->properties; + std::vector &target_props = target_object_schema->properties; // add missing columns for (auto& target_prop : target_props) { @@ -399,7 +398,7 @@ bool ObjectStore::update_realm_with_schema(Group *group, ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { ObjectStore::Schema schema; for (size_t i = 0; i < group->size(); i++) { - string object_type = object_type_for_table_name(group->get_table_name(i)); + std::string object_type = object_type_for_table_name(group->get_table_name(i)); if (object_type.length()) { schema.emplace_back(group, move(object_type)); } diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp index 1c46d341..5f980fb2 100644 --- a/object_store_exceptions.cpp +++ b/object_store_exceptions.cpp @@ -23,7 +23,6 @@ #include using namespace realm; -using namespace std; ObjectStoreException::ObjectStoreException(Kind kind, Info info) : m_kind(kind), m_info(info), m_what(generate_what()) {} @@ -53,18 +52,18 @@ ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_ } ObjectStoreException::ObjectStoreException(uint64_t old_version, uint64_t new_version) : m_kind(Kind::RealmVersionGreaterThanSchemaVersion) { - m_info[InfoKeyOldVersion] = to_string(old_version); - m_info[InfoKeyNewVersion] = to_string(new_version); + m_info[InfoKeyOldVersion] = std::to_string(old_version); + m_info[InfoKeyNewVersion] = std::to_string(new_version); m_what = generate_what(); } -ObjectStoreException::ObjectStoreException(vector validation_errors, const string &object_type) : +ObjectStoreException::ObjectStoreException(std::vector validation_errors, const std::string &object_type) : m_validation_errors(validation_errors), m_kind(Kind::ObjectStoreValidationFailure) { m_info[InfoKeyObjectType] = object_type; m_what = generate_what(); } -string ObjectStoreException::generate_what() const { +std::string ObjectStoreException::generate_what() const { auto format_string = s_custom_format_strings.find(m_kind); if (format_string != s_custom_format_strings.end()) { return populate_format_string(format_string->second); @@ -72,21 +71,21 @@ string ObjectStoreException::generate_what() const { return populate_format_string(s_default_format_strings.at(m_kind)); } -string ObjectStoreException::validation_errors_string() const { - string errors_string; +std::string ObjectStoreException::validation_errors_string() const { + std::string errors_string; for (auto error : m_validation_errors) { - errors_string += string("\n- ") + error.what(); + errors_string += std::string("\n- ") + error.what(); } return errors_string; } std::string ObjectStoreException::populate_format_string(const std::string & format_string) const { - string out_string, current(format_string); - smatch sm; - regex re("\\{(\\w+)\\}"); + std::string out_string, current(format_string); + std::smatch sm; + std::regex re("\\{(\\w+)\\}"); while(regex_search(current, sm, re)) { out_string += sm.prefix(); - const string &key = sm[1]; + const std::string &key = sm[1]; if (key == "ValidationErrors") { out_string += validation_errors_string(); } diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp index b0236f8a..d9ec80c4 100644 --- a/object_store_exceptions.hpp +++ b/object_store_exceptions.hpp @@ -43,8 +43,8 @@ namespace realm { ObjectStoreValidationFailure, // ObjectType, vector }; - typedef const std::string InfoKey; - typedef std::map Info; + using InfoKey = std::string; + using Info = std::map; ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop); diff --git a/shared_realm.cpp b/shared_realm.cpp index 5dd46bdc..d84b49cd 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -20,11 +20,10 @@ #include -using namespace std; using namespace realm; RealmCache Realm::s_global_cache; -mutex Realm::s_init_mutex; +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) { @@ -33,18 +32,18 @@ Realm::Config::Config(const Config& c) : path(c.path), read_only(c.read_only), i } } -Realm::Realm(Config &config) : m_config(config), m_thread_id(this_thread::get_id()), m_auto_refresh(true), m_in_transaction(false) +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 = make_unique(config.path, config.encryption_key.data(), Group::mode_ReadOnly); + m_read_only_group = std::make_unique(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 = make_unique(*m_history, durability, config.encryption_key.data()); + m_shared_group = std::make_unique(*m_history, durability, config.encryption_key.data()); m_group = nullptr; } } @@ -87,12 +86,12 @@ SharedRealm Realm::get_shared_realm(Config &config) realm = SharedRealm(new Realm(config)); // we want to ensure we are only initializing a single realm at a time - lock_guard lock(s_init_mutex); + std::lock_guard lock(s_init_mutex); if (!config.schema) { // get schema from group and skip validation realm->m_config.schema_version = ObjectStore::get_schema_version(realm->read_group()); - realm->m_config.schema = make_unique(ObjectStore::schema_from_group(realm->read_group())); + realm->m_config.schema = std::make_unique(ObjectStore::schema_from_group(realm->read_group())); } else if (config.read_only) { // for read-only validate all existing tables @@ -105,7 +104,7 @@ SharedRealm Realm::get_shared_realm(Config &config) 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 = make_unique(*existing->m_config.schema); + realm->m_config.schema = std::make_unique(*existing->m_config.schema); } else { // its a non-cached realm so update/migrate if needed @@ -125,7 +124,7 @@ bool Realm::update_schema(ObjectStore::Schema &schema, uint64_t version) commit_transaction(); m_config.schema_version = version; if (m_config.schema.get() != &schema) { - m_config.schema = make_unique(schema); + m_config.schema = std::make_unique(schema); } } catch (...) { @@ -147,7 +146,7 @@ static void check_read_write(Realm *realm) void Realm::verify_thread() { - if (m_thread_id != this_thread::get_id()) { + if (m_thread_id != std::this_thread::get_id()) { throw RealmException(RealmException::Kind::IncorrectThread, "Realm accessed from incorrect thread."); } } @@ -259,7 +258,7 @@ void Realm::notify() } -void Realm::send_local_notifications(const string &type) +void Realm::send_local_notifications(const std::string &type) { verify_thread(); for (NotificationFunction notification : m_notifications) { @@ -297,7 +296,7 @@ bool Realm::refresh() SharedRealm RealmCache::get_realm(const std::string &path, std::thread::id thread_id) { - lock_guard lock(m_mutex); + std::lock_guard lock(m_mutex); auto path_iter = m_cache.find(path); if (path_iter == m_cache.end()) { @@ -314,7 +313,7 @@ SharedRealm RealmCache::get_realm(const std::string &path, std::thread::id threa SharedRealm RealmCache::get_any_realm(const std::string &path) { - lock_guard lock(m_mutex); + std::lock_guard lock(m_mutex); auto path_iter = m_cache.find(path); if (path_iter == m_cache.end()) { @@ -333,7 +332,7 @@ SharedRealm RealmCache::get_any_realm(const std::string &path) void RealmCache::remove(const std::string &path, std::thread::id thread_id) { - lock_guard lock(m_mutex); + std::lock_guard lock(m_mutex); auto path_iter = m_cache.find(path); if (path_iter == m_cache.end()) { @@ -352,11 +351,11 @@ void RealmCache::remove(const std::string &path, std::thread::id thread_id) void RealmCache::cache_realm(SharedRealm &realm, std::thread::id thread_id) { - lock_guard lock(m_mutex); + std::lock_guard lock(m_mutex); auto path_iter = m_cache.find(realm->config().path); if (path_iter == m_cache.end()) { - m_cache.emplace(realm->config().path, map{{thread_id, realm}}); + m_cache.emplace(realm->config().path, std::map{{thread_id, realm}}); } else { path_iter->second.emplace(thread_id, realm); From 94c7ea512f2d5cec400843d8c6defd65575cac2a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 27 Jul 2015 12:42:55 -0700 Subject: [PATCH 36/40] update to newest object store apis --- object_schema.cpp | 10 +- object_schema.hpp | 2 + object_store.cpp | 233 ++++++++++++++++++------------------ object_store.hpp | 108 +++++++++++++++-- object_store_exceptions.cpp | 127 -------------------- object_store_exceptions.hpp | 102 ---------------- property.hpp | 2 + shared_realm.cpp | 107 ++++++++++++----- shared_realm.hpp | 104 +++++++++------- 9 files changed, 355 insertions(+), 440 deletions(-) delete mode 100644 object_store_exceptions.cpp delete mode 100644 object_store_exceptions.hpp diff --git a/object_schema.cpp b/object_schema.cpp index b7fe7991..83edd1f1 100644 --- a/object_schema.cpp +++ b/object_schema.cpp @@ -19,8 +19,8 @@ #include "object_schema.hpp" #include "object_store.hpp" -#include -#include +#include +#include using namespace realm; @@ -36,11 +36,7 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { property.type = (PropertyType)table->get_column_type(col); property.is_indexed = table->has_search_index(col); property.is_primary = false; -#ifdef REALM_ENABLE_NULL property.is_nullable = table->is_nullable(col) || property.type == PropertyTypeObject; -#else - property.is_nullable = property.type == PropertyTypeObject; -#endif property.table_column = col; if (property.type == PropertyTypeObject || property.type == PropertyTypeArray) { // set link type for objects and arrays @@ -54,7 +50,7 @@ ObjectSchema::ObjectSchema(Group *group, const std::string &name) : name(name) { if (primary_key.length()) { auto primary_key_prop = primary_key_property(); if (!primary_key_prop) { - throw ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaChangedPrimaryKey, name, primary_key); + throw InvalidPrimaryKeyException(name, primary_key); } primary_key_prop->is_primary = true; } diff --git a/object_schema.hpp b/object_schema.hpp index 56a14d17..1d00e4fe 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -43,6 +43,8 @@ namespace realm { Property *primary_key_property() { return property_for_name(primary_key); } + + void validate(); }; } diff --git a/object_store.cpp b/object_store.cpp index 9fda06e7..4783e326 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -28,30 +28,6 @@ using namespace realm; -template -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); -} - -static inline -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); -} - -static inline -void set_value(TableRef table, size_t row, size_t column, BinaryData value) { - table->set_binary(column, row, value); -} - const char * const c_metadataTableName = "metadata"; const char * const c_versionColumnName = "version"; const size_t c_versionColumnIndex = 0; @@ -160,8 +136,12 @@ TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const return group->get_or_add_table(table_name_for_object_type(object_type), &created); } -std::vector ObjectStore::validate_object_schema(Group *group, ObjectSchema &target_schema) { - std::vector exceptions; +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; +} + +std::vector ObjectStore::validate_object_schema(Group *group, ObjectSchema &target_schema) { + std::vector exceptions; ObjectSchema table_schema(group, target_schema.name); // check to see if properties are the same @@ -169,30 +149,13 @@ std::vector ObjectStore::validate_object_schema(Group *gro auto target_prop = target_schema.property_for_name(current_prop.name); if (!target_prop) { - exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMissingProperty, - table_schema.name, current_prop)); + exceptions.emplace_back(MissingPropertyException(table_schema.name, current_prop)); continue; } - - if (current_prop.type != target_prop->type) { - exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedTypes, - table_schema.name, current_prop, *target_prop)); + if (property_has_changed(current_prop, *target_prop)) { + exceptions.emplace_back(MismatchedPropertiesException(table_schema.name, current_prop, *target_prop)); continue; } - if (current_prop.type == PropertyTypeObject || target_prop->type == PropertyTypeArray) { - if (current_prop.object_type != target_prop->object_type) { - exceptions.emplace_back(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaMismatchedObjectTypes, - table_schema.name, current_prop, *target_prop)); - } - } - if (current_prop.is_nullable != target_prop->is_nullable) { - if (current_prop.is_nullable) { - exceptions.emplace_back(ObjectStoreException::Kind::ObjectSchemaChangedOptionalProperty, table_schema.name, current_prop, *target_prop); - } - else { - exceptions.emplace_back(ObjectStoreException::Kind::ObjectSchemaNewOptionalProperty, table_schema.name, current_prop, *target_prop); - } - } // create new property with aligned column target_prop->table_column = current_prop.table_column; @@ -200,21 +163,13 @@ std::vector ObjectStore::validate_object_schema(Group *gro // check for change to primary key if (table_schema.primary_key != target_schema.primary_key) { - 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)); - } + 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(ObjectStoreException(ObjectStoreException::Kind::ObjectSchemaNewProperty, - table_schema.name, target_prop)); + exceptions.emplace_back(ExtraPropertyException(table_schema.name, target_prop)); } } @@ -231,41 +186,21 @@ void ObjectStore::update_column_mapping(Group *group, ObjectSchema &target_schem } } -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; -} - -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 -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(table, i, old_column); - set_value(table, i, new_column, old_value); - } -} - // 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, ObjectStore::Schema &target_schema, bool update_existing) { +bool ObjectStore::create_tables(Group *group, Schema &target_schema, bool update_existing) { bool changed = false; // first pass to create missing tables std::vector to_update; for (auto& object_schema : target_schema) { bool created = false; - ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created); + 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); + to_update.push_back(&object_schema.second); changed = true; } } @@ -291,27 +226,10 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema break; } default: -#ifdef REALM_ENABLE_NULL - target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name, target_prop.is_nullable); -#else target_prop.table_column = table->add_column(DataType(target_prop.type), target_prop.name); -#endif break; } - if (current_prop && property_can_be_migrated_to_nullable(*current_prop, target_prop)) { - switch (target_prop.type) { - case PropertyTypeString: - copy_property_to_property(*current_prop, target_prop, table); - break; - case PropertyTypeData: - copy_property_to_property(*current_prop, target_prop, table); - break; - default: - REALM_UNREACHABLE(); - } - } - changed = true; } } @@ -348,11 +266,26 @@ bool ObjectStore::create_tables(Group *group, ObjectStore::Schema &target_schema 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 ObjectStoreException(old_version, version); + 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, @@ -368,13 +301,9 @@ bool ObjectStore::update_realm_with_schema(Group *group, changed = create_tables(group, schema, migrating) || changed; for (auto& target_schema : schema) { - // read-only realms may be missing tables entirely - TableRef table = table_for_object_type(group, target_schema.name); - if (table) { - auto errors = validate_object_schema(group, target_schema); - if (errors.size()) { - throw ObjectStoreException(errors, target_schema.name); - } + auto errors = validate_object_schema(group, target_schema.second); + if (errors.size()) { + throw ObjectSchemaValidationException(target_schema.first, errors); } } @@ -395,12 +324,12 @@ bool ObjectStore::update_realm_with_schema(Group *group, return true; } -ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { - ObjectStore::Schema schema; +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_back(group, move(object_type)); + schema.emplace(object_type, std::move(ObjectSchema(group, object_type))); } } return schema; @@ -408,13 +337,13 @@ ObjectStore::Schema ObjectStore::schema_from_group(Group *group) { 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.name); + TableRef table = table_for_object_type(group, object_schema.first); if (!table) { continue; } - update_column_mapping(group, object_schema); - for (auto& property : object_schema.properties) { + 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; } @@ -426,12 +355,12 @@ bool ObjectStore::indexes_are_up_to_date(Group *group, Schema &schema) { 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.name); + TableRef table = table_for_object_type(group, object_schema.first); if (!table) { continue; } - for (auto& property : object_schema.properties) { + for (auto& property : object_schema.second.properties) { if (property.requires_index() == table->has_search_index(property.table_column)) { continue; } @@ -442,7 +371,7 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { table->add_search_index(property.table_column); } catch (LogicError const&) { - throw ObjectStoreException(ObjectStoreException::Kind::RealmPropertyTypeNotIndexable, object_schema.name, property); + throw PropertyTypeNotIndexableException(object_schema.first, property); } } else { @@ -455,14 +384,14 @@ bool ObjectStore::update_indexes(Group *group, Schema &schema) { void ObjectStore::validate_primary_column_uniqueness(Group *group, Schema &schema) { for (auto& object_schema : schema) { - auto primary_prop = object_schema.primary_key_property(); + auto primary_prop = object_schema.second.primary_key_property(); if (!primary_prop) { continue; } - TableRef table = table_for_object_type(group, object_schema.name); + TableRef table = table_for_object_type(group, object_schema.first); if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) { - throw ObjectStoreException(ObjectStoreException::Kind::RealmDuplicatePrimaryKeyValue, object_schema.name, *primary_prop); + throw DuplicatePrimaryKeyValueException(object_schema.first, *primary_prop); } } } @@ -475,3 +404,75 @@ void ObjectStore::delete_data_for_object(Group *group, const StringData &object_ } } + + +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."; +}; + + +ObjectSchemaValidationException::ObjectSchemaValidationException(std::string object_type, std::vector errors) : + m_object_type(object_type), m_validation_errors(errors) +{ + m_what ="Migration is required for object type '" + object_type + "' due to the following errors: "; + for (auto error : errors) { + m_what += std::string("\n- ") + error.what(); + } +} + +PropertyTypeNotIndexableException::PropertyTypeNotIndexableException(std::string object_type, Property &property) : + ObjectSchemaValidationException(object_type), m_property(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) : + ObjectSchemaValidationException(object_type), m_property(property) +{ + m_what = "Property '" + property.name + "' has been added to latest object model."; +} + +MissingPropertyException::MissingPropertyException(std::string object_type, Property &property) : + ObjectSchemaValidationException(object_type), m_property(property) +{ + m_what = "Property '" + property.name + "' is missing from latest object model."; +} + +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(primary) +{ + m_what = "Specified primary key property '" + primary + "' does not exist."; +} diff --git a/object_store.hpp b/object_store.hpp index 48ae4a31..996b4f32 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -22,16 +22,15 @@ #include #include #include +#include +#include #include "object_schema.hpp" -#include "object_store_exceptions.hpp" namespace realm { - class Group; - class StringData; - class Table; - template class BasicTableRef; - typedef BasicTableRef
TableRef; + class ObjectSchemaValidationException; + class Schema : public std::map { + }; class ObjectStore { public: @@ -48,17 +47,21 @@ namespace realm { // updates the column mapping on the target_schema // if no table is provided it is fetched from the group // returns array of validation errors - static std::vector validate_object_schema(Group *group, ObjectSchema &target_schema); + static std::vector validate_object_schema(Group *group, ObjectSchema &target_schema); // 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::vector Schema; typedef std::function MigrationFunction; static bool update_realm_with_schema(Group *group, uint64_t version, Schema &schema, MigrationFunction migration); @@ -68,9 +71,6 @@ namespace realm { // get existing Schema from a group static Schema schema_from_group(Group *group); - // 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); - // deletes the table for the given type static void delete_data_for_object(Group *group, const StringData &object_type); @@ -88,7 +88,7 @@ namespace realm { // 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, ObjectStore::Schema &target_schema, bool update_existing); + static bool create_tables(realm::Group *group, Schema &target_schema, bool update_existing); // get primary key property name for object type static StringData get_primary_key_for_object(Group *group, StringData object_type); @@ -101,6 +101,9 @@ namespace realm { 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); @@ -109,6 +112,87 @@ namespace realm { 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); + private: + uint64_t m_old_version, m_new_version; + }; + + class DuplicatePrimaryKeyValueException : public MigrationException { + public: + DuplicatePrimaryKeyValueException(std::string object_type, Property &property); + private: + std::string m_object_type; + Property m_property; + }; + + // Schema validation exceptions + class ObjectSchemaValidationException : public ObjectStoreException { + public: + ObjectSchemaValidationException(std::string object_type) : m_object_type(object_type) {} + ObjectSchemaValidationException(std::string object_type, std::vector errors); + private: + std::vector m_validation_errors; + protected: + std::string m_object_type; + }; + + class PropertyTypeNotIndexableException : public ObjectSchemaValidationException { + public: + PropertyTypeNotIndexableException(std::string object_type, Property &property); + private: + Property m_property; + }; + + class ExtraPropertyException : public ObjectSchemaValidationException { + public: + ExtraPropertyException(std::string object_type, Property &property); + private: + Property m_property; + }; + + class MissingPropertyException : public ObjectSchemaValidationException { + public: + MissingPropertyException(std::string object_type, Property &property); + private: + Property m_property; + }; + + class MismatchedPropertiesException : public ObjectSchemaValidationException { + public: + MismatchedPropertiesException(std::string object_type, Property &old_property, Property &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); + private: + std::string m_old_primary, m_new_primary; + }; + + class InvalidPrimaryKeyException : public ObjectSchemaValidationException { + public: + InvalidPrimaryKeyException(std::string object_type, std::string primary); + private: + std::string m_primary; + }; } #endif /* defined(REALM_OBJECT_STORE_HPP) */ diff --git a/object_store_exceptions.cpp b/object_store_exceptions.cpp deleted file mode 100644 index 5f980fb2..00000000 --- a/object_store_exceptions.cpp +++ /dev/null @@ -1,127 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_exceptions.hpp" -#include "property.hpp" - -#include -#include - -using namespace realm; - -ObjectStoreException::ObjectStoreException(Kind kind, Info info) : m_kind(kind), m_info(info), m_what(generate_what()) {} - -ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop) : m_kind(kind) { - m_info[InfoKeyObjectType] = object_type; - m_info[InfoKeyPropertyName] = prop.name; - m_info[InfoKeyPropertyType] = string_for_property_type(prop.type); - m_info[InfoKeyPropertyObjectType] = prop.object_type; - m_what = generate_what(); -} - -ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop, const Property &oldProp) : - m_kind(kind) { - m_info[InfoKeyObjectType] = object_type; - m_info[InfoKeyPropertyName] = prop.name; - m_info[InfoKeyPropertyType] = string_for_property_type(prop.type); - m_info[InfoKeyOldPropertyType] = string_for_property_type(oldProp.type); - m_info[InfoKeyPropertyObjectType] = prop.object_type; - m_info[InfoKeyOldPropertyObjectType] = oldProp.object_type; - m_what = generate_what(); -} - -ObjectStoreException::ObjectStoreException(Kind kind, const std::string &object_type, const std::string primary_key) : m_kind(kind) { - m_info[InfoKeyObjectType] = object_type; - m_info[InfoKeyPrimaryKey] = primary_key; - m_what = generate_what(); -} - -ObjectStoreException::ObjectStoreException(uint64_t old_version, uint64_t new_version) : m_kind(Kind::RealmVersionGreaterThanSchemaVersion) { - m_info[InfoKeyOldVersion] = std::to_string(old_version); - m_info[InfoKeyNewVersion] = std::to_string(new_version); - m_what = generate_what(); -} - -ObjectStoreException::ObjectStoreException(std::vector validation_errors, const std::string &object_type) : - m_validation_errors(validation_errors), m_kind(Kind::ObjectStoreValidationFailure) { - m_info[InfoKeyObjectType] = object_type; - m_what = generate_what(); -} - -std::string ObjectStoreException::generate_what() const { - auto format_string = s_custom_format_strings.find(m_kind); - if (format_string != s_custom_format_strings.end()) { - return populate_format_string(format_string->second); - } - return populate_format_string(s_default_format_strings.at(m_kind)); -} - -std::string ObjectStoreException::validation_errors_string() const { - std::string errors_string; - for (auto error : m_validation_errors) { - errors_string += std::string("\n- ") + error.what(); - } - return errors_string; -} - -std::string ObjectStoreException::populate_format_string(const std::string & format_string) const { - std::string out_string, current(format_string); - std::smatch sm; - std::regex re("\\{(\\w+)\\}"); - while(regex_search(current, sm, re)) { - out_string += sm.prefix(); - const std::string &key = sm[1]; - if (key == "ValidationErrors") { - out_string += validation_errors_string(); - } - else { - out_string += m_info.at(key); - } - current = sm.suffix(); - } - out_string += current; - return out_string; -} - -ObjectStoreException::FormatStrings ObjectStoreException::s_custom_format_strings; -const ObjectStoreException::FormatStrings ObjectStoreException::s_default_format_strings = { - {Kind::RealmVersionGreaterThanSchemaVersion, - "Provided schema version {InfoKeyOldVersion} is less than last set version {InfoKeyNewVersion}."}, - {Kind::RealmPropertyTypeNotIndexable, - "Can't index property {InfoKeyObjectType}.{InfoKeyPropertyName}: indexing a property of type '{InfoKeyPropertyType}' is currently not supported"}, - {Kind::RealmDuplicatePrimaryKeyValue, - "Primary key property '{InfoKeyPropertyType}' has duplicate values after migration."}, - {Kind::ObjectSchemaMissingProperty, - "Property '{InfoKeyPropertyName}' is missing from latest object model."}, - {Kind::ObjectSchemaNewProperty, - "Property '{InfoKeyPropertyName}' has been added to latest object model."}, - {Kind::ObjectSchemaMismatchedTypes, - "Property types for '{InfoKeyPropertyName}' property do not match. Old type '{InfoKeyOldPropertyType}', new type '{InfoKeyPropertyType}'"}, - {Kind::ObjectSchemaMismatchedObjectTypes, - "Target object type for property '{InfoKeyPropertyName}' does not match. Old type '{InfoKeyOldPropertyObjectType}', new type '{InfoKeyPropertyObjectType}'."}, - {Kind::ObjectSchemaChangedPrimaryKey, - "Property '{InfoKeyPrimaryKey}' is no longer a primary key."}, - {Kind::ObjectSchemaNewPrimaryKey, - "Property '{InfoKeyPrimaryKey}' has been made a primary key."}, - {Kind::ObjectSchemaChangedOptionalProperty, - "Property '{InfoKeyPrimaryKey}' is no longer optional."}, - {Kind::ObjectSchemaNewOptionalProperty, - "Property '{InfoKeyPrimaryKey}' has been made optional."}, - {Kind::ObjectStoreValidationFailure, - "Migration is required for object type '{InfoKeyObjectType}' due to the following errors: {ValidationErrors}"} -}; diff --git a/object_store_exceptions.hpp b/object_store_exceptions.hpp deleted file mode 100644 index d9ec80c4..00000000 --- a/object_store_exceptions.hpp +++ /dev/null @@ -1,102 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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_EXCEPTIONS_HPP -#define REALM_OBJECT_STORE_EXCEPTIONS_HPP - -#include -#include -#include - -namespace realm { - class Property; - - class ObjectStoreException : public std::exception { - public: - enum class Kind { - RealmVersionGreaterThanSchemaVersion, // OldVersion, NewVersion - RealmPropertyTypeNotIndexable, // ObjectType, PropertyName, PropertyType - RealmDuplicatePrimaryKeyValue, // ObjectType, PropertyName, PropertyType - ObjectSchemaMissingProperty, // ObjectType, PropertyName, PropertyType - ObjectSchemaNewProperty, // ObjectType, PropertyName, PropertyType - ObjectSchemaMismatchedTypes, // ObjectType, PropertyName, PropertyType, OldPropertyType - ObjectSchemaMismatchedObjectTypes, // ObjectType, PropertyName, PropertyType, ObjectType, OldObjectType - ObjectSchemaChangedPrimaryKey, // ObjectType, PrimaryKey - ObjectSchemaNewPrimaryKey, // ObjectType, PrimaryKey - ObjectSchemaChangedOptionalProperty, - ObjectSchemaNewOptionalProperty, - ObjectStoreValidationFailure, // ObjectType, vector - }; - - using InfoKey = std::string; - using Info = std::map; - - ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop); - - // ObjectSchemaMismatchedTypes, ObjectSchemaMismatchedObjectTypes - ObjectStoreException(Kind kind, const std::string &object_type, const Property &prop, const Property &oldProp); - - // ObjectSchemaChangedPrimaryKey, ObjectSchemaNewPrimaryKey - ObjectStoreException(Kind kind, const std::string &object_type, const std::string primary_key); - - // RealmVersionGreaterThanSchemaVersion - ObjectStoreException(uint64_t old_version, uint64_t new_version); - - // ObjectStoreValidationFailure - ObjectStoreException(std::vector validation_errors, const std::string &object_type); - - ObjectStoreException::Kind kind() const { return m_kind; } - const ObjectStoreException::Info &info() const { return m_info; } - - const char *what() const noexcept override { return m_what.c_str(); } - - // implement CustomWhat to customize exception messages per platform/language - typedef std::map FormatStrings; - static void set_custom_format_strings(FormatStrings custom_format_strings) { s_custom_format_strings = custom_format_strings; } - - private: - ObjectStoreException(Kind kind, Info info = Info()); - - Kind m_kind; - Info m_info; - std::vector m_validation_errors; - - std::string m_what; - std::string generate_what() const; - std::string validation_errors_string() const; - std::string populate_format_string(const std::string &format_string) const; - - static const FormatStrings s_default_format_strings; - static FormatStrings s_custom_format_strings; - - public: - #define INFO_KEY(key) InfoKey InfoKey##key = "InfoKey" #key; - INFO_KEY(OldVersion); - INFO_KEY(NewVersion); - INFO_KEY(ObjectType); - INFO_KEY(PropertyName); - INFO_KEY(PropertyType); - INFO_KEY(OldPropertyType); - INFO_KEY(PropertyObjectType); - INFO_KEY(OldPropertyObjectType); - INFO_KEY(PrimaryKey); - #undef INFO_KEY - }; -} - -#endif /* defined(REALM_OBJECT_STORE_EXCEPTIONS_HPP) */ diff --git a/property.hpp b/property.hpp index a24e42f2..22e6f90c 100644 --- a/property.hpp +++ b/property.hpp @@ -47,6 +47,8 @@ namespace realm { struct Property { public: + Property() : object_type(""), is_primary(false), is_indexed(false), is_nullable(false) {} + std::string name; PropertyType type; std::string object_type; diff --git a/shared_realm.cpp b/shared_realm.cpp index d84b49cd..92ac9668 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -17,8 +17,8 @@ //////////////////////////////////////////////////////////////////////////// #include "shared_realm.hpp" - #include +#include using namespace realm; @@ -28,7 +28,7 @@ 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(*c.schema); + schema = std::make_unique(*c.schema); } } @@ -48,22 +48,28 @@ Realm::Realm(Config &config) : m_config(config), m_thread_id(std::this_thread::g } } catch (util::File::PermissionDenied const& ex) { - throw RealmException(RealmException::Kind::FilePermissionDenied, "Unable to open a realm at path '" + config.path + + 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 RealmException(RealmException::Kind::FileExists, "Unable to open a realm at path '" + config.path + "'"); + throw RealmFileException(RealmFileException::Kind::Exists, "Unable to open a realm at path '" + config.path + "'"); } catch (util::File::AccessError const& ex) { - throw RealmException(RealmException::Kind::FileAccessError, "Unable to open a realm at path '" + config.path + "'"); + throw RealmFileException(RealmFileException::Kind::AccessError, "Unable to open a realm at path '" + config.path + "'"); } catch (IncompatibleLockFile const&) { - throw RealmException(RealmException::Kind::IncompatibleLockFile, "Realm file is currently open in another process " + 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."); } } -Group *Realm::read_group() { +Realm::~Realm() +{ + s_global_cache.remove(m_config.path, m_thread_id); +} + +Group *Realm::read_group() +{ if (!m_group) { m_group = &const_cast(m_shared_group->begin_read()); } @@ -75,11 +81,24 @@ 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 RealmException(RealmException::Kind::MismatchedConfig, "Realm at path already opened with different read permissions"); + throw MismatchedConfigException("Realm at path already opened with different read permissions."); } if (realm->config().in_memory != config.in_memory) { - throw RealmException(RealmException::Kind::MismatchedConfig, "Realm at path already opened with different inMemory settings"); + 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; } @@ -89,22 +108,27 @@ SharedRealm Realm::get_shared_realm(Config &config) std::lock_guard lock(s_init_mutex); if (!config.schema) { + uint64_t version = ObjectStore::get_schema_version(realm->read_group()); + if (version == ObjectStore::NotVersioned) { + InvalidSchemaVersionException(ObjectStore::NotVersioned, ObjectStore::NotVersioned); + } + // get schema from group and skip validation - realm->m_config.schema_version = ObjectStore::get_schema_version(realm->read_group()); - realm->m_config.schema = std::make_unique(ObjectStore::schema_from_group(realm->read_group())); + realm->m_config.schema_version = version; + realm->m_config.schema = std::make_unique(ObjectStore::schema_from_group(realm->read_group())); } else if (config.read_only) { // for read-only validate all existing tables for (auto &object_schema : *realm->m_config.schema) { - if (ObjectStore::table_for_object_type(realm->read_group(), object_schema.name)) { - ObjectStore::validate_object_schema(realm->read_group(), object_schema); + if (ObjectStore::table_for_object_type(realm->read_group(), object_schema.first)) { + ObjectStore::validate_object_schema(realm->read_group(), object_schema.second); } } } 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(*existing->m_config.schema); + realm->m_config.schema = std::make_unique(*existing->m_config.schema); } else { // its a non-cached realm so update/migrate if needed @@ -115,39 +139,62 @@ SharedRealm Realm::get_shared_realm(Config &config) return realm; } -bool Realm::update_schema(ObjectStore::Schema &schema, uint64_t version) +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); + } + m_config.schema_version = version; + try { - begin_transaction(); - changed = ObjectStore::update_realm_with_schema(read_group(), version, schema, m_config.migration_function); - commit_transaction(); - m_config.schema_version = version; - if (m_config.schema.get() != &schema) { - m_config.schema = std::make_unique(schema); + if (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 { + // verify all types + for (auto& target_schema : *m_config.schema) { + auto errors = ObjectStore::validate_object_schema(read_group(), target_schema.second); + if (errors.size()) { + throw ObjectSchemaValidationException(target_schema.first, errors); + } + } } } 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 RealmException(RealmException::Kind::InvalidTransaction, "Can't perform transactions on read-only Realms."); + throw InvalidTransactionException("Can't perform transactions on read-only Realms."); } } void Realm::verify_thread() { if (m_thread_id != std::this_thread::get_id()) { - throw RealmException(RealmException::Kind::IncorrectThread, "Realm accessed from incorrect thread."); + throw IncorrectThreadException("Realm accessed from incorrect thread."); } } @@ -157,7 +204,7 @@ void Realm::begin_transaction() verify_thread(); if (m_in_transaction) { - throw RealmException(RealmException::Kind::InvalidTransaction, "The Realm is already in a write 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 @@ -180,7 +227,7 @@ void Realm::commit_transaction() verify_thread(); if (!m_in_transaction) { - throw RealmException(RealmException::Kind::InvalidTransaction, "Can't commit a non-existing write transaction"); + throw InvalidTransactionException("Can't commit a non-existing write transaction"); } LangBindHelper::commit_and_continue_as_read(*m_shared_group); @@ -196,7 +243,7 @@ void Realm::cancel_transaction() verify_thread(); if (!m_in_transaction) { - throw RealmException(RealmException::Kind::InvalidTransaction, "Can't cancel a non-existing write transaction"); + throw InvalidTransactionException("Can't cancel a non-existing write transaction"); } LangBindHelper::rollback_and_continue_as_read(*m_shared_group, *m_history); @@ -226,11 +273,11 @@ bool Realm::compact() bool success = false; if (m_in_transaction) { - throw RealmException(RealmException::Kind::InvalidTransaction, "Can't compact a Realm within a write 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.name)->optimize(); + ObjectStore::table_for_object_type(read_group(), object_schema.first)->optimize(); } m_shared_group->end_read(); @@ -340,7 +387,7 @@ void RealmCache::remove(const std::string &path, std::thread::id thread_id) } auto thread_iter = path_iter->second.find(thread_id); - if (thread_iter == path_iter->second.end()) { + if (thread_iter != path_iter->second.end()) { path_iter->second.erase(thread_iter); } diff --git a/shared_realm.hpp b/shared_realm.hpp index 7aa5aa2d..f3b4573f 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -25,8 +25,8 @@ #include #include #include + #include "object_store.hpp" -#include namespace realm { class RealmCache; @@ -35,9 +35,11 @@ namespace realm { typedef std::weak_ptr WeakRealm; class ClientHistory; - class Realm + class Realm : public std::enable_shared_from_this { public: + typedef std::function MigrationFunction; + struct Config { std::string path; @@ -45,11 +47,12 @@ namespace realm { bool in_memory; StringData encryption_key; - std::unique_ptr schema; + std::unique_ptr schema; uint64_t schema_version; - ObjectStore::MigrationFunction migration_function; - Config() = default; + MigrationFunction migration_function; + + Config() : read_only(false), in_memory(false), schema_version(ObjectStore::NotVersioned) {}; Config(const Config& c); }; @@ -65,7 +68,7 @@ namespace realm { // 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(ObjectStore::Schema &schema, uint64_t version); + bool update_schema(Schema &schema, uint64_t version); const Config &config() const { return m_config; } @@ -94,7 +97,6 @@ namespace realm { private: Realm(Config &config); - //Realm(const Realm& r) = delete; Config m_config; std::thread::id m_thread_id; @@ -113,49 +115,15 @@ namespace realm { Group *m_group; - public: // FIXME private - Group *read_group(); - - ExternalNotificationFunction m_external_notifier; - static std::mutex s_init_mutex; static RealmCache s_global_cache; - }; - class RealmException : public std::exception - { public: - enum class Kind - { - /** Options specified in the config do not match other Realm instances opened on the same thread */ - MismatchedConfig, - /** Thrown for any I/O related exception scenarios when a realm is opened. */ - FileAccessError, - /** 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. */ - FilePermissionDenied, - /** Thrown if no_create was specified and the file did already exist when the realm is opened. */ - FileExists, - /** Thrown if no_create was specified and the file was not found when the realm is opened. */ - FileNotFound, - /** Thrown if the database file is currently open in another - process which cannot share with the current process due to an - architecture mismatch. */ - IncompatibleLockFile, - InvalidTransaction, - IncorrectThread, - /** Thrown when trying to open an unitialized Realm without a target schema or with a mismatching - schema version **/ - InvalidSchemaVersion - }; - RealmException(Kind kind, std::string message) : m_kind(kind), m_what(message) {} + ~Realm(); + ExternalNotificationFunction m_external_notifier; - virtual const char *what() noexcept { return m_what.c_str(); } - Kind kind() const { return m_kind; } - - private: - Kind m_kind; - std::string m_what; + // FIXME private + Group *read_group(); }; class RealmCache @@ -167,9 +135,53 @@ namespace realm { void cache_realm(SharedRealm &realm, std::thread::id thread_id = std::this_thread::get_id()); private: - std::map> m_cache; + std::map> 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) {} + }; } #endif /* defined(REALM_REALM_HPP) */ From e9379491d6a9b5cf7fc78efd626a66eaf13a3d1f Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 27 Jul 2015 15:15:18 -0700 Subject: [PATCH 37/40] fix for swift issues --- shared_realm.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/shared_realm.hpp b/shared_realm.hpp index f3b4573f..bb3fa032 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -85,6 +85,7 @@ namespace realm { typedef std::shared_ptr> 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(); From 373375fa1bc9543ca19a31f61c1ef2b388f316bb Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 28 Jul 2015 11:25:04 -0700 Subject: [PATCH 38/40] cleaup schema initialization --- object_schema.hpp | 2 -- object_store.cpp | 38 ++++++++++++++++++++++++++++---------- object_store.hpp | 34 ++++++++++++++++++++++++++-------- shared_realm.cpp | 33 ++++++++++----------------------- shared_realm.hpp | 6 ++++++ 5 files changed, 70 insertions(+), 43 deletions(-) diff --git a/object_schema.hpp b/object_schema.hpp index 1d00e4fe..56a14d17 100644 --- a/object_schema.hpp +++ b/object_schema.hpp @@ -43,8 +43,6 @@ namespace realm { Property *primary_key_property() { return property_for_name(primary_key); } - - void validate(); }; } diff --git a/object_store.cpp b/object_store.cpp index 4783e326..1d18fd2a 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -140,7 +140,26 @@ 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; } -std::vector ObjectStore::validate_object_schema(Group *group, ObjectSchema &target_schema) { +void ObjectStore::verify_schema(Group *group, Schema &target_schema, bool allow_missing_tables) { + std::vector 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 ObjectStore::verify_object_schema(Group *group, ObjectSchema &target_schema, Schema &schema) { std::vector exceptions; ObjectSchema table_schema(group, target_schema.name); @@ -156,6 +175,10 @@ std::vector ObjectStore::validate_object_schema exceptions.emplace_back(MismatchedPropertiesException(table_schema.name, current_prop, *target_prop)); continue; } + if (current_prop.object_type.length() && schema.find(current_prop.object_type) == schema.end()) { + exceptions.emplace_back(InvalidPropertyException(table_schema.name, current_prop, + "Target type '" + current_prop.object_type + "' doesn't exist for property '" + current_prop.name + "',")); + } // create new property with aligned column target_prop->table_column = current_prop.table_column; @@ -300,12 +323,7 @@ bool ObjectStore::update_realm_with_schema(Group *group, bool changed = create_metadata_tables(group); changed = create_tables(group, schema, migrating) || changed; - for (auto& target_schema : schema) { - auto errors = validate_object_schema(group, target_schema.second); - if (errors.size()) { - throw ObjectSchemaValidationException(target_schema.first, errors); - } - } + verify_schema(group, schema); changed = update_indexes(group, schema) || changed; @@ -419,10 +437,10 @@ DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string }; -ObjectSchemaValidationException::ObjectSchemaValidationException(std::string object_type, std::vector errors) : - m_object_type(object_type), m_validation_errors(errors) +SchemaValidationException::SchemaValidationException(std::vector errors) : + m_validation_errors(errors) { - m_what ="Migration is required for object type '" + object_type + "' due to the following errors: "; + m_what ="Migration is required due to the following errors: "; for (auto error : errors) { m_what += std::string("\n- ") + error.what(); } diff --git a/object_store.hpp b/object_store.hpp index 996b4f32..61e28495 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -43,11 +43,10 @@ namespace realm { // 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 its table, setting the table_column property on each schema object - // updates the column mapping on the target_schema - // if no table is provided it is fetched from the group - // returns array of validation errors - static std::vector validate_object_schema(Group *group, ObjectSchema &target_schema); + // 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); @@ -90,6 +89,11 @@ namespace realm { // 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 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); @@ -142,12 +146,18 @@ namespace realm { }; // Schema validation exceptions + class SchemaValidationException : public ObjectStoreException { + public: + SchemaValidationException(std::vector errors); + private: + std::vector m_validation_errors; + }; + class ObjectSchemaValidationException : public ObjectStoreException { public: ObjectSchemaValidationException(std::string object_type) : m_object_type(object_type) {} - ObjectSchemaValidationException(std::string object_type, std::vector errors); - private: - std::vector m_validation_errors; + ObjectSchemaValidationException(std::string object_type, std::string message) : + m_object_type(object_type) { m_what = message; } protected: std::string m_object_type; }; @@ -193,6 +203,14 @@ namespace realm { private: std::string m_primary; }; + + class InvalidPropertyException : public ObjectSchemaValidationException { + public: + InvalidPropertyException(std::string object_type, Property &property, std::string message) : + ObjectSchemaValidationException(object_type), m_property(property) { m_what = message; } + private: + Property m_property; + }; } #endif /* defined(REALM_OBJECT_STORE_HPP) */ diff --git a/shared_realm.cpp b/shared_realm.cpp index 92ac9668..9704b58e 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -107,23 +107,17 @@ SharedRealm Realm::get_shared_realm(Config &config) // we want to ensure we are only initializing a single realm at a time std::lock_guard lock(s_init_mutex); - if (!config.schema) { - uint64_t version = ObjectStore::get_schema_version(realm->read_group()); - if (version == ObjectStore::NotVersioned) { - InvalidSchemaVersionException(ObjectStore::NotVersioned, ObjectStore::NotVersioned); - } - + 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 = version; + realm->m_config.schema_version = old_version; realm->m_config.schema = std::make_unique(ObjectStore::schema_from_group(realm->read_group())); } - else if (config.read_only) { - // for read-only validate all existing tables - for (auto &object_schema : *realm->m_config.schema) { - if (ObjectStore::table_for_object_type(realm->read_group(), object_schema.first)) { - ObjectStore::validate_object_schema(realm->read_group(), object_schema.second); - } + 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 @@ -132,7 +126,7 @@ SharedRealm Realm::get_shared_realm(Config &config) } else { // its a non-cached realm so update/migrate if needed - realm->update_schema(*realm->m_config.schema, config.schema_version); + realm->update_schema(*realm->m_config.schema, realm->m_config.schema_version); } s_global_cache.cache_realm(realm, realm->m_thread_id); @@ -151,7 +145,7 @@ bool Realm::update_schema(Schema &schema, uint64_t version) m_config.schema_version = version; try { - if (ObjectStore::realm_requires_update(read_group(), version, schema)) { + 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(); @@ -164,13 +158,7 @@ bool Realm::update_schema(Schema &schema, uint64_t version) commit_transaction(); } else { - // verify all types - for (auto& target_schema : *m_config.schema) { - auto errors = ObjectStore::validate_object_schema(read_group(), target_schema.second); - if (errors.size()) { - throw ObjectSchemaValidationException(target_schema.first, errors); - } - } + ObjectStore::verify_schema(read_group(), *m_config.schema, m_config.read_only); } } catch (...) { @@ -409,4 +397,3 @@ void RealmCache::cache_realm(SharedRealm &realm, std::thread::id thread_id) } } - diff --git a/shared_realm.hpp b/shared_realm.hpp index bb3fa032..2807549d 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -183,6 +183,12 @@ namespace realm { 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) */ From 136f9a4640bd75ff7e8196694dba4428d0718282 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 28 Jul 2015 11:45:19 -0700 Subject: [PATCH 39/40] add more object_schema validation --- object_store.cpp | 40 ++++++++++++++++++--- object_store.hpp | 92 +++++++++++++++++++++++++++--------------------- 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 1d18fd2a..4bd9232b 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -164,6 +164,7 @@ std::vector ObjectStore::verify_object_schema(G 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); @@ -175,9 +176,38 @@ std::vector ObjectStore::verify_object_schema(G 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(InvalidPropertyException(table_schema.name, current_prop, - "Target type '" + current_prop.object_type + "' doesn't exist for property '" + current_prop.name + "',")); + "Target type '" + current_prop.object_type + "' doesn't exist for property '" + current_prop.name + "'.")); + } + + // check nullablity + if (current_prop.is_nullable) { + if (current_prop.type != PropertyTypeObject) { + exceptions.emplace_back(InvalidPropertyException(table_schema.name, current_prop, + "Only 'Object' property types are nullable")); + } + } + else if (current_prop.type == PropertyTypeObject) { + exceptions.emplace_back(InvalidPropertyException(table_schema.name, current_prop, + "'Object' property '" + current_prop.name + "' must be nullable.")); + } + + // check primary keys + if (current_prop.is_primary) { + if (primary) { + exceptions.emplace_back(InvalidPropertyException(table_schema.name, current_prop, "Duplicate primary keys.")); + } + 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 @@ -447,19 +477,19 @@ SchemaValidationException::SchemaValidationException(std::vector errors); - private: + std::vector &validation_errors() { return m_validation_errors; } + private: std::vector m_validation_errors; }; class ObjectSchemaValidationException : public ObjectStoreException { - public: + 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; } - protected: + std::string object_type() { return m_object_type; } + protected: std::string m_object_type; }; - class PropertyTypeNotIndexableException : public ObjectSchemaValidationException { - public: + 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); - private: - Property m_property; }; - class ExtraPropertyException : public ObjectSchemaValidationException { - public: + class ExtraPropertyException : public ObjectSchemaPropertyException { + public: ExtraPropertyException(std::string object_type, Property &property); - private: - Property m_property; }; - class MissingPropertyException : public ObjectSchemaValidationException { - public: + class MissingPropertyException : public ObjectSchemaPropertyException { + public: MissingPropertyException(std::string object_type, Property &property); - private: - Property m_property; + }; + + class InvalidPropertyException : public ObjectSchemaPropertyException { + public: + InvalidPropertyException(std::string object_type, Property &property, std::string message) : + ObjectSchemaPropertyException(object_type, property) { m_what = message; } }; class MismatchedPropertiesException : public ObjectSchemaValidationException { - public: + public: MismatchedPropertiesException(std::string object_type, Property &old_property, Property &new_property); - private: + 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: + public: ChangedPrimaryKeyException(std::string object_type, std::string old_primary, std::string new_primary); - private: + 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); - private: - std::string m_primary; - }; - - class InvalidPropertyException : public ObjectSchemaValidationException { - public: - InvalidPropertyException(std::string object_type, Property &property, std::string message) : - ObjectSchemaValidationException(object_type), m_property(property) { m_what = message; } - private: - Property m_property; + public: + InvalidPrimaryKeyException(std::string object_type, std::string primary_key); + std::string primary_key() { return m_primary_key; } + private: + std::string m_primary_key; }; } From 6ef8e214f3e982255f3021489e2418df61f10b3e Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 30 Jul 2015 12:22:01 -0700 Subject: [PATCH 40/40] move exception message creation to exception constructors --- object_store.cpp | 46 ++++++++++++++++++++++++++++++++++++---------- object_store.hpp | 15 ++++++++++++--- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 4bd9232b..cf3dd2ed 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -179,26 +179,25 @@ std::vector ObjectStore::verify_object_schema(G // check object_type existence if (current_prop.object_type.length() && schema.find(current_prop.object_type) == schema.end()) { - exceptions.emplace_back(InvalidPropertyException(table_schema.name, current_prop, - "Target type '" + current_prop.object_type + "' doesn't exist for property '" + current_prop.name + "'.")); + exceptions.emplace_back(MissingObjectTypeException(table_schema.name, current_prop)); } // check nullablity - if (current_prop.is_nullable) { - if (current_prop.type != PropertyTypeObject) { - exceptions.emplace_back(InvalidPropertyException(table_schema.name, current_prop, - "Only 'Object' property types are nullable")); + if (current_prop.type == PropertyTypeObject) { + if (!current_prop.is_nullable) { + exceptions.emplace_back(InvalidNullabilityException(table_schema.name, current_prop)); } } - else if (current_prop.type == PropertyTypeObject) { - exceptions.emplace_back(InvalidPropertyException(table_schema.name, current_prop, - "'Object' property '" + current_prop.name + "' must be nullable.")); + 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(InvalidPropertyException(table_schema.name, current_prop, "Duplicate primary keys.")); + exceptions.emplace_back(DuplicatePrimaryKeysException(table_schema.name)); } primary = ¤t_prop; } @@ -494,6 +493,27 @@ MissingPropertyException::MissingPropertyException(std::string object_type, Prop 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) { @@ -524,3 +544,9 @@ InvalidPrimaryKeyException::InvalidPrimaryKeyException(std::string object_type, { 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 + "'."; +} + diff --git a/object_store.hpp b/object_store.hpp index 65343426..cacdcd8b 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -192,10 +192,19 @@ namespace realm { MissingPropertyException(std::string object_type, Property &property); }; - class InvalidPropertyException : public ObjectSchemaPropertyException { + class InvalidNullabilityException : public ObjectSchemaPropertyException { public: - InvalidPropertyException(std::string object_type, Property &property, std::string message) : - ObjectSchemaPropertyException(object_type, property) { m_what = message; } + 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 {