Merge pull request #83 from realm/tg/format

Add basic string formatting for error messages
This commit is contained in:
Thomas Goyne 2016-06-03 15:52:49 -07:00
commit c35b7d9d19
12 changed files with 282 additions and 84 deletions

View File

@ -14,7 +14,8 @@ set(SOURCES
impl/results_notifier.cpp
impl/transact_log_handler.cpp
parser/parser.cpp
parser/query_builder.cpp)
parser/query_builder.cpp
util/format.cpp)
set(HEADERS
collection_notifications.hpp
@ -37,7 +38,8 @@ set(HEADERS
impl/weak_realm_notifier_base.hpp
parser/parser.hpp
parser/query_builder.hpp
util/atomic_shared_ptr.hpp)
util/atomic_shared_ptr.hpp
util/format.hpp)
if(APPLE)
list(APPEND SOURCES

View File

@ -21,9 +21,8 @@
#include "impl/list_notifier.hpp"
#include "impl/realm_coordinator.hpp"
#include "results.hpp"
#include "util/format.hpp"
#include <realm/link_view.hpp>
#include <realm/util/to_string.hpp>
#include <stdexcept>
using namespace realm;
@ -54,8 +53,7 @@ void List::verify_valid_row(size_t row_ndx, bool insertion) const
{
size_t size = m_link_view->size();
if (row_ndx > size || (!insertion && row_ndx == size)) {
throw std::out_of_range("Index " + util::to_string(row_ndx) + " is outside of range 0..." +
util::to_string(size) + ".");
throw std::out_of_range(util::format("Index %1 is outside of range 0...%2.", row_ndx, size));
}
}

View File

@ -19,12 +19,12 @@
#include "object_store.hpp"
#include "schema.hpp"
#include "util/format.hpp"
#include <realm/group.hpp>
#include <realm/table.hpp>
#include <realm/table_view.hpp>
#include <realm/util/assert.hpp>
#include <realm/util/to_string.hpp>
#include <string.h>
@ -161,8 +161,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;
}
@ -507,22 +507,25 @@ bool ObjectStore::is_empty(const Group *group) {
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 " + util::to_string(old_version) + " is less than last set version " + util::to_string(new_version) + ".";
m_what = util::format("Provided schema version %1 is less than last set version %2.", old_version, new_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);
}
DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string const& object_type, Property const& property, const std::string message) : m_object_type(object_type), m_property(property)
DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string const& object_type,
Property const& property,
const std::string message)
: m_object_type(object_type), m_property(property)
{
m_what = message;
}
SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors) :
m_validation_errors(errors)
SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors)
: m_validation_errors(errors)
{
m_what = "Schema validation failed due to the following errors: ";
for (auto const& error : errors) {
@ -539,74 +542,88 @@ m_validation_errors(errors)
}
}
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) :
ObjectSchemaPropertyException(object_type, property)
{
if (property.type == PropertyType::Object) {
m_what = "'Object' property '" + property.name + "' must be nullable.";
m_what = util::format("'Object' property '%1' must be nullable.", property.name);
}
else {
m_what = "Array or Mixed property '" + property.name + "' cannot be nullable";
m_what = util::format("Array or Mixed property '%1' cannot be nullable", property.name);
}
}
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 '" + util::to_string(old_property.is_nullable) + "' to '" + util::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)
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);
}

View File

@ -19,10 +19,11 @@
#include "query_builder.hpp"
#include "parser.hpp"
#include <realm.hpp>
#include "object_store.hpp"
#include "schema.hpp"
#include "util/format.hpp"
#include <realm.hpp>
#include <assert.h>
namespace realm {
@ -30,12 +31,12 @@ namespace query_builder {
using namespace parser;
template<typename T>
T stot(const std::string s) {
T stot(std::string const& s) {
std::istringstream iss(s);
T value;
iss >> value;
if (iss.fail()) {
throw std::invalid_argument("Cannot convert string '" + s + "'");
throw std::invalid_argument(util::format("Cannot convert string '%1'", s));
}
return value;
}
@ -95,12 +96,13 @@ struct PropertyExpression
for (size_t index = 0; index < key_path.size(); index++) {
if (prop) {
precondition(prop->type == PropertyType::Object || prop->type == PropertyType::Array,
(std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc->name + "'");
util::format("Property '%1' is not a link in object of type '%2'", key_path[index], desc->name));
indexes.push_back(prop->table_column);
}
prop = desc->property_for_name(key_path[index]);
precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc->name + "'");
precondition(prop != nullptr,
util::format("No property '%1' on object of type '%2'", key_path[index], desc->name));
if (prop->object_type.size()) {
desc = schema.find(prop->object_type);
@ -432,9 +434,8 @@ void do_add_comparison_to_query(Query &query, const Schema &schema, const Object
case PropertyType::Array:
add_link_constraint_to_query(query, cmp.op, expr, link_argument(lhs, rhs, args));
break;
default: {
throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported");
}
default:
throw std::runtime_error(util::format("Object type '%1' not supported", string_for_property_type(type)));
}
}

View File

@ -22,8 +22,6 @@
#include "parser.hpp"
#include "object_accessor.hpp"
#include <realm/util/to_string.hpp>
namespace realm {
class Query;
class Schema;
@ -68,10 +66,7 @@ class ArgumentConverter : public Arguments {
ContextType m_ctx;
ValueType &argument_at(size_t index) {
if (index >= m_arguments.size()) {
throw std::out_of_range((std::string)"Argument index " + util::to_string(index) + " out of range 0.." + util::to_string(m_arguments.size()-1));
}
return m_arguments[index];
return m_arguments.at(index);
}
};
}

View File

@ -21,6 +21,7 @@
#include "impl/realm_coordinator.hpp"
#include "impl/results_notifier.hpp"
#include "object_store.hpp"
#include "util/format.hpp"
#include <stdexcept>
@ -324,6 +325,7 @@ size_t Results::index_of(size_t row_ndx)
template<typename Int, typename Float, typename Double, typename Timestamp>
util::Optional<Mixed> 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)
{
@ -362,13 +364,13 @@ util::Optional<Mixed> 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<Mixed> 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); },
@ -377,7 +379,7 @@ util::Optional<Mixed> Results::max(size_t column)
util::Optional<Mixed> 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); },
@ -386,20 +388,20 @@ util::Optional<Mixed> Results::min(size_t column)
util::Optional<Mixed> 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<Mixed> 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()
@ -544,8 +546,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<PropertyType>(table->get_column_type(column)))))
, column_index(column)
, column_name(table->get_column_name(column))
, column_type(table->get_column_type(column))

