use enum for dict/info keys, support per platform/language exception messages

This commit is contained in:
Ari Lazier 2015-06-16 10:07:40 -07:00
parent a29037b47f
commit c3e82a58ae
5 changed files with 146 additions and 62 deletions

View File

@ -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;
}

View File

@ -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<std::string> ObjectStore::validate_schema(Group *group, ObjectSchema &target_schema) {
vector<string> validation_errors;
std::vector<ObjectStoreException> ObjectStore::validate_schema(Group *group, ObjectSchema &target_schema) {
vector<ObjectStoreException> exceptions;
ObjectSchema table_schema(group, target_schema.name);
// check to see if properties are the same
@ -170,29 +170,20 @@ std::vector<std::string> 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<std::string> 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);
}
}
}

View File

@ -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<std::string> validate_schema(Group *group, ObjectSchema &target_schema);
static std::vector<ObjectStoreException> 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);

View File

@ -17,29 +17,92 @@
////////////////////////////////////////////////////////////////////////////
#include "object_store_exceptions.hpp"
#include "property.hpp"
#include <realm/util/assert.hpp>
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<std::string> 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<ObjectStoreException> validation_errors, const string &object_type) :
m_validation_errors(validation_errors), m_kind(Kind::ObjectStoreValidationFailure), m_info({{InfoKey::ObjectType, object_type}}) {
set_what();
}

View File

@ -24,42 +24,63 @@
#include <string>
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<ObjectStoreException>
};
typedef std::map<std::string, std::string> Dict;
ObjectStoreException(Kind kind, Dict dict = Dict());
enum class InfoKey {
OldVersion,
NewVersion,
ObjectType,
PropertyName,
PropertyType,
OldPropertyType,
PropertyObjectType,
OldPropertyObjectType,
PrimaryKey,
OldPrimaryKey,
};
typedef std::map<InfoKey, std::string> 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<ObjectStoreException> 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<ObjectStoreException> &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<ObjectStoreException> m_validation_errors;
std::string m_what;
};
void set_what();
class ObjectStoreValidationException : public std::exception {
public:
ObjectStoreValidationException(std::vector<std::string> validation_errors, std::string object_type);
const std::vector<std::string> &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<std::string> m_validation_errors;
std::string m_object_type;
std::string m_what;
static CustomWhat s_custom_what;
};
}