diff --git a/schema.cpp b/schema.cpp new file mode 100644 index 00000000..bf7a6181 --- /dev/null +++ b/schema.cpp @@ -0,0 +1,106 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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 "schema.hpp" + +#include "object_schema.hpp" +#include "object_store.hpp" +#include "property.hpp" + +using namespace realm; + +static bool compare_by_name(ObjectSchema const& lft, ObjectSchema const& rgt) { + return lft.name < rgt.name; +} + +Schema::Schema(base types) : base(std::move(types)) { + std::sort(begin(), end(), compare_by_name); +} + +Schema::iterator Schema::find(std::string const& name) +{ + ObjectSchema cmp; + cmp.name = name; + return find(cmp); +} + +Schema::const_iterator Schema::find(std::string const& name) const +{ + return const_cast(this)->find(name); +} + +Schema::iterator Schema::find(ObjectSchema const& object) noexcept +{ + auto it = std::lower_bound(begin(), end(), object, compare_by_name); + if (it != end() && it->name != object.name) { + it = end(); + } + return it; +} + +Schema::const_iterator Schema::find(ObjectSchema const& object) const noexcept +{ + return const_cast(this)->find(object); +} + +void Schema::validate() const +{ + std::vector exceptions; + for (auto const& object : *this) { + const Property *primary = nullptr; + for (auto const& prop : object.properties) { + // check object_type existence + if (!prop.object_type.empty() && find(prop.object_type) == end()) { + exceptions.emplace_back(MissingObjectTypeException(object.name, prop)); + } + + // check nullablity + if (prop.is_nullable) { +#if REALM_NULL_STRINGS == 1 + if (prop.type == PropertyTypeArray || prop.type == PropertyTypeAny) { +#else + if (prop.type != PropertyTypeObject) { +#endif + exceptions.emplace_back(InvalidNullabilityException(object.name, prop)); + } + } + else if (prop.type == PropertyTypeObject) { + exceptions.emplace_back(InvalidNullabilityException(object.name, prop)); + } + + // check primary keys + if (prop.is_primary) { + if (primary) { + exceptions.emplace_back(DuplicatePrimaryKeysException(object.name)); + } + primary = ∝ + } + + // check indexable + if (prop.is_indexed) { + if (prop.type != PropertyTypeString && prop.type != PropertyTypeInt) { + exceptions.emplace_back(PropertyTypeNotIndexableException(object.name, prop)); + } + } + } + } + + if (exceptions.size()) { + throw SchemaValidationException(exceptions); + } +} diff --git a/schema.hpp b/schema.hpp new file mode 100644 index 00000000..0a7fa425 --- /dev/null +++ b/schema.hpp @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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_SCHEMA_HPP +#define REALM_SCHEMA_HPP + +#include + +namespace realm { +class ObjectSchema; + +class Schema : private std::vector { +private: + using base = std::vector; +public: + // Create a schema from a vector of ObjectSchema + Schema(base types); + + // find an ObjectSchema by name + iterator find(std::string const& name); + const_iterator find(std::string const& name) const; + + // find an ObjectSchema with the same name as the passed in one + iterator find(ObjectSchema const& object) noexcept; + const_iterator find(ObjectSchema const& object) const noexcept; + + // Verify that this schema is internally consistent (i.e. all properties are + // valid, links link to types that actually exist, etc.) + void validate() const; + + using base::iterator; + using base::const_iterator; + using base::begin; + using base::end; + using base::empty; + using base::size; +}; +} + +#endif /* defined(REALM_SCHEMA_HPP) */ \ No newline at end of file diff --git a/src/object-store/object_store.cpp b/src/object-store/object_store.cpp index bcfa964f..f4ebcfbd 100644 --- a/src/object-store/object_store.cpp +++ b/src/object-store/object_store.cpp @@ -18,6 +18,8 @@ #include "object_store.hpp" +#include "schema.hpp" + #include #include #include @@ -147,17 +149,11 @@ static inline bool property_has_changed(Property const& p1, Property const& p2) || p1.is_nullable != p2.is_nullable; } -static bool compare_by_name(ObjectSchema const& lft, ObjectSchema const& rgt) { - return lft.name < rgt.name; -} - void ObjectStore::verify_schema(Schema const& actual_schema, Schema& target_schema, bool allow_missing_tables) { - std::sort(begin(target_schema), end(target_schema), compare_by_name); - std::vector errors; for (auto &object_schema : target_schema) { - auto matching_schema = std::lower_bound(begin(actual_schema), end(actual_schema), object_schema, compare_by_name); - if (matching_schema == end(actual_schema) || matching_schema->name != object_schema.name) { + auto matching_schema = actual_schema.find(object_schema); + if (matching_schema == actual_schema.end()) { if (!allow_missing_tables) { errors.emplace_back(ObjectSchemaValidationException(object_schema.name, "Missing table for object type '" + object_schema.name + "'.")); @@ -165,7 +161,7 @@ void ObjectStore::verify_schema(Schema const& actual_schema, Schema& target_sche continue; } - auto more_errors = verify_object_schema(*matching_schema, object_schema, target_schema); + auto more_errors = verify_object_schema(*matching_schema, object_schema); errors.insert(errors.end(), more_errors.begin(), more_errors.end()); } if (errors.size()) { @@ -174,18 +170,10 @@ void ObjectStore::verify_schema(Schema const& actual_schema, Schema& target_sche } std::vector ObjectStore::verify_object_schema(ObjectSchema const& table_schema, - ObjectSchema& target_schema, - Schema const& schema) { + ObjectSchema& target_schema) { std::vector exceptions; - ObjectSchema cmp; - auto schema_contains_table = [&](std::string const& name) { - cmp.name = name; - return std::binary_search(begin(schema), end(schema), cmp, compare_by_name); - }; - // check to see if properties are the same - const Property *primary = nullptr; for (auto& current_prop : table_schema.properties) { auto target_prop = target_schema.property_for_name(current_prop.name); @@ -198,40 +186,6 @@ std::vector ObjectStore::verify_object_schema(O continue; } - // check object_type existence - if (current_prop.object_type.length() && !schema_contains_table(current_prop.object_type)) { - exceptions.emplace_back(MissingObjectTypeException(table_schema.name, current_prop)); - } - - // check nullablity - if (current_prop.is_nullable) { -#if REALM_NULL_STRINGS == 1 - if (current_prop.type == PropertyTypeArray || current_prop.type == PropertyTypeAny) { -#else - if (current_prop.type != PropertyTypeObject) { -#endif - exceptions.emplace_back(InvalidNullabilityException(table_schema.name, current_prop)); - } - } - else if (current_prop.type == PropertyTypeObject) { - exceptions.emplace_back(InvalidNullabilityException(table_schema.name, current_prop)); - } - - // check primary keys - if (current_prop.is_primary) { - if (primary) { - exceptions.emplace_back(DuplicatePrimaryKeysException(table_schema.name)); - } - primary = ¤t_prop; - } - - // check indexable - if (current_prop.is_indexed) { - if (current_prop.type != PropertyTypeString && current_prop.type != PropertyTypeInt) { - exceptions.emplace_back(PropertyTypeNotIndexableException(table_schema.name, current_prop)); - } - } - // create new property with aligned column target_prop->table_column = current_prop.table_column; } @@ -347,8 +301,8 @@ bool ObjectStore::is_schema_at_version(const Group *group, uint64_t version) { bool ObjectStore::needs_update(Schema const& old_schema, Schema const& schema) { for (auto const& target_schema : schema) { - auto matching_schema = std::lower_bound(begin(old_schema), end(old_schema), target_schema, compare_by_name); - if (matching_schema == end(old_schema) || matching_schema->name != target_schema.name) { + auto matching_schema = old_schema.find(target_schema); + if (matching_schema == end(old_schema)) { // Table doesn't exist return true; } @@ -405,14 +359,13 @@ bool ObjectStore::update_realm_with_schema(Group *group, Schema const& old_schem } Schema ObjectStore::schema_from_group(const Group *group) { - Schema schema; + std::vector 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, object_type); } } - std::sort(begin(schema), end(schema), compare_by_name); return schema; } diff --git a/src/object-store/object_store.hpp b/src/object-store/object_store.hpp index 9b260df8..f38524f8 100644 --- a/src/object-store/object_store.hpp +++ b/src/object-store/object_store.hpp @@ -29,7 +29,7 @@ namespace realm { class ObjectSchemaValidationException; - using Schema = std::vector; + class Schema; class ObjectStore { public: @@ -90,8 +90,7 @@ namespace realm { // updates the column mapping on the target_schema // returns array of validation errors static std::vector verify_object_schema(ObjectSchema const& expected, - ObjectSchema &target_schema, - Schema const& schema); + ObjectSchema &target_schema); // get primary key property name for object type static StringData get_primary_key_for_object(const Group *group, StringData object_type); diff --git a/src/object-store/shared_realm.cpp b/src/object-store/shared_realm.cpp index ab72b638..55707918 100644 --- a/src/object-store/shared_realm.cpp +++ b/src/object-store/shared_realm.cpp @@ -20,6 +20,7 @@ #include "external_commit_helper.hpp" #include "realm_delegate.hpp" +#include "schema.hpp" #include "transact_log_handler.hpp" #include @@ -45,6 +46,8 @@ Realm::Config::Config(const Config& c) } } +Realm::Config::~Config() = default; + Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c) { if (&c != this) { @@ -154,6 +157,7 @@ SharedRealm Realm::get_shared_realm(Config config) if (realm->m_config.schema_version == ObjectStore::NotVersioned) { throw UnitializedRealmException("Can't open an un-initialized Realm without a Schema"); } + target_schema->validate(); ObjectStore::verify_schema(*realm->m_config.schema, *target_schema, true); realm->m_config.schema = std::move(target_schema); } @@ -171,6 +175,8 @@ SharedRealm Realm::get_shared_realm(Config config) bool Realm::update_schema(std::unique_ptr schema, uint64_t version) { + schema->validate(); + bool needs_update = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema)); if (!needs_update) { ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only); diff --git a/src/object-store/shared_realm.hpp b/src/object-store/shared_realm.hpp index e1c1a0c6..aa19071e 100644 --- a/src/object-store/shared_realm.hpp +++ b/src/object-store/shared_realm.hpp @@ -56,6 +56,7 @@ namespace realm { Config() = default; Config(Config&&) = default; Config(const Config& c); + ~Config(); Config& operator=(Config const&); Config& operator=(Config&&) = default;