From f42ae8c2f5006c4b4ce9193ef47be6df6c7be777 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Mon, 6 Jun 2016 16:09:57 -0700 Subject: [PATCH 1/7] Update `Results` exceptions with changes from the object store repository. --- Realm/ObjectStore/results.cpp | 11 +++++++---- Realm/ObjectStore/results.hpp | 29 ++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Realm/ObjectStore/results.cpp b/Realm/ObjectStore/results.cpp index 6e44c01d9f..b96ab2bc06 100644 --- a/Realm/ObjectStore/results.cpp +++ b/Realm/ObjectStore/results.cpp @@ -274,7 +274,9 @@ size_t Results::index_of(Row const& row) if (m_table && row.get_table() != m_table) { throw IncorrectTableException{ ObjectStore::object_type_for_table_name(m_table->get_name()), - ObjectStore::object_type_for_table_name(row.get_table()->get_name())}; + ObjectStore::object_type_for_table_name(row.get_table()->get_name()), + "Attempting to get the index of a Row of the wrong type" + }; } return index_of(row.get_index()); } @@ -530,8 +532,9 @@ void Results::Internal::set_table_view(Results& results, realm::TableView &&tv) } Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) +: std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns") +, column_index(column) +, column_name(table->get_column_name(column)) +, column_type(table->get_column_type(column)) { - column_index = column; - column_name = table->get_column_name(column); - column_type = table->get_column_type(column); } diff --git a/Realm/ObjectStore/results.hpp b/Realm/ObjectStore/results.hpp index 32e00bb620..69e0cf31b9 100644 --- a/Realm/ObjectStore/results.hpp +++ b/Realm/ObjectStore/results.hpp @@ -25,6 +25,7 @@ #include #include +#include namespace realm { template class BasicRowExpr; @@ -130,25 +131,35 @@ class Results { // The Results object has been invalidated (due to the Realm being invalidated) // All non-noexcept functions can throw this - struct InvalidatedException {}; + struct InvalidatedException : public std::runtime_error { + InvalidatedException() : std::runtime_error("Access to invalidated Results objects") {} + }; // The input index parameter was out of bounds - struct OutOfBoundsIndexException { - size_t requested; - size_t valid_count; + struct OutOfBoundsIndexException : public std::out_of_range { + OutOfBoundsIndexException(size_t r, size_t c) : + std::out_of_range((std::string)"Requested index " + util::to_string(r) + + " greater than max " + util::to_string(c)), + requested(r), valid_count(c) {} + const size_t requested; + const size_t valid_count; }; // The input Row object is not attached - struct DetatchedAccessorException { }; + struct DetatchedAccessorException : public std::runtime_error { + DetatchedAccessorException() : std::runtime_error("Atempting to access an invalid object") {} + }; // The input Row object belongs to a different table - struct IncorrectTableException { - StringData expected; - StringData actual; + struct IncorrectTableException : public std::runtime_error { + IncorrectTableException(StringData e, StringData a, const std::string &error) + : std::runtime_error(error), expected(e), actual(a) {} + const StringData expected; + const StringData actual; }; // The requested aggregate operation is not supported for the column type - struct UnsupportedColumnTypeException { + struct UnsupportedColumnTypeException : public std::runtime_error { size_t column_index; StringData column_name; DataType column_type; From 111c7005733af1ee15ab409420c2719604082cc4 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 1 Apr 2016 14:26:11 -0700 Subject: [PATCH 2/7] Add basic string formatting for error messages The main motivation for this is that building error messages via string concatenation is tedious and makes it hard to judge what the actual message looks like when there are a lot of parts being inserted, to the extent that I've been tempted to leave out some potentially useful information because the code was getting unwieldy. It also has some small functional benefits: bools are printed as true/false rather than 1/0, and it is optimized for minimizing the compiled size. Currently it cuts ~30 KB off librealm-object-store.dylib even with the addition of new functionality. --- Realm.xcodeproj/project.pbxproj | 20 ++++++ Realm/ObjectStore/object_store.cpp | 102 ++++++++++++++++++----------- Realm/ObjectStore/results.cpp | 26 +++++--- Realm/ObjectStore/results.hpp | 13 ++-- Realm/ObjectStore/shared_realm.cpp | 16 ++--- Realm/ObjectStore/shared_realm.hpp | 6 +- Realm/ObjectStore/util/format.cpp | 82 +++++++++++++++++++++++ Realm/ObjectStore/util/format.hpp | 75 +++++++++++++++++++++ Realm/Tests/MigrationTests.mm | 2 +- 9 files changed, 275 insertions(+), 67 deletions(-) create mode 100644 Realm/ObjectStore/util/format.cpp create mode 100644 Realm/ObjectStore/util/format.hpp diff --git a/Realm.xcodeproj/project.pbxproj b/Realm.xcodeproj/project.pbxproj index 2015ec5e13..b31be14dce 100644 --- a/Realm.xcodeproj/project.pbxproj +++ b/Realm.xcodeproj/project.pbxproj @@ -261,6 +261,9 @@ 5D66102A1BE98DD00021E04F /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; }; 5D66102E1BE98E500021E04F /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5D66102F1BE98E540021E04F /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5DB591A91D063DF8001D8F93 /* atomic_shared_ptr.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5DB591A61D063DF8001D8F93 /* atomic_shared_ptr.hpp */; }; + 5DB591AA1D063DF8001D8F93 /* format.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DB591A71D063DF8001D8F93 /* format.cpp */; }; + 5DB591AB1D063DF8001D8F93 /* format.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5DB591A81D063DF8001D8F93 /* format.hpp */; }; 5DD7557F1BE056DE002800DA /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3F2118A81B97CBE1005A4CFE /* external_commit_helper.cpp */; }; 5DD755801BE056DE002800DA /* index_set.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FBD05FA1B94E1C3004559CF /* index_set.cpp */; }; 5DD755811BE056DE002800DA /* object_schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FAE25561B8CEBBE00D01405 /* object_schema.cpp */; }; @@ -614,6 +617,9 @@ 5D6610121BE98D880021E04F /* TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCase.swift; sourceTree = ""; }; 5D6610131BE98D880021E04F /* TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestUtils.h; sourceTree = ""; }; 5D6610141BE98D880021E04F /* TestUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestUtils.mm; sourceTree = ""; }; + 5DB591A61D063DF8001D8F93 /* atomic_shared_ptr.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = atomic_shared_ptr.hpp; path = ObjectStore/util/atomic_shared_ptr.hpp; sourceTree = ""; }; + 5DB591A71D063DF8001D8F93 /* format.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = format.cpp; path = ObjectStore/util/format.cpp; sourceTree = ""; }; + 5DB591A81D063DF8001D8F93 /* format.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = format.hpp; path = ObjectStore/util/format.hpp; sourceTree = ""; }; 5DD755CF1BE056DE002800DA /* Realm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework.static; includeInIndex = 0; path = Realm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5DD755E01BE05C19002800DA /* Tests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Tests.xcconfig; sourceTree = ""; }; 5DD755E31BE05EA1002800DA /* Tests iOS static.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Tests iOS static.xcconfig"; sourceTree = ""; }; @@ -740,6 +746,7 @@ 02D9AFB61B22487E00A1BD87 /* ObjectStore */ = { isa = PBXGroup; children = ( + 5DB591A51D063DE5001D8F93 /* util */, 3FF0B0A31BA861F200E74157 /* impl */, 3F62BA9E1BA0AB9000A4CEB2 /* binding_context.hpp */, 3F9801A91C8E4F6B000A8B07 /* collection_notifications.cpp */, @@ -902,6 +909,16 @@ name = "Supporting Files"; sourceTree = ""; }; + 5DB591A51D063DE5001D8F93 /* util */ = { + isa = PBXGroup; + children = ( + 5DB591A61D063DF8001D8F93 /* atomic_shared_ptr.hpp */, + 5DB591A71D063DF8001D8F93 /* format.cpp */, + 5DB591A81D063DF8001D8F93 /* format.hpp */, + ); + name = util; + sourceTree = ""; + }; E81A1FC81955FE0100FDED82 /* Swift */ = { isa = PBXGroup; children = ( @@ -1137,6 +1154,7 @@ 3F0543EB1C56F71500AA5322 /* realm_coordinator.hpp in Headers */, 3F75566C1BE94CCC0058BC7E /* results.hpp in Headers */, 3F9801AF1C90FD2D000A8B07 /* results_notifier.hpp in Headers */, + 5DB591A91D063DF8001D8F93 /* atomic_shared_ptr.hpp in Headers */, 5D659EA71BE04556006515A0 /* RLMAccessor.h in Headers */, 5D659EA81BE04556006515A0 /* RLMAnalytics.hpp in Headers */, 5D659EA91BE04556006515A0 /* RLMArray.h in Headers */, @@ -1179,6 +1197,7 @@ 5D659ECC1BE04556006515A0 /* schema.hpp in Headers */, 5D659ECD1BE04556006515A0 /* shared_realm.hpp in Headers */, 3F9801A31C8E4F55000A8B07 /* weak_realm_notifier.hpp in Headers */, + 5DB591AB1D063DF8001D8F93 /* format.hpp in Headers */, 3F9801971C8E4F3F000A8B07 /* weak_realm_notifier.hpp in Headers */, 3F9801A21C8E4F55000A8B07 /* weak_realm_notifier_base.hpp in Headers */, ); @@ -1738,6 +1757,7 @@ 5D659E861BE04556006515A0 /* RLMAnalytics.mm in Sources */, 5D659E871BE04556006515A0 /* RLMArray.mm in Sources */, 5D659E881BE04556006515A0 /* RLMArrayLinkView.mm in Sources */, + 5DB591AA1D063DF8001D8F93 /* format.cpp in Sources */, 3FBEF67B1C63D66100F6935B /* RLMCollection.mm in Sources */, 5D659E891BE04556006515A0 /* RLMConstants.m in Sources */, 5D659E8A1BE04556006515A0 /* RLMListBase.mm in Sources */, diff --git a/Realm/ObjectStore/object_store.cpp b/Realm/ObjectStore/object_store.cpp index e54adbe932..bded9531a4 100644 --- a/Realm/ObjectStore/object_store.cpp +++ b/Realm/ObjectStore/object_store.cpp @@ -19,6 +19,7 @@ #include "object_store.hpp" #include "schema.hpp" +#include "util/format.hpp" #include #include @@ -171,8 +172,8 @@ void ObjectStore::verify_schema(Schema const& actual_schema, Schema& target_sche 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 + "'.")); + errors.emplace_back(object_schema.name, + util::format("Missing table for object type '%1'.", object_schema.name)); } continue; } @@ -609,65 +610,72 @@ bool ObjectStore::is_empty(const Group *group) { PropertyRenameException::PropertyRenameException(std::string old_property_name, std::string new_property_name) : m_old_property_name(old_property_name), m_new_property_name(new_property_name) { - m_what = "Old property '" + old_property_name + "' cannot be renamed to property '" + new_property_name + "'."; + m_what = util::format("Old property '%1' cannot be renamed to property '%2'.", + old_property_name, new_property_name); } PropertyRenameMissingObjectTypeException::PropertyRenameMissingObjectTypeException(std::string object_type) : m_object_type(object_type) { - m_what = "Cannot rename properties on type '" + object_type + "'."; + m_what = util::format("Cannot rename properties on type '%1'.", object_type); } PropertyRenameMissingOldObjectTypeException::PropertyRenameMissingOldObjectTypeException(std::string object_type) : PropertyRenameMissingObjectTypeException(object_type) { - m_what = "Cannot rename properties on type '" + object_type + "' because it is missing from the Realm file."; + m_what = util::format("Cannot rename properties on type '%1' because it is missing from the Realm file.", + object_type); } PropertyRenameMissingNewObjectTypeException::PropertyRenameMissingNewObjectTypeException(std::string object_type) : PropertyRenameMissingObjectTypeException(object_type) { - m_what = "Cannot rename properties on type '" + object_type + "' because it is missing from the specified schema."; + m_what = util::format("Cannot rename properties on type '%1' because it is missing from the specified schema.", + object_type); } PropertyRenameMissingOldPropertyException::PropertyRenameMissingOldPropertyException(std::string old_property_name, std::string new_property_name) : PropertyRenameException(old_property_name, new_property_name) { - m_what = "Old property '" + old_property_name + "' is missing from the Realm file so it cannot be renamed to '" + new_property_name + "'."; + m_what = util::format("Old property '%1' is missing from the Realm file so it cannot be renamed to '%2'.", + old_property_name, new_property_name); } PropertyRenameMissingNewPropertyException::PropertyRenameMissingNewPropertyException(std::string new_property_name) : m_new_property_name(new_property_name) { - m_what = "Renamed property '" + new_property_name + "' is not in the latest model."; + m_what = util::format("Renamed property '%1' is not in the latest model.", new_property_name); } PropertyRenameOldStillExistsException::PropertyRenameOldStillExistsException(std::string old_property_name, std::string new_property_name) : PropertyRenameException(old_property_name, new_property_name) { - m_what = "Old property '" + old_property_name + "' cannot be renamed to '" + new_property_name + "' because the old property is still present in the specified schema."; + m_what = util::format("Old property '%1' cannot be renamed to '%2' because the old property is still present " + "in the specified schema.", old_property_name, new_property_name); } PropertyRenameTypeMismatchException::PropertyRenameTypeMismatchException(Property const& old_property, Property const& new_property) : m_old_property(old_property), m_new_property(new_property) { - m_what = "Old property '" + old_property.name + "' of type '" + old_property.type_string() + "' cannot be renamed to property '" + new_property.name + "' of type '" + new_property.type_string() + "'."; + m_what = util::format("Old property '%1' of type '%2' cannot be renamed to property '%3' of type '%4'.", + old_property.name, old_property.type_string(), + new_property.name, new_property.type_string()); } 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(new_version) + " is less than last set version " + std::to_string(old_version) + "."; + m_what = util::format("Provided schema version %1 is less than last set version %2.", new_version, old_version); } DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string const& object_type, Property const& property) : m_object_type(object_type), m_property(property) { - m_what = "Primary key property '" + property.name + "' has duplicate values after migration."; + m_what = util::format("Primary key property '%1' has duplicate values after migration.", property.name); } -SchemaValidationException::SchemaValidationException(std::vector const& errors) : - m_validation_errors(errors) +SchemaValidationException::SchemaValidationException(std::vector const& errors) +: m_validation_errors(errors) { m_what = "Schema validation failed due to the following errors:"; for (auto const& error : errors) { @@ -678,28 +686,30 @@ SchemaValidationException::SchemaValidationException(std::vector const& errors) : m_validation_errors(errors) { - m_what ="Migration is required due to the following errors:"; + m_what = "Migration is required due to the following errors:"; for (auto const& error : errors) { m_what += std::string("\n- ") + error.what(); } } -PropertyTypeNotIndexableException::PropertyTypeNotIndexableException(std::string const& object_type, Property const& property) : - ObjectSchemaPropertyException(object_type, property) +PropertyTypeNotIndexableException::PropertyTypeNotIndexableException(std::string const& object_type, + Property const& property) +: ObjectSchemaPropertyException(object_type, 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"; + m_what = util::format("Can't index property %1.%2: indexing a property of type '%3' is currently not supported.", + object_type, property.name, string_for_property_type(property.type)); } ExtraPropertyException::ExtraPropertyException(std::string const& object_type, Property const& property) : ObjectSchemaPropertyException(object_type, property) { - m_what = "Property '" + property.name + "' has been added to latest object model."; + m_what = util::format("Property '%1' has been added to latest object model.", property.name); } MissingPropertyException::MissingPropertyException(std::string const& object_type, Property const& property) : ObjectSchemaPropertyException(object_type, property) { - m_what = "Property '" + property.name + "' is missing from latest object model."; + m_what = util::format("Property '%1' is missing from latest object model.", property.name); } InvalidNullabilityException::InvalidNullabilityException(std::string const& object_type, Property const& property) : @@ -707,12 +717,13 @@ InvalidNullabilityException::InvalidNullabilityException(std::string const& obje { switch (property.type) { case PropertyType::Object: - m_what = "'Object' property '" + property.name + "' must be nullable."; + m_what = util::format("'Object' property '%1' must be nullable.", property.name); break; case PropertyType::Any: case PropertyType::Array: case PropertyType::LinkingObjects: - m_what = "Property '" + property.name + "' of type '" + string_for_property_type(property.type) + "' cannot be nullable"; + m_what = util::format("Property '%1' of type '%2' cannot be nullable.", + property.name, string_for_property_type(property.type)); break; case PropertyType::Int: case PropertyType::Bool: @@ -725,46 +736,57 @@ InvalidNullabilityException::InvalidNullabilityException(std::string const& obje } } -MissingObjectTypeException::MissingObjectTypeException(std::string const& object_type, Property const& property) : - ObjectSchemaPropertyException(object_type, property) +MissingObjectTypeException::MissingObjectTypeException(std::string const& object_type, Property const& property) +: ObjectSchemaPropertyException(object_type, property) { - m_what = "Target type '" + property.object_type + "' doesn't exist for property '" + property.name + "'."; + m_what = util::format("Target type '%1' doesn't exist for property '%2'.", + property.object_type, property.name); } -MismatchedPropertiesException::MismatchedPropertiesException(std::string const& object_type, Property const& old_property, Property const& new_property) : +MismatchedPropertiesException::MismatchedPropertiesException(std::string const& object_type, + Property const& old_property, + Property const& 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) + "'"; + m_what = util::format("Property types for '%1' property doe not match. Old type '%2', new type '%3'.", + old_property.name, + string_for_property_type(old_property.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 + "'"; + m_what = util::format("Target object type for property '%1' do not match. Old type '%2', new type '%3'.", + old_property.name, old_property.object_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) + "'."; + m_what = util::format("Nullability for property '%1' has been changed from %2 to %3.", + old_property.name, + old_property.is_nullable, new_property.is_nullable); } } -ChangedPrimaryKeyException::ChangedPrimaryKeyException(std::string const& object_type, std::string const& old_primary, std::string const& new_primary) : ObjectSchemaValidationException(object_type), m_old_primary(old_primary), m_new_primary(new_primary) +ChangedPrimaryKeyException::ChangedPrimaryKeyException(std::string const& object_type, + std::string const& old_primary, + std::string const& 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."; + m_what = util::format("Property '%1' is no longer a primary key.", old_primary); } else { - m_what = "Property '" + new_primary + "' has been made a primary key."; + m_what = util::format("Property '%1' has been made a primary key.", new_primary); } } InvalidPrimaryKeyException::InvalidPrimaryKeyException(std::string const& object_type, std::string const& primary) : ObjectSchemaValidationException(object_type), m_primary_key(primary) { - m_what = "Specified primary key property '" + primary + "' does not exist."; + m_what = util::format("Specified primary key property '%1' does not exist.", primary); } DuplicatePrimaryKeysException::DuplicatePrimaryKeysException(std::string const& object_type) : ObjectSchemaValidationException(object_type) { - m_what = "Duplicate primary keys for object '" + object_type + "'."; + m_what = util::format("Duplicate primary keys for object '%1'.", object_type); } InvalidLinkingObjectsPropertyException::InvalidLinkingObjectsPropertyException(Type error_type, std::string const& object_type, Property const& property) @@ -772,13 +794,17 @@ InvalidLinkingObjectsPropertyException::InvalidLinkingObjectsPropertyException(T { switch (error_type) { case Type::OriginPropertyDoesNotExist: - m_what = "Property '" + property.link_origin_property_name + "' declared as origin of linking objects property '" + property.name + "' does not exist."; + m_what = util::format("Property '%1' declared as origin of linking objects property '%2' does not exist.", + property.link_origin_property_name, property.name); break; case Type::OriginPropertyIsNotALink: - m_what = "Property '" + property.link_origin_property_name + "' declared as origin of linking objects property '" + property.name + "' is not a link."; + m_what = util::format("Property '%1' declared as origin of linking objects property '%2' is not a link.", + property.link_origin_property_name, property.name); break; case Type::OriginPropertyInvalidLinkTarget: - m_what = "Property '" + property.link_origin_property_name + "' declared as origin of linking objects property '" + property.name + "' does not link to class '" + object_type + "'."; + m_what = util::format("Property '%1' declared as origin of linking objects property '%2' does not link " + "to class '%3'.", + property.link_origin_property_name, property.name, object_type); break; } } diff --git a/Realm/ObjectStore/results.cpp b/Realm/ObjectStore/results.cpp index b96ab2bc06..8009685864 100644 --- a/Realm/ObjectStore/results.cpp +++ b/Realm/ObjectStore/results.cpp @@ -21,6 +21,7 @@ #include "impl/realm_coordinator.hpp" #include "impl/results_notifier.hpp" #include "object_store.hpp" +#include "util/format.hpp" #include @@ -303,6 +304,7 @@ size_t Results::index_of(size_t row_ndx) template util::Optional Results::aggregate(size_t column, bool return_none_for_empty, + const char* name, Int agg_int, Float agg_float, Double agg_double, Timestamp agg_timestamp) { @@ -341,13 +343,13 @@ util::Optional Results::aggregate(size_t column, bool return_none_for_emp case type_Float: return do_agg(agg_float); case type_Int: return do_agg(agg_int); default: - throw UnsupportedColumnTypeException{column, m_table}; + throw UnsupportedColumnTypeException{column, m_table, name}; } } util::Optional Results::max(size_t column) { - return aggregate(column, true, + return aggregate(column, true, "max", [=](auto const& table) { return table.maximum_int(column); }, [=](auto const& table) { return table.maximum_float(column); }, [=](auto const& table) { return table.maximum_double(column); }, @@ -356,7 +358,7 @@ util::Optional Results::max(size_t column) util::Optional Results::min(size_t column) { - return aggregate(column, true, + return aggregate(column, true, "min", [=](auto const& table) { return table.minimum_int(column); }, [=](auto const& table) { return table.minimum_float(column); }, [=](auto const& table) { return table.minimum_double(column); }, @@ -365,20 +367,20 @@ util::Optional Results::min(size_t column) util::Optional Results::sum(size_t column) { - return aggregate(column, false, + return aggregate(column, false, "sum", [=](auto const& table) { return table.sum_int(column); }, [=](auto const& table) { return table.sum_float(column); }, [=](auto const& table) { return table.sum_double(column); }, - [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; }); + [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table, "sum"}; }); } util::Optional Results::average(size_t column) { - return aggregate(column, true, + return aggregate(column, true, "average", [=](auto const& table) { return table.average_int(column); }, [=](auto const& table) { return table.average_float(column); }, [=](auto const& table) { return table.average_double(column); }, - [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; }); + [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table, "average"}; }); } void Results::clear() @@ -531,8 +533,14 @@ void Results::Internal::set_table_view(Results& results, realm::TableView &&tv) REALM_ASSERT(results.m_table_view.is_attached()); } -Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) -: std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns") +Results::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) +: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c)) +, requested(r), valid_count(c) {} + +Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation) +: std::runtime_error(util::format("Cannot %1 property '%2': operation not supported for '%3' properties", + operation, table->get_column_name(column), + string_for_property_type(static_cast(table->get_column_type(column))))) , column_index(column) , column_name(table->get_column_name(column)) , column_type(table->get_column_type(column)) diff --git a/Realm/ObjectStore/results.hpp b/Realm/ObjectStore/results.hpp index 69e0cf31b9..bcfeb12a35 100644 --- a/Realm/ObjectStore/results.hpp +++ b/Realm/ObjectStore/results.hpp @@ -25,7 +25,6 @@ #include #include -#include namespace realm { template class BasicRowExpr; @@ -137,10 +136,7 @@ class Results { // The input index parameter was out of bounds struct OutOfBoundsIndexException : public std::out_of_range { - OutOfBoundsIndexException(size_t r, size_t c) : - std::out_of_range((std::string)"Requested index " + util::to_string(r) + - " greater than max " + util::to_string(c)), - requested(r), valid_count(c) {} + OutOfBoundsIndexException(size_t r, size_t c); const size_t requested; const size_t valid_count; }; @@ -164,7 +160,7 @@ class Results { StringData column_name; DataType column_type; - UnsupportedColumnTypeException(size_t column, const Table* table); + UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation); }; SharedRealm get_realm() const { return m_realm; } @@ -209,10 +205,11 @@ class Results { void prepare_async(); - template + template util::Optional aggregate(size_t column, bool return_none_for_empty, + const char* name, Int agg_int, Float agg_float, - Double agg_double, DateTime agg_datetime); + Double agg_double, Timestamp agg_timestamp); void set_table_view(TableView&& tv); }; diff --git a/Realm/ObjectStore/shared_realm.cpp b/Realm/ObjectStore/shared_realm.cpp index f71c420e09..d14f14dca1 100644 --- a/Realm/ObjectStore/shared_realm.cpp +++ b/Realm/ObjectStore/shared_realm.cpp @@ -23,6 +23,7 @@ #include "impl/transact_log_handler.hpp" #include "object_store.hpp" #include "schema.hpp" +#include "util/format.hpp" #include #include @@ -76,19 +77,18 @@ REALM_NOINLINE static void translate_file_exception(StringData path, bool read_o } catch (util::File::PermissionDenied const& ex) { throw RealmFileException(RealmFileException::Kind::PermissionDenied, ex.get_path(), - "Unable to open a realm at path '" + ex.get_path() + - "'. Please use a path where your app has " + (read_only ? "read" : "read-write") + " permissions.", + util::format("Unable to open a realm at path '%1'. Please use a path where your app has %2 permissions.", + ex.get_path(), read_only ? "read" : "read-write"), ex.what()); } catch (util::File::Exists const& ex) { throw RealmFileException(RealmFileException::Kind::Exists, ex.get_path(), - "File at path '" + ex.get_path() + "' already exists.", + util::format("File at path '%1' already exists.", ex.get_path()), ex.what()); } catch (util::File::NotFound const& ex) { throw RealmFileException(RealmFileException::Kind::NotFound, ex.get_path(), - "Directory at path '" + ex.get_path() + "' does not exist.", - ex.what()); + util::format("Directory at path '%1' does not exist.", ex.get_path()), ex.what()); } catch (util::File::AccessError const& ex) { // Errors for `open()` include the path, but other errors don't. We @@ -101,13 +101,13 @@ REALM_NOINLINE static void translate_file_exception(StringData path, bool read_o underlying.replace(pos - 1, ex.get_path().size() + 2, ""); } throw RealmFileException(RealmFileException::Kind::AccessError, ex.get_path(), - "Unable to open a realm at path '" + ex.get_path() + "': " + underlying, - ex.what()); + util::format("Unable to open a realm at path '%1': %2.", ex.get_path(), underlying), ex.what()); } catch (IncompatibleLockFile const& ex) { throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, path, "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.", + "which cannot share access with this process. " + "All processes sharing a single file must be the same architecture.", ex.what()); } catch (FileFormatUpgradeRequired const& ex) { diff --git a/Realm/ObjectStore/shared_realm.hpp b/Realm/ObjectStore/shared_realm.hpp index d34a843f5f..56860188e6 100644 --- a/Realm/ObjectStore/shared_realm.hpp +++ b/Realm/ObjectStore/shared_realm.hpp @@ -218,12 +218,12 @@ namespace realm { class MismatchedConfigException : public std::runtime_error { public: - MismatchedConfigException(std::string message) : std::runtime_error(message) {} + MismatchedConfigException(std::string message) : std::runtime_error(move(message)) {} }; class InvalidTransactionException : public std::runtime_error { public: - InvalidTransactionException(std::string message) : std::runtime_error(message) {} + InvalidTransactionException(std::string message) : std::runtime_error(move(message)) {} }; class IncorrectThreadException : public std::runtime_error { @@ -233,7 +233,7 @@ namespace realm { class UninitializedRealmException : public std::runtime_error { public: - UninitializedRealmException(std::string message) : std::runtime_error(message) {} + UninitializedRealmException(std::string message) : std::runtime_error(move(message)) {} }; } diff --git a/Realm/ObjectStore/util/format.cpp b/Realm/ObjectStore/util/format.cpp new file mode 100644 index 0000000000..4103c8d941 --- /dev/null +++ b/Realm/ObjectStore/util/format.cpp @@ -0,0 +1,82 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 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 "util/format.hpp" + +#include + +#include +#include + +namespace realm { namespace _impl { +Printable::Printable(StringData value) : m_type(Type::String), m_string(value.data()) { } + +void Printable::print(std::ostream& out) const +{ + switch (m_type) { + case Printable::Type::Bool: + out << (m_uint ? "true" : "false"); + break; + case Printable::Type::Uint: + out << m_uint; + break; + case Printable::Type::Int: + out << m_int; + break; + case Printable::Type::String: + out << m_string; + break; + } +} + +std::string format(const char* fmt, std::initializer_list values) +{ + std::stringstream ss; + while (*fmt) { + auto next = strchr(fmt, '%'); + + // emit the rest of the format string if there are no more percents + if (!next) { + ss << fmt; + break; + } + + // emit everything up to the next percent + ss.write(fmt, next - fmt); + ++next; + REALM_ASSERT(*next); + + // %% produces a single escaped % + if (*next == '%') { + ss << '%'; + fmt = next + 1; + continue; + } + REALM_ASSERT(isdigit(*next)); + + // The const_cast is safe because stroul does not actually modify + // the pointed-to string, but it lacks a const overload + auto index = strtoul(next, const_cast(&fmt), 10) - 1; + REALM_ASSERT(index < values.size()); + (values.begin() + index)->print(ss); + } + return ss.str(); +} + +} // namespace _impl +} // namespace realm diff --git a/Realm/ObjectStore/util/format.hpp b/Realm/ObjectStore/util/format.hpp new file mode 100644 index 0000000000..8bf9a5d96d --- /dev/null +++ b/Realm/ObjectStore/util/format.hpp @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 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_UTIL_FORMAT_HPP +#define REALM_UTIL_FORMAT_HPP + +#include +#include +#include +#include + +namespace realm { +class StringData; + +namespace _impl { +class Printable { +public: + Printable(bool value) : m_type(Type::Bool), m_uint(value) { } + Printable(unsigned char value) : m_type(Type::Uint), m_uint(value) { } + Printable(unsigned int value) : m_type(Type::Uint), m_uint(value) { } + Printable(unsigned long value) : m_type(Type::Uint), m_uint(value) { } + Printable(unsigned long long value) : m_type(Type::Uint), m_uint(value) { } + Printable(char value) : m_type(Type::Int), m_int(value) { } + Printable(int value) : m_type(Type::Int), m_int(value) { } + Printable(long value) : m_type(Type::Int), m_int(value) { } + Printable(long long value) : m_type(Type::Int), m_int(value) { } + Printable(const char* value) : m_type(Type::String), m_string(value) { } + Printable(std::string const& value) : m_type(Type::String), m_string(value.c_str()) { } + Printable(StringData value); + + void print(std::ostream& out) const; + +private: + enum class Type { + Bool, + Int, + Uint, + String + } m_type; + + union { + uintmax_t m_uint; + intmax_t m_int; + const char* m_string; + }; +}; +std::string format(const char* fmt, std::initializer_list); +} // namespace _impl + +namespace util { +template +std::string format(const char* fmt, Args&&... args) +{ + return _impl::format(fmt, {_impl::Printable(args)...}); +} + +} // namespace util +} // namespace realm + +#endif // REALM_UTIL_FORMAT_HPP diff --git a/Realm/Tests/MigrationTests.mm b/Realm/Tests/MigrationTests.mm index a928862122..a746c070af 100644 --- a/Realm/Tests/MigrationTests.mm +++ b/Realm/Tests/MigrationTests.mm @@ -1442,7 +1442,7 @@ - (void)testMigrationRenamePropertySetOptional { // Unsuccessful Property Rename Tests - (void)testMigrationRenamePropertySetRequired { - [self assertPropertyRenameError:@"Migration is required due to the following errors:\n- Nullability for property 'stringCol' has changed from '1' to '0'." + [self assertPropertyRenameError:@"Migration is required due to the following errors:\n- Nullability for property 'stringCol' has been changed from true to false." firstSchemaTransform:^(__unused RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) { afterProperty.optional = NO; } secondSchemaTransform:nil]; From 83ba654c26ec8e72cad9fd29b7cef7159efa45cb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 6 Jun 2016 18:27:05 +0800 Subject: [PATCH 3/7] Fix compile errors for NDK (#82) make_unique causes ambiguous call with NDK's default gnustl. Compiler fails to decide which constructor of Query to use. --- Realm/ObjectStore/impl/collection_change_builder.cpp | 8 +++++--- Realm/ObjectStore/results.cpp | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Realm/ObjectStore/impl/collection_change_builder.cpp b/Realm/ObjectStore/impl/collection_change_builder.cpp index da0ff5abff..82d44e466c 100644 --- a/Realm/ObjectStore/impl/collection_change_builder.cpp +++ b/Realm/ObjectStore/impl/collection_change_builder.cpp @@ -20,6 +20,8 @@ #include +#include + using namespace realm; using namespace realm::_impl; @@ -49,7 +51,7 @@ void CollectionChangeBuilder::merge(CollectionChangeBuilder&& c) // First update any old moves if (!c.moves.empty() || !c.deletions.empty() || !c.insertions.empty()) { - auto it = remove_if(begin(moves), end(moves), [&](auto& old) { + auto it = std::remove_if(begin(moves), end(moves), [&](auto& old) { // Check if the moved row was moved again, and if so just update the destination auto it = find_if(begin(c.moves), end(c.moves), [&](auto const& m) { return old.to == m.from; @@ -79,7 +81,7 @@ void CollectionChangeBuilder::merge(CollectionChangeBuilder&& c) // Ignore new moves of rows which were previously inserted (the implicit // delete from the move will remove the insert) if (!insertions.empty() && !c.moves.empty()) { - c.moves.erase(remove_if(begin(c.moves), end(c.moves), + c.moves.erase(std::remove_if(begin(c.moves), end(c.moves), [&](auto const& m) { return insertions.contains(m.from); }), end(c.moves)); } @@ -124,7 +126,7 @@ void CollectionChangeBuilder::clean_up_stale_moves() // Look for moves which are now no-ops, and remove them plus the associated // insert+delete. Note that this isn't just checking for from == to due to // that rows can also be shifted by other inserts and deletes - moves.erase(remove_if(begin(moves), end(moves), [&](auto const& move) { + moves.erase(std::remove_if(begin(moves), end(moves), [&](auto const& move) { if (move.from - deletions.count(0, move.from) != move.to - insertions.count(0, move.to)) return false; deletions.remove(move.from); diff --git a/Realm/ObjectStore/results.cpp b/Realm/ObjectStore/results.cpp index 8009685864..46b7cf9c68 100644 --- a/Realm/ObjectStore/results.cpp +++ b/Realm/ObjectStore/results.cpp @@ -425,7 +425,7 @@ Query Results::get_query() const // The TableView has no associated query so create one with no conditions that is restricted // to the rows in the TableView. m_table_view.sync_if_needed(); - return Query(*m_table, std::make_unique(m_table_view)); + return Query(*m_table, std::unique_ptr(new TableView(m_table_view))); } case Mode::LinkView: return m_table->where(m_link_view); From d6a312dbe6af4bc0f595eafd041a1d4fff1dd702 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Mon, 6 Jun 2016 16:56:02 -0700 Subject: [PATCH 4/7] Have `List`s exceptions inherit from standard library exception types. This brings them into line with the equivalent exceptions used by `Results`. `DetatchedAccessorException` is removed as it is never thrown by `List`. --- Realm/ObjectStore/list.cpp | 5 +++++ Realm/ObjectStore/list.hpp | 10 +++++----- Realm/RLMArrayLinkView.mm | 3 --- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Realm/ObjectStore/list.cpp b/Realm/ObjectStore/list.cpp index b0658e6a45..3bf840cc19 100644 --- a/Realm/ObjectStore/list.cpp +++ b/Realm/ObjectStore/list.cpp @@ -22,6 +22,7 @@ #include "impl/realm_coordinator.hpp" #include "results.hpp" #include "shared_realm.hpp" +#include "util/format.hpp" #include @@ -201,3 +202,7 @@ NotificationToken List::add_notification_callback(CollectionChangeCallback cb) } return {m_notifier, m_notifier->add_callback(std::move(cb))}; } + +List::OutOfBoundsIndexException::OutOfBoundsIndexException(size_t r, size_t c) +: std::out_of_range(util::format("Requested index %1 greater than max %2", r, c)) +, requested(r), valid_count(c) {} diff --git a/Realm/ObjectStore/list.hpp b/Realm/ObjectStore/list.hpp index 195afe482b..f6570b4915 100644 --- a/Realm/ObjectStore/list.hpp +++ b/Realm/ObjectStore/list.hpp @@ -91,17 +91,17 @@ class List { // The List object has been invalidated (due to the Realm being invalidated, // or the containing object being deleted) // All non-noexcept functions can throw this - struct InvalidatedException {}; + struct InvalidatedException : public std::runtime_error { + InvalidatedException() : std::runtime_error("Access to invalidated List object") {} + }; // The input index parameter was out of bounds - struct OutOfBoundsIndexException { + struct OutOfBoundsIndexException : public std::out_of_range { + OutOfBoundsIndexException(size_t r, size_t c); size_t requested; size_t valid_count; }; - // The input Row object is not attached - struct DetatchedAccessorException { }; - private: std::shared_ptr m_realm; LinkViewRef m_link_view; diff --git a/Realm/RLMArrayLinkView.mm b/Realm/RLMArrayLinkView.mm index 441ad5d43c..12b73bbd77 100644 --- a/Realm/RLMArrayLinkView.mm +++ b/Realm/RLMArrayLinkView.mm @@ -98,9 +98,6 @@ static void throwError() { catch (realm::List::InvalidatedException const&) { @throw RLMException(@"RLMArray has been invalidated or the containing object has been deleted"); } - catch (realm::List::DetatchedAccessorException const&) { - @throw RLMException(@"Object has been deleted or invalidated"); - } catch (realm::List::OutOfBoundsIndexException const& e) { @throw RLMException(@"Index %zu is out of bounds (must be less than %zu)", e.requested, e.valid_count); From 298b91830858abcaf8035a0c34016ac8bbafd9ec Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 7 Jun 2016 14:36:35 -0700 Subject: [PATCH 5/7] Add format.cpp to the static framework target. --- Realm.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Realm.xcodeproj/project.pbxproj b/Realm.xcodeproj/project.pbxproj index b31be14dce..149f478ec0 100644 --- a/Realm.xcodeproj/project.pbxproj +++ b/Realm.xcodeproj/project.pbxproj @@ -264,6 +264,7 @@ 5DB591A91D063DF8001D8F93 /* atomic_shared_ptr.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5DB591A61D063DF8001D8F93 /* atomic_shared_ptr.hpp */; }; 5DB591AA1D063DF8001D8F93 /* format.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DB591A71D063DF8001D8F93 /* format.cpp */; }; 5DB591AB1D063DF8001D8F93 /* format.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5DB591A81D063DF8001D8F93 /* format.hpp */; }; + 5DB591AC1D0775D2001D8F93 /* format.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5DB591A71D063DF8001D8F93 /* format.cpp */; }; 5DD7557F1BE056DE002800DA /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3F2118A81B97CBE1005A4CFE /* external_commit_helper.cpp */; }; 5DD755801BE056DE002800DA /* index_set.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FBD05FA1B94E1C3004559CF /* index_set.cpp */; }; 5DD755811BE056DE002800DA /* object_schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FAE25561B8CEBBE00D01405 /* object_schema.cpp */; }; @@ -1857,6 +1858,7 @@ 5DD755841BE056DE002800DA /* RLMAnalytics.mm in Sources */, 5DD755851BE056DE002800DA /* RLMArray.mm in Sources */, 5DD755861BE056DE002800DA /* RLMArrayLinkView.mm in Sources */, + 5DB591AC1D0775D2001D8F93 /* format.cpp in Sources */, 3FBEF67C1C63D66400F6935B /* RLMCollection.mm in Sources */, 5DD755871BE056DE002800DA /* RLMConstants.m in Sources */, 5DD755881BE056DE002800DA /* RLMListBase.mm in Sources */, From 5211ed467f5c1acc5979cdd4980867f9826999a0 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 7 Jun 2016 15:09:00 -0700 Subject: [PATCH 6/7] Add object store's util directory to the podspec. --- Realm.podspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Realm.podspec b/Realm.podspec index 76a3f52c48..9bec8b7ae4 100644 --- a/Realm.podspec +++ b/Realm.podspec @@ -47,7 +47,8 @@ Pod::Spec.new do |s| source_files = 'Realm/*.{m,mm}', 'Realm/ObjectStore/*.cpp', 'Realm/ObjectStore/impl/*.cpp', - 'Realm/ObjectStore/impl/apple/*.cpp' + 'Realm/ObjectStore/impl/apple/*.cpp', + 'Realm/ObjectStore/util/*.cpp' s.module_map = 'Realm/module.modulemap' s.compiler_flags = "-DREALM_HAVE_CONFIG -DREALM_COCOA_VERSION='@\"#{s.version}\"' -D__ASSERTMACROS__" From b81362f2c57c3fd044b2404b3b576abce0a90aa7 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 7 Jun 2016 15:41:29 -0700 Subject: [PATCH 7/7] Fix a typo in an error message. --- Realm/ObjectStore/object_store.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Realm/ObjectStore/object_store.cpp b/Realm/ObjectStore/object_store.cpp index bded9531a4..993e2d2253 100644 --- a/Realm/ObjectStore/object_store.cpp +++ b/Realm/ObjectStore/object_store.cpp @@ -749,7 +749,7 @@ MismatchedPropertiesException::MismatchedPropertiesException(std::string const& ObjectSchemaValidationException(object_type), m_old_property(old_property), m_new_property(new_property) { if (new_property.type != old_property.type) { - m_what = util::format("Property types for '%1' property doe not match. Old type '%2', new type '%3'.", + m_what = util::format("Property types for '%1' property do not match. Old type '%2', new type '%3'.", old_property.name, string_for_property_type(old_property.type), string_for_property_type(new_property.type));