Add shouldCompactOnLaunch option to configuration (#1209)

* Adding shouldCompactOnLaunch option to configuration
* Adding Realm.compact()
This commit is contained in:
Kenneth Geisshirt 2017-08-18 14:22:29 +02:00 committed by GitHub
parent 0c557fcfe5
commit bd28c05936
7 changed files with 145 additions and 0 deletions

View File

@ -1,3 +1,16 @@
X.Y.Z Release notes
=============================================================
### Breaking changes
* None
### Enhancements
* Added `shouldCompactOnLaunch` to configuration (#507).
* Added `Realm.compact()` for manually compacting Realm files.
### Bug fixes
* None
1.10.3 Release notes (2017-8-16)
=============================================================
### Breaking changes

View File

@ -182,6 +182,24 @@ class Realm {
* @param {function()} callback
*/
write(callback) {}
/**
* Replaces all string columns in this Realm with a string enumeration column and compacts the
* database file.
*
* Cannot be called from a write transaction.
*
* Compaction will not occur if other `Realm` instances exist.
*
* While compaction is in progress, attempts by other threads or processes to open the database will
* wait.
*
* Be warned that resource requirements for compaction is proportional to the amount of live data in
* the database. Compaction works by writing the database contents to a temporary database file and
* then replacing the database with the temporary one.
* @returns {true} if compaction succeeds.
*/
compact() {}
}
/**
@ -213,6 +231,13 @@ 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 {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:
* - `totalSize` - The total file size (data + free space)
* - `unusedSize` - The total bytes used by data in the file.
* It returns `true` to indicate that an attempt to compact the file should be made. The compaction
* will be skipped if another process is accessing it.
* @property {string} [path={@link Realm.defaultPath}] - The path to the file where the
* Realm database should be stored.
* @property {boolean} [readOnly=false] - Specifies if this Realm should be opened as read-only.

View File

@ -132,6 +132,7 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [
'delete',
'deleteAll',
'write',
'compact',
], true);
const Sync = {

6
lib/index.d.ts vendored
View File

@ -77,6 +77,7 @@ declare namespace Realm {
interface Configuration {
encryptionKey?: ArrayBuffer | ArrayBufferView | Int8Array;
migration?: (oldRealm: Realm, newRealm: Realm) => void;
shouldCompactOnLaunch?: (totalBytes: number, usedBytes: number) => boolean;
path?: string;
readOnly?: boolean;
schema?: ObjectClass[] | ObjectSchema[];
@ -452,6 +453,11 @@ declare class Realm {
* @returns void
*/
write(callback: () => void): void;
/**
* @returns boolean
*/
compact(): boolean;
}
declare module 'realm' {

View File

@ -176,6 +176,8 @@ public:
static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void close(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void compact(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
// properties
static void get_empty(ContextType, ObjectType, ReturnValue &);
@ -223,6 +225,7 @@ public:
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
{"close", wrap<close>},
{"compact", wrap<compact>},
};
PropertyMap<T> const properties = {
@ -390,6 +393,28 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
config.schema_version = 0;
}
static const String compact_on_launch_string = "shouldCompactOnLaunch";
ValueType compact_value = Object::get_property(ctx, object, compact_on_launch_string);
if (!Value::is_undefined(ctx, compact_value)) {
if (config.schema_mode == SchemaMode::ReadOnly) {
throw std::invalid_argument("Cannot set 'shouldCompactOnLaunch' when 'readOnly' is set.");
}
if (config.sync_config) {
throw std::invalid_argument("Cannot set 'shouldCompactOnLaunch' when 'sync' is set.");
}
FunctionType should_compact_on_launch_function = Value::validated_to_function(ctx, compact_value, "shouldCompactOnLaunch");
config.should_compact_on_launch_function = [=](uint64_t total_bytes, uint64_t unused_bytes) {
ValueType arguments[2] = {
Value::from_number(ctx, total_bytes),
Value::from_number(ctx, unused_bytes)
};
ValueType should_compact = Function<T>::callback(ctx, should_compact_on_launch_function, this_object, 2, arguments);
return Value::to_boolean(ctx, should_compact);
};
}
static const String migration_string = "migration";
ValueType migration_value = Object::get_property(ctx, object, migration_string);
if (!Value::is_undefined(ctx, migration_value)) {
@ -836,5 +861,17 @@ void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object,
realm->close();
}
template<typename T>
void RealmClass<T>::compact(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
if (realm->is_in_transaction()) {
throw std::runtime_error("Cannot compact a Realm within a transaction.");
}
return_value.set(realm->compact());
}
} // js
} // realm

View File

@ -943,5 +943,61 @@ module.exports = {
realm.write(() => realm.delete(realm.objects('PersonObject')));
TestCase.assertTrue(realm.empty);
},
testCompact: function() {
var wasCalled = false;
const count = 1000;
// create compactable Realm
const realm1 = new Realm({schema: [schemas.StringOnly]});
realm1.write(() => {
realm1.create('StringOnlyObject', { stringCol: 'A' });
for (var i = 0; i < count; i++) {
realm1.create('StringOnlyObject', { stringCol: 'ABCDEFG' });
}
realm1.create('StringOnlyObject', { stringCol: 'B' });
});
realm1.close();
// open Realm and see if it is compacted
var shouldCompact = function(totalBytes, usedBytes) {
wasCalled = true;
const fiveHundredKB = 500*1024;
return (totalBytes > fiveHundredKB) && (usedBytes / totalBytes) < 0.2;
};
const realm2 = new Realm({schema: [schemas.StringOnly], shouldCompactOnLaunch: shouldCompact});
TestCase.assertTrue(wasCalled);
TestCase.assertEqual(realm2.objects('StringOnlyObject').length, count + 2);
// we don't check if the file is smaller as we assume that Object Store does it
realm2.close();
},
testManualCompact: function() {
const realm1 = new Realm({schema: [schemas.StringOnly]});
realm1.write(() => {
realm1.create('StringOnlyObject', { stringCol: 'A' });
});
TestCase.assertTrue(realm1.compact());
realm1.close();
const realm2 = new Realm({schema: [schemas.StringOnly]});
TestCase.assertEqual(realm2.objects('StringOnlyObject').length, 1);
realm2.close();
},
testManualCompactInWrite: function() {
const realm = new Realm({schema: [schemas.StringOnly]});
realm.write(() => {
TestCase.assertThrows(() => {
realm.compact();
});
});
TestCase.assertTrue(realm.empty);
},
testManualCompactMultipleInstances: function() {
const realm1 = new Realm({schema: [schemas.StringOnly]});
const realm2 = new Realm({schema: [schemas.StringOnly]});
TestCase.assertThrows(realm1.compact());
}
};

View File

@ -126,6 +126,13 @@ exports.StringPrimary = {
}
};
exports.StringOnly = {
name: 'StringOnlyObject',
properties: {
stringCol: 'string',
}
};
exports.AllTypes = {
name: 'AllTypesObject',
primaryKey: 'primaryCol',