Merge branch 'master' of github.com:realm/realm-js into yg/windows-sync

# Conflicts:
#	binding.gyp
#	src/object-store
This commit is contained in:
Yavor Georgiev 2017-10-12 12:52:47 +02:00
commit 2ac8160b32
No known key found for this signature in database
GPG Key ID: 83FC145DA0CCA9C3
49 changed files with 1823 additions and 346 deletions

View File

@ -1,20 +1,11 @@
<!--
Make sure to assign one and only one Type (`T:`) and State (`S:`) label.
Select reviewers if ready for review. Our bot will automatically assign you.
-->
<!-- Make sure to assign one and only one Type (`T:`) and State (`S:`) label.
Select reviewers if ready for review. Our bot will automatically assign you. -->
## What, How & Why?
<!-- Describe the changes and give some hints to guide your reviewers if possible. -->
<!-- E.g. reference to other repos: This closes realm/realm-sync#??? -->
<!--
Describe the changes and give some hints to guide your reviewers if possible.
-->
Reference to the issue(s) addressed by this PR: # ???
<!--
- This fixes #???
- This closes realm/realm-sync#???
-->
This closes # ???
## ☑️ ToDos
<!-- Add your own todos here -->

View File

@ -1,16 +1,103 @@
2.0.0 Release notes (2017-9-29)
2.0.0-rc20 Release notes (2017-10-11)
=============================================================
### Breaking changes
* None
### Enhancements
* Added `update` method to `Realm.Results` to support bulk updates (#808).
* Added support for aggregate functions on `Realm.Results` and `Realm.List` of primitive types.
### Bug fixes
* Avoid closing then reopening a sync session when using `Realm.open` (#1391).
* Respect custom Realm paths when using `Realm.open` (#1392 and #1393).
### Internal
* Upgrading to Realm Sync 2.0.0-rc28.
* Upgrading to Realm Object Server 2.0.0-rc.4.
* OpenSSL for Android is distributed in a separate package, and the build system needed updates to accommendate this.
* Added `-fvisibility=hidden` to Android builds (reduces size of `.so` file).
2.0.0-rc19 Release notes (2017-10-7)
=============================================================
### Breaking changes
* None.
### Enhancements
* None
### Bug fixes
* None
### Internal
* Update object store libs to fix partial sync
1.13.0 Release notes (2017-10-5)
=============================================================
### 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).
* Added `deleteRealmIfMigrationNeeded` to configuration to delete a Realm if migration needed (#502).
### 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-rc18 Release notes (2017-10-4)
=============================================================
### Breaking changes
* Deprecate node 4 and node 5 support
### Enhancements
* None
### Bug fixes
* Fixed bug in `Realm.subscribeToObjects()`.
### Internal
* None
2.0.0-rc17 Release notes (2017-10-3)
=============================================================
### Breaking changes
* Removed `setAccessToken()`; use `setFeatureToken()` instead.
* During iteration (`for ... of`) of `Realm.Results`, the results will be frozen using the `snapshot()` method (#1366).
### Enhancements
* Support migration from Realms sync 1.0 to sync 2.0 versions
* Handling of the situation when the client has to reset the Realm due to diverging histories (#795).
* Added `Realm.subscribeToObjects()` to listen for changes in partially synced Realms.
### Bug fixes
* None.
### Internal
* Upgraded to Realm Sync 2.0.0-rc27.
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
* None.
* None
### 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,19 +114,11 @@
### 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).
2.0.0-rc10 Release notes (2017-9-19)
=============================================================
### Breaking changes

19
Jenkinsfile vendored
View File

@ -175,7 +175,24 @@ def doAndroidBuild(target, postStep = null) {
def doDockerBuild(target, postStep = null) {
return {
node('docker') {
doDockerInside("./scripts/docker-wrapper.sh ./scripts/test.sh", target, postStep)
deleteDir()
unstash 'source'
try {
reportStatus(target, 'PENDING', 'Build has started')
docker.image('node:6').inside('-e HOME=/tmp') {
sh "scripts/test.sh ${target}"
if(postStep) {
postStep.call()
}
deleteDir()
reportStatus(target, 'SUCCESS', 'Success!')
}
} catch(Exception e) {
reportStatus(target, 'FAILURE', e.toString())
throw e
}
}
}
}

View File

@ -1,5 +1,5 @@
PACKAGE_NAME=realm-js
VERSION=2.0.0-rc14
REALM_CORE_VERSION=4.0.0
REALM_SYNC_VERSION=2.0.0-rc25
REALM_OBJECT_SERVER_VERSION=2.0.0-alpha.36
VERSION=2.0.0-rc20
REALM_CORE_VERSION=4.0.1
REALM_SYNC_VERSION=2.0.0-rc28
REALM_OBJECT_SERVER_VERSION=2.0.0-rc.4

View File

@ -24,6 +24,11 @@
* accessed in any of the ways that a normal Javascript Array can, including
* subscripting, enumerating with `for-of` and so on.
*
* A Collection always reflect the current state of the Realm. The one exception to this is
* when using `for...in` or `for...of` enumeration, which will always enumerate over the
* objects which matched the query when the enumeration is begun, even if some of them are
* deleted or modified to be excluded by the filter during the enumeration.
*
* @memberof Realm
* @since 0.11.0
*/
@ -233,6 +238,64 @@ class Collection {
*/
indexOf(object) {}
/**
* Returns the minimum value of the values in the collection or of the
* given property among all the objects in the collection, or `undefined`
* if the collection is empty.
*
* Only supported for int, float, double and date properties. `null` values
* are ignored entirely by this method and will not be returned.
*
* @param {string} [property] - For a collection of objects, the property to take the minimum of.
* @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) {}
/**
* Returns the maximum value of the values in the collection or of the
* given property among all the objects in the collection, or `undefined`
* if the collection is empty.
*
* Only supported for int, float, double and date properties. `null` values
* are ignored entirely by this method and will not be returned.
*
* @param {string} [property] - For a collection of objects, the property to take the maximum of.
* @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 the values in the collection or of the given
* property among all the objects in the collection, or 0 if the collection
* is empty.
*
* Only supported for int, float and double properties. `null` values are
* ignored entirely by this method.
* @param {string} [property] - For a collection of objects, the property to take the sum of.
* @throws {Error} If no property with the name exists or if property is not numeric.
* @returns {number} the sum.
* @since 1.12.1
*/
sum(property) {}
/**
* Computes the average of the values in the collection or of the given
* property among all the objects in the collection, or `undefined` if the collection
* is empty.
*
* Only supported for int, float and double properties. `null` values are
* ignored entirely by this method and will not be factored into the average.
* @param {string} [property] - For a collection of objects, the property to take the average of.
* @throws {Error} If no property with the name exists or if property is not numeric.
* @returns {number} the sum.
* @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.

View File

@ -88,6 +88,7 @@ class Realm {
* migrated to use the new schema.
* @param {Realm~Configuration} [config] - **Required** when first creating the Realm.
* @throws {Error} If anything in the provided `config` is invalid.
* @throws {IncompatibleSyncedRealmError} when an incompatible synced Realm is opened
*/
constructor(config) {}
@ -105,7 +106,8 @@ class Realm {
* @param {Realm~Configuration} config
* @param {callback(error, realm)} - will be called when the Realm is ready.
* @param {callback(transferred, transferable)} [progressCallback] - an optional callback for download progress notifications
* @throws {Error} If anything in the provided `config` is invalid.
* @throws {Error} If anything in the provided `config` is invalid
* @throws {IncompatibleSyncedRealmError} when an incompatible synced Realm is opened
*/
static openAsync(config, callback, progressCallback) {}
@ -214,7 +216,7 @@ class Realm {
*/
cancelTransaction() {}
/*
/**
* Replaces all string columns in this Realm with a string enumeration column and compacts the
* database file.
*
@ -231,6 +233,17 @@ class Realm {
* @returns {true} if compaction succeeds.
*/
compact() {}
/**
* If the Realm is a partially synchronized Realm, fetch and synchronize the objects
* of a given object type that match the given query (in string format).
*
* **Partial synchronization is a tech preview. Its APIs are subject to change.**
* @param {Realm~ObjectType} type - The type of Realm objects to retrieve.
* @param {string} query - Query used to filter objects.
* @return {Promise} - a promise that will be resolved with the Realm.Results instance when it's available.
*/
subscribeToObjects(className, query, callback) {}
}
/**
@ -269,6 +282,8 @@ Realm.defaultPath;
* This function takes two arguments:
* - `oldRealm` - The Realm before migration is performed.
* - `newRealm` - The Realm that uses the latest `schema`, which should be modified as necessary.
* @property {boolean} [deleteRealmIfMigrationNeeded=false] - Specifies if this Realm should be deleted
* if a migration is needed.
* @property {callback(number, number)} [shouldCompactOnLaunch] - The function called when opening
* a Realm for the first time during the life of a process to determine if it should be compacted
* before being returned to the user. The function takes two arguments:
@ -331,7 +346,10 @@ Realm.defaultPath;
* The purpose of open_ssl_verify_callback is to enable custom certificate handling and to solve cases where
* OpenSSL erroneously rejects valid certificates possibly because OpenSSL doesn't have access to the
* proper trust certificates.
*
* - `partial` - Whether this Realm should be opened in 'partial synchronization' mode.
* Partial synchronisation only synchronizes those objects that match the query specified in contrast
* to the normal mode of operation that synchronises all objects in a remote Realm.
* **Partial synchronization is a tech preview. Its APIs are subject to change.**
*/
/**

View File

@ -28,4 +28,12 @@
* @memberof Realm
*/
class Results extends Collection {
/**
* Bulk update objects in the collection.
* @param {string} property - The name of the property.
* @param {string} value - The updated property value.
* @throws {Error} If no property with the name exists.
* @since 2.0.0-rc20
*/
update(property, value) {}
}

View File

@ -17,6 +17,13 @@
////////////////////////////////////////////////////////////////////////////
/**
* When opening a Realm created with Realm Mobile Platform v1.x, it is automatically
* migrated to the v2.x format. In case this migration
* is not possible, an exception is thrown. The exception´s `message` property will be equal
* to `IncompatibleSyncedRealmException`. The Realm is backed up, and the property `configuration`
* is a {Realm~Configuration} which refers to it. You can open it as a local, read-only Realm, and
* copy objects to a new synced Realm.
*
* @memberof Realm
*/
class Sync {
@ -24,9 +31,9 @@ class Sync {
* Add a sync listener to listen to changes across multiple Realms
* @param {string} server_url - the sync server to listen to
* @param {SyncUser} admin_user - an admin user obtained by calling `new Realm.Sync.User.adminUser`
* @param {string} regex - a regular expression used to determine which cahnged Realms should trigger events -
* @param {string} regex - a regular expression used to determine which changed Realms should trigger events -
* Use `.*` to match all all Realms
* @param {string} name - The name of event that should cause the callback to be called
* @param {string} name - The name of the event that should trigger the callback to be called
* _Currently only the 'change' event is supported_
* @param {function(change_event)} change_callback - called when changes are made to any Realm which
* match the given regular expression
@ -55,11 +62,26 @@ class Sync {
*/
static setLogLevel(log_level) {}
/**
* Initiate a client reset. The Realm must be closed prior to the reset.
* @param {string} [path] - The path to the Realm to reset.
* Throws error if reset is not possible.
* @example
* {
* const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } };
* config.sync.error = (sender, error) => {
* if (error.code === 7) { // 7 -> client reset
* Realm.Sync.initiateClientReset(original_path);
* // copy required objects from Realm at error.config.path
* }
* }
* }
*/
static initiateClientReset(path) {}
}
/**
* Change info passed when receiving sync 'change' events
* Change information passed when receiving sync 'change' events
* @memberof Realm.Sync
*/
class ChangeEvent {
@ -122,6 +144,23 @@ class AuthError extends Error {
get type() {}
}
/**
* Describes an error when an incompatible synced Realm is opened. The old version of the Realm can be accessed in readonly mode using the configuration() member
* @memberof Realm.Sync
*/
class IncompatibleSyncedRealmError {
/**
* The name of the error is 'IncompatibleSyncedRealmError'
*/
get name() {}
/**
* The {Realm~Configuration} of the backed up Realm.
* @type {Realm~Configuration}
*/
get configuration() {}
}
/**
* Class for logging in and managing Sync users.
* @memberof Realm.Sync
@ -248,7 +287,7 @@ class User {
* @param {string} recipient the optional recipient of the permission. Can be either
* 'any' which is the default, or 'currentUser' or 'otherUser' if you want only permissions
* belonging to the user or *not* belonging to the user.
* @returns {Results} a queryable collection of permission objects that provides detailed
* @returns {Promise} a Promise with a queryable collection of permission objects that provides detailed
* information regarding the granted access.
* The collection is a live query similar to what you would get by callig Realm.objects,
* so the same features apply - you can listen for notifications or filter it.

View File

@ -60,6 +60,7 @@ function setupRealm(realm, realmId) {
'schemaVersion',
'syncSession',
'isInTransaction',
'subscribeToObjects',
].forEach((name) => {
Object.defineProperty(realm, name, {get: util.getterForProperty(name)});
});
@ -127,6 +128,7 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [
'removeListener',
'removeAllListeners',
'close',
'_waitForDownload',
]);
// Mutating methods:
@ -188,12 +190,6 @@ Object.defineProperties(Realm, {
rpc.clearTestState();
},
},
_waitForDownload: {
value: function(_config, sessionCallback, callback) {
sessionCallback();
callback();
}
},
});
for (let i = 0, len = debugHosts.length; i < len; i++) {

View File

@ -32,6 +32,10 @@ createMethods(List.prototype, objectTypes.LIST, [
'snapshot',
'isValid',
'indexOf',
'min',
'max',
'sum',
'avg',
'addListener',
'removeListener',
'removeAllListeners',

View File

@ -25,17 +25,27 @@ import { createMethods } from './util';
export default class Results extends Collection {
}
// Non-mutating methods:
createMethods(Results.prototype, objectTypes.RESULTS, [
'filtered',
'sorted',
'snapshot',
'isValid',
'indexOf',
'min',
'max',
'sum',
'avg',
'addListener',
'removeListener',
'removeAllListeners',
]);
// Mutating methods:
createMethods(Results.prototype, objectTypes.RESULTS, [
'update',
], true);
export function createResults(realmId, info) {
return createCollection(Results.prototype, realmId, info);
}

View File

@ -212,6 +212,23 @@ function makeRequest(url, data) {
return JSON.parse(responseText);
}
//returns an object from rpc serialized json value
function deserialize_json_value(value) {
let result = {};
for (let index = 0; index < value.keys.length; index++) {
var propName = value.keys[index];
var propValue = value.values[index];
if (propValue.type && propValue.type == 'dict') {
result[propName] = deserialize_json_value(propValue);
}
else {
result[propName] = propValue.value;
}
}
return result;
}
function sendRequest(command, data, host = sessionHost) {
if (!host) {
throw new Error('Must first create RPC session with a valid host');
@ -226,9 +243,21 @@ function sendRequest(command, data, host = sessionHost) {
let error = response && response.error;
// Remove the type prefix from the error message (e.g. "Error: ").
if (error) {
if (error && error.replace) {
error = error.replace(/^[a-z]+: /i, '');
}
else if (error.type && error.type === 'dict') {
const responseError = deserialize_json_value(error);
let responeMessage;
if (response.message && response.message !== '') {
// Remove the type prefix from the error message (e.g. "Error: ").
responeMessage = response.message.replace(/^[a-z]+: /i, '');
}
const exceptionToReport = new Error(responeMessage);
Object.assign(exceptionToReport, responseError);
throw exceptionToReport;
}
throw new Error(error || `Invalid response for "${command}"`);
}

View File

@ -55,7 +55,7 @@ Object.defineProperty(iteratorPrototype, Symbol.iterator, {
['entries', 'keys', 'values'].forEach(function(methodName) {
var method = function() {
var self = this;
var self = this.snapshot();
var index = 0;
return Object.create(iteratorPrototype, {

View File

@ -20,7 +20,7 @@
function AuthError(problem) {
const error = Error.call(this, problem.title);
this.name = 'AuthError';
this.message = error.message;
this.stack = error.stack;

View File

@ -31,6 +31,25 @@ function setConstructorOnPrototype(klass) {
}
}
// Return a configuration usable by `Realm.open` when waiting for a download.
// It must have caching disabled, and no schema or schema version specified.
function waitForDownloadConfig(config) {
if (!config) {
return {_cache: false};
}
if (typeof config == 'string') {
return {path: config, _cache: false};
}
if (typeof config == 'object') {
return Object.assign({}, config, {schema: undefined, schemaVersion: undefined, _cache: false});
}
// Unknown type. Pass the config through.
return config;
}
module.exports = function(realmConstructor) {
// Add the specified Array methods to the Collection prototype.
Object.defineProperties(realmConstructor.Collection.prototype, require('./collection-methods'));
@ -45,7 +64,8 @@ module.exports = function(realmConstructor) {
open(config) {
let syncSession;
let promise = new Promise((resolve, reject) => {
realmConstructor._waitForDownload(config,
let realm = new realmConstructor(waitForDownloadConfig(config));
realm._waitForDownload(
(session) => {
syncSession = session;
},
@ -55,7 +75,7 @@ module.exports = function(realmConstructor) {
}
else {
try {
let syncedRealm = new this(config);
let syncedRealm = new realmConstructor(config);
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented
setTimeout(() => { resolve(syncedRealm); }, 1);
} catch (e) {
@ -79,27 +99,17 @@ module.exports = function(realmConstructor) {
openAsync(config, callback, progressCallback) {
const message = "Realm.openAsync is now deprecated in favor of Realm.open. This function will be removed in future versions.";
(console.warn || console.log).call(console, message);
realmConstructor._waitForDownload(config,
(syncSession) => {
if (progressCallback) {
syncSession.addProgressNotification('download', 'forCurrentlyOutstandingWork', progressCallback);
}
},
(error) => {
if (error) {
setTimeout(() => { callback(error); }, 1);
}
else {
try {
let syncedRealm = new this(config);
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented
setTimeout(() => { callback(null, syncedRealm); }, 1);
} catch (e) {
setTimeout(() => { callback(e); }, 1);
}
}
});
let promise = this.open(config)
if (progressCallback) {
promise.progress(progressCallback)
}
promise.then(realm => {
callback(null, realm)
}).catch(error => {
callback(error);
});
},
}));
@ -137,10 +147,21 @@ module.exports = function(realmConstructor) {
realmConstructor.Sync._setFeatureToken(featureToken.trim());
}
//enable deprecated setAccessToken
realmConstructor.Sync.setAccessToken = realmConstructor.Sync.setFeatureToken;
}
realmConstructor.prototype.subscribeToObjects = function(objectType, query) {
const realm = this;
let promise = new Promise((resolve, reject) => {
realm._subscribeToObjects(objectType, query, function(err, results) {
if (err) {
reject(err);
} else {
resolve(results);
}
});
});
return promise;
};
}
// TODO: Remove this now useless object.

36
lib/index.d.ts vendored
View File

@ -84,6 +84,7 @@ declare namespace Realm {
schema?: ObjectClass[] | ObjectSchema[];
schemaVersion?: number;
sync?: Realm.Sync.SyncConfiguration;
deleteRealmIfMigrationNeeded?: boolean;
}
// object props type
@ -143,6 +144,11 @@ declare namespace Realm {
*/
isValid(): boolean;
min(property?: string): number|Date|null;
max(property?: string): number|Date|null;
sum(property?: string): number|null;
avg(property?: string): number;
/**
* @param {string} query
* @param {any[]} ...arg
@ -221,7 +227,13 @@ declare namespace Realm {
* @see { @link https://realm.io/docs/javascript/latest/api/Realm.Results.html }
*/
interface Results<T> extends Collection<T> {
/**
* Bulk update objects in the collection.
* @param {string} property
* @param {any} value
* @returns void
*/
update(property: string, value: any): void;
}
const Results: {
@ -281,7 +293,7 @@ declare namespace Realm.Sync {
openManagementRealm(): Realm;
retrieveAccount(provider: string, username: string): Promise<Account>;
getGrantedPermissions(recipient: 'any' | 'currentUser' | 'otherUser'): Results<Permission>;
getGrantedPermissions(recipient: 'any' | 'currentUser' | 'otherUser'): Promise<Results<Permission>>;
applyPermissions(condition: PermissionCondition, realmUrl: string, accessLevel: AccessLevel): Promise<PermissionChange>;
offerPermissions(realmUrl: string, accessLevel: AccessLevel, expiresAt?: Date): Promise<string>;
acceptPermissionOffer(token: string): Promise<string>
@ -333,7 +345,14 @@ declare namespace Realm.Sync {
expiresAt?: Date;
}
type ErrorCallback = (message?: string, isFatal?: boolean, category?: string, code?: number) => void;
interface SyncError {
message: string;
isFatal: boolean
category?: string
code: number;
}
type ErrorCallback = (session: Session, error: SyncError) => void;
type SSLVerifyCallback = (serverAddress: string, serverPort: number, pemCertificate: string, preverifyOk: number, depth: number) => boolean;
interface SyncConfiguration {
@ -387,13 +406,9 @@ declare namespace Realm.Sync {
function removeAllListeners(name?: string): void;
function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): void;
function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void;
function initiateClientReset(path: string): void;
function setFeatureToken(token: string): void;
/**
* @deprecated, to be removed in 2.0
*/
function setAccessToken(accessToken: string): void;
type Instruction = {
type: 'INSERT' | 'SET' | 'DELETE' | 'CLEAR' | 'LIST_SET' | 'LIST_INSERT' | 'LIST_ERASE' | 'LIST_CLEAR' | 'ADD_TYPE' | 'ADD_PROPERTIES' | 'CHANGE_IDENTITY' | 'SWAP_IDENTITY'
object_type: string,
@ -571,6 +586,11 @@ declare class Realm {
* @returns boolean
*/
compact(): boolean;
/**
* @returns Promise<Results<T>>
*/
subscribeToObjects<T>(objectType: string, query: string): Promise<Realm.Results<T>>;
}
declare module 'realm' {

View File

@ -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':

View File

@ -72,31 +72,11 @@ function getSpecialPurposeRealm(user, realmName, schema) {
}
};
const _Realm = user.constructor._realmConstructor;
return new Promise((resolve, reject) => {
_Realm._waitForDownload(config, (error) => {
// FIXME: I don't understand why, but removing the following setTimeout causes the subsequent
// setTimeout call (when resolving the promise) to hang on RN iOS.
// This might be related to our general makeCallback issue: #1255.
setTimeout(() => {}, 1);
if (error) {
setTimeout(() => reject(error), 1);
}
else {
try {
let syncedRealm = new _Realm(config);
user[specialPurposeRealmsKey][realmName] = syncedRealm;
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented (#1255)
setTimeout(() => resolve(syncedRealm), 1);
} catch (e) {
setTimeout(() => reject(e), 1);
}
}
});
});
let Realm = user.constructor._realmConstructor;
return Realm.open(config).then(realm => {
user[specialPurposeRealmsKey][realmName] = realm;
return realm;
})
}
function createInManagementRealm(user, modelName, modelInitializer) {

View File

@ -25,6 +25,11 @@
'use strict';
if ('REALM_DISABLE_ANALYTICS' in process.env) {
module.exports = function(){};
return;
}
const os = require('os');
const crypto = require('crypto');
const fs = require('fs');
@ -69,9 +74,6 @@ function getAnonymizedMachineIdentifier() {
}
module.exports = function(eventName) {
if ('REALM_DISABLE_ANALYTICS' in process.env)
return;
const identifier = getAnonymizedMachineIdentifier();
const payload = {
'event': eventName,

View File

@ -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) {

View File

@ -1,7 +1,7 @@
{
"name": "realm",
"description": "Realm is a mobile database: an alternative to SQLite and key-value stores",
"version": "2.0.0-rc14",
"version": "2.0.0-rc20",
"license": "Apache-2.0",
"homepage": "https://realm.io",
"keywords": [

View File

@ -92,6 +92,33 @@ task prepareRealmCore(dependsOn: downloadRealmCore, type:Copy) {
}
}
task downloadOpenSSL_x86(type: Download) {
src "https://static.realm.io/downloads/openssl/1.0.2k/Android/x86/openssl-release-1.0.2k-Android-x86.tar.gz"
onlyIfNewer true
overwrite true
dest new File(downloadsDir, "openssl-release-1.0.2k-Android-x86.tar.gz")
}
task prepareOpenSSL_x86(dependsOn: downloadOpenSSL_x86, type:Copy) {
from tarTree(downloadOpenSSL_x86.dest)
into "$coreDownloadDir/core"
}
task downloadOpenSSL_arm(type: Download) {
src "https://static.realm.io/downloads/openssl/1.0.2k/Android/armeabi-v7a/openssl-release-1.0.2k-Android-armeabi-v7a.tar.gz"
onlyIfNewer true
overwrite true
dest new File(downloadsDir, "openssl-release-1.0.2k-Android-armeabi-v7a.tar.gz")
}
task prepareOpenSSL_arm(dependsOn: downloadOpenSSL_arm, type:Copy) {
from tarTree(downloadOpenSSL_arm.dest)
into "$coreDownloadDir/core"
rename { String fileName ->
fileName.replace("-arm-", "-armeabi-")
}
}
def getDependenciesVersion(keyName) {
def inputFile = new File(buildscript.sourceFile.getParent() + "/../../dependencies.list")
def line
@ -194,7 +221,7 @@ def getNdkBuildFullPath() {
return ndkBuildFullPath
}
task buildReactNdkLib(dependsOn: [downloadJSCHeaders,prepareRealmCore], type: Exec) {
task buildReactNdkLib(dependsOn: [downloadJSCHeaders,prepareRealmCore,prepareOpenSSL_x86,prepareOpenSSL_arm], type: Exec) {
inputs.file('src/main/jni')
outputs.dir("$buildDir/realm-react-ndk/all")
commandLine getNdkBuildFullPath(),

View File

@ -158,6 +158,11 @@ class RealmReactModule extends ReactContextBaseJavaModule {
e.printStackTrace();
}
final String json = map.get("postData");
if (json == null) {
Response response = newFixedLengthResponse("");
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");
return response;
}
final String jsonResponse = processChromeDebugCommand(cmdUri, json);
Response response = newFixedLengthResponse(jsonResponse);

View File

@ -6,6 +6,12 @@ LOCAL_MODULE := realm-android-sync-$(TARGET_ARCH_ABI)
LOCAL_EXPORT_C_INCLUDES := core/include
LOCAL_SRC_FILES := core/librealm-sync-android-$(TARGET_ARCH_ABI).a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := crypto-$(TARGET_ARCH_ABI)
LOCAL_EXPORT_C_INCLUDES := core/openssl-release-1.0.2k-Android-$(TARGET_ARCH_ABI)/include
LOCAL_SRC_FILES := core/openssl-release-1.0.2k-Android-$(TARGET_ARCH_ABI)/lib/libcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
endif
include $(CLEAR_VARS)
@ -19,6 +25,12 @@ LOCAL_MODULE := libjsc
LOCAL_EXPORT_C_INCLUDES := jsc
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ssl-$(TARGET_ARCH_ABI)
LOCAL_EXPORT_C_INCLUDES := core/openssl-release-1.0.2k-Android-$(TARGET_ARCH_ABI)/include
LOCAL_SRC_FILES := core/openssl-release-1.0.2k-Android-$(TARGET_ARCH_ABI)/lib/libssl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := librealmreact
@ -43,6 +55,7 @@ LOCAL_SRC_FILES += src/object-store/src/impl/epoll/external_commit_helper.cpp
LOCAL_SRC_FILES += src/object-store/src/parser/parser.cpp
LOCAL_SRC_FILES += src/object-store/src/parser/query_builder.cpp
LOCAL_SRC_FILES += src/object-store/src/util/format.cpp
LOCAL_SRC_FILES += src/object-store/src/util/uuid.cpp
LOCAL_SRC_FILES += src/object-store/src/binding_callback_thread_observer.cpp
LOCAL_SRC_FILES += src/object-store/src/collection_notifications.cpp
LOCAL_SRC_FILES += src/object-store/src/index_set.cpp
@ -61,6 +74,8 @@ 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/partial_sync.cpp
LOCAL_SRC_FILES += src/object-store/src/sync/sync_permission.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
@ -76,6 +91,7 @@ LOCAL_C_INCLUDES += $(JAVA_HOME)/include
LOCAL_C_INCLUDES += $(JAVA_HOME)/include/darwin
LOCAL_C_INCLUDES += $(JAVA_HOME)/include/linux
LOCAL_C_INCLUDES += core/include
LOCAL_C_INCLUDES += core/openssl-release-1.0.2k-Android-$(TARGET_ARCH_ABI)/include
ifeq ($(strip $(BUILD_TYPE_SYNC)),1)
LOCAL_C_INCLUDES += src/object-store/src/sync
endif
@ -84,8 +100,11 @@ LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
ifeq ($(strip $(BUILD_TYPE_SYNC)),1)
LOCAL_STATIC_LIBRARIES := realm-android-sync-$(TARGET_ARCH_ABI)
LOCAL_STATIC_LIBRARIES += realm-android-$(TARGET_ARCH_ABI)
LOCAL_STATIC_LIBRARIES += ssl-$(TARGET_ARCH_ABI)
LOCAL_STATIC_LIBRARIES += crypto-$(TARGET_ARCH_ABI)
else
LOCAL_STATIC_LIBRARIES := realm-android-$(TARGET_ARCH_ABI)
LOCAL_STATIC_LIBRARIES += crypto-$(TARGET_ARCH_ABI)
endif

View File

@ -13,11 +13,13 @@ APP_CPPFLAGS += -frtti
APP_CPPFLAGS += -fexceptions
APP_CPPFLAGS += -DREALM_HAVE_CONFIG
APP_CPPFLAGS += -fomit-frame-pointer
APP_CPPFLAGS += -fvisibility=hidden
# Make sure every shared lib includes a .note.gnu.build-id header
APP_LDFLAGS := -Wl,--build-id
APP_LDFLAGS += -llog
APP_LDFLAGS += -landroid
APP_LDFLAGS += -fvisibility=hidden
ifeq ($(strip $(BUILD_TYPE_SYNC)),1)
APP_CPPFLAGS += -DREALM_ENABLE_SYNC=1

View File

@ -349,6 +349,7 @@
"$(SRCROOT)/../../tests/react-test-app/node_modules/react-native/React/**",
"$(SRCROOT)/../../examples/ReactExample/node_modules/react-native/React/**",
"$(SRCROOT)/../../vendor",
"$(SRCROOT)/../../../../ios/Pods/Headers/Public/**"
);
OTHER_LIBTOOLFLAGS = "$(BUILT_PRODUCTS_DIR)/libGCDWebServers.a";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -368,6 +369,7 @@
"$(SRCROOT)/../../tests/react-test-app/node_modules/react-native/React/**",
"$(SRCROOT)/../../examples/ReactExample/node_modules/react-native/React/**",
"$(SRCROOT)/../../vendor",
"$(SRCROOT)/../../../../ios/Pods/Headers/Public/**"
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View File

@ -16,11 +16,13 @@
}],
["use_realm_debug", {
"variables": {
"debug_library_suffix": "-dbg"
"debug_library_suffix": "-dbg",
"build_directory": "build.debug",
}
}, {
"variables": {
"debug_library_suffix": ""
"debug_library_suffix": "",
"build_directory": "build.release",
}
}]
],
@ -42,10 +44,10 @@
"conditions": [
["prefix!=''", {
"all_dependent_settings": {
"include_dirs": [ "<(prefix)/src" ],
"include_dirs": [ "<(prefix)/src", "<(prefix)/<(build_directory)/src" ],
},
"direct_dependent_settings": {
"library_dirs": [ "<(prefix)/src/realm" ]
"library_dirs": [ "<(prefix)/<(build_directory)/src/realm" ]
}
}, {
"dependencies": [ "vendored-realm" ]
@ -74,15 +76,15 @@
},
"conditions": [
["prefix!=''", {
"all_dependent_settings+": {
"all_dependent_settings": {
"include_dirs": [ "<(prefix)/src" ],
},
"direct_dependent_settings+": {
"direct_dependent_settings": {
"library_dirs": [ "<(prefix)/src/realm" ]
}
}, {
"dependencies+": [ "vendored-realm" ]
}]
"dependencies": [ "vendored-realm" ]
}],
],
},
{

View File

@ -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

View File

@ -43,6 +43,7 @@
3FCE2A8B1F58BDEF00D4855B /* primitive_list_notifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A891F58BDE500D4855B /* primitive_list_notifier.cpp */; };
3FCE2A931F58BE0300D4855B /* uuid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A911F58BDFF00D4855B /* uuid.cpp */; };
3FCE2A971F58BE2200D4855B /* sync_permission.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3FCE2A951F58BE1D00D4855B /* sync_permission.cpp */; };
420FB79F1F7FBFE900D43D0F /* partial_sync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 423737AF1F7E333400FAEDFF /* partial_sync.cpp */; };
502B07E41E2CD201007A84ED /* object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 502B07E31E2CD1FA007A84ED /* object.cpp */; };
504CF85E1EBCAE3600A9A4B6 /* network_reachability_observer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 504CF8541EBCAE3600A9A4B6 /* network_reachability_observer.cpp */; };
504CF85F1EBCAE3600A9A4B6 /* system_configuration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 504CF8561EBCAE3600A9A4B6 /* system_configuration.cpp */; };
@ -194,6 +195,8 @@
3FCE2A951F58BE1D00D4855B /* sync_permission.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_permission.cpp; path = src/sync/sync_permission.cpp; sourceTree = "<group>"; };
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; };
423737AF1F7E333400FAEDFF /* partial_sync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = partial_sync.cpp; path = src/sync/partial_sync.cpp; sourceTree = "<group>"; };
423737B01F7E333400FAEDFF /* partial_sync.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = partial_sync.hpp; path = src/sync/partial_sync.hpp; sourceTree = "<group>"; };
426FCDFF1F7DA2F9005565DC /* sync_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sync_config.cpp; path = src/sync/sync_config.cpp; sourceTree = "<group>"; };
502B07E31E2CD1FA007A84ED /* object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = object.cpp; path = src/object.cpp; sourceTree = "<group>"; };
502B07E51E2CD20D007A84ED /* object.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = object.hpp; path = src/object.hpp; sourceTree = "<group>"; };
@ -464,6 +467,8 @@
02E315CC1DB80DE000555337 /* sync */ = {
isa = PBXGroup;
children = (
423737AF1F7E333400FAEDFF /* partial_sync.cpp */,
423737B01F7E333400FAEDFF /* partial_sync.hpp */,
426FCDFF1F7DA2F9005565DC /* sync_config.cpp */,
504CF8521EBCAE3600A9A4B6 /* impl */,
02E315CD1DB80DF200555337 /* sync_client.hpp */,
@ -936,6 +941,7 @@
5D97DC4E1F7DAB1400B856A4 /* sync_config.cpp in Sources */,
504CF8601EBCAE3600A9A4B6 /* sync_file.cpp in Sources */,
02E315D21DB80DF200555337 /* sync_file.cpp in Sources */,
420FB79F1F7FBFE900D43D0F /* partial_sync.cpp in Sources */,
02E315C91DB80DDD00555337 /* sync_manager.cpp in Sources */,
504CF8611EBCAE3600A9A4B6 /* sync_metadata.cpp in Sources */,
02E315D31DB80DF200555337 /* sync_metadata.cpp in Sources */,

View File

@ -51,6 +51,12 @@ struct Arguments {
throw std::invalid_argument(util::format("Invalid arguments: at most %1 expected, but %2 supplied.", max, count));
}
}
void validate_count(size_t actual) const {
if (count != actual) {
throw std::invalid_argument(util::format("Invalid arguments: %1 expected, but %s supplied.", actual, count));
}
}
};
template<typename T>
@ -60,7 +66,7 @@ template<typename T>
struct PropertyType {
using GetterType = void(typename T::Context, typename T::Object, ReturnValue<T> &);
using SetterType = void(typename T::Context, typename T::Object, typename T::Value);
typename T::PropertyGetterCallback getter;
typename T::PropertySetterCallback setter;
};
@ -95,7 +101,7 @@ template<typename T, typename U, typename V = void>
struct ClassDefinition {
using Internal = U;
using Parent = V;
// Every subclass *must* at least have a name.
// std::string const name;

View File

@ -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;
@ -81,7 +82,7 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
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 +96,10 @@ struct ListClass : ClassDefinition<T, realm::js::List<T>, CollectionClass<T>> {
{"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>},
{"indexOf", wrap<index_of>},
{"min", wrap<compute_aggregate_on_collection<ListClass<T>, AggregateFunc::Min>>},
{"max", wrap<compute_aggregate_on_collection<ListClass<T>, AggregateFunc::Max>>},
{"sum", wrap<compute_aggregate_on_collection<ListClass<T>, AggregateFunc::Sum>>},
{"avg", wrap<compute_aggregate_on_collection<ListClass<T>, AggregateFunc::Avg>>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
@ -124,7 +129,7 @@ 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>::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 +234,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 +268,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 +289,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);

View File

@ -34,12 +34,14 @@
#include "js_sync.hpp"
#include "sync/sync_config.hpp"
#include "sync/sync_manager.hpp"
#include "sync/partial_sync.hpp"
#endif
#include "shared_realm.hpp"
#include "binding_context.hpp"
#include "object_accessor.hpp"
#include "platform.hpp"
#include "results.hpp"
namespace realm {
namespace js {
@ -156,7 +158,7 @@ class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
public:
using ObjectDefaultsMap = typename Schema<T>::ObjectDefaultsMap;
using ConstructorMap = typename Schema<T>::ConstructorMap;
using WaitHandler = void(std::error_code);
using ProgressHandler = void(uint64_t transferred_bytes, uint64_t transferrable_bytes);
@ -180,6 +182,9 @@ public:
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
static void compact(ContextType, ObjectType, Arguments, ReturnValue &);
static void delete_model(ContextType, ObjectType, Arguments, ReturnValue &);
#if REALM_ENABLE_SYNC
static void subscribe_to_objects(ContextType, ObjectType, Arguments, ReturnValue &);
#endif
// properties
static void get_empty(ContextType, ObjectType, ReturnValue &);
@ -213,7 +218,6 @@ public:
{"clearTestState", wrap<clear_test_state>},
{"copyBundledRealmFiles", wrap<copy_bundled_realm_files>},
{"deleteFile", wrap<delete_file>},
{"_waitForDownload", wrap<wait_for_download_completion>},
};
PropertyMap<T> const static_properties = {
@ -236,6 +240,10 @@ public:
{"close", wrap<close>},
{"compact", wrap<compact>},
{"deleteModel", wrap<delete_model>},
{"_waitForDownload", wrap<wait_for_download_completion>},
#if REALM_ENABLE_SYNC
{"_subscribeToObjects", wrap<subscribe_to_objects>},
#endif
};
PropertyMap<T> const properties = {
@ -252,6 +260,26 @@ public:
};
private:
static void handleRealmFileException(ContextType ctx, realm::Realm::Config config, const RealmFileException& ex) {
switch (ex.kind()) {
case RealmFileException::Kind::IncompatibleSyncedRealm: {
ObjectType configuration = Object::create_empty(ctx);
Object::set_property(ctx, configuration, "path", Value::from_string(ctx, ex.path()));
Object::set_property(ctx, configuration, "readOnly", Value::from_boolean(ctx, true));
if (!config.encryption_key.empty()) {
Object::set_property(ctx, configuration, "encryption_key", Value::from_binary(ctx, BinaryData(&config.encryption_key[0], 64)));
}
ObjectType object = Object::create_empty(ctx);
Object::set_property(ctx, object, "name", Value::from_string(ctx, "IncompatibleSyncedRealmError"));
Object::set_property(ctx, object, "configuration", configuration);
throw Exception<T>(ctx, object);
}
default:
throw;
}
}
static std::string validated_notification_name(ContextType ctx, const ValueType &value) {
std::string name = Value::validated_to_string(ctx, value, "notification name");
if (name != "change") {
@ -381,7 +409,7 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
else if (config.path.empty()) {
config.path = js::default_path();
}
static const String in_memory_string = "inMemory";
ValueType in_memory_value = Object::get_property(ctx, object, in_memory_string);
if (!Value::is_undefined(ctx, in_memory_value) && Value::validated_to_boolean(ctx, in_memory_value, "inMemory")) {
@ -394,6 +422,16 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
config.schema_mode = SchemaMode::Immutable;
}
static const String delete_realm_if_migration_needed_string = "deleteRealmIfMigrationNeeded";
ValueType delete_realm_if_migration_needed_value = Object::get_property(ctx, object, delete_realm_if_migration_needed_string);
if (!Value::is_undefined(ctx, delete_realm_if_migration_needed_value) && Value::validated_to_boolean(ctx, delete_realm_if_migration_needed_value, "deleteRealmIfMigrationNeeded")) {
if (config.schema_mode == SchemaMode::Immutable) {
throw std::invalid_argument("Cannot set 'deleteRealmIfMigrationNeeded' when 'readOnly' is set.");
}
config.schema_mode = SchemaMode::ResetFile;
}
static const String schema_string = "schema";
ValueType schema_value = Object::get_property(ctx, object, schema_string);
if (!Value::is_undefined(ctx, schema_value)) {
@ -437,6 +475,11 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
ValueType migration_value = Object::get_property(ctx, object, migration_string);
if (!Value::is_undefined(ctx, migration_value)) {
FunctionType migration_function = Value::validated_to_function(ctx, migration_value, "migration");
if (config.schema_mode == SchemaMode::ResetFile) {
throw std::invalid_argument("Cannot include 'migration' when 'deleteRealmIfMigrationNeeded' is set.");
}
config.migration_function = [=](SharedRealm old_realm, SharedRealm realm, realm::Schema&) {
auto old_realm_ptr = new SharedRealm(old_realm);
auto realm_ptr = new SharedRealm(realm);
@ -460,6 +503,12 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
realm_ptr->reset();
};
}
static const String cache_string = "_cache";
ValueType cache_value = Object::get_property(ctx, object, cache_string);
if (!Value::is_undefined(ctx, cache_value)) {
config.cache = Value::validated_to_boolean(ctx, cache_value, "_cache");
}
}
}
else {
@ -482,7 +531,16 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
ObjectDefaultsMap && defaults, ConstructorMap && constructors) {
config.execution_context = Context<T>::get_execution_context_id(ctx);
SharedRealm realm = realm::Realm::get_shared_realm(config);
SharedRealm realm;
try {
realm = realm::Realm::get_shared_realm(config);
}
catch (const RealmFileException& ex) {
handleRealmFileException(ctx, config, ex);
}
catch (...) {
throw;
}
GlobalContextType global_context = Context<T>::get_global_context(ctx);
if (!realm->m_binding_context) {
@ -642,34 +700,21 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
template<typename T>
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)]);
args.validate_maximum(2);
auto callback_function = Value::validated_to_function(ctx, args[0 + (args.count == 2)]);
ValueType session_callback = Value::from_null(ctx);
if (args.count == 3) {
session_callback = Value::validated_to_function(ctx, args[1]);
if (args.count == 2) {
session_callback = Value::validated_to_function(ctx, args[0]);
}
#if REALM_ENABLE_SYNC
ValueType sync_config_value = Object::get_property(ctx, config_object, "sync");
if (!Value::is_undefined(ctx, sync_config_value)) {
realm::Realm::Config config;
config.cache = false;
static const String encryption_key_string = "encryptionKey";
ValueType encryption_key_value = Object::get_property(ctx, config_object, encryption_key_string);
if (!Value::is_undefined(ctx, encryption_key_value)) {
auto encryption_key = Value::validated_to_binary(ctx, encryption_key_value, "encryptionKey");
config.encryption_key.assign(encryption_key.data(), encryption_key.data() + encryption_key.size());
}
Protected<ObjectType> thiz(ctx, this_object);
SyncClass<T>::populate_sync_config(ctx, thiz, config_object, config);
auto realm = *get_internal<T, RealmClass<T>>(this_object);
if (auto* sync_config = realm->config().sync_config.get()) {
Protected<FunctionType> protected_callback(ctx, callback_function);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
EventLoopDispatcher<WaitHandler> wait_handler([=](std::error_code error_code) {
HANDLESCOPE
if (!error_code) {
@ -686,75 +731,39 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType thi
callback_arguments[0] = object;
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
}
// We keep our Realm instance alive until the callback has had a chance to open its own instance.
// This allows it to share the sync session that our Realm opened.
if (realm)
realm->close();
});
std::function<WaitHandler> waitFunc = std::move(wait_handler);
std::function<ProgressHandler> progressFunc;
auto realm = realm::Realm::get_shared_realm(config);
if (auto sync_config = config.sync_config)
{
static const String progressFuncName = "_onDownloadProgress";
bool progressFuncDefined = false;
if (!Value::is_boolean(ctx, sync_config_value) && !Value::is_undefined(ctx, sync_config_value))
{
auto sync_config_object = Value::validated_to_object(ctx, sync_config_value);
ValueType progressFuncValue = Object::get_property(ctx, sync_config_object, progressFuncName);
progressFuncDefined = !Value::is_undefined(ctx, progressFuncValue);
if (progressFuncDefined)
{
Protected<FunctionType> protected_progressCallback(protected_ctx, Value::validated_to_function(protected_ctx, progressFuncValue));
EventLoopDispatcher<ProgressHandler> progress_handler([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) {
HANDLESCOPE
ValueType callback_arguments[2];
callback_arguments[0] = Value::from_number(protected_ctx, transferred_bytes);
callback_arguments[1] = Value::from_number(protected_ctx, transferrable_bytes);
Function<T>::callback(protected_ctx, protected_progressCallback, protected_this, 2, callback_arguments);
});
progressFunc = std::move(progress_handler);
std::shared_ptr<SyncUser> user = sync_config->user;
if (user && user->state() != SyncUser::State::Error) {
if (auto session = user->session_for_on_disk_path(realm->config().path)) {
if (!Value::is_null(ctx, session_callback)) {
FunctionType session_callback_func = Value::to_function(ctx, session_callback);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
ValueType callback_arguments[1];
callback_arguments[0] = syncSession;
Function<T>::callback(protected_ctx, session_callback_func, protected_this, 1, callback_arguments);
}
session->wait_for_download_completion(std::move(wait_handler));
return;
}
std::shared_ptr<SyncUser> user = sync_config->user;
if (user && user->state() != SyncUser::State::Error) {
if (auto session = user->session_for_on_disk_path(config.path)) {
if (!Value::is_null(ctx, session_callback)) {
FunctionType session_callback_func = Value::to_function(ctx, session_callback);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
ValueType callback_arguments[1];
callback_arguments[0] = syncSession;
Function<T>::callback(protected_ctx, session_callback_func, protected_this, 1, callback_arguments);
}
if (progressFuncDefined) {
session->register_progress_notifier(std::move(progressFunc), SyncSession::NotifierType::download, false);
}
session->wait_for_download_completion([=](std::error_code error_code) {
realm->close(); //capture and keep realm instance for until here
waitFunc(error_code);
});
return;
}
}
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, "errorCode", Value::from_number(protected_ctx, 1));
ValueType callback_arguments[1];
callback_arguments[0] = object;
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
return;
}
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, "errorCode", Value::from_number(protected_ctx, 1));
ValueType callback_arguments[1];
callback_arguments[0] = object;
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
return;
}
#else
static_cast<void>(config_object);
#endif
Function<T>::callback(ctx, callback_function, this_object, 0, nullptr);
@ -977,5 +986,51 @@ void RealmClass<T>::compact(ContextType ctx, ObjectType this_object, Arguments a
return_value.set(realm->compact());
}
#if REALM_ENABLE_SYNC
template<typename T>
void RealmClass<T>::subscribe_to_objects(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_count(3);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
std::string object_type = Value::validated_to_string(ctx, args[0]);
std::string query = Value::validated_to_string(ctx, args[1]);
auto callback = Value::validated_to_function(ctx, args[2]);
auto &schema = realm->schema();
auto object_schema = schema.find(object_type);
if (object_schema == schema.end()) {
throw std::runtime_error("Object type '" + object_type + "' not found in schema.");
}
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
Protected<FunctionType> protected_callback(ctx, callback);
auto cb = [=](realm::Results results, std::exception_ptr err) {
HANDLESCOPE
if (err) {
try {
std::rethrow_exception(err);
}
catch (const std::exception& e) {
ValueType callback_arguments[2];
callback_arguments[0] = Value::from_string(protected_ctx, e.what());
callback_arguments[1] = Value::from_null(protected_ctx);
Function<T>::callback(ctx, protected_callback, protected_this, 2, callback_arguments);
}
return;
}
ValueType callback_arguments[2];
callback_arguments[0] = Value::from_null(protected_ctx);
callback_arguments[1] = ResultsClass<T>::create_instance(protected_ctx, results);
Function<T>::callback(protected_ctx, protected_callback, protected_this, 2, callback_arguments);
};
partial_sync::register_query(realm, object_type, query, std::move(cb));
}
#endif
} // js
} // realm

View File

@ -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,9 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
template<typename Fn>
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 &);
@ -94,7 +98,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 +106,23 @@ struct ResultsClass : ClassDefinition<T, realm::js::Results<T>, CollectionClass<
{"filtered", wrap<filtered>},
{"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>},
{"min", wrap<compute_aggregate_on_collection<ResultsClass<T>, AggregateFunc::Min>>},
{"max", wrap<compute_aggregate_on_collection<ResultsClass<T>, AggregateFunc::Max>>},
{"sum", wrap<compute_aggregate_on_collection<ResultsClass<T>, AggregateFunc::Sum>>},
{"avg", wrap<compute_aggregate_on_collection<ResultsClass<T>, AggregateFunc::Avg>>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
{"indexOf", wrap<index_of>},
{"update", wrap<update>},
};
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};
};
@ -211,7 +220,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 +250,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) {
@ -267,6 +275,32 @@ void ResultsClass<T>::index_of(ContextType ctx, Fn& fn, Arguments args, ReturnVa
}
}
template<typename T>
void ResultsClass<T>::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<T, ResultsClass<T>>(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<T>::create_instance(ctx, realm_object);
RealmObjectClass<T>::set_property(ctx, obj, property, arguments[1]);
}
}
template<typename T>
void ResultsClass<T>::index_of(ContextType ctx, ObjectType this_object,
Arguments args, ReturnValue &return_value) {
@ -287,7 +321,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 +367,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

View File

@ -18,10 +18,6 @@
#pragma once
#include <list>
#include <map>
#include <set>
#include <regex>
#include <mutex>
#include <condition_variable>
@ -40,11 +36,11 @@ namespace realm {
namespace js {
inline realm::SyncManager& syncManagerShared() {
static bool configured = []{
static std::once_flag flag;
std::call_once(flag, [] {
ensure_directory_exists_for_file(default_realm_file_directory());
SyncManager::shared().configure_file_system(default_realm_file_directory(), SyncManager::MetadataMode::NoEncryption);
return true;
}();
});
return SyncManager::shared();
}
@ -229,10 +225,21 @@ public:
HANDLESCOPE
auto error_object = Object<T>::create_empty(m_ctx);
auto error_code = error.error_code.value();
if (error.is_client_reset_requested()) {
error_code = 7; // FIXME: define a proper constant
auto config_object = Object<T>::create_empty(m_ctx);
Object<T>::set_property(m_ctx, config_object, "path", Value<T>::from_string(m_ctx, error.user_info[SyncError::c_recovery_file_path_key]));
Object<T>::set_property(m_ctx, config_object, "readOnly", Value<T>::from_boolean(m_ctx, true));
Object<T>::set_property(m_ctx, error_object, "config", config_object);
}
Object<T>::set_property(m_ctx, error_object, "message", Value<T>::from_string(m_ctx, error.message));
Object<T>::set_property(m_ctx, error_object, "isFatal", Value<T>::from_boolean(m_ctx, error.is_fatal));
Object<T>::set_property(m_ctx, error_object, "category", Value<T>::from_string(m_ctx, error.error_code.category().name()));
Object<T>::set_property(m_ctx, error_object, "code", Value<T>::from_number(m_ctx, error.error_code.value()));
Object<T>::set_property(m_ctx, error_object, "code", Value<T>::from_number(m_ctx, error_code));
auto user_info = Object<T>::create_empty(m_ctx);
for (auto& kvp : error.user_info) {
@ -478,9 +485,7 @@ void SessionClass<T>::add_progress_notification(ContextType ctx, FunctionType, O
progressFunc = std::move(progress_handler);
auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, is_streaming);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete;
Object::set_property(ctx, callback_function, "_syncSession", syncSession, attributes);
@ -525,6 +530,7 @@ public:
static FunctionType create_constructor(ContextType);
static void set_sync_log_level(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void initiate_client_reset(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
// private
static std::function<SyncBindSessionHandler> session_bind_callback(ContextType ctx, ObjectType sync_constructor);
@ -535,6 +541,7 @@ public:
MethodMap<T> const static_methods = {
{"setLogLevel", wrap<set_sync_log_level>},
{"initiateClientReset", wrap<initiate_client_reset>},
};
};
@ -549,6 +556,15 @@ inline typename T::Function SyncClass<T>::create_constructor(ContextType ctx) {
return sync_constructor;
}
template<typename T>
void SyncClass<T>::initiate_client_reset(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue & return_value) {
validate_argument_count(argc, 1);
std::string path = Value::validated_to_string(ctx, arguments[0]);
if (!SyncManager::shared().immediately_run_file_actions(std::string(path))) {
throw std::runtime_error(util::format("Realm was not configured correctly. Client Reset could not be run for Realm at: %1", path));
}
}
template<typename T>
void SyncClass<T>::set_sync_log_level(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
@ -607,8 +623,10 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
std::string raw_realm_url = Object::validated_get_string(ctx, sync_config_object, "url");
if (shared_user->token_type() == SyncUser::TokenType::Admin) {
static std::regex tilde("/~/");
raw_realm_url = std::regex_replace(raw_realm_url, tilde, "/__auth/");
size_t pos = raw_realm_url.find("/~/");
if (pos != std::string::npos) {
raw_realm_url.replace(pos + 1, 1, "__auth");
}
}
bool client_validate_ssl = true;
@ -633,18 +651,24 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
ssl_verify_callback = std::move(ssl_verify_functor);
}
bool is_partial = false;
ValueType partial_value = Object::get_property(ctx, sync_config_object, "partial");
if (!Value::is_undefined(ctx, partial_value)) {
is_partial = Value::validated_to_boolean(ctx, partial_value);
}
// FIXME - use make_shared
config.sync_config = std::shared_ptr<SyncConfig>(new SyncConfig{shared_user, raw_realm_url,
SyncSessionStopPolicy::AfterChangesUploaded,
std::move(bind), std::move(error_handler),
nullptr, util::none,
client_validate_ssl, ssl_trust_certificate_path,
std::move(ssl_verify_callback)});
std::move(ssl_verify_callback),
is_partial});
config.schema_mode = SchemaMode::Additive;
config.path = syncManagerShared().path_for_realm(*shared_user, raw_realm_url);
config.path = syncManagerShared().path_for_realm(*shared_user, config.sync_config->realm_url());
if (!config.encryption_key.empty()) {
config.sync_config->realm_encryption_key = std::array<char, 64>();

View File

@ -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, const 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, const 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

View File

@ -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,43 @@ static inline void validate_argument_count_at_least(size_t count, size_t expecte
}
}
template<typename T, AggregateFunc func>
void compute_aggregate_on_collection(typename T::ContextType ctx, typename T::ObjectType this_object,
typename T::Arguments args, typename T::ReturnValue &return_value) {
auto list = get_internal<typename T::Type, T>(this_object);
size_t column = 0;
if (list->get_type() == realm::PropertyType::Object) {
const ObjectSchema& object_schema = list->get_object_schema();
std::string property_name = T::Value::validated_to_string(ctx, args[0]);
const Property* property = object_schema.property_for_name(property_name);
if (!property) {
throw std::invalid_argument(util::format("Property '%1' does not exist on object '%2'",
property_name, object_schema.name));
}
column = property->table_column;
}
else {
args.validate_maximum(0);
}
util::Optional<Mixed> mixed;
switch (func) {
case AggregateFunc::Min:
return_value.set(list->min(column));
break;
case AggregateFunc::Max:
return_value.set(list->max(column));
break;
case AggregateFunc::Sum:
return_value.set(list->sum(column));
break;
case AggregateFunc::Avg:
return_value.set(list->average(column));
break;
}
}
} // js
} // realm

View File

@ -53,12 +53,26 @@ class ReturnValue<jsc::Types> {
void set(uint32_t number) {
m_value = JSValueMakeNumber(m_context, number);
}
void set(const util::Optional<realm::Mixed>& mixed) {
m_value = Value<jsc::Types>::from_mixed(m_context, mixed);
}
void set_null() {
m_value = JSValueMakeNull(m_context);
}
void set_undefined() {
m_value = JSValueMakeUndefined(m_context);
}
template<typename T>
void set(const util::Optional<T>& value) {
if (value) {
set(*value);
}
else {
set_undefined();
}
}
operator JSValueRef() const {
return m_value;
}

View File

@ -64,12 +64,25 @@ class ReturnValue<node::Types> {
void set(uint32_t number) {
m_value.Set(number);
}
void set(realm::Mixed mixed) {
m_value.Set(Value<node::Types>::from_mixed(nullptr, mixed));
}
void set_null() {
m_value.SetNull();
}
void set_undefined() {
m_value.SetUndefined();
}
template<typename T>
void set(util::Optional<T> value) {
if (value) {
set(*value);
}
else {
m_value.SetUndefined();
}
}
};
} // js

@ -1 +1 @@
Subproject commit 3df8be344da02212e8d4e05b775e1569d2a3d785
Subproject commit db790aa652d9c135e7ef4925161119268b7166a8

View File

@ -360,8 +360,24 @@ json RPCServer::perform_request(std::string name, const json &args) {
assert(action);
m_worker.add_task([=] {
return action(args);
try {
return action(args);
}
catch (jsc::Exception ex) {
json exceptionAsJson = nullptr;
try {
exceptionAsJson = serialize_json_value(ex);
}
catch (...) {
exceptionAsJson = {{"error", "An exception occured while processing the request. Could not serialize the exception as JSON"}};
}
return (json){{"error", exceptionAsJson}, {"message", ex.what()}};
}
catch (std::exception &exception) {
return (json){{"error", exception.what()}};
}
});
}
try {

BIN
tests/data/sync-v1.realm Normal file

Binary file not shown.

View File

@ -42,7 +42,7 @@ if (global.enableSyncTests) {
// FIXME: Permission tests currently fail in chrome debugging mode.
if (typeof navigator === 'undefined' ||
!/Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef
//TESTS.PermissionTests = require('./permission-tests');
TESTS.PermissionTests = require('./permission-tests');
}
}

View File

@ -1004,4 +1004,226 @@ module.exports = {
TestCase.assertEqual(list.isValid(), false);
TestCase.assertThrowsContaining(() => list.length, 'invalidated');
},
testListAggregateFunctions: function() {
const NullableBasicTypesList = {
name: 'NullableBasicTypesList',
properties: {
list: 'NullableBasicTypesObject[]',
}
};
const realm = new Realm({schema: [schemas.NullableBasicTypes, NullableBasicTypesList]});
const N = 50;
const list = [];
for (let i = 0; i < N; i++) {
list.push({
intCol: i+1,
floatCol: i+1,
doubleCol: i+1,
dateCol: new Date(i+1)
});
}
let object;
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: 'NullableBasicTypesObject[]',
}
};
const realm = new Realm({schema: [schemas.NullableBasicTypes, NullableBasicTypesList]});
const N = 50;
const M = 10;
const list = [];
for (let i = 0; i < N; i++) {
list.push({
intCol: i+1,
floatCol: i+1,
doubleCol: i+1,
dateCol: new Date(i+1)
});
}
for (let j = 0; j < M; j++) {
list.push({});
}
let object, objectEmptyList;
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'));
},
testPrimitiveListAggregateFunctions: function() {
const realm = new Realm({schema: [schemas.PrimitiveArrays]});
let object;
realm.write(() => {
object = realm.create('PrimitiveArrays', {
int: [1, 2, 3],
float: [1.1, 2.2, 3.3],
double: [1.11, 2.22, 3.33],
date: [DATE1, DATE2, DATE3],
optInt: [1, null, 2],
optFloat: [1.1, null, 3.3],
optDouble: [1.11, null, 3.33],
optDate: [DATE1, null, DATE3]
});
});
for (let prop of ['int', 'float', 'double', 'date', 'optInt', 'optFloat', 'optDouble', 'optDate']) {
const list = object[prop];
TestCase.assertSimilar(list.type, list.min(), list[0]);
TestCase.assertSimilar(list.type, list.max(), list[2]);
if (list.type === 'date') {
TestCase.assertThrowsContaining(() => list.sum(), "Cannot sum 'date' array: operation not supported")
TestCase.assertThrowsContaining(() => list.avg(), "Cannot average 'date' array: operation not supported")
continue;
}
const sum = list[0] + list[1] + list[2];
const avg = sum / (list[1] === null ? 2 : 3);
TestCase.assertSimilar(list.type, list.sum(), sum);
TestCase.assertSimilar(list.type, list.avg(), avg);
}
TestCase.assertThrowsContaining(() => object.bool.min(), "Cannot min 'bool' array: operation not supported")
TestCase.assertThrowsContaining(() => object.int.min("foo"), "Invalid arguments: at most 0 expected, but 1 supplied")
},
testListAggregateFunctionsUnsupported: function() {
const NullableBasicTypesList = {
name: 'NullableBasicTypesList',
properties: {
list: {type: 'list', objectType: 'NullableBasicTypesObject'},
}
};
const realm = new Realm({schema: [schemas.NullableBasicTypes, NullableBasicTypesList]});
const N = 5;
var list = [];
for (let i = 0; i < N; i++) {
list.push({
intCol: i+1,
floatCol: i+1,
doubleCol: i+1,
dateCol: new Date(i+1)
});
}
let object;
realm.write(() => {
object = realm.create('NullableBasicTypesList', {list: list});
});
TestCase.assertEqual(object.list.length, N);
// bool, string & data columns don't support 'min'
['bool', 'string', 'data'].forEach(colName => {
TestCase.assertThrowsContaining(() => object.list.min(colName + 'Col'),
`Cannot min property '${colName}Col': operation not supported for '${colName}' properties`);
});
// bool, string & data columns don't support 'max'
['bool', 'string', 'data'].forEach(colName => {
TestCase.assertThrowsContaining(() => object.list.max(colName + 'Col'),
`Cannot max property '${colName}Col': operation not supported for '${colName}' properties`);
});
// bool, string, date & data columns don't support 'avg'
['bool', 'string', 'date', 'data'].forEach(colName => {
TestCase.assertThrowsContaining(() => object.list.avg(colName + 'Col'),
`Cannot average property '${colName}Col': operation not supported for '${colName}' properties`);
});
// bool, string, date & data columns don't support 'sum'
['bool', 'string', 'date', 'data'].forEach(colName => {
TestCase.assertThrowsContaining(() => object.list.sum(colName + 'Col'),
`Cannot sum property '${colName}Col': operation not supported for '${colName}' properties`);
});
},
testListAggregateFunctionsWrongProperty: function() {
const realm = new Realm({schema: [schemas.PersonObject, schemas.PersonList]});
let object;
realm.write(() => {
object = realm.create('PersonList', {list: [
{name: 'Ari', age: 10},
{name: 'Tim', age: 11},
{name: 'Bjarne', age: 12},
]});
});
TestCase.assertThrowsContaining(() => object.list.min('foo'),
"Property 'foo' does not exist on object 'PersonObject'");
TestCase.assertThrowsContaining(() => object.list.max('foo'),
"Property 'foo' does not exist on object 'PersonObject'");
TestCase.assertThrowsContaining(() => object.list.sum('foo'),
"Property 'foo' does not exist on object 'PersonObject'");
TestCase.assertThrowsContaining(() => object.list.avg('foo'),
"Property 'foo' does not exist on object 'PersonObject'");
TestCase.assertThrowsContaining(() => object.list.min(),
"JS value must be of type 'string', got (undefined)");
TestCase.assertThrowsContaining(() => object.list.max(),
"JS value must be of type 'string', got (undefined)");
TestCase.assertThrowsContaining(() => object.list.sum(),
"JS value must be of type 'string', got (undefined)");
TestCase.assertThrowsContaining(() => object.list.avg(),
"JS value must be of type 'string', got (undefined)");
},
};

View File

@ -67,20 +67,20 @@ function repeatUntil(fn, predicate) {
}
module.exports = {
testApplyAndGetGrantedPermissions() {
return createUsersWithTestRealms(1)
.then(([user]) => {
return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
.then(repeatUntil(() => user.getGrantedPermissions('any'),
permissions => permissions.length > 1))
.then(permissions => {
TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);
TestCase.assertEqual(permissions[1].mayRead, true);
TestCase.assertEqual(permissions[1].mayWrite, false);
TestCase.assertEqual(permissions[1].mayManage, false);
});
});
},
// testApplyAndGetGrantedPermissions() {
// return createUsersWithTestRealms(1)
// .then(([user]) => {
// return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
// .then(repeatUntil(() => user.getGrantedPermissions('any'),
// permissions => permissions.length > 1))
// .then(permissions => {
// TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);
// TestCase.assertEqual(permissions[1].mayRead, true);
// TestCase.assertEqual(permissions[1].mayWrite, false);
// TestCase.assertEqual(permissions[1].mayManage, false);
// });
// });
// },
testOfferPermissions() {
return createUsersWithTestRealms(2)

View File

@ -1041,5 +1041,137 @@ module.exports = {
const realm2 = new Realm(config);
TestCase.assertEqual(realm2.objects('TestObject').length, 0);
realm.close();
}
},
testRealmDeleteRealmIfMigrationNeededVersionChanged: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
realm = new Realm({schema: schema, deleteRealmIfMigrationNeeded: true, schemaVersion: 1, migration: undefined });
// object should be gone as Realm should get deleted
TestCase.assertEqual(realm.objects('TestObject').length, 0);
// create a new object
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
var migrationWasCalled = false;
realm = new Realm({schema: schema, deleteRealmIfMigrationNeeded: false, schemaVersion: 2, migration: function(oldRealm, newRealm) {
migrationWasCalled = true;
}});
// migration function should get called as deleteRealmIfMigrationNeeded is false
TestCase.assertEqual(migrationWasCalled, true);
// object should be there because Realm shouldn't get deleted
TestCase.assertEqual(realm.objects('TestObject').length, 1);
realm.close();
},
testRealmDeleteRealmIfMigrationNeededSchemaChanged: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
const schema1 = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
prop2: 'float',
}
}];
const schema2 = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
prop2: 'float',
prop3: 'double'
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', {prop0: 'stringValue', prop1: 1});
});
realm.close();
// change schema
realm = new Realm({schema: schema1, deleteRealmIfMigrationNeeded: true, migration: undefined});
// object should be gone as Realm should get deleted
TestCase.assertEqual(realm.objects('TestObject').length, 0);
// create a new object
realm.write(function() {
realm.create('TestObject', {prop0: 'stringValue', prop1: 1, prop2: 1.0});
});
realm.close();
TestCase.assertThrows(function(e) {
// updating schema without changing schemaVersion OR setting deleteRealmIfMigrationNeeded = true should raise an error
new Realm({schema: schema2, deleteRealmIfMigrationNeeded: false, migration: function(oldRealm, newRealm) {}});
});
var migrationWasCalled = false;
// change schema again, but increment schemaVersion
realm = new Realm({schema: schema2, deleteRealmIfMigrationNeeded: false, schemaVersion: 1, migration: function(oldRealm, newRealm) {
migrationWasCalled = true;
}});
// migration function should get called as deleteRealmIfMigrationNeeded is false
TestCase.assertEqual(migrationWasCalled, true);
// object should be there because Realm shouldn't get deleted
TestCase.assertEqual(realm.objects('TestObject').length, 1);
realm.close();
},
testRealmDeleteRealmIfMigrationNeededIncompatibleConfig: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
TestCase.assertThrows(function() {
new Realm({schema: schema, deleteRealmIfMigrationNeeded: true, readOnly: true});
}, "Cannot set 'deleteRealmIfMigrationNeeded' when 'readOnly' is set.")
TestCase.assertThrows(function() {
new Realm({schema: schema, deleteRealmIfMigrationNeeded: true, migration: function(oldRealm, newRealm) {}});
}, "Cannot include 'migration' when 'deleteRealmIfMigrationNeeded' is set.")
},
};

View File

@ -455,6 +455,402 @@ 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')
});
},
testIterator: function() {
var realm = new Realm({ schema: [ schemas.TestObject ]});
realm.write(() => {
realm.create('TestObject', { doubleCol: 2 });
realm.create('TestObject', { doubleCol: 3 });
});
var results = realm.objects('TestObject').filtered('doubleCol >= 2');
TestCase.assertEqual(results.length, 2);
var calls = 0;
for(let obj of results) {
realm.write(() => {
obj.doubleCol = 1;
});
calls++;
}
TestCase.assertEqual(results.length, 0);
TestCase.assertEqual(calls, 2);
},
testResultsUpdate: function() {
const N = 5;
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', { intCol: 10 });
}
});
// update should work on a basic result set
var results = realm.objects('NullableBasicTypesObject');
TestCase.assertEqual(results.length, 5);
realm.write(() => {
results.update('intCol', 20);
});
TestCase.assertEqual(results.length, 5);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 20').length, 5);
// update should work on a filtered result set
results = realm.objects('NullableBasicTypesObject').filtered('intCol = 20');
realm.write(() => {
results.update('intCol', 10);
});
TestCase.assertEqual(results.length, 0);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 10').length, 5);
// update should work on a sorted result set
results = realm.objects('NullableBasicTypesObject').filtered('intCol == 10').sorted('intCol');
realm.write(() => {
results.update('intCol', 20);
});
TestCase.assertEqual(results.length, 0);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 20').length, 5);
// update should work on a result snapshot
results = realm.objects('NullableBasicTypesObject').filtered('intCol == 20').snapshot();
realm.write(() => {
results.update('intCol', 10);
});
TestCase.assertEqual(results.length, 5); // snapshot length should not change
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('intCol = 10').length, 5);
realm.close();
},
testResultsUpdateDataTypes: function() {
const N = 5;
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
boolCol: false,
stringCol: 'hello',
intCol: 10,
floatCol: 10.0,
doubleCol: 10.0,
dateCol: new Date(10)
});
}
});
const testCases = [
// col name, initial filter, initial filter pre-update count, initial filter post-update count, updated value, updated filter, updated filter post-update count
[ 'boolCol', 'boolCol = false', N, 0, true, 'boolCol = true', N ],
[ 'stringCol', 'stringCol = "hello"', N, 0, 'world', 'stringCol = "world"', N ],
[ 'intCol', 'intCol = 10', N, 0, 20, 'intCol = 20', N ],
[ 'floatCol', 'floatCol = 10.0', N, 0, 20.0, 'floatCol = 20.0', N ],
[ 'doubleCol', 'doubleCol = 10.0', N, 0, 20.0, 'doubleCol = 20.0', N ],
];
testCases.forEach(function(testCase) {
var results = realm.objects('NullableBasicTypesObject').filtered(testCase[1]);
TestCase.assertEqual(results.length, testCase[2]);
realm.write(() => {
results.update(testCase[0], testCase[4]);
});
TestCase.assertEqual(results.length, testCase[3]);
results = realm.objects('NullableBasicTypesObject').filtered(testCase[5]);
TestCase.assertEqual(results.length, testCase[6]);
});
realm.close();
},
testResultUpdateDateColumn: function() {
const N = 5;
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
// date column
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
dateCol: new Date(1000)
});
}
});
var results = realm.objects('NullableBasicTypesObject').filtered('dateCol = $0', new Date(1000));
TestCase.assertEqual(results.length, N);
realm.write(() => {
results.update('dateCol', new Date(2000));
});
TestCase.assertEqual(results.length, 0);
results = realm.objects('NullableBasicTypesObject').filtered('dateCol = $0', new Date(2000));
TestCase.assertEqual(results.length, N);
realm.close();
},
testResultsUpdateDataColumn: function() {
const N = 5;
var 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,
]);
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
// date column
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
dataCol: null
});
}
});
var results = realm.objects('NullableBasicTypesObject');
TestCase.assertEqual(results.length, N);
realm.write(() => {
results.update('dataCol', RANDOM_DATA);
});
for(var i = 0; i < results.length; i++) {
TestCase.assertArraysEqual(new Uint8Array(results[i].dataCol), RANDOM_DATA);
}
realm.close();
},
testResultsUpdateEmpty() {
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
var emptyResults = realm.objects('NullableBasicTypesObject').filtered('stringCol = "hello"');
TestCase.assertEqual(emptyResults.length, 0);
realm.write(() => {
emptyResults.update('stringCol', 'no-op');
});
TestCase.assertEqual(emptyResults.length, 0);
TestCase.assertEqual(realm.objects('NullableBasicTypesObject').filtered('stringCol = "no-op"').length, 0);
realm.close();
},
testResultsUpdateInvalidated() {
var realm = new Realm({schema: [schemas.TestObject]});
realm.write(function() {
for (var i = 10; i > 0; i--) {
realm.create('TestObject', [i]);
}
});
var resultsVariants = [
realm.objects('TestObject'),
realm.objects('TestObject').filtered('doubleCol > 1'),
realm.objects('TestObject').filtered('doubleCol > 1').sorted('doubleCol'),
realm.objects('TestObject').filtered('doubleCol > 1').snapshot()
];
// test isValid
resultsVariants.forEach(function(objects) {
TestCase.assertEqual(objects.isValid(), true);
});
// close and test update
realm.close();
realm = new Realm({
schemaVersion: 1,
schema: [schemas.TestObject, schemas.BasicTypes]
});
resultsVariants.forEach(function(objects) {
TestCase.assertEqual(objects.isValid(), false);
TestCase.assertThrows(function() { objects.update('doubleCol', 42); });
});
},
testResultsUpdateWrongProperty() {
var realm = new Realm({schema: [schemas.NullableBasicTypes]});
const N = 5;
realm.write(() => {
for(var i = 0; i < N; i++) {
realm.create('NullableBasicTypesObject', {
stringCol: 'hello'
});
}
});
var results = realm.objects('NullableBasicTypesObject').filtered('stringCol = "hello"');
TestCase.assertEqual(results.length, N);
TestCase.assertThrows(function() {
realm.write(() => {
results.update('unknownCol', 'world');
});
});
TestCase.assertThrows(function() {
results.update('stringCol', 'world');
});
realm.close();
}
};

