diff --git a/CHANGELOG.md b/CHANGELOG.md index e8cc6af8..d8fd43ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,33 @@ -x.x.x Release notes (yyyy-MM-dd) +X.Y.Z-rc Release notes ============================================================= ### Breaking changes -* None. +* None ### Enhancements -* None. +* Support migration from Realms sync 1.0 to sync 2.0 versions ### Bug fixes -* Configuration of sync file system is not done on module import but later when actually needed by sync (#1351) +* None + +2.0.0-rc16 Release notes (2017-9-29) +============================================================= +### Breaking changes +* Upgtading to Realm Core 4.0.1 (bug fixes) +* Upgrading to Realm Sync 2.0.0-rc26 (sync protocol 22 + bug fixes) + +2.0.0-rc14 Release notes (2017-9-29) +============================================================= +### Breaking changes +* Upgrading to Realm Core 4.0.0 and Realm Sync 2.0.0-rc25. + +### Enhancements +** Support Realm migration from sync 1.0 to sync 2.0 version + +### Bug fixes +* Configuration of sync file system is not done on module import but later when actually needed by sync (#1351) -2.0.0 Release notes (2017-9-28) +2.0.0-rc12 Release notes (2017-9-28) ============================================================= ### Breaking changes * None. @@ -27,18 +44,22 @@ x.x.x Release notes (yyyy-MM-dd) ### Breaking changes * None -### Enhancements -* Add a callback function used to verify SSL certificates in the sync config. -* Throw exception with recovery configuration when upgrading from 1.x to 2.x. - -### Bug fixes -* Fixed port conflict between RN >= 0.48 inspector proxy and RPC server used for Chrome debugging (#1294). - ### Internal * Alignment of permission schemas. * Updating sync (2.0.0-rc24). +1.13.0 Release notes (to be released) +============================================================= +### Breaking changes +* None. +### Enhancements +* Add a callback function used to verify SSL certificates in the sync config. +* Added aggregate functions `min()`, `max()`, `sum()`, and `avg()` to `Realm.Results` and `Realm.List` (#807). + +### Bug fixes +* Fixed port conflict between RN >= 0.48 inspector proxy and RPC server used for Chrome debugging (#1294). +* Workaround for RN >= 0.49 metro-bundler check for single string literal argument to `require()` (#1342) 2.0.0-rc10 Release notes (2017-9-19) diff --git a/binding.gyp b/binding.gyp index 49f4f3b2..b4634166 100644 --- a/binding.gyp +++ b/binding.gyp @@ -92,6 +92,7 @@ "src/object-store/src/sync/sync_manager.cpp", "src/object-store/src/sync/sync_user.cpp", "src/object-store/src/sync/sync_session.cpp", + "src/object-store/src/sync/sync_config.cpp", "src/object-store/src/sync/impl/sync_file.cpp", "src/object-store/src/sync/impl/sync_metadata.cpp" ], diff --git a/dependencies.list b/dependencies.list index 5c6a21d7..ccf0679e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=2.0.0-rc13 -REALM_CORE_VERSION=3.2.1 -REALM_SYNC_VERSION=2.0.0-rc24 -REALM_OBJECT_SERVER_VERSION=2.0.0-alpha.40 +VERSION=2.0.0-rc16 +REALM_CORE_VERSION=4.0.1 +REALM_SYNC_VERSION=2.0.0-rc26 +REALM_OBJECT_SERVER_VERSION=2.0.0-alpha.42 diff --git a/docs/collection.js b/docs/collection.js index 0db883aa..6310262f 100644 --- a/docs/collection.js +++ b/docs/collection.js @@ -233,6 +233,42 @@ class Collection { */ indexOf(object) {} + /** + * Computes the minimum value of a property. + * @param {string} property - The name of the property. + * @throws {Error} If no property with the name exists or if property is not numeric/date. + * @returns {number} the minimum value. + * @since 1.12.1 + */ + min(property) {} + + /** + * Computes the maximum value of a property. + * @param {string} property - The name of the property. + * @throws {Error} If no property with the name exists or if property is not numeric/date. + * @returns {number} the maximum value. + * @since 1.12.1 + */ + max(property) {} + + /** + * Computes the sum of a property. + * @param {string} property - The name of the property. + * @throws {Error} If no property with the name exists or if property is not numeric/date. + * @returns {number} the sum. + * @since 1.12.1 + */ + sum(property) {} + + /** + * Computes the average of a property. + * @param {string} property - The name of the property. + * @throws {Error} If no property with the name exists or if property is not numeric/date. + * @returns {number} the average value. + * @since 1.12.1 + */ + avg(property) {} + /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach Array.prototype.forEach} * @param {function} callback - Function to execute on each object in the collection. diff --git a/lib/browser/lists.js b/lib/browser/lists.js index 3b67de52..5e803d5a 100644 --- a/lib/browser/lists.js +++ b/lib/browser/lists.js @@ -32,6 +32,10 @@ createMethods(List.prototype, objectTypes.LIST, [ 'snapshot', 'isValid', 'indexOf', + 'min', + 'max', + 'sum', + 'avg', 'addListener', 'removeListener', 'removeAllListeners', diff --git a/lib/browser/results.js b/lib/browser/results.js index 72abba67..efb0306e 100644 --- a/lib/browser/results.js +++ b/lib/browser/results.js @@ -31,6 +31,10 @@ createMethods(Results.prototype, objectTypes.RESULTS, [ 'snapshot', 'isValid', 'indexOf', + 'min', + 'max', + 'sum', + 'avg', 'addListener', 'removeListener', 'removeAllListeners', diff --git a/lib/index.d.ts b/lib/index.d.ts index 06637d21..ee2ee3ca 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -120,7 +120,7 @@ declare namespace Realm { * SortDescriptor * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Collection.html#~SortDescriptor } */ - type SortDescriptor = string | [string, boolean] | any[]; + type SortDescriptor = [string] | [string, boolean]; interface CollectionChangeSet { insertions: number[]; @@ -135,11 +135,42 @@ declare namespace Realm { * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Collection.html } */ interface Collection extends ReadonlyArray { + readonly type: PropertyType; + readonly optional: boolean; + /** * @returns boolean */ isValid(): boolean; + /** + * Computes the minimum value. + * @param {string} property + * @returns number + */ + min(property: string): number; + + /** + * Computes the maximum value. + * @param {string} property + * @returns number + */ + max(property: string): number; + + /** + * Computes the sum. + * @param {string} property + * @returns number + */ + sum(property: string): number; + + /** + * Computes the average. + * @param {string} property + * @returns number + */ + avg(property: string): number; + /** * @param {string} query * @param {any[]} ...arg @@ -147,12 +178,9 @@ declare namespace Realm { */ filtered(query: string, ...arg: any[]): Results; - /** - * @param {string|SortDescriptor} descriptor - * @param {boolean} reverse? - * @returns Results - */ - sorted(descriptor: string | SortDescriptor, reverse?: boolean): Results; + sorted(reverse?: boolean): Results; + sorted(descriptor: SortDescriptor[]): Results; + sorted(descriptor: string, reverse?: boolean): Results; /** * @returns Results @@ -188,22 +216,21 @@ declare namespace Realm { interface List extends Collection { [n: number]: T; - /** - * @returns T - */ pop(): T | null | undefined; /** * @param {T} object * @returns number */ - push(object: T): number; + push(...object: T[]): number; /** * @returns T */ shift(): T | null | undefined; + unshift(...object: T[]): number; + /** * @param {number} index * @param {number} count? @@ -211,12 +238,6 @@ declare namespace Realm { * @returns T */ splice(index: number, count?: number, object?: any): T[]; - - /** - * @param {T} object - * @returns number - */ - unshift(object: T): number; } const List: { @@ -296,8 +317,7 @@ declare namespace Realm.Sync { } type PermissionCondition = { - userId: string | - { metadataKey: string, metadataValue: string } + userId: string | { metadataKey: string, metadataValue: string } }; type AccessLevel = 'none' | 'read' | 'write' | 'admin'; diff --git a/lib/index.js b/lib/index.js index aa7bb148..813cde3a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -18,9 +18,11 @@ 'use strict'; +const require_method = require; + // Prevent React Native packager from seeing modules required with this function nodeRequire(module) { - return require(module); + return require_method(module); } function getContext() { @@ -91,7 +93,7 @@ switch(getContext()) { var pkg = path.resolve(path.join(__dirname,'../package.json')); var binding_path = binary.find(pkg); - realmConstructor = require(binding_path).Realm; + realmConstructor = require_method(binding_path).Realm; break; case 'reactnative': diff --git a/lib/user-methods.js b/lib/user-methods.js index de390447..ba21ddc4 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -21,8 +21,10 @@ const AuthError = require('./errors').AuthError; const permissionApis = require('./permission-api'); +const require_method = require; + function node_require(module) { - return require(module); + return require_method(module); } function checkTypes(args, types) { diff --git a/package.json b/package.json index cd60f701..74417170 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "2.0.0-rc13", + "version": "2.0.0-rc16", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/react-native/android/src/main/jni/Android.mk b/react-native/android/src/main/jni/Android.mk index 475527e0..60d6c30d 100644 --- a/react-native/android/src/main/jni/Android.mk +++ b/react-native/android/src/main/jni/Android.mk @@ -58,6 +58,7 @@ ifeq ($(strip $(BUILD_TYPE_SYNC)),1) LOCAL_SRC_FILES += src/object-store/src/sync/sync_manager.cpp LOCAL_SRC_FILES += src/object-store/src/sync/sync_session.cpp LOCAL_SRC_FILES += src/object-store/src/sync/sync_user.cpp +LOCAL_SRC_FILES += src/object-store/src/sync/sync_config.cpp LOCAL_SRC_FILES += src/object-store/src/sync/impl/sync_file.cpp LOCAL_SRC_FILES += src/object-store/src/sync/impl/sync_metadata.cpp endif diff --git a/scripts/test.sh b/scripts/test.sh index c12393db..1bc08ccb 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -57,7 +57,7 @@ start_server() { } stop_server() { - echo stopping server + echo stopping server if [[ ${SERVER_PID} -gt 0 ]] ; then echo server is running. killing it kill -9 ${SERVER_PID} || true @@ -352,12 +352,12 @@ case "$TARGET" in ;; "node") npm run check-environment - if [ "$(uname)" = 'Darwin' ]; then + if [ "$(uname)" = 'Darwin' ]; then echo "downloading server" download_server echo "starting server" start_server - + npm_tests_cmd="npm run test" npm install --build-from-source=realm --realm_enable_sync diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 509ca76c..c10d5eb2 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 50C671001E1D2D31003CB63C /* thread_safe_reference.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5D02C7791E1C83650048C13E /* thread_safe_reference.cpp */; }; 5D1BF0571EF1DB4800B7DC87 /* jsc_value.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5D1BF0561EF1DB4800B7DC87 /* jsc_value.cpp */; }; 5D25F5A11D6284FD00EBBB30 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F63FF3301C16434400B3B8E0 /* libz.tbd */; }; + 5D97DC4E1F7DAB1400B856A4 /* sync_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 426FCDFF1F7DA2F9005565DC /* sync_config.cpp */; }; 8507156E1E2CFCD000E548DB /* object_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8507156B1E2CFC0100E548DB /* object_notifier.cpp */; }; F61378791C18EAC5008BFC51 /* js in Resources */ = {isa = PBXBuildFile; fileRef = F61378781C18EAAC008BFC51 /* js */; }; F63FF2C61C12469E00B3B8E0 /* jsc_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048011C0428DF00ABDED4 /* jsc_init.cpp */; }; @@ -193,6 +194,7 @@ 3FCE2A951F58BE1D00D4855B /* sync_permission.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_permission.cpp; path = src/sync/sync_permission.cpp; sourceTree = ""; }; 3FCE2A981F58BE3600D4855B /* descriptor_ordering.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = descriptor_ordering.hpp; path = "object-store/src/descriptor_ordering.hpp"; sourceTree = SOURCE_ROOT; }; 3FCE2A991F58BE3600D4855B /* feature_checks.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = feature_checks.hpp; path = "object-store/src/feature_checks.hpp"; sourceTree = SOURCE_ROOT; }; + 426FCDFF1F7DA2F9005565DC /* sync_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_config.cpp; path = src/sync/sync_config.cpp; sourceTree = ""; }; 502B07E31E2CD1FA007A84ED /* object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = object.cpp; path = src/object.cpp; sourceTree = ""; }; 502B07E51E2CD20D007A84ED /* object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = object.hpp; path = src/object.hpp; sourceTree = ""; }; 504CF8541EBCAE3600A9A4B6 /* network_reachability_observer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = network_reachability_observer.cpp; sourceTree = ""; }; @@ -462,6 +464,7 @@ 02E315CC1DB80DE000555337 /* sync */ = { isa = PBXGroup; children = ( + 426FCDFF1F7DA2F9005565DC /* sync_config.cpp */, 504CF8521EBCAE3600A9A4B6 /* impl */, 02E315CD1DB80DF200555337 /* sync_client.hpp */, 02E315C21DB80DDD00555337 /* sync_config.hpp */, @@ -930,6 +933,7 @@ F63FF2CD1C12469E00B3B8E0 /* rpc.cpp in Sources */, 02F59EC41C88F17D007F774C /* schema.cpp in Sources */, 02F59EC51C88F17D007F774C /* shared_realm.cpp in Sources */, + 5D97DC4E1F7DAB1400B856A4 /* sync_config.cpp in Sources */, 504CF8601EBCAE3600A9A4B6 /* sync_file.cpp in Sources */, 02E315D21DB80DF200555337 /* sync_file.cpp in Sources */, 02E315C91DB80DDD00555337 /* sync_manager.cpp in Sources */, diff --git a/src/js_list.hpp b/src/js_list.hpp index 0bb33ef2..b96b103a 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -47,6 +47,7 @@ class List : public realm::List { template struct ListClass : ClassDefinition, CollectionClass> { + using Type = T; using ContextType = typename T::Context; using ObjectType = typename T::Object; using ValueType = typename T::Value; @@ -77,11 +78,17 @@ struct ListClass : ClassDefinition, CollectionClass> { static void is_valid(ContextType, ObjectType, Arguments, ReturnValue &); static void index_of(ContextType, ObjectType, Arguments, ReturnValue &); + // aggregate functions + static void min(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void max(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void sum(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void avg(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 &); - + std::string const name = "List"; MethodMap const methods = { @@ -95,6 +102,10 @@ struct ListClass : ClassDefinition, CollectionClass> { {"sorted", wrap}, {"isValid", wrap}, {"indexOf", wrap}, + {"min", wrap}, + {"max", wrap}, + {"sum", wrap}, + {"avg", wrap}, {"addListener", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, @@ -124,7 +135,27 @@ void ListClass::get_length(ContextType, ObjectType object, ReturnValue &retur } template -void ListClass::get_type(ContextType, ObjectType object, ReturnValue &return_value) { +void ListClass::min(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Min, ctx, this_object, argc, arguments, return_value); +} + +template +void ListClass::max(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Max, ctx, this_object, argc, arguments, return_value); +} + +template +void ListClass::sum(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Sum, ctx, this_object, argc, arguments, return_value); +} + +template +void ListClass::avg(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Avg, ctx, this_object, argc, arguments, return_value); +} + +template +void ListClass::get_type(ContextType ctx, ObjectType object, ReturnValue &return_value) { auto list = get_internal>(object); return_value.set(string_for_property_type(list->get_type() & ~realm::PropertyType::Flags)); } @@ -229,7 +260,7 @@ void ListClass::splice(ContextType ctx, ObjectType this_object, Arguments arg remove = std::max(Value::to_number(ctx, args[1]), 0); remove = std::min(remove, size - index); } - + std::vector removed_objects; removed_objects.reserve(remove); @@ -263,7 +294,7 @@ void ListClass::sorted(ContextType ctx, ObjectType this_object, Arguments arg auto list = get_internal>(this_object); return_value.set(ResultsClass::create_instance(ctx, list->sort(ResultsClass::get_keypaths(ctx, args)))); } - + template void ListClass::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); @@ -284,7 +315,7 @@ void ListClass::add_listener(ContextType ctx, ObjectType this_object, Argumen auto list = get_internal>(this_object); ResultsClass::add_listener(ctx, *list, this_object, args); } - + template void ListClass::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { auto list = get_internal>(this_object); diff --git a/src/js_results.hpp b/src/js_results.hpp index afb6e43f..45e68a18 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -20,6 +20,7 @@ #include "js_collection.hpp" #include "js_realm_object.hpp" +#include "js_util.hpp" #include "results.hpp" #include "list.hpp" @@ -53,6 +54,7 @@ class Results : public realm::Results { template struct ResultsClass : ClassDefinition, CollectionClass> { + using Type = T; using ContextType = typename T::Context; using ObjectType = typename T::Object; using ValueType = typename T::Value; @@ -84,7 +86,13 @@ struct ResultsClass : ClassDefinition, CollectionClass< template static void index_of(ContextType, Fn&, Arguments, ReturnValue &); - + + // aggregate functions + static void min(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void max(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void sum(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void avg(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 &); @@ -94,7 +102,7 @@ struct ResultsClass : ClassDefinition, CollectionClass< 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 = { @@ -102,18 +110,22 @@ struct ResultsClass : ClassDefinition, CollectionClass< {"filtered", wrap}, {"sorted", wrap}, {"isValid", wrap}, + {"min", wrap}, + {"max", wrap}, + {"sum", wrap}, + {"avg", wrap}, {"addListener", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, {"indexOf", wrap}, }; - + PropertyMap const properties = { {"length", {wrap, nullptr}}, {"type", {wrap, nullptr}}, {"optional", {wrap, nullptr}}, }; - + IndexPropertyType const index_accessor = {wrap, nullptr}; }; @@ -199,6 +211,26 @@ void ResultsClass::get_length(ContextType ctx, ObjectType object, ReturnValue return_value.set((uint32_t)results->size()); } +template +void ResultsClass::min(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Min, ctx, this_object, argc, arguments, return_value); +} + +template +void ResultsClass::max(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Max, ctx, this_object, argc, arguments, return_value); +} + +template +void ResultsClass::sum(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Sum, ctx, this_object, argc, arguments, return_value); +} + +template +void ResultsClass::avg(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + compute_aggregate_on_collection>(AggregateFunc::Avg, ctx, this_object, argc, arguments, return_value); +} + template void ResultsClass::get_type(ContextType, ObjectType object, ReturnValue &return_value) { auto results = get_internal>(object); @@ -211,7 +243,6 @@ void ResultsClass::get_optional(ContextType, ObjectType object, ReturnValue & 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); @@ -242,7 +273,7 @@ template void ResultsClass::is_valid(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } - + template template void ResultsClass::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) { @@ -287,7 +318,7 @@ void ResultsClass::add_listener(ContextType ctx, U& collection, ObjectType th 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[] { @@ -333,6 +364,6 @@ void ResultsClass::remove_all_listeners(ContextType ctx, ObjectType this_obje auto results = get_internal>(this_object); results->m_notification_tokens.clear(); } - + } // js } // realm diff --git a/src/js_sync.hpp b/src/js_sync.hpp index f8f30ace..8c7b4adf 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -358,7 +358,7 @@ void SessionClass::get_config(ContextType ctx, ObjectType object, ReturnValue if (auto session = get_internal>(object)->lock()) { ObjectType config = Object::create_empty(ctx); Object::set_property(ctx, config, "user", create_object>(ctx, new SharedUser(session->config().user))); - Object::set_property(ctx, config, "url", Value::from_string(ctx, session->config().realm_url)); + Object::set_property(ctx, config, "url", Value::from_string(ctx, session->config().realm_url())); if (auto* dispatcher = session->config().error_handler.template target>()) { auto& handler = *dispatcher->func().template target>(); Object::set_property(ctx, config, "error", handler.func()); @@ -576,7 +576,7 @@ std::function SyncClass::session_bind_callback(Contex ValueType arguments[3]; arguments[0] = create_object>(protected_ctx, new SharedUser(config.user)); arguments[1] = Value::from_string(protected_ctx, path); - arguments[2] = Value::from_string(protected_ctx, config.realm_url); + arguments[2] = Value::from_string(protected_ctx, config.realm_url()); Function::call(protected_ctx, refreshAccessToken, 3, arguments); }); } diff --git a/src/js_types.hpp b/src/js_types.hpp index 24f13c33..09b5e05a 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #if defined(__GNUC__) && !(defined(DEBUG) && DEBUG) # define REALM_JS_INLINE inline __attribute__((always_inline)) @@ -136,6 +138,8 @@ struct Value { static ValueType from_nonnull_string(ContextType, const String&); static ValueType from_nonnull_binary(ContextType, BinaryData); static ValueType from_undefined(ContextType); + static ValueType from_timestamp(ContextType, Timestamp); + static ValueType from_mixed(ContextType, util::Optional &); static ObjectType to_array(ContextType, const ValueType &); static bool to_boolean(ContextType, const ValueType &); @@ -436,5 +440,37 @@ inline bool Value::is_valid_for_property_type(ContextType context, const Valu return true; } +template +inline typename T::Value Value::from_timestamp(typename T::Context ctx, Timestamp ts) { + return Object::create_date(ctx, ts.get_seconds() * 1000 + ts.get_nanoseconds() / 1000000); +} + +template +inline typename T::Value Value::from_mixed(typename T::Context ctx, util::Optional& mixed) { + if (!mixed) { + return from_undefined(ctx); + } + + Mixed value = *mixed; + switch (value.get_type()) { + case type_Bool: + return from_boolean(ctx, value.get_bool()); + case type_Int: + return from_number(ctx, static_cast(value.get_int())); + case type_Float: + return from_number(ctx, value.get_float()); + case type_Double: + return from_number(ctx, value.get_double()); + case type_Timestamp: + return from_timestamp(ctx, value.get_timestamp()); + case type_String: + return from_string(ctx, value.get_string().data()); + case type_Binary: + return from_binary(ctx, value.get_binary()); + default: + throw std::invalid_argument("Value not convertible."); + } +} + } // js } // realm diff --git a/src/js_util.hpp b/src/js_util.hpp index 07ba6e30..cf803798 100644 --- a/src/js_util.hpp +++ b/src/js_util.hpp @@ -22,11 +22,19 @@ #include #include +#include "object_schema.hpp" #include "shared_realm.hpp" namespace realm { namespace js { +enum class AggregateFunc { + Min, + Max, + Sum, + Avg +}; + template class RealmDelegate; @@ -75,5 +83,51 @@ static inline void validate_argument_count_at_least(size_t count, size_t expecte } } +template +static inline void compute_aggregate_on_collection(AggregateFunc func, typename T::ContextType ctx, typename T::ObjectType this_object, size_t argc, const typename T::ValueType arguments[], typename T::ReturnValue &return_value) { + validate_argument_count(argc, 1); + std::string property_name = T::Value::validated_to_string(ctx, arguments[0]); + + auto list = get_internal(this_object); + + const ObjectSchema& object_schema = list->get_object_schema(); + const Property* property = object_schema.property_for_name(property_name); + if (!property) { + throw std::invalid_argument(util::format("No such property: %1", property_name)); + } + + util::Optional mixed; + switch (func) { + case AggregateFunc::Min: { + mixed = list->min(property->table_column); + break; + } + case AggregateFunc::Max: { + mixed = list->max(property->table_column); + break; + } + case AggregateFunc::Sum: { + mixed = list->sum(property->table_column); + break; + } + case AggregateFunc::Avg: { + util::Optional avg = list->average(property->table_column); + if (!avg) { + return_value.set_undefined(); + } + else { + return_value.set(*avg); + } + return; + } + default: { + REALM_ASSERT(false && "Unknown aggregate function"); + REALM_UNREACHABLE(); + } + } + + return_value.set(T::Value::from_mixed(ctx, mixed)); +} + } // js } // realm diff --git a/src/object-store b/src/object-store index 49705c0f..0d0615ca 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 49705c0fbdbff8536cfa134fc17b837db4385d31 +Subproject commit 0d0615caaf0df6dbccca3f86a05303892c3a7a2e diff --git a/tests/js/list-tests.js b/tests/js/list-tests.js index e7a84420..b05fc302 100644 --- a/tests/js/list-tests.js +++ b/tests/js/list-tests.js @@ -1004,4 +1004,199 @@ module.exports = { TestCase.assertEqual(list.isValid(), false); TestCase.assertThrowsContaining(() => list.length, 'invalidated'); }, + + testListAggregateFunctions: function() { + const NullableBasicTypesList = { + name: 'NullableBasicTypesList', + properties: { + list: {type: 'list', objectType: 'NullableBasicTypesObject'}, + } + }; + + var realm = new Realm({schema: [schemas.NullableBasicTypes, NullableBasicTypesList]}); + var object; + + const N = 50; + + var list = []; + for(var i = 0; i < N; i++) { + list.push({ + intCol: i+1, + floatCol: i+1, + doubleCol: i+1, + dateCol: new Date(i+1) + }); + } + + realm.write(() => { + object = realm.create('NullableBasicTypesList', {list: list}); + }); + + TestCase.assertEqual(object.list.length, N); + + // int, float & double columns support all aggregate functions + ['intCol', 'floatCol', 'doubleCol'].forEach(colName => { + TestCase.assertEqual(object.list.min(colName), 1); + TestCase.assertEqual(object.list.max(colName), N); + TestCase.assertEqual(object.list.sum(colName), N*(N+1)/2); + TestCase.assertEqual(object.list.avg(colName), (N+1)/2); + }); + + // date columns support only 'min' & 'max' + TestCase.assertEqual(object.list.min('dateCol').getTime(), new Date(1).getTime()); + TestCase.assertEqual(object.list.max('dateCol').getTime(), new Date(N).getTime()); + }, + + testListAggregateFunctionsWithNullColumnValues: function() { + const NullableBasicTypesList = { + name: 'NullableBasicTypesList', + properties: { + list: {type: 'list', objectType: 'NullableBasicTypesObject'}, + } + }; + + var realm = new Realm({schema: [schemas.NullableBasicTypes, NullableBasicTypesList]}); + var object; + var objectEmptyList; + + const N = 50; + const M = 10; + + var list = []; + for(var i = 0; i < N; i++) { + list.push({ + intCol: i+1, + floatCol: i+1, + doubleCol: i+1, + dateCol: new Date(i+1) + }); + } + + for(var j = 0; j < M; j++) { + list.push({ + intCol: null, + floatCol: null, + doubleCol: null, + dateCol: null + }); + } + + realm.write(() => { + object = realm.create('NullableBasicTypesList', {list: list}); + objectEmptyList = realm.create('NullableBasicTypesList', {list: []}); + }); + + TestCase.assertEqual(object.list.length, N + M); + + + // int, float & double columns support all aggregate functions + // the M null valued objects should be ignored + ['intCol', 'floatCol', 'doubleCol'].forEach(colName => { + TestCase.assertEqual(object.list.min(colName), 1); + TestCase.assertEqual(object.list.max(colName), N); + TestCase.assertEqual(object.list.sum(colName), N*(N+1)/2); + TestCase.assertEqual(object.list.avg(colName), (N+1)/2); + }); + + // date columns support only 'min' & 'max' + TestCase.assertEqual(object.list.min('dateCol').getTime(), new Date(1).getTime()); + TestCase.assertEqual(object.list.max('dateCol').getTime(), new Date(N).getTime()); + + // call aggregate functions on empty list + TestCase.assertEqual(objectEmptyList.list.length, 0); + ['intCol', 'floatCol', 'doubleCol'].forEach(colName => { + TestCase.assertUndefined(objectEmptyList.list.min(colName)); + TestCase.assertUndefined(objectEmptyList.list.max(colName)); + TestCase.assertEqual(objectEmptyList.list.sum(colName), 0); + TestCase.assertUndefined(objectEmptyList.list.avg(colName)); + }); + + TestCase.assertUndefined(objectEmptyList.list.min('dateCol')); + TestCase.assertUndefined(objectEmptyList.list.max('dateCol')); + }, + + testListAggregateFunctionsUnsupported: function() { + const NullableBasicTypesList = { + name: 'NullableBasicTypesList', + properties: { + list: {type: 'list', objectType: 'NullableBasicTypesObject'}, + } + }; + + var realm = new Realm({schema: [schemas.NullableBasicTypes, NullableBasicTypesList]}); + var object; + + const N = 5; + + var list = []; + for(var i = 0; i < N; i++) { + list.push({ + intCol: i+1, + floatCol: i+1, + doubleCol: i+1, + dateCol: new Date(i+1) + }); + } + + realm.write(() => { + object = realm.create('NullableBasicTypesList', {list: list}); + }); + + TestCase.assertEqual(object.list.length, N); + + // bool, string & data columns don't support 'min' + ['boolCol', 'stringCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + object.list.min(colName); + } + )}); + + // bool, string & data columns don't support 'max' + ['boolCol', 'stringCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + object.list.max(colName); + } + )}); + + // bool, string, date & data columns don't support 'avg' + ['boolCol', 'stringCol', 'dateCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + object.list.avg(colName); + } + )}); + + // bool, string, date & data columns don't support 'sum' + ['boolCol', 'stringCol', 'dateCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + object.list.sum(colName); + } + )}); + }, + + testListAggregateFunctionsWrongProperty: function() { + var realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]}); + var object; + var list; + realm.write(() => { + object = realm.create('PersonList', {list: [ + {name: 'Ari', age: 10}, + {name: 'Tim', age: 11}, + {name: 'Bjarne', age: 12}, + ]}); + }); + + TestCase.assertThrows(function() { + object.list.min('foo') + }); + TestCase.assertThrows(function() { + object.list.max('foo') + }); + TestCase.assertThrows(function() { + object.list.sum('foo') + }); + TestCase.assertThrows(function() { + object.list.avg('foo') + }); + + }, }; diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index b8458870..71cf3ab5 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -455,6 +455,156 @@ module.exports = { }); resolve = r; }); + }) + }, + + testResultsAggregateFunctions: function() { + var realm = new Realm({ schema: [schemas.NullableBasicTypes] }); + const N = 50; + realm.write(() => { + for(var i = 0; i < N; i++) { + realm.create('NullableBasicTypesObject', { + intCol: i+1, + floatCol: i+1, + doubleCol: i+1, + dateCol: new Date(i+1) + }); + } }); + + var results = realm.objects('NullableBasicTypesObject'); + TestCase.assertEqual(results.length, N); + + // int, float & double columns support all aggregate functions + ['intCol', 'floatCol', 'doubleCol'].forEach(colName => { + TestCase.assertEqual(results.min(colName), 1); + TestCase.assertEqual(results.max(colName), N); + TestCase.assertEqual(results.sum(colName), N*(N+1)/2); + TestCase.assertEqual(results.avg(colName), (N+1)/2); + }); + + // date columns support only 'min' & 'max' + TestCase.assertEqual(results.min('dateCol').getTime(), new Date(1).getTime()); + TestCase.assertEqual(results.max('dateCol').getTime(), new Date(N).getTime()); + }, + + testResultsAggregateFunctionsWithNullColumnValues: function() { + var realm = new Realm({ schema: [schemas.NullableBasicTypes] }); + + const N = 50; + const M = 10; + + realm.write(() => { + for(var i = 0; i < N; i++) { + realm.create('NullableBasicTypesObject', { + intCol: i+1, + floatCol: i+1, + doubleCol: i+1, + dateCol: new Date(i+1) + }); + } + + // add some null valued data, which should be ignored by the aggregate functions + for(var j = 0; j < M; j++) { + realm.create('NullableBasicTypesObject', { + intCol: null, + floatCol: null, + doubleCol: null, + dateCol: null + }); + } + }); + + var results = realm.objects('NullableBasicTypesObject'); + + TestCase.assertEqual(results.length, N + M); + + // int, float & double columns support all aggregate functions + // the M null valued objects should be ignored + ['intCol', 'floatCol', 'doubleCol'].forEach(colName => { + TestCase.assertEqual(results.min(colName), 1); + TestCase.assertEqual(results.max(colName), N); + TestCase.assertEqual(results.sum(colName), N*(N+1)/2); + TestCase.assertEqual(results.avg(colName), (N+1)/2); + }); + + // date columns support only 'min' & 'max' + TestCase.assertEqual(results.min('dateCol').getTime(), new Date(1).getTime()); + TestCase.assertEqual(results.max('dateCol').getTime(), new Date(N).getTime()); + + // call aggregate functions on empty results + var emptyResults = realm.objects('NullableBasicTypesObject').filtered('intCol < 0'); + TestCase.assertEqual(emptyResults.length, 0); + ['intCol', 'floatCol', 'doubleCol'].forEach(colName => { + TestCase.assertUndefined(emptyResults.min(colName)); + TestCase.assertUndefined(emptyResults.max(colName)); + TestCase.assertEqual(emptyResults.sum(colName), 0); + TestCase.assertUndefined(emptyResults.avg(colName)); + }); + + TestCase.assertUndefined(emptyResults.min('dateCol')); + TestCase.assertUndefined(emptyResults.max('dateCol')); + }, + + testResultsAggregateFunctionsUnsupported: function() { + var realm = new Realm({ schema: [schemas.NullableBasicTypes] }); + realm.write(() => { + realm.create('NullableBasicTypesObject', { + boolCol: true, + stringCol: "hello", + dataCol: new ArrayBuffer(12), + }); + }); + + var results = realm.objects('NullableBasicTypesObject'); + + // bool, string & data columns don't support 'min' + ['boolCol', 'stringCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + results.min(colName); + } + )}); + + // bool, string & data columns don't support 'max' + ['boolCol', 'stringCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + results.max(colName); + } + )}); + + // bool, string, date & data columns don't support 'avg' + ['boolCol', 'stringCol', 'dateCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + results.avg(colName); + } + )}); + + // bool, string, date & data columns don't support 'sum' + ['boolCol', 'stringCol', 'dateCol', 'dataCol'].forEach(colName => { + TestCase.assertThrows(function() { + results.sum(colName); + } + )}); + }, + + testResultsAggregateFunctionsWrongProperty: function() { + var realm = new Realm({ schema: [ schemas.TestObject ]}); + realm.write(() => { + realm.create('TestObject', { doubleCol: 42 }); + }); + var results = realm.objects('TestObject'); + TestCase.assertThrows(function() { + results.min('foo') + }); + TestCase.assertThrows(function() { + results.max('foo') + }); + TestCase.assertThrows(function() { + results.sum('foo') + }); + TestCase.assertThrows(function() { + results.avg('foo') + }); + } }; diff --git a/tests/js/schemas.js b/tests/js/schemas.js index 6a67bd31..6d9ecc75 100644 --- a/tests/js/schemas.js +++ b/tests/js/schemas.js @@ -252,6 +252,19 @@ exports.NullQueryObject = { ] }; +exports.NullableBasicTypes = { + name: 'NullableBasicTypesObject', + properties: [ + {name: 'boolCol', type: 'bool?'}, + {name: 'intCol', type: 'int?'}, + {name: 'floatCol', type: 'float?'}, + {name: 'doubleCol', type: 'double?'}, + {name: 'stringCol', type: 'string?'}, + {name: 'dateCol', type: 'date?'}, + {name: 'dataCol', type: 'data?'}, + ] +}; + exports.DateObject = { name: 'Date', properties: {