Add a Schema class, move lookup by name and internal-consistency checks there

This commit is contained in:
Thomas Goyne 2015-09-04 14:03:58 -07:00 committed by Ari Lazier
parent 5fa1ff21f3
commit 00d8bf4ef0
6 changed files with 179 additions and 59 deletions

106
schema.cpp Normal file
View File

@ -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<Schema *>(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<Schema *>(this)->find(object);
}
void Schema::validate() const
{
std::vector<ObjectSchemaValidationException> 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 = &prop;
}
// 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);
}
}

55
schema.hpp Normal file
View File

@ -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 <vector>
namespace realm {
class ObjectSchema;
class Schema : private std::vector<ObjectSchema> {
private:
using base = std::vector<ObjectSchema>;
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) */

View File

@ -18,6 +18,8 @@
#include "object_store.hpp" #include "object_store.hpp"
#include "schema.hpp"
#include <realm/group.hpp> #include <realm/group.hpp>
#include <realm/link_view.hpp> #include <realm/link_view.hpp>
#include <realm/table.hpp> #include <realm/table.hpp>
@ -147,17 +149,11 @@ static inline bool property_has_changed(Property const& p1, Property const& p2)
|| p1.is_nullable != p2.is_nullable; || 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) { 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<ObjectSchemaValidationException> errors; std::vector<ObjectSchemaValidationException> errors;
for (auto &object_schema : target_schema) { for (auto &object_schema : target_schema) {
auto matching_schema = std::lower_bound(begin(actual_schema), end(actual_schema), object_schema, compare_by_name); auto matching_schema = actual_schema.find(object_schema);
if (matching_schema == end(actual_schema) || matching_schema->name != object_schema.name) { if (matching_schema == actual_schema.end()) {
if (!allow_missing_tables) { if (!allow_missing_tables) {
errors.emplace_back(ObjectSchemaValidationException(object_schema.name, errors.emplace_back(ObjectSchemaValidationException(object_schema.name,
"Missing table for object type '" + 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; 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()); errors.insert(errors.end(), more_errors.begin(), more_errors.end());
} }
if (errors.size()) { if (errors.size()) {
@ -174,18 +170,10 @@ void ObjectStore::verify_schema(Schema const& actual_schema, Schema& target_sche
} }
std::vector<ObjectSchemaValidationException> ObjectStore::verify_object_schema(ObjectSchema const& table_schema, std::vector<ObjectSchemaValidationException> ObjectStore::verify_object_schema(ObjectSchema const& table_schema,
ObjectSchema& target_schema, ObjectSchema& target_schema) {
Schema const& schema) {
std::vector<ObjectSchemaValidationException> exceptions; std::vector<ObjectSchemaValidationException> 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 // check to see if properties are the same
const Property *primary = nullptr;
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); auto target_prop = target_schema.property_for_name(current_prop.name);
@ -198,40 +186,6 @@ std::vector<ObjectSchemaValidationException> ObjectStore::verify_object_schema(O
continue; 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 = &current_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 // create new property with aligned column
target_prop->table_column = current_prop.table_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) { bool ObjectStore::needs_update(Schema const& old_schema, Schema const& schema) {
for (auto const& target_schema : schema) { for (auto const& target_schema : schema) {
auto matching_schema = std::lower_bound(begin(old_schema), end(old_schema), target_schema, compare_by_name); auto matching_schema = old_schema.find(target_schema);
if (matching_schema == end(old_schema) || matching_schema->name != target_schema.name) { if (matching_schema == end(old_schema)) {
// Table doesn't exist // Table doesn't exist
return true; 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 ObjectStore::schema_from_group(const Group *group) {
Schema schema; std::vector<ObjectSchema> schema;
for (size_t i = 0; i < group->size(); i++) { for (size_t i = 0; i < group->size(); i++) {
std::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()) { if (object_type.length()) {
schema.emplace_back(group, object_type); schema.emplace_back(group, object_type);
} }
} }
std::sort(begin(schema), end(schema), compare_by_name);
return schema; return schema;
} }

View File

@ -29,7 +29,7 @@
namespace realm { namespace realm {
class ObjectSchemaValidationException; class ObjectSchemaValidationException;
using Schema = std::vector<ObjectSchema>; class Schema;
class ObjectStore { class ObjectStore {
public: public:
@ -90,8 +90,7 @@ namespace realm {
// updates the column mapping on the target_schema // updates the column mapping on the target_schema
// returns array of validation errors // returns array of validation errors
static std::vector<ObjectSchemaValidationException> verify_object_schema(ObjectSchema const& expected, static std::vector<ObjectSchemaValidationException> verify_object_schema(ObjectSchema const& expected,
ObjectSchema &target_schema, ObjectSchema &target_schema);
Schema const& schema);
// get primary key property name for object type // get primary key property name for object type
static StringData get_primary_key_for_object(const Group *group, StringData object_type); static StringData get_primary_key_for_object(const Group *group, StringData object_type);

View File

@ -20,6 +20,7 @@
#include "external_commit_helper.hpp" #include "external_commit_helper.hpp"
#include "realm_delegate.hpp" #include "realm_delegate.hpp"
#include "schema.hpp"
#include "transact_log_handler.hpp" #include "transact_log_handler.hpp"
#include <realm/commit_log.hpp> #include <realm/commit_log.hpp>
@ -45,6 +46,8 @@ Realm::Config::Config(const Config& c)
} }
} }
Realm::Config::~Config() = default;
Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c) Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c)
{ {
if (&c != this) { if (&c != this) {
@ -154,6 +157,7 @@ SharedRealm Realm::get_shared_realm(Config config)
if (realm->m_config.schema_version == ObjectStore::NotVersioned) { if (realm->m_config.schema_version == ObjectStore::NotVersioned) {
throw UnitializedRealmException("Can't open an un-initialized Realm without a Schema"); 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); ObjectStore::verify_schema(*realm->m_config.schema, *target_schema, true);
realm->m_config.schema = std::move(target_schema); 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> schema, uint64_t version) bool Realm::update_schema(std::unique_ptr<Schema> 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)); bool needs_update = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema));
if (!needs_update) { if (!needs_update) {
ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only); ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only);

View File

@ -56,6 +56,7 @@ namespace realm {
Config() = default; Config() = default;
Config(Config&&) = default; Config(Config&&) = default;
Config(const Config& c); Config(const Config& c);
~Config();
Config& operator=(Config const&); Config& operator=(Config const&);
Config& operator=(Config&&) = default; Config& operator=(Config&&) = default;