View File

@ -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: {

View File

@ -34,12 +34,14 @@ function node_require(module) {
let tmp;
let fs;
let execFile;
let path;
if (isNodeProccess) {
tmp = node_require('tmp');
fs = node_require('fs');
execFile = node_require('child_process').execFile;
tmp.setGracefulCleanup();
path = node_require("path");
}
@ -76,6 +78,14 @@ function promisifiedLogin(server, username, password) {
});
}
function copyFileToTempDir(filename) {
let tmpDir = tmp.dirSync();
let content = fs.readFileSync(filename);
let tmpFile = tmp.fileSync({ dir: tmpDir.name });
fs.appendFileSync(tmpFile.fd, content);
return tmpFile.name;
}
function runOutOfProcess(nodeJsFilePath) {
var nodeArgs = Array.prototype.slice.call(arguments);
let tmpDir = tmp.dirSync();
@ -104,7 +114,6 @@ function runOutOfProcess(nodeJsFilePath) {
}
module.exports = {
testLocalRealmHasNoSession() {
let realm = new Realm();
TestCase.assertNull(realm.syncSession);
@ -240,89 +249,6 @@ module.exports = {
});
},
testProgressNotificationsForRealmOpen() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
const expectedObjectsCount = 3;
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => {
const accessTokenRefreshed = this;
let successCounter = 0;
let progressNotificationCalled = false;
let config = {
sync: {
user,
url: `realm://localhost:9080/~/${realmName}`,
_onDownloadProgress: (transferred, total) => {
progressNotificationCalled = true
},
},
schema: [{ name: 'Dog', properties: { name: 'string' } }],
};
return Realm.open(config)
.then(realm => {
return realm.syncSession;
}).then(session => {
TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.open");
});
});
});
},
testProgressNotificationsForRealmOpenAsync() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
const expectedObjectsCount = 3;
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => {
return new Promise((resolve, reject) => {
let progressNotificationCalled = false;
let config = {
sync: { user, url: `realm://localhost:9080/~/${realmName}`,
_onDownloadProgress: (transferred, total) => {
progressNotificationCalled = true
},
},
schema: [{ name: 'Dog', properties: { name: 'string' } }],
};
Realm.openAsync(config, (error, realm) => {
try {
if (error) {
reject(error);
}
setTimeout(() => {
try {
TestCase.assertTrue(progressNotificationCalled, "Progress notification not called for Realm.openAsync");
resolve();
} catch (e) {
reject(e);
}
}, 50);
}
catch (e) {
reject(e);
}
});
});
});
});
},
testRealmOpenAsyncNoSchema() {
if (!isNodeProccess) {
return Promise.resolve();
@ -461,6 +387,130 @@ module.exports = {
});
},
testIncompatibleSyncedRealmOpen() {
let realm = "sync-v1.realm";
if (isNodeProccess) {
realm = copyFileToTempDir(path.join(process.cwd(), "data", realm));
}
else {
//copy the bundled RN realm files for the test
Realm.copyBundledRealmFiles();
}
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
return new Promise((resolve, _reject) => {
const config = {
path: realm,
sync: {
user,
error : err => cosole.log(err),
url: 'realm://localhost:9080/~/sync-v1'
}
};
Realm.open(config)
.then(realm =>
_reject("Should fail with IncompatibleSyncedRealmError"))
.catch(e => {
if (e.name == "IncompatibleSyncedRealmError") {
const backupRealm = new Realm(e.configuration);
TestCase.assertEqual(backupRealm.objects('Dog').length, 3);
resolve();
return;
}
function printObject(o) {
var out = '';
for (var p in o) {
out += p + ': ' + o[p] + '\n';
}
return out;
}
_reject("Failed with unexpected error " + printObject(e));
});
});
});
},
testIncompatibleSyncedRealmOpenAsync() {
let realm = "sync-v1.realm";
if (isNodeProccess) {
realm = copyFileToTempDir(path.join(process.cwd(), "data", realm));
}
else {
//copy the bundled RN realm files for the test
Realm.copyBundledRealmFiles();
}
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
return new Promise((resolve, _reject) => {
const config = {
path: realm,
sync: {
user,
error : err => cosole.log(err),
url: 'realm://localhost:9080/~/sync-v1'
}
};
Realm.openAsync(config, (error, realm) => {
if (!error) {
_reject("Should fail with IncompatibleSyncedRealmError");
return;
}
if (error.name == "IncompatibleSyncedRealmError") {
const backupRealm = new Realm(error.configuration);
TestCase.assertEqual(backupRealm.objects('Dog').length, 3);
resolve();
return;
}
_reject("Failed with unexpected error" + JSON.stringify(error));
});
});
});
},
testIncompatibleSyncedRealmConsructor() {
let realm = "sync-v1.realm";
if (isNodeProccess) {
realm = copyFileToTempDir(path.join(process.cwd(), "data", realm));
}
else {
//copy the bundled RN realm files for the test
Realm.copyBundledRealmFiles();
}
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
return new Promise((resolve, _reject) => {
const config = {
path: realm,
sync: {
user,
error : err => cosole.log(err),
url: 'realm://localhost:9080/~/sync-v1'
}
};
try {
const realm = new Realm(config);
_reject("Should fail with IncompatibleSyncedRealmError");
}
catch (e) {
if (e.name == "IncompatibleSyncedRealmError") {
const backupRealm = new Realm(e.configuration);
TestCase.assertEqual(backupRealm.objects('Dog').length, 3);
resolve();
return;
}
_reject("Failed with unexpected error" + JSON.stringify(e));
}
});
});
},
testProgressNotificationsForRealmConstructor() {
if (!isNodeProccess) {
@ -564,7 +614,7 @@ module.exports = {
});
},
testProgressNotificationsForRealmOpen2() {
testProgressNotificationsForRealmOpen() {
if (!isNodeProccess) {
return Promise.resolve();
}
@ -603,7 +653,7 @@ module.exports = {
});
},
testProgressNotificationsForRealmOpenAsync2() {
testProgressNotificationsForRealmOpenAsync() {
if (!isNodeProccess) {
return Promise.resolve();
}
@ -646,4 +696,71 @@ module.exports = {
});
});
},
testPartialSync() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => {
let config = {
sync: {
user: user,
url: `realm://localhost:9080/~/${realmName}`,
partial: true,
error: (session, error) => console.log(error)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
Realm.deleteFile(config);
const realm = new Realm(config);
TestCase.assertEqual(realm.objects('Dog').length, 0);
return realm.subscribeToObjects("Dog", "name == 'Lassy 1'").then(results => {
TestCase.assertEqual(results.length, 1);
TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly");
});
})
})
},
testClientReset() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return Promise.resolve();
}
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => {
return new Promise((resolve, _reject) => {
var realm;
const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } };
config.sync.error = (sender, error) => {
try {
TestCase.assertEqual(error.code, 7); // 7 -> client reset
TestCase.assertDefined(error.config);
TestCase.assertNotEqual(error.config.path, '');
const original_path = realm.path;
realm.close();
Realm.Sync.initiateClientReset(original_path);
// copy required objects from Realm at error.config.path
resolve();
}
catch (e) {
_reject(e);
}
};
realm = new Realm(config);
const session = realm.syncSession;
TestCase.assertEqual(session.config.error, config.sync.error);
session._simulateError(211, 'ClientReset'); // 211 -> divering histories
});
});
}
}

