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__" diff --git a/Realm.xcodeproj/project.pbxproj b/Realm.xcodeproj/project.pbxproj index a4dbdc68c8..661667d1aa 100644 --- a/Realm.xcodeproj/project.pbxproj +++ b/Realm.xcodeproj/project.pbxproj @@ -261,6 +261,10 @@ 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 */; }; + 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 */; }; @@ -614,6 +618,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 +747,7 @@ 02D9AFB61B22487E00A1BD87 /* ObjectStore */ = { isa = PBXGroup; children = ( + 5DB591A51D063DE5001D8F93 /* util */, 3FF0B0A31BA861F200E74157 /* impl */, 3F62BA9E1BA0AB9000A4CEB2 /* binding_context.hpp */, 3F9801A91C8E4F6B000A8B07 /* collection_notifications.cpp */, @@ -902,6 +910,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 = ( @@ -1139,6 +1157,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 */, @@ -1181,6 +1200,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 */, ); @@ -1740,6 +1760,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 */, @@ -1839,6 +1860,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 */, 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/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/ObjectStore/object_store.cpp b/Realm/ObjectStore/object_store.cpp index e54adbe932..993e2d2253 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 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)); } 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 6e44c01d9f..46b7cf9c68 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 @@ -274,7 +275,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()); } @@ -301,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) { @@ -339,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); }, @@ -354,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); }, @@ -363,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() @@ -421,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); @@ -529,9 +533,16 @@ 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) +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)) { - 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..bcfeb12a35 100644 --- a/Realm/ObjectStore/results.hpp +++ b/Realm/ObjectStore/results.hpp @@ -130,30 +130,37 @@ 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); + 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; - UnsupportedColumnTypeException(size_t column, const Table* table); + UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation); }; SharedRealm get_realm() const { return m_realm; } @@ -198,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/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); 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];