View File

@ -25,7 +25,6 @@
#include <realm/table_view.hpp>
#include <realm/util/optional.hpp>
#include <realm/util/to_string.hpp>
namespace realm {
template<typename T> class BasicRowExpr;
@ -138,18 +137,13 @@ public:
// The Results object has been invalidated (due to the Realm being invalidated)
// All non-noexcept functions can throw this
struct InvalidatedException : public std::runtime_error
{
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 : 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) {}
struct OutOfBoundsIndexException : public std::out_of_range {
OutOfBoundsIndexException(size_t r, size_t c);
const size_t requested;
const size_t valid_count;
};
@ -161,8 +155,8 @@ public:
// The input Row object belongs to a different table
struct IncorrectTableException : public std::runtime_error {
IncorrectTableException(StringData e, StringData a, const std::string &error) :
std::runtime_error(error), expected(e), actual(a) {}
IncorrectTableException(StringData e, StringData a, const std::string &error)
: std::runtime_error(error), expected(e), actual(a) {}
const StringData expected;
const StringData actual;
};
@ -173,7 +167,7 @@ public:
StringData column_name;
DataType column_type;
UnsupportedColumnTypeException(size_t column, const Table* table);
UnsupportedColumnTypeException(size_t column, const Table* table, const char* operation);
};
// Create an async query from this Results
@ -220,6 +214,7 @@ private:
template<typename Int, typename Float, typename Double, typename Timestamp>
util::Optional<Mixed> aggregate(size_t column, bool return_none_for_empty,
const char* name,
Int agg_int, Float agg_float,
Double agg_double, Timestamp agg_timestamp);

View File

@ -23,6 +23,7 @@
#include "impl/transact_log_handler.hpp"
#include "object_store.hpp"
#include "schema.hpp"
#include "util/format.hpp"
#include <realm/commit_log.hpp>
#include <realm/group_shared.hpp>
@ -75,19 +76,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 exists", ex.get_path()), ex.what());
}
catch (util::File::AccessError const& ex) {
// Errors for `open()` include the path, but other errors don't. We
@ -100,13 +100,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) {

View File

@ -217,12 +217,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 {
@ -232,7 +232,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)) {}
};
class InvalidEncryptionKeyException : public std::runtime_error {

82
src/util/format.cpp Normal file
View File

@ -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 <sstream>
#include <realm/string_data.hpp>
#include <realm/util/assert.hpp>
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<Printable> 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<char**>(&fmt), 10) - 1;
REALM_ASSERT(index < values.size());
(values.begin() + index)->print(ss);
}
return ss.str();
}
} // namespace _impl
} // namespace realm

75
src/util/format.hpp Normal file
View File

@ -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 <cstdint>
#include <iosfwd>
#include <initializer_list>
#include <string>
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<Printable>);
} // namespace _impl
namespace util {
template<typename... Args>
std::string format(const char* fmt, Args&&... args)
{
return _impl::format(fmt, {_impl::Printable(args)...});
}
} // namespace util
} // namespace realm
#endif // REALM_UTIL_FORMAT_HPP

View File

@ -17,7 +17,7 @@
using namespace realm;
TEST_CASE("Results") {
TEST_CASE("[results] notifications") {
InMemoryTestFile config;
config.cache = false;
config.automatic_change_notifications = false;
@ -420,7 +420,7 @@ TEST_CASE("Results") {
}
}
TEST_CASE("Async Results error handling") {
TEST_CASE("[results] async error handling") {
InMemoryTestFile config;
config.cache = false;
config.automatic_change_notifications = false;
@ -534,7 +534,7 @@ TEST_CASE("Async Results error handling") {
}
}
TEST_CASE("Notifications on moved Results") {
TEST_CASE("[results] notifications after move") {
InMemoryTestFile config;
config.cache = false;
config.automatic_change_notifications = false;
@ -584,3 +584,28 @@ TEST_CASE("Notifications on moved Results") {
REQUIRE(notification_calls == 2);
}
}
TEST_CASE("[results] error messages") {
InMemoryTestFile config;
config.schema = std::make_unique<Schema>(Schema{
{"object", "", {
{"value", PropertyType::String},
}},
});
auto r = Realm::get_shared_realm(config);
auto table = r->read_group()->get_table("class_object");
Results results(r, *config.schema->find("object"), *table);
r->begin_transaction();
table->add_empty_row();
r->commit_transaction();
SECTION("out of bounds access") {
REQUIRE_THROWS_WITH(results.get(5), "Requested index 5 greater than max 1");
}
SECTION("unsupported aggregate operation") {
REQUIRE_THROWS_WITH(results.sum(0), "Cannot sum property 'value': operation not supported for 'string' properties");
}
}