Add support for lists of things other than objects

And add a shorthand syntax for schema definitions.
This commit is contained in:
Thomas Goyne 2017-09-11 14:57:31 -07:00
parent 54bbc708e9
commit 9a31febc4c
25 changed files with 1624 additions and 978 deletions

View File

@ -54,7 +54,7 @@ struct Arguments {
};
template<typename T>
using ArgumentsMethodType = void(typename T::Context, typename T::Function, typename T::Object, Arguments<T>, ReturnValue<T> &);
using ArgumentsMethodType = void(typename T::Context, typename T::Object, Arguments<T>, ReturnValue<T> &);
template<typename T>
struct PropertyType {

View File

@ -54,30 +54,33 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
using Object = js::Object<T>;
using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>;
using Arguments = js::Arguments<T>;
static ObjectType create_instance(ContextType, realm::List);
// properties
static void get_length(ContextType, ObjectType, ReturnValue &);
static void get_type(ContextType, ObjectType, ReturnValue &);
static void get_optional(ContextType, ObjectType, ReturnValue &);
static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &);
static bool set_index(ContextType, ObjectType, uint32_t, ValueType);
// methods
static void push(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void pop(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void unshift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void shift(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void splice(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void push(ContextType, ObjectType, Arguments, ReturnValue &);
static void pop(ContextType, ObjectType, Arguments, ReturnValue &);
static void unshift(ContextType, ObjectType, Arguments, ReturnValue &);
static void shift(ContextType, ObjectType, Arguments, ReturnValue &);
static void splice(ContextType, ObjectType, Arguments, ReturnValue &);
static void snapshot(ContextType, ObjectType, Arguments, ReturnValue &);
static void filtered(ContextType, ObjectType, Arguments, ReturnValue &);
static void sorted(ContextType, ObjectType, Arguments, ReturnValue &);
static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &);
static void index_of(ContextType, ObjectType, Arguments, ReturnValue &);
// observable
static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
std::string const name = "List";
@ -99,9 +102,14 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
PropertyMap<T> const properties = {
{"length", {wrap<get_length>, nullptr}},
{"type", {wrap<get_type>, nullptr}},
{"optional", {wrap<get_optional>, nullptr}},
};
IndexPropertyType<T> const index_accessor = {wrap<get_index>, wrap<set_index>};
private:
static void validate_value(ContextType, realm::List&, ValueType);
};
template<typename T>
@ -115,70 +123,83 @@ void ListClass<T>::get_length(ContextType, ObjectType object, ReturnValue &retur
return_value.set((uint32_t)list->size());
}
template<typename T>
void ListClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(object);
return_value.set(string_for_property_type(list->get_type() & ~realm::PropertyType::Flags));
}
template<typename T>
void ListClass<T>::get_optional(ContextType, ObjectType object, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(object);
return_value.set(is_nullable(list->get_type()));
}
template<typename T>
void ListClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(object);
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
NativeAccessor<T> accessor(ctx, *list);
return_value.set(list->get(accessor, index));
}
template<typename T>
bool ListClass<T>::set_index(ContextType ctx, ObjectType object, uint32_t index, ValueType value) {
auto list = get_internal<T, ListClass<T>>(object);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
validate_value(ctx, *list, value);
NativeAccessor<T> accessor(ctx, *list);
list->set(accessor, index, value);
return true;
}
template<typename T>
void ListClass<T>::push(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::push(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
for (size_t i = 0; i < argc; i++) {
list->add(accessor, arguments[i]);
for (size_t i = 0; i < args.count; i++) {
validate_value(ctx, *list, args[i]);
}
NativeAccessor<T> accessor(ctx, *list);
for (size_t i = 0; i < args.count; i++) {
list->add(accessor, args[i]);
}
return_value.set((uint32_t)list->size());
}
template<typename T>
void ListClass<T>::pop(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ListClass<T>::pop(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
size_t size = list->size();
auto size = static_cast<unsigned int>(list->size());
if (size == 0) {
list->verify_in_transaction();
return_value.set_undefined();
}
else {
size_t index = size - 1;
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
list->remove(index);
get_index(ctx, this_object, size - 1, return_value);
list->remove(size - 1);
}
}
template<typename T>
void ListClass<T>::unshift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::unshift(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
for (size_t i = 0; i < argc; i++) {
list->insert(accessor, i, arguments[i]);
for (size_t i = 0; i < args.count; i++) {
validate_value(ctx, *list, args[i]);
}
NativeAccessor<T> accessor(ctx, *list);
for (size_t i = 0; i < args.count; i++) {
list->insert(accessor, i, args[i]);
}
return_value.set((uint32_t)list->size());
}
template<typename T>
void ListClass<T>::shift(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ListClass<T>::shift(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
if (list->size() == 0) {
@ -186,151 +207,108 @@ void ListClass<T>::shift(ContextType ctx, FunctionType, ObjectType this_object,
return_value.set_undefined();
}
else {
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(0));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
get_index(ctx, this_object, 0, return_value);
list->remove(0);
}
}
template<typename T>
void ListClass<T>::splice(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::splice(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
size_t size = list->size();
long index = std::min<long>(Value::to_number(ctx, arguments[0]), size);
long index = std::min<long>(Value::to_number(ctx, args[0]), size);
if (index < 0) {
index = std::max<long>(size + index, 0);
}
size_t remove;
if (argc < 2) {
if (args.count < 2) {
remove = size - index;
}
else {
remove = std::max<long>(Value::to_number(ctx, arguments[1]), 0);
remove = std::max<long>(Value::to_number(ctx, args[1]), 0);
remove = std::min<long>(remove, size - index);
}
std::vector<ValueType> removed_objects;
removed_objects.reserve(remove);
NativeAccessor<T> accessor(ctx, list->get_realm(), list->get_object_schema());
NativeAccessor<T> accessor(ctx, *list);
for (size_t i = 0; i < remove; i++) {
auto realm_object = realm::Object(list->get_realm(), list->get_object_schema(), list->get(index));
removed_objects.push_back(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
removed_objects.push_back(list->get(accessor, index));
list->remove(index);
}
for (size_t i = 2; i < argc; i++) {
list->insert(accessor, index + i - 2, arguments[i]);
for (size_t i = 2; i < args.count; i++) {
list->insert(accessor, index + i - 2, args[i]);
}
return_value.set(Object::create_array(ctx, removed_objects));
}
template<typename T>
void ListClass<T>::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ListClass<T>::snapshot(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, list->snapshot()));
}
template<typename T>
void ListClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ListClass<T>::filtered(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_filtered(ctx, *list, argc, arguments));
return_value.set(ResultsClass<T>::create_filtered(ctx, *list, args));
}
template<typename T>
void ListClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ListClass<T>::sorted(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, list->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
return_value.set(ResultsClass<T>::create_instance(ctx, list->sort(ResultsClass<T>::get_keypaths(ctx, args))));
}
template<typename T>
void ListClass<T>::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ListClass<T>::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid());
}
template<typename T>
void ListClass<T>::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
ObjectType arg = Value::validated_to_object(ctx, arguments[0]);
if (Object::template is_instance<RealmObjectClass<T>>(ctx, arg)) {
auto object = get_internal<T, RealmObjectClass<T>>(arg);
if (!object->is_valid()) {
throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.");
}
void ListClass<T>::index_of(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto fn = [&](auto&& row) {
auto list = get_internal<T, ListClass<T>>(this_object);
size_t ndx = list->find(object->row());
if (ndx == realm::not_found) {
return_value.set(-1);
}
else {
return_value.set((uint32_t)ndx);
}
}
else {
return_value.set(-1);
}
NativeAccessor<T> accessor(ctx, *list);
return list->find(accessor, row);
};
ResultsClass<T>::index_of(ctx, fn, args, return_value);
}
template<typename T>
void ListClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void ListClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
auto token = list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
HANDLESCOPE
ValueType arguments[2];
arguments[0] = static_cast<ObjectType>(protected_this);
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
list->m_notification_tokens.emplace_back(protected_callback, std::move(token));
ResultsClass<T>::add_listener(ctx, *list, this_object, args);
}
template<typename T>
void ListClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void ListClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto list = get_internal<T, ListClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto iter = list->m_notification_tokens.begin();
typename Protected<FunctionType>::Comparator compare;
while (iter != list->m_notification_tokens.end()) {
if(compare(iter->first, protected_function)) {
iter = list->m_notification_tokens.erase(iter);
}
else {
iter++;
}
}
ResultsClass<T>::remove_listener(ctx, *list, this_object, args);
}
template<typename T>
void ListClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
template<typename T>
void ListClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto list = get_internal<T, ListClass<T>>(this_object);
list->m_notification_tokens.clear();
}
template<typename T>
void ListClass<T>::validate_value(ContextType ctx, realm::List& list, ValueType value) {
auto type = list.get_type();
StringData object_type;
if (type == realm::PropertyType::Object) {
object_type = list.get_object_schema().name;
}
if (!Value::is_valid_for_property_type(ctx, value, type, object_type)) {
throw TypeErrorException("Property", object_type ? object_type : string_for_property_type(type), Value::to_string(ctx, value));
}
}
} // js
} // realm

View File