View File

@ -29,6 +29,7 @@
855301CF1E20069D00FF108E /* dates-v3.realm in Resources */ = {isa = PBXBuildFile; fileRef = 855301CD1E20069D00FF108E /* dates-v3.realm */; };
855301D01E20069D00FF108E /* dates-v5.realm in Resources */ = {isa = PBXBuildFile; fileRef = 855301CE1E20069D00FF108E /* dates-v5.realm */; };
855301D31E2006F700FF108E /* RealmReactTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 855301D11E2006F400FF108E /* RealmReactTests.m */; };
A4CEF4BB1F7F862D00BA3B26 /* sync-v1.realm in Resources */ = {isa = PBXBuildFile; fileRef = A4CEF4BA1F7F862D00BA3B26 /* sync-v1.realm */; };
E2050A7A5BE14CEA9A9E0722 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B37A7097A134D5CBB4C462A /* libc++.tbd */; };
/* End PBXBuildFile section */
@ -253,6 +254,7 @@
855301CE1E20069D00FF108E /* dates-v5.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dates-v5.realm"; sourceTree = "<group>"; };
855301D11E2006F400FF108E /* RealmReactTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RealmReactTests.m; path = ReactTests/RealmReactTests.m; sourceTree = "<group>"; };
8B37A7097A134D5CBB4C462A /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
A4CEF4BA1F7F862D00BA3B26 /* sync-v1.realm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "sync-v1.realm"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -476,6 +478,7 @@
855301CC1E20069D00FF108E /* data */ = {
isa = PBXGroup;
children = (
A4CEF4BA1F7F862D00BA3B26 /* sync-v1.realm */,
855301CD1E20069D00FF108E /* dates-v3.realm */,
855301CE1E20069D00FF108E /* dates-v5.realm */,
);
@ -805,6 +808,7 @@
files = (
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
A4CEF4BB1F7F862D00BA3B26 /* sync-v1.realm in Resources */,
855301D01E20069D00FF108E /* dates-v5.realm in Resources */,
855301CF1E20069D00FF108E /* dates-v3.realm in Resources */,
);