//////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// #pragma once #include "js_collection.hpp" #include "js_realm_object.hpp" #include "js_util.hpp" #include "results.hpp" #include "list.hpp" #include "object_store.hpp" #include #include #include #ifdef REALM_ENABLE_SYNC #include "js_sync.hpp" #include "sync/partial_sync.hpp" #endif namespace realm { namespace js { template class NativeAccessor; struct NonRealmObjectException : public std::logic_error { NonRealmObjectException() : std::logic_error("Object is not a Realm object") { } }; template class Results : public realm::Results { public: Results(Results const& r) : realm::Results(r) {}; Results(realm::Results const& r) : realm::Results(r) {}; Results(Results&&) = default; Results& operator=(Results&&) = default; Results& operator=(Results const&) = default; using realm::Results::Results; std::vector, NotificationToken>> m_notification_tokens; }; template struct ResultsClass : ClassDefinition, CollectionClass> { using Type = T; using ContextType = typename T::Context; using ObjectType = typename T::Object; using ValueType = typename T::Value; using FunctionType = typename T::Function; using Object = js::Object; using Value = js::Value; using ReturnValue = js::ReturnValue; using Arguments = js::Arguments; static ObjectType create_instance(ContextType, realm::Results); static ObjectType create_instance(ContextType, SharedRealm, const std::string &object_type); template static ObjectType create_filtered(ContextType, const U &, Arguments); static std::vector> 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, 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 &); #if REALM_ENABLE_SYNC static void subscribe(ContextType, ObjectType, Arguments, ReturnValue &); #endif static void index_of(ContextType, ObjectType, Arguments, ReturnValue &); template static void index_of(ContextType, Fn&, Arguments, ReturnValue &); static void update(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // observable 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 static void add_listener(ContextType, U&, ObjectType, Arguments); template static void remove_listener(ContextType, U&, ObjectType, Arguments); std::string const name = "Results"; MethodMap const methods = { {"snapshot", wrap}, {"filtered", wrap}, {"sorted", wrap}, {"isValid", wrap}, #if REALM_ENABLE_SYNC {"subscribe", wrap}, #endif {"min", wrap, AggregateFunc::Min>>}, {"max", wrap, AggregateFunc::Max>>}, {"sum", wrap, AggregateFunc::Sum>>}, {"avg", wrap, AggregateFunc::Avg>>}, {"addListener", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, {"indexOf", wrap}, {"update", wrap}, }; PropertyMap const properties = { {"length", {wrap, nullptr}}, {"type", {wrap, nullptr}}, {"optional", {wrap, nullptr}}, }; IndexPropertyType const index_accessor = {wrap, nullptr}; }; template typename T::Object ResultsClass::create_instance(ContextType ctx, realm::Results results) { return create_object>(ctx, new realm::js::Results(std::move(results))); } template typename T::Object ResultsClass::create_instance(ContextType ctx, SharedRealm realm, const std::string &object_type) { auto table = ObjectStore::table_for_object_type(realm->read_group(), object_type); if (!table) { throw std::runtime_error("Table does not exist. Object type: " + object_type); } return create_object>(ctx, new realm::js::Results(realm, *table)); } inline void alias_backlinks(parser::KeyPathMapping &mapping, const realm::SharedRealm &realm) { const realm::Schema &schema = realm->schema(); for (auto it = schema.begin(); it != schema.end(); ++it) { for (const Property &property : it->computed_properties) { if (property.type == realm::PropertyType::LinkingObjects) { auto target_object_schema = schema.find(property.object_type); const TableRef table = ObjectStore::table_for_object_type(realm->read_group(), it->name); std::string native_name = "@links." + target_object_schema->name + "." + property.link_origin_property_name; mapping.add_mapping(table, property.name, native_name); } } } } template template typename T::Object ResultsClass::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(); DescriptorOrdering ordering; parser::KeyPathMapping mapping; mapping.set_backlink_class_prefix(ObjectStore::table_name_for_object_type("")); alias_backlinks(mapping, realm); parser::ParserResult result = parser::parse(query_string); NativeAccessor accessor(ctx, realm, object_schema); query_builder::ArgumentConverter> converter(accessor, &args.value[1], args.count - 1); query_builder::apply_predicate(query, result.predicate, converter, mapping); query_builder::apply_ordering(ordering, query.get_table(), result.ordering); return create_instance(ctx, collection.filter(std::move(query)).apply_ordering(std::move(ordering))); } template std::vector> ResultsClass::get_keypaths(ContextType ctx, Arguments args) { args.validate_maximum(2); std::vector> 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"); 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); for (unsigned int i = 0; i < prop_count; i++) { ValueType value = Object::validated_get_property(ctx, js_prop_names, i); if (Value::is_array(ctx, value)) { ObjectType array = Value::to_array(ctx, value); sort_order.emplace_back(Object::validated_get_string(ctx, array, 0), !Object::validated_get_boolean(ctx, array, 1)); } else { sort_order.emplace_back(Value::validated_to_string(ctx, value), true); } } } else { 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; } template void ResultsClass::get_length(ContextType ctx, ObjectType object, ReturnValue &return_value) { auto results = get_internal>(object); return_value.set((uint32_t)results->size()); } template void ResultsClass::get_type(ContextType, ObjectType object, ReturnValue &return_value) { auto results = get_internal>(object); return_value.set(string_for_property_type(results->get_type() & ~realm::PropertyType::Flags)); } template void ResultsClass::get_optional(ContextType, ObjectType object, ReturnValue &return_value) { auto results = get_internal>(object); return_value.set(is_nullable(results->get_type())); } template void ResultsClass::get_index(ContextType ctx, ObjectType object, uint32_t index, ReturnValue &return_value) { auto results = get_internal>(object); NativeAccessor accessor(ctx, *results); return_value.set(results->get(accessor, index)); } template void ResultsClass::snapshot(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { args.validate_maximum(0); auto results = get_internal>(this_object); return_value.set(ResultsClass::create_instance(ctx, results->snapshot())); } template void ResultsClass::filtered(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { auto results = get_internal>(this_object); return_value.set(create_filtered(ctx, *results, args)); } template void ResultsClass::sorted(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { auto results = get_internal>(this_object); return_value.set(ResultsClass::create_instance(ctx, results->sort(ResultsClass::get_keypaths(ctx, args)))); } template void ResultsClass::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } #if REALM_ENABLE_SYNC template void ResultsClass::subscribe(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { args.validate_maximum(1); auto results = get_internal>(this_object); auto realm = results->get_realm(); auto sync_config = realm->config().sync_config; util::Optional subscription_name; if (args.count == 1) { subscription_name = util::Optional(Value::validated_to_string(ctx, args[0])); } else { subscription_name = util::none; } auto subscription = partial_sync::subscribe(*results, subscription_name); return_value.set(SubscriptionClass::create_instance(ctx, std::move(subscription))); } #endif template template void ResultsClass::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) { args.validate_maximum(1); size_t ndx; try { ndx = fn(args[0]); } 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 void ResultsClass::update(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); std::string property = Value::validated_to_string(ctx, arguments[0], "property"); auto results = get_internal>(this_object); auto schema = results->get_object_schema(); if (!schema.property_for_name(StringData(property))) { throw std::invalid_argument(util::format("No such property: %1", property)); } auto realm = results->get_realm(); if (!realm->is_in_transaction()) { throw std::runtime_error("Can only 'update' objects within a transaction."); } // TODO: This approach just moves the for-loop from JS to C++ // Ideally, we'd implement this in OS or Core in an optimized fashion for (auto i = results->size(); i > 0; i--) { auto realm_object = realm::Object(realm, schema, results->get(i - 1)); auto obj = RealmObjectClass::create_instance(ctx, realm_object); RealmObjectClass::set_property(ctx, obj, property, arguments[1]); } } template void ResultsClass::index_of(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { auto fn = [&](auto&& row) { auto results = get_internal>(this_object); NativeAccessor accessor(ctx, *results); return results->index_of(accessor, row); }; index_of(ctx, fn, args, return_value); } template template void ResultsClass::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 protected_callback(ctx, callback); Protected protected_this(ctx, this_object); Protected protected_ctx(Context::get_global_context(ctx)); auto token = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) { HANDLESCOPE ValueType arguments[] { static_cast(protected_this), CollectionClass::create_collection_change_set(protected_ctx, change_set) }; Function::callback(protected_ctx, protected_callback, protected_this, 2, arguments); }); collection.m_notification_tokens.emplace_back(protected_callback, std::move(token)); } template void ResultsClass::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { auto results = get_internal>(this_object); add_listener(ctx, *results, this_object, args); } template template void ResultsClass::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(ctx, callback); auto& tokens = collection.m_notification_tokens; auto compare = [&](auto&& token) { return typename Protected::Comparator()(token.first, protected_function); }; tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end()); } template void ResultsClass::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { auto results = get_internal>(this_object); remove_listener(ctx, *results, this_object, args); } template void ResultsClass::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { args.validate_maximum(0); auto results = get_internal>(this_object); results->m_notification_tokens.clear(); } } // js } // realm