@ -47,12 +47,19 @@ public:
using OptionalValue = util::Optional<ValueType>;
NativeAccessor(ContextType ctx, std::shared_ptr<Realm> realm, const ObjectSchema& object_schema)
: m_ctx(ctx), m_realm(std::move(realm)), m_object_schema(object_schema) { }
: m_ctx(ctx), m_realm(std::move(realm)), m_object_schema(&object_schema) { }
template<typename Collection>
NativeAccessor(ContextType ctx, Collection const& collection)
: m_ctx(ctx)
, m_realm(collection.get_realm())
, m_object_schema(collection.get_type() == realm::PropertyType::Object ? &collection.get_object_schema() : nullptr)
{ }
NativeAccessor(NativeAccessor& parent, const Property& prop)
: m_ctx(parent.m_ctx)
, m_realm(parent.m_realm)
, m_object_schema(*m_realm->schema().find(prop.object_type))
, m_object_schema(&*m_realm->schema().find(prop.object_type))
{ }
OptionalValue value_for_property(ValueType dict, std::string const& prop_name, size_t prop_index) {
@ -61,11 +68,9 @@ public:
return util::none;
}
ValueType value = Object::get_property(m_ctx, object, prop_name);
const auto& prop = m_object_schema.persisted_properties[prop_index];
const auto& prop = m_object_schema->persisted_properties[prop_index];
if (!Value::is_valid_for_property(m_ctx, value, prop)) {
throw TypeErrorException(m_object_schema.name, prop.name,
js_type_name_for_property_type(prop.type),
print(value));
throw TypeErrorException(*this, m_object_schema->name, prop, value);
}
return value;
}
@ -79,6 +84,14 @@ public:
template<typename T>
T unbox(ValueType value, bool create = false, bool update = false);
template<typename T>
util::Optional<T> unbox_optional(ValueType value) {
return is_null(value) ? util::none : util::make_optional(unbox<T>(value));
}
template<typename T>
ValueType box(util::Optional<T> v) { return v ? box(*v) : null_value(); }
ValueType box(bool boolean) { return Value::from_boolean(m_ctx, boolean); }
ValueType box(int64_t number) { return Value::from_number(m_ctx, number); }
ValueType box(float number) { return Value::from_number(m_ctx, number); }
@ -88,11 +101,20 @@ public:
ValueType box(Mixed) { throw std::runtime_error("'Any' type is unsupported"); }
ValueType box(Timestamp ts) {
if (ts.is_null()) {
return null_value();
}
return Object::create_date(m_ctx, ts.get_seconds() * 1000 + ts.get_nanoseconds() / 1000000);
}
ValueType box(realm::Object realm_object) {
return RealmObjectClass<JSEngine>::create_instance(m_ctx, std::move(realm_object));
}
ValueType box(RowExpr row) {
if (!row.is_attached()) {
return Value::from_null(m_ctx);
}
return RealmObjectClass<JSEngine>::create_instance(m_ctx, realm::Object(m_realm, *m_object_schema, row));
}
ValueType box(realm::List list) {
return ListClass<JSEngine>::create_instance(m_ctx, std::move(list));
}
@ -112,7 +134,7 @@ public:
auto obj = Value::validated_to_object(m_ctx, value);
uint32_t size = Object::validated_get_length(m_ctx, obj);
for (uint32_t i = 0; i < size; ++i) {
func(Object::validated_get_object(m_ctx, obj, i));
func(Object::get_property(m_ctx, obj, i));
}
}
@ -128,12 +150,14 @@ public:
void will_change(realm::Object&, realm::Property const&) { }
void did_change() { }
std::string print(ValueType const& v) { return Value::to_string(m_ctx, v); }
std::string print(ValueType const&);
void print(std::string&, ValueType const&);
const char *typeof(ValueType const& v) { return Value::typeof(m_ctx, v); }
private:
ContextType m_ctx;
std::shared_ptr<Realm> m_realm;
const ObjectSchema& m_object_schema;
const ObjectSchema* m_object_schema;
std::string m_string_buffer;
OwnedBinaryData m_owned_binary_data;
@ -173,34 +197,37 @@ struct Unbox<JSEngine, double> {
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<bool>> {
static util::Optional<bool> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_boolean(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<bool>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<int64_t>> {
static util::Optional<int64_t> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<int64_t>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<float>> {
static util::Optional<float> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<float>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, util::Optional<double>> {
static util::Optional<double> call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
return js::Value<JSEngine>::validated_to_number(ctx->m_ctx, value, "Property");
return ctx->template unbox_optional<double>(value);
}
};
template<typename JSEngine>
struct Unbox<JSEngine, StringData> {
static StringData call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
if (ctx->is_null(value)) {
return StringData();
}
ctx->m_string_buffer = js::Value<JSEngine>::validated_to_string(ctx->m_ctx, value, "Property");
return ctx->m_string_buffer;
}
@ -209,6 +236,9 @@ struct Unbox<JSEngine, StringData> {
template<typename JSEngine>
struct Unbox<JSEngine, BinaryData> {
static BinaryData call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value value, bool, bool) {
if (ctx->is_null(value)) {
return BinaryData();
}
ctx->m_owned_binary_data = js::Value<JSEngine>::validated_to_binary(ctx->m_ctx, value, "Property");
return ctx->m_owned_binary_data.get();
}
@ -224,6 +254,9 @@ struct Unbox<JSEngine, Mixed> {
template<typename JSEngine>
struct Unbox<JSEngine, Timestamp> {
static Timestamp call(NativeAccessor<JSEngine> *ctx, typename JSEngine::Value const& value, bool, bool) {
if (ctx->is_null(value)) {
return Timestamp();
}
auto date = js::Value<JSEngine>::validated_to_date(ctx->m_ctx, value, "Property");
double milliseconds = js::Value<JSEngine>::to_number(ctx->m_ctx, date);
int64_t seconds = milliseconds / 1000;
@ -248,15 +281,15 @@ struct Unbox<JSEngine, RowExpr> {
throw std::runtime_error("Realm object is from another Realm");
}
}
else if (!create) {
throw std::runtime_error("object is not a Realm Object");
if (!create) {
throw NonRealmObjectException();
}
if (Value::is_array(ctx->m_ctx, object)) {
object = Schema<JSEngine>::dict_for_property_array(ctx->m_ctx, ctx->m_object_schema, object);
object = Schema<JSEngine>::dict_for_property_array(ctx->m_ctx, *ctx->m_object_schema, object);
}
auto child = realm::Object::create<ValueType>(*ctx, ctx->m_realm, ctx->m_object_schema,
auto child = realm::Object::create<ValueType>(*ctx, ctx->m_realm, *ctx->m_object_schema,
static_cast<ValueType>(object), try_update);
return child.row();
}
@ -269,6 +302,62 @@ U NativeAccessor<T>::unbox(ValueType value, bool create, bool update) {
return _impl::Unbox<T, U>::call(this, std::move(value), create, update);
}
template<typename T>
std::string NativeAccessor<T>::print(ValueType const& value) {
std::string ret;
print(ret, value);
return ret;
}
template<typename T>
void NativeAccessor<T>::print(std::string& str, ValueType const& value) {
if (Value::is_null(m_ctx, value)) {
str += "null";
}
else if (Value::is_undefined(m_ctx, value)) {
str += "undefined";
}
else if (Value::is_array(m_ctx, value)) {
auto array = Value::to_array(m_ctx, value);
auto length = Object::validated_get_length(m_ctx, array);
str += "[";
for (uint32_t i = 0; i < length; i++) {
print(str, Object::get_property(m_ctx, array, i));
if (i + 1 < length) {
str += ", ";
}
}
str += "]";
}
else if (Value::is_object(m_ctx, value)) {
auto object = Value::to_object(m_ctx, value);
if (Object::template is_instance<RealmObjectClass<T>>(m_ctx, object)) {
auto realm_object = get_internal<T, RealmObjectClass<T>>(object);
auto& object_schema = realm_object->get_object_schema();
str += object_schema.name;
str += "{";
for (size_t i = 0, count = object_schema.persisted_properties.size(); i < count; ++i) {
print(str, realm_object->template get_property_value<ValueType>(*this, object_schema.persisted_properties[i].name));
if (i + 1 < count) {
str += ", ";
}
}
str += "}";
}
else {
str += Value::to_string(m_ctx, value);
}
}
else if (Value::is_string(m_ctx, value)) {
str += "'";
str += Value::to_string(m_ctx, value);
str += "'";
}
else {
str += Value::to_string(m_ctx, value);
}
}
} // js
} // realm

View File

@ -18,6 +18,7 @@
#include "platform.hpp"
#include "realm_coordinator.hpp"
#include "js_types.hpp"
#if REALM_ENABLE_SYNC
#include "sync/sync_manager.hpp"
@ -61,6 +62,49 @@ void clear_test_state() {
SyncManager::shared().configure_file_system(default_realm_file_directory(), SyncManager::MetadataMode::NoEncryption);
#endif
}
std::string TypeErrorException::type_string(Property const& prop)
{
using realm::PropertyType;
std::string ret;
switch (prop.type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
ret = "number";
break;
case PropertyType::Bool:
ret = "boolean";
break;
case PropertyType::String:
ret = "string";
break;
case PropertyType::Date:
ret = "date";
break;
case PropertyType::Data:
ret = "binary";
break;
case PropertyType::LinkingObjects:
case PropertyType::Object:
ret = prop.object_type;
break;
case PropertyType::Any:
throw std::runtime_error("'Any' type is not supported");
default:
REALM_UNREACHABLE();
}
if (realm::is_nullable(prop.type)) {
ret += "?";
}
if (realm::is_array(prop.type)) {
ret += "[]";
}
return ret;
}
} // js
} // realm

View File

@ -164,22 +164,22 @@ public:
static FunctionType create_constructor(ContextType);
// methods
static void objects(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void object_for_primary_key(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void create(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_one(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_all(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void write(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void begin_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void commit_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void cancel_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void add_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void wait_for_download_completion(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void close(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void compact(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_model(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void objects(ContextType, ObjectType, Arguments, ReturnValue &);
static void object_for_primary_key(ContextType, ObjectType, Arguments, ReturnValue &);
static void create(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_one(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_all(ContextType, ObjectType, Arguments, ReturnValue &);
static void write(ContextType, ObjectType, Arguments, ReturnValue &);
static void begin_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void cancel_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void wait_for_download_completion(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
static void compact(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &);
// properties
static void get_empty(ContextType, ObjectType, ReturnValue &);
@ -197,10 +197,10 @@ public:
static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&);
static void schema_version(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void clear_test_state(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_file(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void schema_version(ContextType, ObjectType, Arguments, ReturnValue &);
static void clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &);
static void copy_bundled_realm_files(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_file(ContextType, ObjectType, Arguments, ReturnValue &);
// static properties
static void get_default_path(ContextType, ObjectType, ReturnValue &);
@ -503,7 +503,7 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
}
template<typename T>
void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::schema_version(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
realm::Realm::Config config;
@ -524,19 +524,19 @@ void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType thi
template<typename T>
void RealmClass<T>::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::clear_test_state(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
js::clear_test_state();
}
template<typename T>
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
realm::copy_bundled_realm_files();
}
template<typename T>
void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_file(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
ValueType value = args[0];
@ -567,7 +567,7 @@ void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_o
}
template<typename T>
void RealmClass<T>::delete_model(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_model(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
ValueType value = args[0];
@ -641,7 +641,7 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
#endif
template<typename T>
void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(3);
auto config_object = Value::validated_to_object(ctx, args[0]);
auto callback_function = Value::validated_to_function(ctx, args[1 + (args.count == 3)]);
@ -743,7 +743,8 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
}
ObjectType object = Object::create_empty(protected_ctx);
Object::set_property(protected_ctx, object, "message", Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error"));
Object::set_property(protected_ctx, object, "message",
Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error"));
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1));
ValueType callback_arguments[1];
@ -760,7 +761,7 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
}
template<typename T>
void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -771,7 +772,7 @@ void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_objec
}
template<typename T>
void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::object_for_primary_key(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -789,7 +790,7 @@ void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, Object
}
template<typename T>
void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::create(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(3);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -813,7 +814,7 @@ void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object
}
template<typename T>
void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_one(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -861,7 +862,7 @@ void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_ob
}
template<typename T>
void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::delete_all(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -877,7 +878,7 @@ void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_ob
}
template<typename T>
void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::write(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -897,7 +898,7 @@ void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object,
}
template<typename T>
void RealmClass<T>::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::begin_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -905,7 +906,7 @@ void RealmClass<T>::begin_transaction(ContextType ctx, FunctionType, ObjectType
}
template<typename T>
void RealmClass<T>::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::commit_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -913,7 +914,7 @@ void RealmClass<T>::commit_transaction(ContextType ctx, FunctionType, ObjectType
}
template<typename T>
void RealmClass<T>::cancel_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::cancel_transaction(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -921,7 +922,7 @@ void RealmClass<T>::cancel_transaction(ContextType ctx, FunctionType, ObjectType
}
template<typename T>
void RealmClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
validated_notification_name(ctx, args[0]);
@ -933,7 +934,7 @@ void RealmClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_
}
template<typename T>
void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
validated_notification_name(ctx, args[0]);
@ -945,7 +946,7 @@ void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType th
}
template<typename T>
void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
if (args.count) {
validated_notification_name(ctx, args[0]);
@ -957,7 +958,7 @@ void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectTy
}
template<typename T>
void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::close(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
@ -965,7 +966,7 @@ void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object,
}
template<typename T>
void RealmClass<T>::compact(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
void RealmClass<T>::compact(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);

View File

@ -127,9 +127,7 @@ bool RealmObjectClass<T>::set_property(ContextType ctx, ObjectType object, const
NativeAccessor<T> accessor(ctx, realm_object->realm(), realm_object->get_object_schema());
if (!Value::is_valid_for_property(ctx, value, *prop)) {
throw TypeErrorException(realm_object->get_object_schema().name, property_name,
js_type_name_for_property_type(prop->type),
accessor.print(value));
throw TypeErrorException(accessor, realm_object->get_object_schema().name, *prop, value);
}
realm_object->set_property_value(accessor, property_name, value, true);

View File

@ -33,6 +33,10 @@ namespace js {
template<typename>
class NativeAccessor;
struct NonRealmObjectException : public std::logic_error {
NonRealmObjectException() : std::logic_error("Object is not a Realm object") { }
};
template<typename T>
class Results : public realm::Results {
public:
@ -56,29 +60,40 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
using Object = js::Object<T>;
using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>;
using Arguments = js::Arguments<T>;
static ObjectType create_instance(ContextType, realm::Results);
static ObjectType create_instance(ContextType, SharedRealm, const std::string &object_type);
template<typename U>
static ObjectType create_filtered(ContextType, const U &, size_t, const ValueType[]);
static ObjectType create_filtered(ContextType, const U &, Arguments);
static std::vector<std::pair<std::string, bool>> get_keypaths(ContextType, size_t, const ValueType[]);
static std::vector<std::pair<std::string, bool>> get_keypaths(ContextType, Arguments);
static void get_length(ContextType, ObjectType, ReturnValue &);
static void get_type(ContextType, ObjectType, ReturnValue &);
static void get_optional(ContextType, ObjectType, ReturnValue &);
static void get_index(ContextType, ObjectType, uint32_t, ReturnValue &);
static void snapshot(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void snapshot(ContextType, ObjectType, Arguments, ReturnValue &);
static void filtered(ContextType, ObjectType, Arguments, ReturnValue &);
static void sorted(ContextType, ObjectType, Arguments, ReturnValue &);
static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &);
static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void index_of(ContextType, ObjectType, Arguments, ReturnValue &);
template<typename Fn>
static void index_of(ContextType, Fn&, Arguments, ReturnValue &);
// observable
static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
template<typename U>
static void add_listener(ContextType, U&, ObjectType, Arguments);
template<typename U>
static void remove_listener(ContextType, U&, ObjectType, Arguments);
std::string const name = "Results";
@ -95,6 +110,8 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
PropertyMap<T> const properties = {
{"length", {wrap<get_length>, nullptr}},
{"type", {wrap<get_type>, nullptr}},
{"optional", {wrap<get_optional>, nullptr}},
};
IndexPropertyType<T> const index_accessor = {wrap<get_index>, nullptr};
@ -116,15 +133,19 @@ typename T::Object ResultsClass<T>::create_instance(ContextType ctx, SharedRealm
template<typename T>
template<typename U>
typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &collection, size_t argc, const ValueType arguments[]) {
auto query_string = Value::validated_to_string(ctx, arguments[0], "predicate");
typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &collection, Arguments args) {
if (collection.get_type() != realm::PropertyType::Object) {
throw std::runtime_error("Filtering non-object Lists and Results is not yet implemented.");
}
auto query_string = Value::validated_to_string(ctx, args[0], "predicate");
auto query = collection.get_query();
auto const &realm = collection.get_realm();
auto const &object_schema = collection.get_object_schema();
parser::Predicate predicate = parser::parse(query_string);
NativeAccessor<T> accessor(ctx, realm, object_schema);
query_builder::ArgumentConverter<ValueType, NativeAccessor<T>> converter(accessor, &arguments[1], argc - 1);
query_builder::ArgumentConverter<ValueType, NativeAccessor<T>> converter(accessor, &args.value[1], args.count - 1);
query_builder::apply_predicate(query, predicate, converter, realm->schema(), object_schema.name);
return create_instance(ctx, collection.filter(std::move(query)));
@ -132,15 +153,18 @@ typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &co
template<typename T>
std::vector<std::pair<std::string, bool>>
ResultsClass<T>::get_keypaths(ContextType ctx, size_t argc, const ValueType arguments[]) {
validate_argument_count(argc, 1, 2);
ResultsClass<T>::get_keypaths(ContextType ctx, Arguments args) {
args.validate_maximum(2);
std::vector<std::pair<std::string, bool>> sort_order;
if (args.count == 0) {
sort_order.emplace_back("self", true);
return sort_order;
}
else if (Value::is_array(ctx, args[0])) {
validate_argument_count(args.count, 1, "Second argument is not allowed if passed an array of sort descriptors");
if (argc > 0 && Value::is_array(ctx, arguments[0])) {
validate_argument_count(argc, 1, "Second argument is not allowed if passed an array of sort descriptors");
ObjectType js_prop_names = Value::validated_to_object(ctx, arguments[0]);
ObjectType js_prop_names = Value::validated_to_object(ctx, args[0]);
size_t prop_count = Object::validated_get_length(ctx, js_prop_names);
sort_order.reserve(prop_count);
@ -158,8 +182,13 @@ ResultsClass<T>::get_keypaths(ContextType ctx, size_t argc, const ValueType argu
}
}
else {
sort_order.emplace_back(Value::validated_to_string(ctx, arguments[0]),
argc == 1 || !Value::to_boolean(ctx, arguments[1]));
if (Value::is_boolean(ctx, args[0])) {
sort_order.emplace_back("self", !Value::to_boolean(ctx, args[0]));
}
else {
sort_order.emplace_back(Value::validated_to_string(ctx, args[0]),
args.count == 1 || !Value::to_boolean(ctx, args[1]));
}
}
return sort_order;
}
@ -171,124 +200,136 @@ void ResultsClass<T>::get_length(ContextType ctx, ObjectType object, ReturnValue
}
template<typename T>
void ResultsClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
void ResultsClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(object);
auto row = results->get(index);
// Return null for deleted objects in a snapshot.
if (!row.is_attached()) {
return_value.set_null();
return;
}
auto realm_object = realm::Object(results->get_realm(), results->get_object_schema(), results->get(index));
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
return_value.set(string_for_property_type(results->get_type() & ~realm::PropertyType::Flags));
}
template<typename T>
void ResultsClass<T>::snapshot(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void ResultsClass<T>::get_optional(ContextType, ObjectType object, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(object);
return_value.set(is_nullable(results->get_type()));
}
template<typename T>
void ResultsClass<T>::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(object);
NativeAccessor<T> accessor(ctx, *results);
return_value.set(results->get(accessor, index));
}
template<typename T>
void ResultsClass<T>::snapshot(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto results = get_internal<T, ResultsClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, results->snapshot()));
}
template<typename T>
void ResultsClass<T>::filtered(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count_at_least(argc, 1);
void ResultsClass<T>::filtered(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
return_value.set(create_filtered(ctx, *results, argc, arguments));
return_value.set(create_filtered(ctx, *results, args));
}
template<typename T>
void ResultsClass<T>::sorted(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ResultsClass<T>::sorted(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
return_value.set(ResultsClass<T>::create_instance(ctx, results->sort(ResultsClass<T>::get_keypaths(ctx, argc, arguments))));
return_value.set(ResultsClass<T>::create_instance(ctx, results->sort(ResultsClass<T>::get_keypaths(ctx, args))));
}
template<typename T>
void ResultsClass<T>::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid());
}
template<typename T>
void ResultsClass<T>::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
ObjectType arg = Value::validated_to_object(ctx, arguments[0]);
if (Object::template is_instance<RealmObjectClass<T>>(ctx, arg)) {
auto object = get_internal<T, RealmObjectClass<T>>(arg);
if (!object->is_valid()) {
throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.");
}
size_t ndx;
try {
auto results = get_internal<T, ResultsClass<T>>(this_object);
ndx = results->index_of(object->row());
}
catch (realm::Results::IncorrectTableException &) {
throw std::runtime_error("Object type does not match the type contained in result");
}
if (ndx == realm::not_found) {
return_value.set(-1);
}
else {
return_value.set((uint32_t)ndx);
}
template<typename Fn>
void ResultsClass<T>::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
size_t ndx;
try {
ndx = fn(args[0]);
}
else {
catch (realm::Results::IncorrectTableException &) {
throw std::runtime_error("Object type does not match the type contained in result");
}
catch (NonRealmObjectException&) {
ndx = realm::not_found;
}
if (ndx == realm::not_found) {
return_value.set(-1);
}
else {
return_value.set((uint32_t)ndx);
}
}
template<typename T>
void ResultsClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
void ResultsClass<T>::index_of(ContextType ctx, ObjectType this_object,
Arguments args, ReturnValue &return_value) {
auto fn = [&](auto&& row) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
NativeAccessor<T> accessor(ctx, *results);
return results->index_of(accessor, row);
};
index_of(ctx, fn, args, return_value);
}
template<typename T>
template<typename U>
void ResultsClass<T>::add_listener(ContextType ctx, U& collection, ObjectType this_object, Arguments args) {
args.validate_maximum(1);
auto callback = Value::validated_to_function(ctx, args[0]);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
auto token = results->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
auto token = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) {
HANDLESCOPE
ValueType arguments[2];
arguments[0] = static_cast<ObjectType>(protected_this);
arguments[1] = CollectionClass<T>::create_collection_change_set(protected_ctx, change_set);
ValueType arguments[] {
static_cast<ObjectType>(protected_this),
CollectionClass<T>::create_collection_change_set(protected_ctx, change_set)
};
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, arguments);
});
results->m_notification_tokens.emplace_back(protected_callback, std::move(token));
collection.m_notification_tokens.emplace_back(protected_callback, std::move(token));
}
template<typename T>
void ResultsClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void ResultsClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto iter = results->m_notification_tokens.begin();
typename Protected<FunctionType>::Comparator compare;
while (iter != results->m_notification_tokens.end()) {
if(compare(iter->first, protected_function)) {
iter = results->m_notification_tokens.erase(iter);
}
else {
iter++;
}
}
add_listener(ctx, *results, this_object, args);
}
template<typename T>
void ResultsClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
template<typename U>
void ResultsClass<T>::remove_listener(ContextType ctx, U& collection, ObjectType this_object, Arguments args) {
args.validate_maximum(1);
auto callback = Value::validated_to_function(ctx, args[0]);
auto protected_function = Protected<FunctionType>(ctx, callback);
auto& tokens = collection.m_notification_tokens;
auto compare = [&](auto&& token) {
return typename Protected<FunctionType>::Comparator()(token.first, protected_function);
};
tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end());
}
template<typename T>
void ResultsClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
auto results = get_internal<T, ResultsClass<T>>(this_object);
remove_listener(ctx, *results, this_object, args);
}
template<typename T>
void ResultsClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto results = get_internal<T, ResultsClass<T>>(this_object);
results->m_notification_tokens.clear();
}

View File

@ -23,6 +23,8 @@
#include "js_types.hpp"
#include "schema.hpp"
#include "util/format.hpp"
namespace realm {
namespace js {
@ -41,7 +43,7 @@ struct Schema {
using ConstructorMap = std::map<std::string, Protected<FunctionType>>;
static ObjectType dict_for_property_array(ContextType, const ObjectSchema &, ObjectType);
static Property parse_property(ContextType, ValueType, std::string, ObjectDefaults &);
static Property parse_property(ContextType, ValueType, StringData, std::string, ObjectDefaults &);
static ObjectSchema parse_object_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
static realm::Schema parse_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
@ -68,8 +70,67 @@ typename T::Object Schema<T>::dict_for_property_array(ContextType ctx, const Obj
return dict;
}
static inline void parse_property_type(StringData object_name, Property& prop, StringData type)
{
using realm::PropertyType;
if (!type) {
throw std::logic_error(util::format("Property %1.%2 must have a non-empty type", object_name, prop.name));
}
if (type.ends_with("[]")) {
prop.type |= PropertyType::Array;
type = type.substr(0, type.size() - 2);
}
if (type.ends_with("?")) {
prop.type |= PropertyType::Nullable;
type = type.substr(0, type.size() - 1);
}
if (type == "bool") {
prop.type |= PropertyType::Bool;
}
else if (type == "int") {
prop.type |= PropertyType::Int;
}
else if (type == "float") {
prop.type |= PropertyType::Float;
}
else if (type == "double") {
prop.type |= PropertyType::Double;
}
else if (type == "string") {
prop.type |= PropertyType::String;
}
else if (type == "date") {
prop.type |= PropertyType::Date;
}
else if (type == "data") {
prop.type |= PropertyType::Data;
}
else if (type == "list") {
prop.type |= PropertyType::Object | PropertyType::Array;
}
else if (type == "linkingObjects") {
prop.type |= PropertyType::LinkingObjects | PropertyType::Array;
}
else if (type == "object") {
prop.type |= PropertyType::Object;
}
else {
// The type could be the name of another object type in the same schema.
prop.type |= PropertyType::Object;
prop.object_type = type;
}
// Object properties are implicitly optional
if (prop.type == PropertyType::Object && !is_array(prop.type)) {
prop.type |= PropertyType::Nullable;
}
}
template<typename T>
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::string property_name, ObjectDefaults &object_defaults) {
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, StringData object_name,
std::string property_name, ObjectDefaults &object_defaults) {
static const String default_string = "default";
static const String indexed_string = "indexed";
static const String type_string = "type";
@ -78,79 +139,23 @@ Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::s
static const String property_string = "property";
Property prop;
prop.name = property_name;
prop.name = std::move(property_name);
ObjectType property_object = {};
std::string type;
using realm::PropertyType;
PropertyType is_optional = PropertyType::Required;
if (Value::is_object(ctx, attributes)) {
property_object = Value::validated_to_object(ctx, attributes);
type = Object::validated_get_string(ctx, property_object, type_string);
std::string property_type = Object::validated_get_string(ctx, property_object, type_string);
parse_property_type(object_name, prop, property_type);
ValueType optional_value = Object::get_property(ctx, property_object, optional_string);
if (!Value::is_undefined(ctx, optional_value) && Value::validated_to_boolean(ctx, optional_value, "optional")) {
is_optional = PropertyType::Nullable;
prop.type |= PropertyType::Nullable;
}
}
else {
type = Value::validated_to_string(ctx, attributes);
}
if (type == "bool") {
prop.type = PropertyType::Bool | is_optional;
}
else if (type == "int") {
prop.type = PropertyType::Int | is_optional;
}
else if (type == "float") {
prop.type = PropertyType::Float | is_optional;
}
else if (type == "double") {
prop.type = PropertyType::Double | is_optional;
}
else if (type == "string") {
prop.type = PropertyType::String | is_optional;
}
else if (type == "date") {
prop.type = PropertyType::Date | is_optional;
}
else if (type == "data") {
prop.type = PropertyType::Data | is_optional;
}
else if (type == "list") {
if (!Value::is_valid(property_object)) {
throw std::runtime_error("List property must specify 'objectType'");
}
prop.type = PropertyType::Object | PropertyType::Array;
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
}
else if (type == "linkingObjects") {
prop.type = PropertyType::LinkingObjects | PropertyType::Array;
if (!Value::is_valid(property_object)) {
throw std::runtime_error("Object property must specify 'objectType'");
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
prop.link_origin_property_name = Object::validated_get_string(ctx, property_object, property_string);
}
else if (type == "object") {
prop.type = PropertyType::Object | PropertyType::Nullable;
if (!Value::is_valid(property_object)) {
throw std::runtime_error("Object property must specify 'objectType'");
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
}
else {
// The type could be the name of another object type in the same schema.
prop.type = PropertyType::Object | PropertyType::Nullable;
prop.object_type = type;
}
if (Value::is_valid(property_object)) {
ValueType default_value = Object::get_property(ctx, property_object, default_string);
if (!Value::is_undefined(ctx, default_value)) {
object_defaults.emplace(prop.name, Protected<ValueType>(ctx, default_value));
@ -161,6 +166,27 @@ Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::s
prop.is_indexed = Value::validated_to_boolean(ctx, indexed_value);
}
}
else {
std::string property_type = Value::validated_to_string(ctx, attributes);
parse_property_type(object_name, prop, property_type);
}
if (prop.type == PropertyType::Object && prop.object_type.empty()) {
if (!Value::is_valid(property_object)) {
throw std::logic_error(util::format("%1 property %2.%3 must specify 'objectType'",
is_array(prop.type) ? "List" : "Object", object_name, prop.name));
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
}
if (prop.type == PropertyType::LinkingObjects) {
if (!Value::is_valid(property_object)) {
throw std::logic_error(util::format("Linking objects property %1.%2 must specify 'objectType'",
object_name, prop.name));
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
prop.link_origin_property_name = Object::validated_get_string(ctx, property_object, property_string);
}
return prop;
}
@ -188,7 +214,7 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
for (uint32_t i = 0; i < length; i++) {
ObjectType property_object = Object::validated_get_object(ctx, properties_object, i);
std::string property_name = Object::validated_get_string(ctx, property_object, name_string);
Property property = parse_property(ctx, property_object, property_name, object_defaults);
Property property = parse_property(ctx, property_object, object_schema.name, std::move(property_name), object_defaults);
if (property.type == realm::PropertyType::LinkingObjects) {
object_schema.computed_properties.emplace_back(std::move(property));
}
@ -200,9 +226,9 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
}
else {
auto property_names = Object::get_property_names(ctx, properties_object);
for (auto &property_name : property_names) {
for (auto& property_name : property_names) {
ValueType property_value = Object::get_property(ctx, properties_object, property_name);
Property property = parse_property(ctx, property_value, property_name, object_defaults);
Property property = parse_property(ctx, property_value, object_schema.name, property_name, object_defaults);
if (property.type == realm::PropertyType::LinkingObjects) {
object_schema.computed_properties.emplace_back(std::move(property));
}
@ -291,13 +317,25 @@ typename T::Object Schema<T>::object_for_property(ContextType ctx, const Propert
Object::set_property(ctx, object, name_string, Value::from_string(ctx, property.name));
static const String type_string = "type";
const std::string type = is_array(property.type) ? "list" : string_for_property_type(property.type);
Object::set_property(ctx, object, type_string, Value::from_string(ctx, type));
if (is_array(property.type)) {
if (property.type == realm::PropertyType::LinkingObjects) {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "linkingObjects"));
}
else {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "list"));
}
}
else {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, string_for_property_type(property.type)));
}
static const String object_type_string = "objectType";
if (property.object_type.size()) {
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, property.object_type));
}
else if (is_array(property.type)) {
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, string_for_property_type(property.type & ~realm::PropertyType::Flags)));
}
static const String property_string = "property";
if (property.type == realm::PropertyType::LinkingObjects) {
@ -305,14 +343,10 @@ typename T::Object Schema<T>::object_for_property(ContextType ctx, const Propert
}
static const String indexed_string = "indexed";
if (property.is_indexed) {
Object::set_property(ctx, object, indexed_string, Value::from_boolean(ctx, true));
}
Object::set_property(ctx, object, indexed_string, Value::from_boolean(ctx, property.is_indexed));
static const String optional_string = "optional";
if (is_nullable(property.type)) {
Object::set_property(ctx, object, optional_string, Value::from_boolean(ctx, true));
}
Object::set_property(ctx, object, optional_string, Value::from_boolean(ctx, is_nullable(property.type)));
return object;
}

View File

@ -65,7 +65,7 @@ struct String {
String(const char *);
String(const StringType &);
String(StringType &&);
String(const std::string &);
String(StringData);
operator StringType() const;
operator std::string() const;
@ -82,16 +82,21 @@ struct Context {
class TypeErrorException : public std::invalid_argument {
public:
TypeErrorException(StringData object_type, StringData property,
std::string const& type, std::string const& value)
: std::invalid_argument(util::format("%1.%2 must be of type '%3', got (%4)",
object_type, property, type, value))
template<typename NativeAccessor, typename ValueType>
TypeErrorException(NativeAccessor& accessor, StringData object_type,
Property const& prop, ValueType value)
: std::invalid_argument(util::format("%1.%2 must be of type '%3', got '%4' (%5)",
object_type, prop.name, type_string(prop),
accessor.typeof(value),
accessor.print(value)))
{}
TypeErrorException(const char *name, std::string const& type, std::string const& value)
: std::invalid_argument(util::format("%1 must be of type '%2', got (%3)",
name ? name : "JS value", type, value))
{}
static std::string type_string(Property const& prop);
};
template<typename T>
@ -101,6 +106,8 @@ struct Value {
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
static const char *typeof(ContextType, const ValueType &);
static bool is_array(ContextType, const ValueType &);
static bool is_array_buffer(ContextType, const ValueType &);
static bool is_array_buffer_view(ContextType, const ValueType &);
@ -117,12 +124,17 @@ struct Value {
static bool is_valid(const ValueType &);
static bool is_valid_for_property(ContextType, const ValueType&, const Property&);
static bool is_valid_for_property_type(ContextType, const ValueType&, realm::PropertyType type, StringData object_type);
static ValueType from_boolean(ContextType, bool);
static ValueType from_null(ContextType);
static ValueType from_number(ContextType, double);
static ValueType from_string(ContextType, const String<T> &);
static ValueType from_binary(ContextType, BinaryData);
static ValueType from_string(ContextType ctx, const char *s) { return s ? from_nonnull_string(ctx, s) : from_null(ctx); }
static ValueType from_string(ContextType ctx, StringData s) { return s ? from_nonnull_string(ctx, s) : from_null(ctx); }
static ValueType from_string(ContextType ctx, const std::string& s) { return from_nonnull_string(ctx, s.c_str()); }
static ValueType from_binary(ContextType ctx, BinaryData b) { return b ? from_nonnull_binary(ctx, b) : from_null(ctx); }
static ValueType from_nonnull_string(ContextType, const String<T>&);
static ValueType from_nonnull_binary(ContextType, BinaryData);
static ValueType from_undefined(ContextType);
static ObjectType to_array(ContextType, const ValueType &);
@ -135,6 +147,7 @@ struct Value {
static String<T> to_string(ContextType, const ValueType &);
static OwnedBinaryData to_binary(ContextType, ValueType);
#define VALIDATED(return_t, type) \
static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \
if (!is_##type(ctx, value)) { \
@ -351,82 +364,76 @@ REALM_JS_INLINE void set_internal(const typename T::Object &object, typename Cla
template<typename T>
inline bool Value<T>::is_valid_for_property(ContextType context, const ValueType &value, const Property& prop)
{
if (is_nullable(prop.type) && (is_null(context, value) || is_undefined(context, value))) {
return true;
}
return is_valid_for_property_type(context, value, prop.type, prop.object_type);
}
template<typename T>
inline bool Value<T>::is_valid_for_property_type(ContextType context, const ValueType &value, realm::PropertyType type, StringData object_type) {
using realm::PropertyType;
if (realm::is_array(prop.type)) {
if (prop.type != PropertyType::Object) {
return false;
}
// FIXME: Do we need to validate the types of the contained objects?
if (is_array(context, value)) {
auto check_value = [&](auto&& value) {
if (is_nullable(type) && (is_null(context, value) || is_undefined(context, value))) {
return true;
}
if (is_object(context, value)) {
auto object = to_object(context, value);
return Object<T>::template is_instance<ResultsClass<T>>(context, object)
|| Object<T>::template is_instance<ListClass<T>>(context, object);
switch (type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
return is_number(context, value);
case PropertyType::Bool:
return is_boolean(context, value);
case PropertyType::String:
return is_string(context, value);
case PropertyType::Data:
return is_binary(context, value);
case PropertyType::Date:
return is_date(context, value);
case PropertyType::Object:
return true;
case PropertyType::Any:
return false;
default:
REALM_UNREACHABLE();
}
};
auto check_collection_type = [&](auto&& list) {
auto list_type = list->get_type();
return list_type == type
&& is_nullable(list_type) == is_nullable(type)
&& (type != PropertyType::Object || list->get_object_schema().name == object_type);
};
if (!realm::is_array(type)) {
return check_value(value);
}
if (is_object(context, value)) {
auto object = to_object(context, value);
if (Object<T>::template is_instance<ResultsClass<T>>(context, object)) {
return check_collection_type(get_internal<T, ResultsClass<T>>(object));
}
if (Object<T>::template is_instance<ListClass<T>>(context, object)) {
return check_collection_type(get_internal<T, ListClass<T>>(object));
}
}
if (type == PropertyType::Object) {
// FIXME: Do we need to validate the types of the contained objects?
return is_array(context, value);
}
if (!is_array(context, value)) {
return false;
}
switch (prop.type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
return is_number(context, value);
case PropertyType::Bool:
return is_boolean(context, value);
case PropertyType::String:
return is_string(context, value);
case PropertyType::Data:
return is_binary(context, value);
case PropertyType::Date:
return is_date(context, value);
case PropertyType::Object:
return true;
case PropertyType::Any:
auto array = to_array(context, value);
uint32_t size = Object<T>::validated_get_length(context, array);
for (uint32_t i = 0; i < size; ++i) {
if (!check_value(Object<T>::get_property(context, array, i))) {
return false;
default:
REALM_UNREACHABLE();
}
}
inline std::string js_type_name_for_property_type(realm::PropertyType type)
{
using realm::PropertyType;
if (realm::is_array(type)) {
if (type == PropertyType::LinkingObjects) {
throw std::runtime_error("LinkingObjects' type is not supported");
}
return "array";
}
switch (type & ~PropertyType::Flags) {
case PropertyType::Int:
case PropertyType::Float:
case PropertyType::Double:
return "number";
case PropertyType::Bool:
return "boolean";
case PropertyType::String:
return "string";
case PropertyType::Date:
return "date";
case PropertyType::Data:
return "binary";
case PropertyType::Object:
return "object";
case PropertyType::Any:
throw std::runtime_error("'Any' type is not supported");
default:
REALM_UNREACHABLE();
}
return true;
}
} // js

View File

@ -395,10 +395,10 @@ JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object,
}
template<jsc::ArgumentsMethodType F>
JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
JSValueRef wrap(JSContextRef ctx, JSObjectRef, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
jsc::ReturnValue return_value(ctx);
try {
F(ctx, function, this_object, jsc::Arguments{ctx, argc, arguments}, return_value);
F(ctx, this_object, jsc::Arguments{ctx, argc, arguments}, return_value);
return return_value;
}
catch (std::exception &e) {

View File

@ -38,6 +38,9 @@ class ReturnValue<jsc::Types> {
void set(const std::string &string) {
m_value = JSValueMakeString(m_context, jsc::String(string));
}
void set(const char *string) {
m_value = JSValueMakeString(m_context, jsc::String(string));
}
void set(bool boolean) {
m_value = JSValueMakeBoolean(m_context, boolean);
}

View File

@ -32,7 +32,8 @@ class String<jsc::Types> {
public:
String(const char *s) : m_str(JSStringCreateWithUTF8CString(s)) {}
String(const JSStringRef &s) : m_str(JSStringRetain(s)) {}
String(const std::string &str) : String(str.c_str()) {}
String(StringData str) : String(str.data()) {}
String(const std::string& str) : String(str.c_str()) {}
String(const StringType &o) : String(o.m_str) {}
String(StringType &&o) : m_str(o.m_str) {
o.m_str = nullptr;

View File

@ -47,7 +47,7 @@ bool jsc::Value::is_binary(JSContextRef ctx, const JSValueRef &value)
}
template<>
JSValueRef jsc::Value::from_binary(JSContextRef ctx, BinaryData data)
JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data)
{
static jsc::String s_buffer = "buffer";
static jsc::String s_uint8_array = "Uint8Array";

View File

@ -41,6 +41,18 @@ static inline bool is_object_of_type(JSContextRef ctx, JSValueRef value, jsc::St
return result;
}
template<>
inline const char *jsc::Value::typeof(JSContextRef ctx, const JSValueRef &value) {
switch (JSValueGetType(ctx, value)) {
case kJSTypeNull: return "null";
case kJSTypeNumber: return "number";
case kJSTypeObject: return "object";
case kJSTypeString: return "string";
case kJSTypeBoolean: return "boolean";
case kJSTypeUndefined: return "undefined";
}
}
template<>
inline bool jsc::Value::is_array(JSContextRef ctx, const JSValueRef &value) {
// JSValueIsArray() is not available until iOS 9.
@ -124,7 +136,7 @@ inline JSValueRef jsc::Value::from_number(JSContextRef ctx, double number) {
}
template<>
inline JSValueRef jsc::Value::from_string(JSContextRef ctx, const jsc::String &string) {
inline JSValueRef jsc::Value::from_nonnull_string(JSContextRef ctx, const jsc::String &string) {
return JSValueMakeString(ctx, string);
}
@ -134,26 +146,12 @@ inline JSValueRef jsc::Value::from_undefined(JSContextRef ctx) {
}
template<>
JSValueRef jsc::Value::from_binary(JSContextRef ctx, BinaryData data);
JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data);
template<>
inline bool jsc::Value::to_boolean(JSContextRef ctx, const JSValueRef &value) {
return JSValueToBoolean(ctx, value);
}
template<>
inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;
double number = JSValueToNumber(ctx, value, &exception);
if (exception) {
throw jsc::Exception(ctx, exception);
}
if (isnan(number)) {
throw std::invalid_argument("Value not convertible to a number.");
}
return number;
}
template<>
inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;
@ -168,6 +166,21 @@ inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef &val
return string;
}
template<>
inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;
double number = JSValueToNumber(ctx, value, &exception);
if (exception) {
throw jsc::Exception(ctx, exception);
}
if (isnan(number)) {
throw std::invalid_argument(util::format("Value '%1' not convertible to a number.",
(std::string)to_string(ctx, value)));
}
return number;
}
template<>
inline JSObjectRef jsc::Value::to_object(JSContextRef ctx, const JSValueRef &value) {
JSValueRef exception = nullptr;

View File

@ -317,7 +317,7 @@ void wrap(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto arguments = node::get_arguments(info);
try {
F(isolate, info.Callee(), info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value);
F(isolate, info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value);
}
catch (std::exception &e) {
Nan::ThrowError(node::Exception::value(isolate, e));

View File

@ -41,6 +41,17 @@ class ReturnValue<node::Types> {
m_value.Set(Nan::New(string).ToLocalChecked());
}
}
void set(const char *str) {
if (!str) {
m_value.SetNull();
}
else if (!*str) {
m_value.SetEmptyString();
}
else {
m_value.Set(Nan::New(str).ToLocalChecked());
}
}
void set(bool boolean) {
m_value.Set(boolean);
}

View File

@ -23,6 +23,17 @@
namespace realm {
namespace js {
template<>
inline const char *node::Value::typeof(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
if (value->IsNull()) { return "null"; }
if (value->IsNumber()) { return "number"; }
if (value->IsString()) { return "string"; }
if (value->IsBoolean()) { return "boolean"; }
if (value->IsUndefined()) { return "undefined"; }
if (value->IsObject()) { return "object"; }
return "unknown";
}
template<>
inline bool node::Value::is_array(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
return value->IsArray();
@ -110,12 +121,12 @@ inline v8::Local<v8::Value> node::Value::from_number(v8::Isolate* isolate, doubl
}
template<>
inline v8::Local<v8::Value> node::Value::from_string(v8::Isolate* isolate, const node::String &string) {
inline v8::Local<v8::Value> node::Value::from_nonnull_string(v8::Isolate* isolate, const node::String &string) {
return v8::Local<v8::String>(string);
}
template<>
inline v8::Local<v8::Value> node::Value::from_binary(v8::Isolate* isolate, BinaryData data) {
inline v8::Local<v8::Value> node::Value::from_nonnull_binary(v8::Isolate* isolate, BinaryData data) {
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, data.size());
v8::ArrayBuffer::Contents contents = buffer->GetContents();
@ -137,17 +148,18 @@ inline bool node::Value::to_boolean(v8::Isolate* isolate, const v8::Local<v8::Va
}
template<>
inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
double number = Nan::To<double>(value).FromMaybe(NAN);
if (std::isnan(number)) {
throw std::invalid_argument("Value not convertible to a number.");
}
return number;
inline node::String node::Value::to_string(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
return value->ToString();
}
template<>
inline node::String node::Value::to_string(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
return value->ToString();
inline double node::Value::to_number(v8::Isolate* isolate, const v8::Local<v8::Value> &value) {
double number = Nan::To<double>(value).FromMaybe(NAN);
if (std::isnan(number)) {
throw std::invalid_argument(util::format("Value '%1' not convertible to a number.",
(std::string)to_string(isolate, value)));
}
return number;
}
template<>

@ -1 +1 @@
Subproject commit 30b8a7853b17762719aa7671aac6ebea04473e33
Subproject commit 49705c0fbdbff8536cfa134fc17b837db4385d31

View File

@ -21,14 +21,27 @@
module.exports = {
assertSimilar: function(type, val1, val2, errorMessage, depth) {
depth = depth || 0;
if (type == 'float' || type == 'double') {
this.assertEqualWithTolerance(val1, val2, errorMessage, depth + 1);
this.assertDefined(type, depth + 1);
type = type.replace('?', '');
if (val2 === null) {
this.assertNull(val1, errorMessage, depth + 1);
}
else if (type == 'data') {
else if (type === 'float' || type === 'double') {
this.assertEqualWithTolerance(val1, val2, 0.000001, errorMessage, depth + 1);
}
else if (type === 'data') {
this.assertArraysEqual(new Uint8Array(val1), val2, errorMessage, depth + 1);
}
else if (type == 'date') {
this.assertEqual(val1.getTime(), val2.getTime(), errorMessage, depth + 1);
else if (type === 'date') {
this.assertEqual(val1 && val1.getTime(), val2.getTime(), errorMessage, depth + 1);
}
else if (type === 'object') {
for (const key of Object.keys(val1)) {
this.assertEqual(val1[key], val2[key], errorMessage, depth + 1);
}
}
else if (type === 'list') {
this.assertArraysEqual(val1, val2, errorMessage, depth + 1);
}
else {
this.assertEqual(val1, val2, errorMessage, depth + 1);
@ -79,6 +92,8 @@ module.exports = {
},
assertArraysEqual: function(val1, val2, errorMessage, depth) {
this.assertDefined(val1, `val1 should be non-null but is ${val1}`, 1 + (depth || 0));
this.assertDefined(val2, `val2 should be non-null but is ${val2}`, 1 + (depth || 0));
const len1 = val1.length;
const len2 = val2.length;
@ -90,8 +105,25 @@ module.exports = {
throw new TestFailureError(message, depth);
}
let compare;
if (val1.type === "data") {
compare = (i, a, b) => a === b || this.assertArraysEqual(new Uint8Array(a), b, `Data elements at index ${i}`, 1) || true;
}
else if (val1.type === "date") {
compare = (i, a, b) => (a && a.getTime()) === (b && b.getTime());
}
else if (val1.type === "float" || val1.type === "double") {
compare = (i, a, b) => a >= b - 0.000001 && a <= b + 0.000001;
}
else if (val1.type === 'object') {
compare = (i, a, b) => Object.keys(a).every(key => a[key] === b[key]);
}
else {
compare = (i, a, b) => a === b;
}
for (let i = 0; i < len1; i++) {
if (val1[i] !== val2[i]) {
if (!compare(i, val1[i], val2[i])) {
let message = `Array contents not equal at index ${i} (${val1[i]} != ${val2[i]})`;
if (errorMessage) {
message = `${errorMessage} - ${message}`;
@ -136,7 +168,7 @@ module.exports = {
},
assertThrowsContaining: function(func, expectedMessage, depth) {
var caught = false;
let caught = false;
try {
func();
}
@ -158,6 +190,12 @@ module.exports = {
}
},
assertFalse: function(condition, errorMessage, depth) {
if (condition) {
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be false`, depth);
}
},
assertInstanceOf: function(object, type, errorMessage, depth) {
if (!(object instanceof type)) {
throw new TestFailureError(errorMessage || `Object ${object} expected to be of type ${type}`, depth);
@ -168,6 +206,12 @@ module.exports = {
this.assertEqual(typeof value, type, `Value ${value} expected to be of type ${type}`, 1 + depth || 0);
},
assertDefined: function(value, errorMessage, depth) {
if (value === undefined || value === null) {
throw new TestFailureError(errorMessage || `Value ${value} expected to be non-null`, depth);
}
},
assertUndefined: function(value, errorMessage, depth) {
if (value !== undefined) {
throw new TestFailureError(errorMessage || `Value ${value} expected to be undefined`, depth);

View File

@ -18,34 +18,78 @@
'use strict';
var Realm = require('realm');
var TestCase = require('./asserts');
var schemas = require('./schemas');
const Realm = require('realm');
let TestCase = require('./asserts');
let schemas = require('./schemas');
const DATA1 = new Uint8Array([0x01]);
const DATA2 = new Uint8Array([0x02]);
const DATA3 = new Uint8Array([0x03]);
const DATE1 = new Date(1);
const DATE2 = new Date(2);
const DATE3 = new Date(3);
module.exports = {
testListConstructor: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
realm.write(function() {
var obj = realm.create('PersonList', {list: []});
TestCase.assertTrue(obj.list instanceof Realm.List);
TestCase.assertTrue(obj.list instanceof Realm.Collection);
realm.write(() => {
let obj = realm.create('PersonList', {list: []});
TestCase.assertInstanceOf(obj.list, Realm.List);
TestCase.assertInstanceOf(obj.list, Realm.Collection);
});
TestCase.assertThrows(function() {
new Realm.List();
TestCase.assertThrowsContaining(() => new Realm.List(), 'constructor');
TestCase.assertType(Realm.List, 'function');
TestCase.assertInstanceOf(Realm.List, Function);
},
testListType: function() {
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject, schemas.PrimitiveArrays]});
let obj, prim;
realm.write(() => {
obj = realm.create('LinkTypesObject', {});
prim = realm.create('PrimitiveArrays', {});
});
TestCase.assertEqual(typeof Realm.List, 'function');
TestCase.assertTrue(Realm.List instanceof Function);
TestCase.assertEqual(obj.arrayCol.type, 'object');
TestCase.assertEqual(obj.arrayCol1.type, 'object');
TestCase.assertEqual(prim.bool.type, 'bool');
TestCase.assertEqual(prim.int.type, 'int');
TestCase.assertEqual(prim.float.type, 'float');
TestCase.assertEqual(prim.double.type, 'double');
TestCase.assertEqual(prim.string.type, 'string');
TestCase.assertEqual(prim.date.type, 'date');
TestCase.assertEqual(prim.optBool.type, 'bool');
TestCase.assertEqual(prim.optInt.type, 'int');
TestCase.assertEqual(prim.optFloat.type, 'float');
TestCase.assertEqual(prim.optDouble.type, 'double');
TestCase.assertEqual(prim.optString.type, 'string');
TestCase.assertEqual(prim.optDate.type, 'date');
TestCase.assertFalse(prim.bool.optional);
TestCase.assertFalse(prim.int.optional);
TestCase.assertFalse(prim.float.optional);
TestCase.assertFalse(prim.double.optional);
TestCase.assertFalse(prim.string.optional);
TestCase.assertFalse(prim.date.optional);
TestCase.assertTrue(prim.optBool.optional);
TestCase.assertTrue(prim.optInt.optional);
TestCase.assertTrue(prim.optFloat.optional);
TestCase.assertTrue(prim.optDouble.optional);
TestCase.assertTrue(prim.optString.optional);
TestCase.assertTrue(prim.optDate.optional);
},
testListLength: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}],
@ -60,94 +104,286 @@ module.exports = {
obj.arrayCol = [{doubleCol: 1}, {doubleCol: 2}];
TestCase.assertEqual(array.length, 2);
TestCase.assertThrows(function() {
array.length = 0;
}, 'cannot set length property on lists');
TestCase.assertThrowsContaining(() => array.length = 0,
"Cannot assign to read only property 'length'");
});
TestCase.assertEqual(array.length, 2);
},
testListSubscriptGetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject, schemas.PrimitiveArrays]});
let obj, prim;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
arrayCol1: [{doubleCol: 5}, {doubleCol: 6}],
});
prim = realm.create('PrimitiveArrays', {
bool: [true, false],
int: [1, 2],
float: [1.1, 2.2],
double: [1.11, 2.22],
string: ['a', 'b'],
date: [new Date(1), new Date(2)],
data: [DATA1, DATA2],
array = obj.arrayCol;
optBool: [true, null],
optInt: [1, null],
optFloat: [1.1, null],
optDouble: [1.11, null],
optString: ['a', null],
optDate: [new Date(1), null],
optData: [DATA1, null],
});
});
TestCase.assertEqual(array[0].doubleCol, 3);
TestCase.assertEqual(array[1].doubleCol, 4);
TestCase.assertEqual(array[2], undefined);
TestCase.assertEqual(array[-1], undefined);
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 3);
TestCase.assertEqual(obj.arrayCol[1].doubleCol, 4);
TestCase.assertEqual(obj.arrayCol[2], undefined);
TestCase.assertEqual(obj.arrayCol[-1], undefined);
TestCase.assertEqual(obj.arrayCol['foo'], undefined);
TestCase.assertEqual(obj.arrayCol1[0].doubleCol, 5);
TestCase.assertEqual(obj.arrayCol1[1].doubleCol, 6);
TestCase.assertEqual(obj.arrayCol1[2], undefined);
TestCase.assertEqual(obj.arrayCol1[-1], undefined);
TestCase.assertEqual(obj.arrayCol1['foo'], undefined);
for (let field of Object.keys(prim)) {
TestCase.assertEqual(prim[field][2], undefined);
TestCase.assertEqual(prim[field][-1], undefined);
TestCase.assertEqual(prim[field]['foo'], undefined);
if (field.includes('opt')) {
TestCase.assertEqual(prim[field][1], null);
}
}
TestCase.assertSimilar('bool', prim.bool[0], true);
TestCase.assertSimilar('bool', prim.bool[1], false);
TestCase.assertSimilar('int', prim.int[0], 1);
TestCase.assertSimilar('int', prim.int[1], 2);
TestCase.assertSimilar('float', prim.float[0], 1.1);
TestCase.assertSimilar('float', prim.float[1], 2.2);
TestCase.assertSimilar('double', prim.double[0], 1.11);
TestCase.assertSimilar('double', prim.double[1], 2.22);
TestCase.assertSimilar('string', prim.string[0], 'a');
TestCase.assertSimilar('string', prim.string[1], 'b');
TestCase.assertSimilar('data', prim.data[0], DATA1);
TestCase.assertSimilar('data', prim.data[1], DATA2);
TestCase.assertSimilar('date', prim.date[0], new Date(1));
TestCase.assertSimilar('date', prim.date[1], new Date(2));
TestCase.assertSimilar('bool', prim.optBool[0], true);
TestCase.assertSimilar('int', prim.optInt[0], 1);
TestCase.assertSimilar('float', prim.optFloat[0], 1.1);
TestCase.assertSimilar('double', prim.optDouble[0], 1.11);
TestCase.assertSimilar('string', prim.optString[0], 'a');
TestCase.assertSimilar('data', prim.optData[0], DATA1);
TestCase.assertSimilar('date', prim.optDate[0], new Date(1));
},
testListSubscriptSetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject,
schemas.PrimitiveArrays]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
});
let prim = realm.create('PrimitiveArrays', {});
array = obj.arrayCol;
array[0] = {doubleCol: 5};
array[1] = {doubleCol: 6};
TestCase.assertEqual(array[0].doubleCol, 5);
TestCase.assertEqual(array[1].doubleCol, 6);
array[0] = obj.objectCol;
array[1] = obj.objectCol1;
TestCase.assertEqual(array[0].doubleCol, 1);
TestCase.assertEqual(array[1].doubleCol, 2);
TestCase.assertThrows(function() {
array[2] = {doubleCol: 1};
}, 'cannot set list item beyond its bounds');
TestCase.assertThrowsContaining(() => array[0] = null,
"JS value must be of type 'object', got (null)");
TestCase.assertThrowsContaining(() => array[0] = {},
"Missing value for property 'TestObject.doubleCol'");
TestCase.assertThrowsContaining(() => array[0] = {foo: 'bar'},
"Missing value for property 'TestObject.doubleCol'");
TestCase.assertThrowsContaining(() => array[0] = prim,
"Object of type (PrimitiveArrays) does not match List type (TestObject)");
TestCase.assertThrowsContaining(() => array[0] = array,
"Missing value for property 'TestObject.doubleCol'");
TestCase.assertThrowsContaining(() => array[2] = {doubleCol: 1},
"Requested index 2 greater than max 1");
TestCase.assertThrowsContaining(() => array[-1] = {doubleCol: 1},
"Index -1 cannot be less than zero.");
TestCase.assertThrows(function() {
array[-1] = {doubleCol: 1};
}, 'cannot set list item with negative index');
array['foo'] = 'bar';
TestCase.assertEqual(array.foo, 'bar');
function testAssign(name, v1, v2) {
prim[name].push(v1);
TestCase.assertSimilar(prim[name].type, prim[name][0], v1, undefined, 1);
prim[name][0] = v2;
TestCase.assertSimilar(prim[name].type, prim[name][0], v2, undefined, 1);
}
testAssign('bool', true, false);
testAssign('int', 1, 2);
testAssign('float', 1.1, 2.2);
testAssign('double', 1.1, 2.2);
testAssign('string', 'a', 'b');
testAssign('data', DATA1, DATA2);
testAssign('date', DATE1, DATE2);
function testAssignNull(name, expected) {
TestCase.assertThrowsContaining(() => prim[name][0] = null,
`Property must be of type '${expected}', got (null)`,
undefined, 1);
}
testAssignNull('bool', 'bool');
testAssignNull('int', 'int');
testAssignNull('float', 'float');
testAssignNull('double', 'double');
testAssignNull('string', 'string');
testAssignNull('data', 'data');
testAssignNull('date', 'date');
testAssign('optBool', true, null);
testAssign('optInt', 1, null);
testAssign('optFloat', 1.1, null);
testAssign('optDouble', 1.1, null);
testAssign('optString', 'a', null);
testAssign('optData', DATA1, null);
testAssign('optDate', DATE1, null);
});
TestCase.assertThrows(function() {
array[0] = {doubleCol: 1};
}, 'cannot set list item outside write transaction');
TestCase.assertThrowsContaining(() => array[0] = {doubleCol: 1},
"Cannot modify managed objects outside of a write transaction.");
array['foo'] = 'baz';
TestCase.assertEqual(array.foo, 'baz');
},
testListInvalidProperty: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
testListAssignment: function() {
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject,
schemas.PersonList, schemas.PersonObject,
schemas.PrimitiveArrays]});
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
});
let obj, prim;
realm.write(() => {
obj = realm.create('LinkTypesObject', {});
prim = realm.create('PrimitiveArrays', {});
let person = realm.create('PersonObject', {name: 'a', age: 2.0});
let personList = realm.create('PersonList', {list: [person]}).list;
array = obj.arrayCol;
TestCase.assertThrowsContaining(() => obj.arrayCol = [0],
"JS value must be of type 'object', got (0)");
TestCase.assertThrowsContaining(() => obj.arrayCol = [null],
"JS value must be of type 'object', got (null)");
TestCase.assertThrowsContaining(() => obj.arrayCol = [person],
"Object of type (PersonObject) does not match List type (TestObject)");
TestCase.assertThrowsContaining(() => obj.arrayCol = personList,
"LinkTypesObject.arrayCol must be of type 'TestObject[]', got 'object' (a)");
obj.arrayCol = [realm.create('TestObject', {doubleCol: 1.0})]
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 1.0);
obj.arrayCol = obj.arrayCol;
TestCase.assertEqual(obj.arrayCol[0].doubleCol, 1.0);
TestCase.assertThrowsContaining(() => prim.bool = [person],
"PrimitiveArrays.bool must be of type 'boolean[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.int = [person],
"PrimitiveArrays.int must be of type 'number[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.float = [person],
"PrimitiveArrays.float must be of type 'number[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.double = [person],
"PrimitiveArrays.double must be of type 'number[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.string = [person],
"PrimitiveArrays.string must be of type 'string[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.data = [person],
"PrimitiveArrays.data must be of type 'binary[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.date = [person],
"PrimitiveArrays.date must be of type 'date[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optBool = [person],
"PrimitiveArrays.optBool must be of type 'boolean?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optInt = [person],
"PrimitiveArrays.optInt must be of type 'number?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optFloat = [person],
"PrimitiveArrays.optFloat must be of type 'number?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optDouble = [person],
"PrimitiveArrays.optDouble must be of type 'number?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optString = [person],
"PrimitiveArrays.optString must be of type 'string?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optData = [person],
"PrimitiveArrays.optData must be of type 'binary?[]', got 'object' ([PersonObject{");
TestCase.assertThrowsContaining(() => prim.optDate = [person],
"PrimitiveArrays.optDate must be of type 'date?[]', got 'object' ([PersonObject{");
function testAssign(name, value) {
prim[name] = [value];
TestCase.assertSimilar(prim[name].type, prim[name][0], value, undefined, 1);
}
testAssign('bool', true);
testAssign('int', 1);
testAssign('float', 1.1);
testAssign('double', 1.1);
testAssign('string', 'a');
testAssign('data', DATA1);
testAssign('date', DATE1);
function testAssignNull(name, expected) {
TestCase.assertThrowsContaining(() => prim[name] = [null],
`PrimitiveArrays.${name} must be of type '${expected}[]', got 'object' ([null])`,
undefined, 1);
TestCase.assertEqual(prim[name].length, 1,
"List should not have been cleared by invalid assignment", 1);
}
testAssignNull('bool', 'boolean');
testAssignNull('int', 'number');
testAssignNull('float', 'number');
testAssignNull('double', 'number');
testAssignNull('string', 'string');
testAssignNull('data', 'binary');
testAssignNull('date', 'date');
testAssign('optBool', true);
testAssign('optInt', 1);
testAssign('optFloat', 1.1);
testAssign('optDouble', 1.1);
testAssign('optString', 'a');
testAssign('optData', DATA1);
testAssign('optDate', DATE1);
testAssign('optBool', null);
testAssign('optInt', null);
testAssign('optFloat', null);
testAssign('optDouble', null);
testAssign('optString', null);
testAssign('optData', null);
testAssign('optDate', null);
});
TestCase.assertEqual(undefined, array.ablasdf);
TestCase.assertThrowsContaining(() => obj.arrayCol = [],
"Cannot modify managed objects outside of a write transaction.");
TestCase.assertThrowsContaining(() => prim.bool = [],
"Cannot modify managed objects outside of a write transaction.");
},
testListEnumerate: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var obj;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let obj;
realm.write(function() {
realm.write(() => {
obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
@ -155,19 +391,18 @@ module.exports = {
});
});
var index;
for (index in obj.arrayCol) {
for (const index in obj.arrayCol) {
TestCase.assertTrue(false, "No objects should have been enumerated: " + index);
}
realm.write(function() {
realm.write(() => {
obj.arrayCol = [{doubleCol: 0}, {doubleCol: 1}];
TestCase.assertEqual(obj.arrayCol.length, 2);
});
TestCase.assertEqual(obj.arrayCol.length, 2);
var count = 0;
var keys = Object.keys(obj.arrayCol);
for (index in obj.arrayCol) {
let count = 0;
let keys = Object.keys(obj.arrayCol);
for (const index in obj.arrayCol) {
TestCase.assertEqual(count++, +index);
TestCase.assertEqual(keys[index], index);
}
@ -177,11 +412,11 @@ module.exports = {
},
testListPush: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}],
@ -199,23 +434,22 @@ module.exports = {
TestCase.assertEqual(array[2].doubleCol, 1);
TestCase.assertEqual(array[3].doubleCol, 2);
TestCase.assertThrows(function() {
array.push();
});
TestCase.assertEqual(array.push(), 4);
TestCase.assertEqual(array.length, 4);
});
TestCase.assertEqual(array.length, 4);
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.push([1]);
}, 'can only push in a write transaction');
}, "Cannot modify managed objects outside of a write transaction.");
},
testListPop: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
@ -228,22 +462,19 @@ module.exports = {
TestCase.assertEqual(array.pop(), undefined);
TestCase.assertThrows(function() {
array.pop(1);
});
TestCase.assertThrowsContaining(() => array.pop(1), 'Invalid argument');
});
TestCase.assertThrows(function() {
array.pop();
}, 'can only pop in a write transaction');
TestCase.assertThrowsContaining(() => array.pop(),
"Cannot modify managed objects outside of a write transaction.");
},
testListUnshift: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}],
@ -260,20 +491,22 @@ module.exports = {
TestCase.assertEqual(array.length, 4);
TestCase.assertEqual(array[0].doubleCol, 1);
TestCase.assertEqual(array[1].doubleCol, 2);
TestCase.assertEqual(array.unshift(), 4);
TestCase.assertEqual(array.length, 4);
});
TestCase.assertEqual(array.length, 4);
TestCase.assertThrows(function() {
array.unshift({doubleCol: 1});
}, 'can only unshift in a write transaction');
TestCase.assertThrowsContaining(() => array.unshift({doubleCol: 1}),
'Cannot modify managed objects outside of a write transaction.');
},
testListShift: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
@ -286,29 +519,27 @@ module.exports = {
TestCase.assertEqual(array.shift(), undefined);
TestCase.assertThrows(function() {
array.shift(1);
});
TestCase.assertThrowsContaining(() => array.shift(1), 'Invalid argument');
});
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.shift();
}, 'can only shift in a write transaction');
}, "Cannot modify managed objects outside of a write transaction.");
},
testListSplice: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
});
array = obj.arrayCol;
var removed;
let removed;
removed = array.splice(0, 0, obj.objectCol, obj.objectCol1);
TestCase.assertEqual(removed.length, 0);
@ -355,26 +586,26 @@ module.exports = {
TestCase.assertEqual(removed.length, 1);
TestCase.assertEqual(array.length, 0);
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.splice('cat', 1);
});
}, "Value 'cat' not convertible to a number");
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.splice(0, 0, 0);
});
}, "JS value must be of type 'object', got (0)");
});
TestCase.assertThrows(function() {
TestCase.assertThrowsContaining(() => {
array.splice(0, 0, {doubleCol: 1});
}, 'can only splice in a write transaction');
}, "Cannot modify managed objects outside of a write transaction");
},
testListDeletions: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var object;
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let object;
let array;
realm.write(function() {
realm.write(() => {
object = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
@ -385,7 +616,7 @@ module.exports = {
});
try {
realm.write(function() {
realm.write(() => {
realm.delete(array[0]);
TestCase.assertEqual(array.length, 1);
TestCase.assertEqual(array[0].doubleCol, 4);
@ -398,22 +629,20 @@ module.exports = {
TestCase.assertEqual(array.length, 2);
TestCase.assertEqual(array[0].doubleCol, 3);
realm.write(function() {
realm.write(() => {
realm.delete(object);
});
TestCase.assertThrows(function() {
array[0];
});
TestCase.assertThrowsContaining(() => array[0], 'invalidated');
},
testLiveUpdatingResults: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let objects = realm.objects('TestObject');
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', {
realm.write(() => {
let obj = realm.create('LinkTypesObject', {
objectCol: {doubleCol: 1},
objectCol1: {doubleCol: 2},
arrayCol: [{doubleCol: 3}, {doubleCol: 4}],
@ -426,7 +655,7 @@ module.exports = {
TestCase.assertEqual(objects.length, 4);
try {
realm.write(function() {
realm.write(() => {
array.push({doubleCol: 5});
TestCase.assertEqual(objects.length, 5);
@ -449,50 +678,50 @@ module.exports = {
},
testListSnapshot: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var array;
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
let objects = realm.objects('TestObject');
let array;
realm.write(function() {
var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]);
realm.write(() => {
let obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]], [[5], [6]]]);
array = obj.arrayCol;
});
var objectsCopy = objects.snapshot();
var arrayCopy = array.snapshot();
let objectsCopy = objects.snapshot();
let arrayCopy = array.snapshot();
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
realm.write(function() {
realm.write(() => {
array.push([5]);
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
TestCase.assertEqual(objectsCopy.snapshot().length, 4);
TestCase.assertEqual(objectsCopy.snapshot().length, 6);
TestCase.assertEqual(arrayCopy.snapshot().length, 2);
TestCase.assertEqual(objects.snapshot().length, 5);
TestCase.assertEqual(objects.snapshot().length, 7);
TestCase.assertEqual(array.snapshot().length, 3);
realm.delete(array[0]);
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
TestCase.assertEqual(arrayCopy[0], null);
realm.deleteAll();
TestCase.assertEqual(objectsCopy.length, 4);
TestCase.assertEqual(objectsCopy.length, 6);
TestCase.assertEqual(arrayCopy.length, 2);
TestCase.assertEqual(arrayCopy[1], null);
});
},
testListFiltered: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
var list;
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
let list;
realm.write(function() {
var object = realm.create('PersonList', {list: [
realm.write(() => {
let object = realm.create('PersonList', {list: [
{name: 'Ari', age: 10},
{name: 'Tim', age: 11},
{name: 'Bjarne', age: 12},
@ -515,10 +744,11 @@ module.exports = {
{name: 'Target', properties: {value: 'int'}},
{name: 'Mid', properties: {value: 'int', link: 'Target'}},
{name: 'List', properties: {list: {type: 'list', objectType: 'Mid'}}},
schemas.PrimitiveArrays
];
const realm = new Realm({schema: schema});
let list;
let list, prim;
realm.write(() => {
list = realm.create('List', {list: [
{value: 3, link: {value: 1}},
@ -528,13 +758,31 @@ module.exports = {
realm.create('List', {list: [
{value: 4, link: {value: 4}},
]});
prim = realm.create('PrimitiveArrays', {
bool: [true, false],
int: [3, 1, 2],
float: [3, 1, 2],
double: [3, 1, 2],
string: ['c', 'a', 'b'],
data: [DATA3, DATA1, DATA2],
date: [DATE3, DATE1, DATE2],
optBool: [true, false, null],
optInt: [3, 1, 2, null],
optFloat: [3, 1, 2, null],
optDouble: [3, 1, 2, null],
optString: ['c', 'a', 'b', null],
optData: [DATA3, DATA1, DATA2, null],
optDate: [DATE3, DATE1, DATE2, null],
});
});
const values = (results) => results.map((o) => o.value);
TestCase.assertThrows(() => list.sorted());
TestCase.assertThrows(() => list.sorted('nonexistent property'));
TestCase.assertThrows(() => list.sorted('link'));
// TestCase.assertThrowsContaining(() => list.sorted());
TestCase.assertThrowsContaining(() => list.sorted('nonexistent property'),
"Cannot sort on key path 'nonexistent property': property 'Mid.nonexistent property' does not exist.");
TestCase.assertThrowsContaining(() => list.sorted('link'),
"Cannot sort on key path 'link': property 'Mid.link' of type 'object' cannot be the final property in the key path.");
TestCase.assertArraysEqual(values(list.sorted([])), [3, 1, 2]);
@ -551,24 +799,50 @@ module.exports = {
TestCase.assertArraysEqual(values(list.sorted(['link.value'])), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted([['link.value', false]])), [3, 2, 1]);
TestCase.assertArraysEqual(values(list.sorted([['link.value', true]])), [1, 2, 3]);
TestCase.assertThrowsContaining(() => prim.int.sorted('value', true),
"Cannot sort on key path 'value': arrays of 'int' can only be sorted on 'self'");
TestCase.assertThrowsContaining(() => prim.int.sorted('!ARRAY_VALUE', true),
"Cannot sort on key path '!ARRAY_VALUE': arrays of 'int' can only be sorted on 'self'");
TestCase.assertArraysEqual(prim.int.sorted([]), [3, 1, 2]);
TestCase.assertArraysEqual(prim.int.sorted(), [1, 2, 3]);
TestCase.assertArraysEqual(prim.int.sorted(false), [1, 2, 3]);
TestCase.assertArraysEqual(prim.int.sorted(true), [3, 2, 1]);
TestCase.assertArraysEqual(prim.optInt.sorted([]), [3, 1, 2, null]);
TestCase.assertArraysEqual(prim.optInt.sorted(), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optInt.sorted(false), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optInt.sorted(true), [3, 2, 1, null]);
TestCase.assertArraysEqual(prim.bool.sorted(), [false, true]);
TestCase.assertArraysEqual(prim.float.sorted(), [1, 2, 3]);
TestCase.assertArraysEqual(prim.double.sorted(), [1, 2, 3]);
TestCase.assertArraysEqual(prim.string.sorted(), ['a', 'b', 'c']);
TestCase.assertArraysEqual(prim.data.sorted(), [DATA1, DATA2, DATA3]);
TestCase.assertArraysEqual(prim.date.sorted(), [DATE1, DATE2, DATE3]);
TestCase.assertArraysEqual(prim.optBool.sorted(), [null, false, true]);
TestCase.assertArraysEqual(prim.optFloat.sorted(), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optDouble.sorted(), [null, 1, 2, 3]);
TestCase.assertArraysEqual(prim.optString.sorted(), [null, 'a', 'b', 'c']);
TestCase.assertArraysEqual(prim.optData.sorted(), [null, DATA1, DATA2, DATA3]);
TestCase.assertArraysEqual(prim.optDate.sorted(), [null, DATE1, DATE2, DATE3]);
},
testArrayMethods: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
var object;
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList, schemas.PrimitiveArrays]});
let object, prim;
realm.write(function() {
realm.write(() => {
object = realm.create('PersonList', {list: [
{name: 'Ari', age: 10},
{name: 'Tim', age: 11},
{name: 'Bjarne', age: 12},
]});
prim = realm.create('PrimitiveArrays', {int: [10, 11, 12]});
});
[
object.list,
realm.objects('PersonObject'),
].forEach(function(list) {
for (const list of [object.list, realm.objects('PersonObject')]) {
TestCase.assertEqual(list.slice().length, 3);
TestCase.assertEqual(list.slice(-1).length, 1);
TestCase.assertEqual(list.slice(-1)[0].age, 12);
@ -581,43 +855,43 @@ module.exports = {
TestCase.assertEqual(list.join(' '), 'Ari Tim Bjarne');
}
var count = 0;
list.forEach(function(p, i) {
let count = 0;
list.forEach((p, i) => {
TestCase.assertEqual(p.name, list[i].name);
count++;
});
TestCase.assertEqual(count, list.length);
TestCase.assertArraysEqual(list.map(function(p) {return p.age}), [10, 11, 12]);
TestCase.assertTrue(list.some(function(p) {return p.age > 10}));
TestCase.assertTrue(list.every(function(p) {return p.age > 0}));
TestCase.assertArraysEqual(list.map(p => p.age), [10, 11, 12]);
TestCase.assertTrue(list.some(p => p.age > 10));
TestCase.assertTrue(list.every(p => p.age > 0));
var person = list.find(function(p) {return p.name == 'Tim'});
let person = list.find(p => p.name == 'Tim');
TestCase.assertEqual(person.name, 'Tim');
var index = list.findIndex(function(p) {return p.name == 'Tim'});
let index = list.findIndex(p => p.name == 'Tim');
TestCase.assertEqual(index, 1);
TestCase.assertEqual(list.indexOf(list[index]), index);
TestCase.assertEqual(list.reduce(function(n, p) {return n + p.age}, 0), 33);
TestCase.assertEqual(list.reduceRight(function(n, p) {return n + p.age}, 0), 33);
TestCase.assertEqual(list.reduce((n, p) => n + p.age, 0), 33);
TestCase.assertEqual(list.reduceRight((n, p) => n + p.age, 0), 33);
// eslint-disable-next-line no-undef
var iteratorMethodNames = ['entries', 'keys', 'values'];
let iteratorMethodNames = ['entries', 'keys', 'values'];
iteratorMethodNames.push(Symbol.iterator);
iteratorMethodNames.forEach(function(methodName) {
var iterator = list[methodName]();
var count = 0;
var result;
iteratorMethodNames.forEach(methodName => {
let iterator = list[methodName]();
let count = 0;
let result;
// This iterator should itself be iterable.
// TestCase.assertEqual(iterator[iteratorSymbol](), iterator);
TestCase.assertEqual(iterator[Symbol.iterator](), iterator);
while ((result = iterator.next()) && !result.done) {
var value = result.value;
let value = result.value;
switch (methodName) {
case 'entries':
@ -640,14 +914,83 @@ module.exports = {
TestCase.assertEqual(result.value, undefined);
TestCase.assertEqual(count, list.length);
});
}
const list = prim.int;
TestCase.assertEqual(list.slice().length, 3);
TestCase.assertEqual(list.slice(-1).length, 1);
TestCase.assertEqual(list.slice(-1)[0], 12);
TestCase.assertEqual(list.slice(1, 3).length, 2);
TestCase.assertEqual(list.slice(1, 3)[1], 12);
TestCase.assertEqual(list.join(' '), '10 11 12');
let count = 0;
list.forEach((v, i) => {
TestCase.assertEqual(v, i + 10);
count++;
});
TestCase.assertEqual(count, list.length);
TestCase.assertArraysEqual(list.map(p => p + 1), [11, 12, 13]);
TestCase.assertTrue(list.some(p => p > 10));
TestCase.assertTrue(list.every(p => p > 0));
let value = list.find(p => p == 11);
TestCase.assertEqual(value, 11)
let index = list.findIndex(p => p == 11);
TestCase.assertEqual(index, 1);
TestCase.assertEqual(list.indexOf(list[index]), index);
TestCase.assertEqual(list.reduce((n, p) => n + p, 0), 33);
TestCase.assertEqual(list.reduceRight((n, p) => n + p, 0), 33);
// eslint-disable-next-line no-undef
let iteratorMethodNames = ['entries', 'keys', 'values'];
iteratorMethodNames.push(Symbol.iterator);
iteratorMethodNames.forEach(methodName => {
let iterator = list[methodName]();
let count = 0;
let result;
// This iterator should itself be iterable.
// TestCase.assertEqual(iterator[iteratorSymbol](), iterator);
TestCase.assertEqual(iterator[Symbol.iterator](), iterator);
while ((result = iterator.next()) && !result.done) {
let value = result.value;
switch (methodName) {
case 'entries':
TestCase.assertEqual(value.length, 2);
TestCase.assertEqual(value[0], count);
TestCase.assertEqual(value[1], list[count]);
break;
case 'keys':
TestCase.assertEqual(value, count);
break;
default:
TestCase.assertEqual(value.name, list[count].name);
break;
}
count++;
}
TestCase.assertEqual(result.done, true);
TestCase.assertEqual(result.value, undefined);
TestCase.assertEqual(count, list.length);
});
},
testIsValid: function() {
var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
var object;
var list;
realm.write(function() {
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
let object;
let list;
realm.write(() => {
object = realm.create('PersonList', {list: [
{name: 'Ari', age: 10},
{name: 'Tim', age: 11},
@ -659,8 +1002,6 @@ module.exports = {
});
TestCase.assertEqual(list.isValid(), false);
TestCase.assertThrows(function() {
list.length;
});
TestCase.assertThrowsContaining(() => list.length, 'invalidated');
},
};

View File

@ -88,8 +88,8 @@ module.exports = {
renamed: 'string',
prop1: 'int',
}
}],
schemaVersion: 1,
}],
schemaVersion: 1,
migration: function(oldRealm, newRealm) {
var oldObjects = oldRealm.objects('TestObject');
var newObjects = newRealm.objects('TestObject');
@ -136,8 +136,8 @@ module.exports = {
renamed: 'string',
prop1: 'int',
}
}],
schemaVersion: 1,
}],
schemaVersion: 1,
migration: function(oldRealm, newRealm) {
var oldSchema = oldRealm.schema;
var newSchema = newRealm.schema;
@ -324,4 +324,44 @@ module.exports = {
realm.close();
},
testMigrateToListOfInts: function() {
let realm = new Realm({schema: [{name: 'TestObject', properties: {values: 'IntObject[]'}},
{name: 'IntObject', properties: {value: 'int'}}]});
realm.write(function() {
realm.create('TestObject', {values: [{value: 1}, {value: 2}, {value: 3}]});
realm.create('TestObject', {values: [{value: 1}, {value: 4}, {value: 5}]});
});
realm.close();
realm = new Realm({
schema: [{name: 'TestObject', properties: {values: 'int[]'}}],
schemaVersion: 1,
migration: function(oldRealm, newRealm) {
const oldObjects = oldRealm.objects('TestObject');
const newObjects = newRealm.objects('TestObject');
TestCase.assertEqual(oldObjects.length, 2);
TestCase.assertEqual(newObjects.length, 2);
for (let i = 0; i < oldObjects.length; ++i) {
TestCase.assertEqual(oldObjects[i].values.length, 3);
TestCase.assertEqual(newObjects[i].values.length, 0);
newObjects[i].values = oldObjects[i].values.map(o => o.value);
TestCase.assertEqual(newObjects[i].values.length, 3);
}
newRealm.deleteModel('IntObject');
}
});
const objects = realm.objects('TestObject');
TestCase.assertEqual(objects.length, 2);
TestCase.assertEqual(objects[0].values.length, 3);
TestCase.assertEqual(objects[1].values.length, 3);
TestCase.assertEqual(objects[0].values[0], 1);
TestCase.assertEqual(objects[0].values[1], 2);
TestCase.assertEqual(objects[0].values[2], 3);
TestCase.assertEqual(objects[1].values[0], 1);
TestCase.assertEqual(objects[1].values[1], 4);
TestCase.assertEqual(objects[1].values[2], 5);
},
};

View File

@ -18,226 +18,158 @@
'use strict';
var Realm = require('realm');
var TestCase = require('./asserts');
var schemas = require('./schemas');
const Realm = require('realm');
const TestCase = require('./asserts');
const schemas = require('./schemas');
var RANDOM_DATA = new Uint8Array([
const RANDOM_DATA = new Uint8Array([
0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9,
0x67, 0x1e, 0x40, 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff,
]);
module.exports = {
testBasicTypesPropertyGetters: function() {
var realm = new Realm({schema: [schemas.BasicTypes]});
var object;
const allTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
objectCol: {doubleCol: 2.2},
var basicTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
};
optBoolCol: true,
optIntCol: 1,
optFloatCol: 1.1,
optDoubleCol: 1.11,
optStringCol: 'string',
optDateCol: new Date(1),
optDataCol: RANDOM_DATA,
boolArrayCol: [true],
intArrayCol: [1],
floatArrayCol: [1.1],
doubleArrayCol: [1.11],
stringArrayCol: ['string'],
dateArrayCol: [new Date(1)],
dataArrayCol: [RANDOM_DATA],
objectArrayCol: [{doubleCol: 2.2}],
optBoolArrayCol: [true],
optIntArrayCol: [1],
optFloatArrayCol: [1.1],
optDoubleArrayCol: [1.11],
optStringArrayCol: ['string'],
optDateArrayCol: [new Date(1)],
optDataArrayCol: [RANDOM_DATA],
};
const nullPropertyValues = (() => {
let values = {}
for (let name in allTypesValues) {
if (name.includes('opt')) {
values[name] = name.includes('Array') ? [null] : null;
}
else {
values[name] = allTypesValues[name];
}
}
return values;
})();
module.exports = {
testAllPropertyGetters: function() {
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
let object, nullObject;
realm.write(function() {
object = realm.create('BasicTypesObject', basicTypesValues);
object = realm.create('AllTypesObject', allTypesValues);
nullObject = realm.create('AllTypesObject', nullPropertyValues);
});
for (const name in schemas.BasicTypes.properties) {
const prop = schemas.BasicTypes.properties[name];
const type = typeof prop == 'object' ? prop.type : prop;
TestCase.assertSimilar(type, object[name], basicTypesValues[name]);
const objectSchema = realm.schema[0];
for (const name of Object.keys(objectSchema.properties)) {
const type = objectSchema.properties[name].type;
if (type === 'linkingObjects') {
TestCase.assertEqual(object[name].length, 0);
TestCase.assertEqual(nullObject[name].length, 0);
continue;
}
TestCase.assertSimilar(type, object[name], allTypesValues[name]);
TestCase.assertSimilar(type, nullObject[name], nullPropertyValues[name]);
}
TestCase.assertEqual(object.nonexistent, undefined);
},
testNullableBasicTypesPropertyGetters: function() {
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
var object, nullObject;
var basicTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
};
testAllTypesPropertySetters: function() {
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
let obj;
realm.write(function() {
object = realm.create('NullableBasicTypesObject', basicTypesValues);
nullObject = realm.create('NullableBasicTypesObject', {
boolCol: null,
intCol: null,
floatCol: null,
doubleCol: null,
stringCol: null,
dateCol: null,
dataCol: null,
});
obj = realm.create('AllTypesObject', allTypesValues);
});
for (var name in schemas.BasicTypes.properties) {
var prop = schemas.BasicTypes.properties[name];
var type = typeof prop == 'object' ? prop.type : prop;
TestCase.assertEqual(nullObject[name], null);
if (type == 'float' || type == 'double') {
TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001);
}
else if (type == 'data') {
TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA);
}
else if (type == 'date') {
TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime());
}
else {
TestCase.assertEqual(object[name], basicTypesValues[name]);
}
}
},
testBasicTypesPropertySetters: function() {
var realm = new Realm({schema: [schemas.BasicTypes]});
var obj;
var basicTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: new ArrayBuffer(),
};
realm.write(function() {
obj = realm.create('BasicTypesObject', basicTypesValues);
TestCase.assertThrows(function() {
obj.boolCol = false;
obj.intCol = 2;
obj.floatCol = 2.2;
obj.doubleCol = 2.22;
obj.stringCol = 'STRING';
obj.dateCol = new Date(2);
obj.dataCol = RANDOM_DATA;
});
TestCase.assertEqual(obj.boolCol, false, 'wrong bool value');
TestCase.assertEqual(obj.intCol, 2, 'wrong int value');
TestCase.assertEqualWithTolerance(obj.floatCol, 2.2, 0.000001, 'wrong float value');
TestCase.assertEqualWithTolerance(obj.doubleCol, 2.22, 0.000001, 'wrong double value');
TestCase.assertEqual(obj.stringCol, 'STRING', 'wrong string value');
TestCase.assertEqual(obj.dateCol.getTime(), 2, 'wrong date value');
TestCase.assertArraysEqual(new Uint8Array(obj.dataCol), RANDOM_DATA, 'wrong data value');
realm.write(function() {
TestCase.assertThrows(function() {
obj.boolCol = 'cat';
});
TestCase.assertThrows(function() {
obj.intCol = 'dog';
});
TestCase.assertThrows(function() {
obj.boolCol = null;
});
TestCase.assertThrows(function() {
obj.boolCol = undefined;
});
TestCase.assertThrows(function() {
obj.intCol = null;
});
TestCase.assertThrows(function() {
obj.intCol = undefined;
});
TestCase.assertThrows(function() {
obj.floatCol = null;
});
TestCase.assertThrows(function() {
obj.floatCol = undefined;
});
TestCase.assertThrows(function() {
obj.doubleCol = null;
});
TestCase.assertThrows(function() {
obj.doubleCol = undefined;
});
TestCase.assertThrows(function() {
obj.stringCol = null;
});
TestCase.assertThrows(function() {
obj.stringCol = undefined;
});
TestCase.assertThrows(function() {
obj.dateCol = null;
});
TestCase.assertThrows(function() {
obj.dateCol = undefined;
});
TestCase.assertThrows(function() {
obj.dataCol = null;
});
TestCase.assertThrows(function() {
obj.dataCol = undefined;
});
});
TestCase.assertThrows(function() {
obj.boolCol = true;
}, 'can only set property values in a write transaction');
TestCase.assertEqual(obj.boolCol, false, 'bool value changed outside transaction');
},
testNullableBasicTypesPropertySetters: function() {
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
var obj, obj1;
var basicTypesValues = {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
};
TestCase.assertEqual(obj.boolCol, true, 'bool value changed outside transaction');
realm.write(function() {
obj = realm.create('NullableBasicTypesObject', basicTypesValues);
obj1 = realm.create('NullableBasicTypesObject', basicTypesValues);
TestCase.assertThrows(() => obj.boolCol = 'cat');
TestCase.assertThrows(() => obj.intCol = 'dog');
for (var name in schemas.NullableBasicTypes.properties) {
obj[name] = null;
obj1[name] = undefined;
// Non-optional properties should complain about null
for (const name of ['boolCol', 'intCol', 'floatCol', 'doubleCol', 'stringCol', 'dataCol', 'dateCol']) {
TestCase.assertThrows(() => obj[name] = null, `Setting ${name} to null should throw`);
TestCase.assertThrows(() => obj[name] = undefined, `Setting ${name} to undefined should throw`);
}
// Optional properties should allow it
for (const name of ['optBoolCol', 'optIntCol', 'optFloatCol', 'optDoubleCol',
'optStringCol', 'optDataCol', 'optDateCol', 'objectCol']) {
obj[name] = null;
TestCase.assertEqual(obj[name], null);
obj[name] = undefined;
TestCase.assertEqual(obj[name], null);
}
function tryAssign(name, value) {
var prop = schemas.AllTypes.properties[name];
var type = typeof prop == 'object' ? prop.type : prop;
obj[name] = value;
TestCase.assertSimilar(type, obj[name], value, undefined, 1);
}
tryAssign('boolCol', false);
tryAssign('intCol', 10);
tryAssign('floatCol', 2.2);
tryAssign('doubleCol', 3.3);
tryAssign('stringCol', 'new str');
tryAssign('dateCol', new Date(2));
tryAssign('dataCol', RANDOM_DATA);
tryAssign('optBoolCol', null);
tryAssign('optIntCol', null);
tryAssign('optFloatCol', null);
tryAssign('optDoubleCol', null);
tryAssign('optStringCol', null);
tryAssign('optDateCol', null);
tryAssign('optDataCol', null);
tryAssign('optBoolCol', false);
tryAssign('optIntCol', 10);
tryAssign('optFloatCol', 2.2);
tryAssign('optDoubleCol', 3.3);
tryAssign('optStringCol', 'new str');
tryAssign('optDateCol', new Date(2));
tryAssign('optDataCol', RANDOM_DATA);
});
for (var name in schemas.NullableBasicTypes.properties) {
TestCase.assertEqual(obj[name], null);
TestCase.assertEqual(obj1[name], null);
}
realm.write(function() {
TestCase.assertThrows(function() {
obj.boolCol = 'cat';
});
TestCase.assertThrows(function() {
obj.intCol = 'dog';
});
});
TestCase.assertThrows(function() {
obj.boolCol = null;
}, 'can only set property values in a write transaction');
},
testLinkTypesPropertyGetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var obj = null;
realm.write(function() {
@ -261,8 +193,9 @@ module.exports = {
TestCase.assertEqual(arrayVal.length, 1);
TestCase.assertEqual(arrayVal[0].doubleCol, 3);
},
testLinkTypesPropertySetters: function() {
var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
const realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]});
var objects = realm.objects('TestObject');
var obj;
@ -328,33 +261,25 @@ module.exports = {
});
TestCase.assertEqual(obj.objectCol.doubleCol, 3);
},
testEnumerablePropertyNames: function() {
var realm = new Realm({schema: [schemas.BasicTypes]});
var object;
const realm = new Realm({schema: [schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
let object;
realm.write(function() {
object = realm.create('BasicTypesObject', {
boolCol: true,
intCol: 1,
floatCol: 1.1,
doubleCol: 1.11,
stringCol: 'string',
dateCol: new Date(1),
dataCol: RANDOM_DATA,
});
});
realm.write(() => object = realm.create('AllTypesObject', allTypesValues));
var propNames = Object.keys(schemas.BasicTypes.properties);
const propNames = Object.keys(schemas.AllTypes.properties);
TestCase.assertArraysEqual(Object.keys(object), propNames, 'Object.keys');
for (var key in object) {
for (let key in object) {
TestCase.assertEqual(key, propNames.shift());
}
TestCase.assertEqual(propNames.length, 0);
},
testDataProperties: function() {
var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]});
const realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]});
var object;
// Should be be able to set a data property with a typed array.
@ -432,7 +357,7 @@ module.exports = {
},
testObjectConstructor: function() {
var realm = new Realm({schema: [schemas.TestObject]});
const realm = new Realm({schema: [schemas.TestObject]});
realm.write(function() {
var obj = realm.create('TestObject', {doubleCol: 1});
@ -444,7 +369,7 @@ module.exports = {
},
testIsValid: function() {
var realm = new Realm({schema: [schemas.TestObject]});
const realm = new Realm({schema: [schemas.TestObject]});
var obj;
realm.write(function() {
obj = realm.create('TestObject', {doubleCol: 1});
@ -458,9 +383,9 @@ module.exports = {
obj.doubleCol;
});
},
testObjectSchema: function() {
var realm = new Realm({schema: [schemas.TestObject]});
const realm = new Realm({schema: [schemas.TestObject]});
var obj;
realm.write(function() {
obj = realm.create('TestObject', {doubleCol: 1});
@ -503,7 +428,7 @@ module.exports = {
TestCase.assertEqual(realm_v3.objects('Date')[0].nullDate.getTime(), 1462500087955);
TestCase.assertEqual(realm_v3.objects('Date')[1].currentDate.getTime(), -10000);
TestCase.assertEqual(realm_v3.objects('Date')[1].nullDate, null);
// test different dates
var realm = new Realm({schema: [schemas.DateObject]});
realm.write(function() {

View File

@ -95,7 +95,8 @@ module.exports = {
},
testRealmConstructorSchemaValidation: function() {
TestCase.assertThrowsContaining(() => new Realm({schema: schemas.AllTypes}), "schema must be of type 'array', got");
TestCase.assertThrowsContaining(() => new Realm({schema: schemas.AllTypes}),
"schema must be of type 'array', got");
TestCase.assertThrowsContaining(() => new Realm({schema: ['SomeType']}),
"Failed to read ObjectSchema: JS value must be of type 'object', got (SomeType)");
TestCase.assertThrowsContaining(() => new Realm({schema: [{}]}),
@ -142,7 +143,7 @@ module.exports = {
}]});
}, "Property 'InvalidObject.link' declared as origin of linking objects property 'InvalidObject.linkingObjects' links to type 'IntObject'")
},
testRealmConstructorInMemory: function() {
// open in-memory realm instance
const realm1 = new Realm({inMemory: true, schema: [schemas.TestObject]});
@ -166,7 +167,7 @@ module.exports = {
// Open the same in-memory realm again and verify that it is now empty
const realm3 = new Realm({inMemory: true});
TestCase.assertEqual(realm3.schema.length, 0);
// try to open the same realm in persistent mode (should fail as you cannot mix modes)
TestCase.assertThrowsContaining(() => new Realm({}), 'already opened with different inMemory settings.');
},
@ -296,23 +297,10 @@ module.exports = {
});
},
testRealmCreateOptionals: function() {
const realm = new Realm({schema: [schemas.NullableBasicTypes, schemas.LinkTypes, schemas.TestObject]});
let basic, links;
realm.write(() => {
basic = realm.create('NullableBasicTypesObject', {});
links = realm.create('LinkTypesObject', {});
});
for (const name in schemas.NullableBasicTypes.properties) {
TestCase.assertEqual(basic[name], null);
}
TestCase.assertEqual(links.objectCol, null);
TestCase.assertEqual(links.arrayCol.length, 0);
},
testRealmCreateUpsert: function() {
const realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]});
realm.write(() => {
const realm = new Realm({schema: [schemas.AllPrimaryTypes, schemas.TestObject,
schemas.StringPrimary]});
realm.write(function() {
const values = {
primaryCol: '0',
boolCol: true,
@ -326,12 +314,12 @@ module.exports = {
arrayCol: [],
};
const obj0 = realm.create('AllTypesObject', values);
const obj0 = realm.create('AllPrimaryTypesObject', values);
TestCase.assertThrowsContaining(() => realm.create('AllTypesObject', values),
"Attempting to create an object of type 'AllTypesObject' with an existing primary key value '0'.");
TestCase.assertThrowsContaining(() => realm.create('AllPrimaryTypesObject', values),
"Attempting to create an object of type 'AllPrimaryTypesObject' with an existing primary key value ''0''.");
const obj1 = realm.create('AllTypesObject', {
const obj1 = realm.create('AllPrimaryTypesObject', {
primaryCol: '1',
boolCol: false,
intCol: 2,
@ -344,10 +332,10 @@ module.exports = {
arrayCol: [{doubleCol: 2}],
}, true);
const objects = realm.objects('AllTypesObject');
const objects = realm.objects('AllPrimaryTypesObject');
TestCase.assertEqual(objects.length, 2);
realm.create('AllTypesObject', {
realm.create('AllPrimaryTypesObject', {
primaryCol: '0',
boolCol: false,
intCol: 2,
@ -371,13 +359,13 @@ module.exports = {
TestCase.assertEqual(obj0.objectCol, null);
TestCase.assertEqual(obj0.arrayCol.length, 1);
realm.create('AllTypesObject', {primaryCol: '0'}, true);
realm.create('AllTypesObject', {primaryCol: '1'}, true);
realm.create('AllPrimaryTypesObject', {primaryCol: '0'}, true);
realm.create('AllPrimaryTypesObject', {primaryCol: '1'}, true);
TestCase.assertEqual(obj0.stringCol, '2');
TestCase.assertEqual(obj0.objectCol, null);
TestCase.assertEqual(obj1.objectCol.doubleCol, 0);
realm.create('AllTypesObject', {
realm.create('AllPrimaryTypesObject', {
primaryCol: '0',
stringCol: '3',
objectCol: {doubleCol: 0},
@ -393,13 +381,13 @@ module.exports = {
TestCase.assertEqual(obj0.objectCol.doubleCol, 0);
TestCase.assertEqual(obj0.arrayCol.length, 1);
realm.create('AllTypesObject', {primaryCol: '0', objectCol: undefined}, true);
realm.create('AllTypesObject', {primaryCol: '1', objectCol: null}, true);
realm.create('AllPrimaryTypesObject', {primaryCol: '0', objectCol: undefined}, true);
realm.create('AllPrimaryTypesObject', {primaryCol: '1', objectCol: null}, true);
TestCase.assertEqual(obj0.objectCol, null);
TestCase.assertEqual(obj1.objectCol, null);
// test with string primaries
const obj =realm.create('StringPrimaryObject', {
const obj = realm.create('StringPrimaryObject', {
primaryCol: '0',
valueCol: 0
});
@ -784,8 +772,9 @@ module.exports = {
},
testSchema: function() {
const originalSchema = [schemas.TestObject, schemas.BasicTypes, schemas.NullableBasicTypes, schemas.IndexedTypes, schemas.IntPrimary,
schemas.PersonObject, schemas.LinkTypes, schemas.LinkingObjectsObject];
const originalSchema = [schemas.TestObject, schemas.AllTypes, schemas.LinkToAllTypes,
schemas.IndexedTypes, schemas.IntPrimary, schemas.PersonObject,
schemas.LinkTypes, schemas.LinkingObjectsObject];
const schemaMap = {};
originalSchema.forEach(objectSchema => {
@ -801,45 +790,67 @@ module.exports = {
const schema = realm.schema;
TestCase.assertEqual(schema.length, originalSchema.length);
function isString(val) {
return typeof val === 'string' || val instanceof String;
}
const normalizeProperty = (val) => {
let prop;
if (typeof val !== 'string' && !(val instanceof String)) {
prop = val;
prop.optional = val.optional || false;
prop.indexed = val.indexed || false;
}
else {
prop = {type: val, indexed: false, optional: false};
}
if (prop.type.includes('?')) {
prop.optional = true;
prop.type = prop.type.replace('?', '');
}
if (prop.type.includes('[]')) {
prop.objectType = prop.type.replace('[]', '');
prop.type = 'list';
}
return prop;
};
function verifyObjectSchema(returned) {
let original = schemaMap[returned.name];
for (const objectSchema of schema) {
let original = schemaMap[objectSchema.name];
if (original.schema) {
original = original.schema;
}
TestCase.assertEqual(returned.primaryKey, original.primaryKey);
for (const propName in returned.properties) {
const prop1 = returned.properties[propName];
const prop2 = original.properties[propName];
if (prop1.type == 'object') {
TestCase.assertEqual(prop1.objectType, isString(prop2) ? prop2 : prop2.objectType);
TestCase.assertEqual(prop1.optional, true);
TestCase.assertEqual(objectSchema.primaryKey, original.primaryKey);
for (const propName in objectSchema.properties) {
TestCase.assertDefined(original.properties[propName], `schema has unexpected property ${propName}`);
const actual = objectSchema.properties[propName];
const expected = normalizeProperty(original.properties[propName]);
TestCase.assertEqual(actual.name, propName);
TestCase.assertEqual(actual.indexed, expected.indexed);
if (actual.type == 'object') {
TestCase.assertEqual(actual.objectType, expected.type === 'object' ? expected.objectType : expected.type);
TestCase.assertEqual(actual.optional, true);
TestCase.assertUndefined(actual.property);
}
else if (prop1.type == 'list') {
TestCase.assertEqual(prop1.objectType, prop2.objectType);
TestCase.assertEqual(prop1.optional, undefined);
else if (actual.type == 'list') {
TestCase.assertEqual(actual.type, expected.type);
TestCase.assertEqual(actual.objectType, expected.objectType);
TestCase.assertEqual(actual.optional, expected.optional);
TestCase.assertUndefined(actual.property);
}
else if (prop1.type == 'linking objects') {
TestCase.assertEqual(prop1.objectType, prop2.objectType);
TestCase.assertEqual(prop1.property, prop2.property);
TestCase.assertEqual(prop1.optional, undefined);
else if (actual.type == 'linkingObjects') {
TestCase.assertEqual(actual.type, expected.type);
TestCase.assertEqual(actual.objectType, expected.objectType);
TestCase.assertEqual(actual.property, expected.property);
TestCase.assertEqual(actual.optional, false);
}
else {
TestCase.assertEqual(prop1.type, isString(prop2) ? prop2 : prop2.type);
TestCase.assertEqual(prop1.optional, prop2.optional || undefined);
TestCase.assertEqual(actual.type, expected.type);
TestCase.assertEqual(actual.optional, expected.optional);
TestCase.assertUndefined(actual.property);
TestCase.assertUndefined(actual.objectType);
}
TestCase.assertEqual(prop1.indexed, prop2.indexed || undefined);
}
}
for (let i = 0; i < originalSchema.length; i++) {
verifyObjectSchema(schema[i]);
}
},
testCopyBundledRealmFiles: function() {
@ -869,7 +880,7 @@ module.exports = {
const p1 = realm.create('PersonObject', { name: 'Ari', age: 10 });
p1.age = "Ten";
});
}, new Error("PersonObject.age must be of type 'number', got (Ten)"));
}, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')"));
},
testErrorMessageFromInvalidCreate: function() {
@ -879,7 +890,7 @@ module.exports = {
realm.write(() => {
const p1 = realm.create('PersonObject', { name: 'Ari', age: 'Ten' });
});
}, new Error("PersonObject.age must be of type 'number', got (Ten)"));
}, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')"));
},
testValidTypesForListProperties: function() {
@ -934,7 +945,7 @@ module.exports = {
realm.cancelTransaction();
TestCase.assertTrue(!realm.isInTransaction);
},
testCompact: function() {
let wasCalled = false;
const count = 1000;

View File

@ -299,7 +299,7 @@ module.exports = {
},
testResultsInvalidation: function() {
var realm = new Realm({schema: [schemas.TestObject]});
let realm = new Realm({schema: [schemas.TestObject]});
realm.write(function() {
for (var i = 10; i > 0; i--) {
realm.create('TestObject', [i]);
@ -322,7 +322,7 @@ module.exports = {
realm.close();
realm = new Realm({
schemaVersion: 1,
schema: [schemas.TestObject, schemas.BasicTypes]
schema: [schemas.TestObject, schemas.DateObject]
});
resultsVariants.forEach(function(objects) {

View File

@ -33,7 +33,7 @@ PersonObject.schema = {
properties: {
name: 'string',
age: 'double',
married: {type: 'bool', default: false},
married: {type: 'bool', default: false},
children: {type: 'list', objectType: 'PersonObject'},
parents: {type: 'linkingObjects', objectType: 'PersonObject', property: 'children'},
}
@ -51,7 +51,7 @@ exports.PersonObject = PersonObject;
exports.PersonList = {
name: 'PersonList',
properties: {
list: {type: 'list', objectType: 'PersonObject'},
list: 'PersonObject[]',
}
};
@ -68,26 +68,82 @@ exports.BasicTypes = {
}
};
exports.NullableBasicTypes = {
name: 'NullableBasicTypesObject',
exports.AllTypes = {
name: 'AllTypesObject',
properties: {
boolCol: {type: 'bool', optional: true},
intCol: {type: 'int', optional: true},
floatCol: {type: 'float', optional: true},
doubleCol: {type: 'double', optional: true},
stringCol: {type: 'string', optional: true},
dateCol: {type: 'date', optional: true},
dataCol: {type: 'data', optional: true},
boolCol: 'bool',
intCol: 'int',
floatCol: 'float',
doubleCol: 'double',
stringCol: 'string',
dateCol: 'date',
dataCol: 'data',
objectCol: 'TestObject',
optBoolCol: 'bool?',
optIntCol: 'int?',
optFloatCol: 'float?',
optDoubleCol: 'double?',
optStringCol: 'string?',
optDateCol: 'date?',
optDataCol: 'data?',
boolArrayCol: 'bool[]',
intArrayCol: 'int[]',
floatArrayCol: 'float[]',
doubleArrayCol: 'double[]',
stringArrayCol: 'string[]',
dateArrayCol: 'date[]',
dataArrayCol: 'data[]',
objectArrayCol: 'TestObject[]',
optBoolArrayCol: 'bool?[]',
optIntArrayCol: 'int?[]',
optFloatArrayCol: 'float?[]',
optDoubleArrayCol: 'double?[]',
optStringArrayCol: 'string?[]',
optDateArrayCol: 'date?[]',
optDataArrayCol: 'data?[]',
linkingObjectsCol: {type: 'linkingObjects', objectType: 'LinkToAllTypesObject', property: 'allTypesCol'},
}
};
exports.AllPrimaryTypes = {
name: 'AllPrimaryTypesObject',
primaryKey: 'primaryCol',
properties: {
primaryCol: 'string',
boolCol: 'bool',
intCol: 'int',
floatCol: 'float',
doubleCol: 'double',
stringCol: 'string',
dateCol: 'date',
dataCol: 'data',
objectCol: 'TestObject',
arrayCol: {type: 'list', objectType: 'TestObject'},
}
};
exports.LinkToAllTypes = {
name: 'LinkToAllTypesObject',
properties: {
allTypesCol: 'AllTypesObject',
}
}
exports.IndexedTypes = {
name: 'IndexedTypesObject',
properties: {
boolCol: {type: 'bool', indexed: true},
intCol: {type: 'int', indexed: true},
stringCol: {type: 'string', indexed: true},
dateCol: {type: 'date', indexed: true},
boolCol: {type: 'bool', indexed: true},
intCol: {type: 'int', indexed: true},
stringCol: {type: 'string', indexed: true},
dateCol: {type: 'date', indexed: true},
optBoolCol: {type: 'bool?', indexed: true},
optIntCol: {type: 'int?', indexed: true},
optStringCol: {type: 'string?', indexed: true},
optDateCol: {type: 'date?', indexed: true},
}
};
@ -95,9 +151,31 @@ exports.IndexedTypes = {
exports.LinkTypes = {
name: 'LinkTypesObject',
properties: {
objectCol: 'TestObject',
objectCol: 'TestObject',
objectCol1: {type: 'object', objectType: 'TestObject'},
arrayCol: {type: 'list', objectType: 'TestObject'},
arrayCol: 'TestObject[]',
arrayCol1: {type: 'list', objectType: 'TestObject'},
}
};
exports.PrimitiveArrays = {
name: 'PrimitiveArrays',
properties: {
bool: 'bool[]',
int: 'int[]',
float: 'float[]',
double: 'double[]',
string: 'string[]',
date: 'date[]',
data: 'data[]',
optBool: 'bool?[]',
optInt: 'int?[]',
optFloat: 'float?[]',
optDouble: 'double?[]',
optString: 'string?[]',
optDate: 'date?[]',
optData: 'data?[]',
}
};
@ -126,44 +204,19 @@ exports.StringOnly = {
}
};
exports.AllTypes = {
name: 'AllTypesObject',
primaryKey: 'primaryCol',
properties: {
primaryCol: 'string',
boolCol: 'bool',
intCol: 'int',
floatCol: 'float',
doubleCol: 'double',
stringCol: 'string',
dateCol: 'date',
dataCol: 'data',
objectCol: 'TestObject',
arrayCol: {type: 'list', objectType: 'TestObject'},
linkingObjectsCol: {type: 'linkingObjects', objectType: 'LinkToAllTypesObject', property: 'allTypesCol'},
}
};
exports.LinkToAllTypes = {
name: 'LinkToAllTypesObject',
properties: {
allTypesCol: 'AllTypesObject',
}
}
exports.DefaultValues = {
name: 'DefaultValuesObject',
properties: {
boolCol: {type: 'bool', default: true},
intCol: {type: 'int', default: -1},
floatCol: {type: 'float', default: -1.1},
doubleCol: {type: 'double', default: -1.11},
stringCol: {type: 'string', default: 'defaultString'},
dateCol: {type: 'date', default: new Date(1.111)},
dataCol: {type: 'data', default: new ArrayBuffer(1)},
objectCol: {type: 'TestObject', default: {doubleCol: 1}},
nullObjectCol: {type: 'TestObject', default: null},
arrayCol: {type: 'list', objectType: 'TestObject', default: [{doubleCol: 2}]},
boolCol: {type: 'bool', default: true},
intCol: {type: 'int', default: -1},
floatCol: {type: 'float', default: -1.1},
doubleCol: {type: 'double', default: -1.11},
stringCol: {type: 'string', default: 'defaultString'},
dateCol: {type: 'date', default: new Date(1.111)},
dataCol: {type: 'data', default: new ArrayBuffer(1)},
objectCol: {type: 'TestObject', default: {doubleCol: 1}},
nullObjectCol: {type: 'TestObject', default: null},
arrayCol: {type: 'TestObject[]', default: [{doubleCol: 2}]},
}
};
@ -203,7 +256,7 @@ exports.DateObject = {
name: 'Date',
properties: {
currentDate: 'date',
nullDate: { type: 'date', optional: true }
nullDate: 'date?'
}
};
@ -211,7 +264,7 @@ exports.LinkingObjectsObject = {
name: 'LinkingObjectsObject',
properties: {
value: 'int',
links: {type: 'list', objectType: 'LinkingObjectsObject'},
links: 'LinkingObjectsObject[]',
linkingObjects: {type: 'linkingObjects', objectType: 'LinkingObjectsObject', property: 'links'}
}
}