Merge pull request #1365 from realm/kneth/master-to-2.0.x
Merging `master` into `2.0.x`
This commit is contained in:
commit
87efc0bd60
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -33,17 +33,22 @@
|
|||
### Breaking changes
|
||||
* None
|
||||
|
||||
### Enhancements
|
||||
* Add a callback function used to verify SSL certificates in the sync config.
|
||||
|
||||
### 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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -32,6 +32,10 @@ createMethods(List.prototype, objectTypes.LIST, [
|
|||
'snapshot',
|
||||
'isValid',
|
||||
'indexOf',
|
||||
'min',
|
||||
'max',
|
||||
'sum',
|
||||
'avg',
|
||||
'addListener',
|
||||
'removeListener',
|
||||
'removeAllListeners',
|
||||
|
|
|
@ -31,6 +31,10 @@ createMethods(Results.prototype, objectTypes.RESULTS, [
|
|||
'snapshot',
|
||||
'isValid',
|
||||
'indexOf',
|
||||
'min',
|
||||
'max',
|
||||
'sum',
|
||||
'avg',
|
||||
'addListener',
|
||||
'removeListener',
|
||||
'removeAllListeners',
|
||||
|
|
|
@ -143,6 +143,34 @@ declare namespace Realm {
|
|||
*/
|
||||
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
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ class List : public realm::List {
|
|||
|
||||
template<typename T>
|
||||
struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
|
||||
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<T, realm::js::List<T>, CollectionClass<T>> {
|
|||
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<T> const methods = {
|
||||
|
@ -95,6 +102,10 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
|
|||
{"sorted", wrap<sorted>},
|
||||
{"isValid", wrap<is_valid>},
|
||||
{"indexOf", wrap<index_of>},
|
||||
{"min", wrap<min>},
|
||||
{"max", wrap<max>},
|
||||
{"sum", wrap<sum>},
|
||||
{"avg", wrap<avg>},
|
||||
{"addListener", wrap<add_listener>},
|
||||
{"removeListener", wrap<remove_listener>},
|
||||
{"removeAllListeners", wrap<remove_all_listeners>},
|
||||
|
@ -124,7 +135,27 @@ void ListClass<T>::get_length(ContextType, ObjectType object, ReturnValue &retur
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
|
||||
void ListClass<T>::min(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ListClass<T>>(AggregateFunc::Min, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::max(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ListClass<T>>(AggregateFunc::Max, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::sum(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ListClass<T>>(AggregateFunc::Sum, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::avg(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ListClass<T>>(AggregateFunc::Avg, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::get_type(ContextType ctx, 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));
|
||||
}
|
||||
|
@ -229,7 +260,7 @@ void ListClass<T>::splice(ContextType ctx, ObjectType this_object, Arguments arg
|
|||
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);
|
||||
|
||||
|
@ -263,7 +294,7 @@ void ListClass<T>::sorted(ContextType ctx, ObjectType this_object, Arguments arg
|
|||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
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, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid());
|
||||
|
@ -284,7 +315,7 @@ void ListClass<T>::add_listener(ContextType ctx, ObjectType this_object, Argumen
|
|||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
ResultsClass<T>::add_listener(ctx, *list, this_object, args);
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
void ListClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
auto list = get_internal<T, ListClass<T>>(this_object);
|
||||
|
|
|
@ -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<typename T>
|
||||
struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<T>> {
|
||||
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<T, realm::js::Results<T>, CollectionClass<
|
|||
|
||||
template<typename Fn>
|
||||
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<T, realm::js::Results<T>, CollectionClass<
|
|||
static void add_listener(ContextType, U&, ObjectType, Arguments);
|
||||
template<typename U>
|
||||
static void remove_listener(ContextType, U&, ObjectType, Arguments);
|
||||
|
||||
|
||||
std::string const name = "Results";
|
||||
|
||||
MethodMap<T> const methods = {
|
||||
|
@ -102,18 +110,22 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
|
|||
{"filtered", wrap<filtered>},
|
||||
{"sorted", wrap<sorted>},
|
||||
{"isValid", wrap<is_valid>},
|
||||
{"min", wrap<min>},
|
||||
{"max", wrap<max>},
|
||||
{"sum", wrap<sum>},
|
||||
{"avg", wrap<avg>},
|
||||
{"addListener", wrap<add_listener>},
|
||||
{"removeListener", wrap<remove_listener>},
|
||||
{"removeAllListeners", wrap<remove_all_listeners>},
|
||||
{"indexOf", wrap<index_of>},
|
||||
};
|
||||
|
||||
|
||||
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};
|
||||
};
|
||||
|
||||
|
@ -199,6 +211,26 @@ void ResultsClass<T>::get_length(ContextType ctx, ObjectType object, ReturnValue
|
|||
return_value.set((uint32_t)results->size());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::min(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ResultsClass<T>>(AggregateFunc::Min, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::max(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ResultsClass<T>>(AggregateFunc::Max, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::sum(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ResultsClass<T>>(AggregateFunc::Sum, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::avg(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
|
||||
compute_aggregate_on_collection<ResultsClass<T>>(AggregateFunc::Avg, ctx, this_object, argc, arguments, return_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ResultsClass<T>::get_type(ContextType, ObjectType object, ReturnValue &return_value) {
|
||||
auto results = get_internal<T, ResultsClass<T>>(object);
|
||||
|
@ -211,7 +243,6 @@ void ResultsClass<T>::get_optional(ContextType, ObjectType object, ReturnValue &
|
|||
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);
|
||||
|
@ -242,7 +273,7 @@ template<typename T>
|
|||
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>
|
||||
template<typename Fn>
|
||||
void ResultsClass<T>::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnValue &return_value) {
|
||||
|
@ -287,7 +318,7 @@ void ResultsClass<T>::add_listener(ContextType ctx, U& collection, ObjectType th
|
|||
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 = collection.add_notification_callback([=](CollectionChangeSet const& change_set, std::exception_ptr exception) {
|
||||
HANDLESCOPE
|
||||
ValueType arguments[] {
|
||||
|
@ -333,6 +364,6 @@ void ResultsClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_obje
|
|||
auto results = get_internal<T, ResultsClass<T>>(this_object);
|
||||
results->m_notification_tokens.clear();
|
||||
}
|
||||
|
||||
|
||||
} // js
|
||||
} // realm
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#include <realm/binary_data.hpp>
|
||||
#include <realm/string_data.hpp>
|
||||
#include <realm/util/to_string.hpp>
|
||||
#include <realm/util/optional.hpp>
|
||||
#include <realm/mixed.hpp>
|
||||
|
||||
#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<T>&);
|
||||
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<Mixed> &);
|
||||
|
||||
static ObjectType to_array(ContextType, const ValueType &);
|
||||
static bool to_boolean(ContextType, const ValueType &);
|
||||
|
@ -436,5 +440,37 @@ inline bool Value<T>::is_valid_for_property_type(ContextType context, const Valu
|
|||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename T::Value Value<T>::from_timestamp(typename T::Context ctx, Timestamp ts) {
|
||||
return Object<T>::create_date(ctx, ts.get_seconds() * 1000 + ts.get_nanoseconds() / 1000000);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename T::Value Value<T>::from_mixed(typename T::Context ctx, util::Optional<Mixed>& 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<double>(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
|
||||
|
|
|
@ -22,11 +22,19 @@
|
|||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "object_schema.hpp"
|
||||
#include "shared_realm.hpp"
|
||||
|
||||
namespace realm {
|
||||
namespace js {
|
||||
|
||||
enum class AggregateFunc {
|
||||
Min,
|
||||
Max,
|
||||
Sum,
|
||||
Avg
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class RealmDelegate;
|
||||
|
||||
|
@ -75,5 +83,51 @@ static inline void validate_argument_count_at_least(size_t count, size_t expecte
|
|||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<typename T::Type, T>(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> 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<double> 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
|
||||
|
|
|
@ -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')
|
||||
});
|
||||
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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')
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue