Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delay constructing the config handle #455

Merged
merged 7 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ x.x.x Release notes (yyyy-MM-dd)

**This project is in the Alpha stage. All API's might change without warning and no guarantees are given about stability. Do not use it in production.**

### Breaking Changes
* Made all `Configuration` fields final so they can only be initialized in the constructor. This better conveys the immutability of the configuration class. ([#455](https://github.com/realm/realm-dart/pull/455))

### Enhancements
* Support result value from write transaction callbacks ([#294](https://github.com/realm/realm-dart/pull/294/))
* Added a property `Realm.isInTransaction` that indicates whether the Realm instance has an open write transaction associated with it.
Expand Down
75 changes: 14 additions & 61 deletions lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,16 @@ import 'package:path/path.dart' as _path;
/// Configuration used to create a [Realm] instance
/// {@category Configuration}
class Configuration {
final ConfigHandle _handle;
final RealmSchema _schema;
bool _isInUse = false;

static String? _defaultPath;

/// The [RealmSchema] for this [Configuration]
RealmSchema get schema => _schema;
final RealmSchema schema;

/// Creates a [Configuration] with schema objects for opening a [Realm].
///
/// [fifoFilesFallbackPath] enables FIFO special files.
/// [readOnly] controls whether a [Realm] is opened as read-only.
/// [inMemory] specifies if a [Realm] should be opened in-memory.
Configuration(List<SchemaObject> schemaObjects, {String? fifoFilesFallbackPath, bool readOnly = false, bool inMemory = false})
: _schema = RealmSchema(schemaObjects),
_handle = realmCore.createConfig() {
schemaVersion = 0;
path = defaultPath;

if (fifoFilesFallbackPath != null) {
this.fifoFilesFallbackPath = fifoFilesFallbackPath;
}

if (readOnly) {
isReadOnly = true;
}
if (inMemory) {
isInMemory = true;
}
realmCore.setSchema(this);
}
Configuration(List<SchemaObject> schemaObjects,
{this.fifoFilesFallbackPath, this.isReadOnly = false, this.isInMemory = false, this.schemaVersion = 0, String? path})
: schema = RealmSchema(schemaObjects),
path = path ?? defaultPath;

static String _initDefaultPath() {
var path = "default.realm";
Expand All @@ -84,55 +62,43 @@ class Configuration {
return "";
}

@override
bool operator ==(Object other) {
blagoev marked this conversation as resolved.
Show resolved Hide resolved
if (identical(this, other)) return true;
if (other is! Configuration) return false;
return realmCore.configurationEquals(this, other);
}

/// The schema version used to open the [Realm]
///
/// If omitted the default value of `0` is used to open the [Realm]
/// It is required to specify a schema version when initializing an existing
/// Realm with a schema that contains objects that differ from their previous
/// specification. If the schema was updated and the schemaVersion was not,
/// an [RealmException] will be thrown.
int get schemaVersion => realmCore.getSchemaVersion(this);
set schemaVersion(int value) => realmCore.setSchemaVersion(this, value);
final int schemaVersion;

///The path where the Realm should be stored.
/// The path where the Realm should be stored.
///
/// If omitted the [defaultPath] for the platform will be used.
String get path => realmCore.getConfigPath(this);
set path(String value) => realmCore.setConfigPath(this, value);
final String path;

/// Gets or sets a value indicating whether a [Realm] is opened as readonly.
/// Specifies whether a [Realm] should be opened as read-only.
/// This allows opening it from locked locations such as resources,
/// bundled with an application.
///
/// The realm file must already exists at [path]
bool get isReadOnly => realmCore.getConfigReadOnly(this);
set isReadOnly(bool value) => realmCore.setConfigReadOnly(this, value);
final bool isReadOnly;

/// Specifies if a [Realm] should be opened in-memory.
/// Specifies whether a [Realm] should be opened in-memory.
///
/// This still requires a [path] (can be the default path) to identify the [Realm] so other processes can open the same [Realm].
/// The file will also be used as swap space if the [Realm] becomes bigger than what fits in memory,
/// but it is not persistent and will be removed when the last instance is closed.
/// When all in-memory instance of [Realm] is closed all data in that [Realm] is deleted.
bool get isInMemory => realmCore.getConfigInMemory(this);
set isInMemory(bool value) => realmCore.setConfigInMemory(this, value);
final bool isInMemory;

/// Gets or sets a value of FIFO special files location.
/// Specifies the FIFO special files fallback location.
/// Opening a [Realm] creates a number of FIFO special files in order to
/// coordinate access to the [Realm] across threads and processes. If the [Realm] file is stored in a location
/// that does not allow the creation of FIFO special files (e.g. FAT32 filesystems), then the [Realm] cannot be opened.
/// In that case [Realm] needs a different location to store these files and this property defines that location.
/// The FIFO special files are very lightweight and the main [Realm] file will still be stored in the location defined
/// by the [path] you property. This property is ignored if the directory defined by [path] allow FIFO special files.
String get fifoFilesFallbackPath => realmCore.getConfigFifoPath(this);
set fifoFilesFallbackPath(String value) => realmCore.setConfigFifoPath(this, value);
final String? fifoFilesFallbackPath;
}

/// A collection of properties describing the underlying schema of a [RealmObject].
Expand All @@ -156,9 +122,6 @@ class SchemaObject {
///
/// {@category Configuration}
class RealmSchema extends Iterable<SchemaObject> {
///@nodoc
late final SchemaHandle handle;

late final List<SchemaObject> _schema;

/// Initializes [RealmSchema] instance representing ```schemaObjects``` collection
Expand All @@ -168,7 +131,6 @@ class RealmSchema extends Iterable<SchemaObject> {
}

_schema = schemaObjects;
handle = realmCore.createSchema(schemaObjects);
}

@override
Expand All @@ -182,12 +144,3 @@ class RealmSchema extends Iterable<SchemaObject> {
@override
SchemaObject elementAt(int index) => _schema.elementAt(index);
}

/// @nodoc
extension ConfigurationInternal on Configuration {
///@nodoc
ConfigHandle get handle => _handle;

bool get isInUse => _isInUse;
set isInUse(bool value) => _isInUse = value;
}
84 changes: 21 additions & 63 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class _RealmCore {
});
}

SchemaHandle createSchema(List<SchemaObject> schema) {
SchemaHandle _createSchema(Iterable<SchemaObject> schema) {
return using((Arena arena) {
final classCount = schema.length;

Expand Down Expand Up @@ -136,63 +136,25 @@ class _RealmCore {
});
}

void setSchema(Configuration config) {
_realmLib.realm_config_set_schema(config.handle._pointer, config.schema.handle._pointer);
}

void validateSchema(RealmSchema schema) {
_realmLib.invokeGetBool(
() => _realmLib.realm_schema_validate(schema.handle._pointer, realm_schema_validation_mode.RLM_SCHEMA_VALIDATION_BASIC), "Invalid Realm schema.");
}

int getSchemaVersion(Configuration config) {
return _realmLib.realm_config_get_schema_version(config.handle._pointer);
}

void setSchemaVersion(Configuration config, int version) {
_realmLib.realm_config_set_schema_version(config.handle._pointer, version);
}

bool getConfigReadOnly(Configuration config) {
int mode = _realmLib.realm_config_get_schema_mode(config.handle._pointer);
return mode == realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE;
}

void setConfigReadOnly(Configuration config, bool value) {
int mode = value ? realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE : realm_schema_mode.RLM_SCHEMA_MODE_AUTOMATIC;
_realmLib.realm_config_set_schema_mode(config.handle._pointer, mode);
}

bool getConfigInMemory(Configuration config) {
return _realmLib.realm_config_get_in_memory(config.handle._pointer);
}

void setConfigInMemory(Configuration config, bool value) {
_realmLib.realm_config_set_in_memory(config.handle._pointer, value);
}

String getConfigFifoPath(Configuration config) {
return _realmLib.realm_config_get_fifo_path(config.handle._pointer).cast<Utf8>().toDartString();
}

void setConfigFifoPath(Configuration config, String path) {
ConfigHandle _createConfig(Configuration config, SchedulerHandle schedulerHandle) {
return using((Arena arena) {
_realmLib.realm_config_set_fifo_path(config.handle._pointer, path.toUtf8Ptr(arena));
});
}

ConfigHandle createConfig() {
final configPtr = _realmLib.realm_config_new();
return ConfigHandle._(configPtr);
}

String getConfigPath(Configuration config) {
return _realmLib.realm_config_get_path(config.handle._pointer).cast<Utf8>().toDartString();
}
final schemaHandle = _createSchema(config.schema);
final configPtr = _realmLib.realm_config_new();
final configHandle = ConfigHandle._(configPtr);
_realmLib.realm_config_set_schema(configHandle._pointer, schemaHandle._pointer);
_realmLib.realm_config_set_schema_version(configHandle._pointer, config.schemaVersion);

final schemaMode = config.isReadOnly ? realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE : realm_schema_mode.RLM_SCHEMA_MODE_AUTOMATIC;
_realmLib.realm_config_set_schema_mode(configHandle._pointer, schemaMode);
_realmLib.realm_config_set_in_memory(configHandle._pointer, config.isInMemory);

if (config.fifoFilesFallbackPath != null) {
_realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toUtf8Ptr(arena));
}
_realmLib.realm_config_set_path(configHandle._pointer, config.path.toUtf8Ptr(arena));
_realmLib.realm_config_set_scheduler(configHandle._pointer, schedulerHandle._pointer);

void setConfigPath(Configuration config, String path) {
return using((Arena arena) {
_realmLib.realm_config_set_path(config.handle._pointer, path.toUtf8Ptr(arena));
return configHandle;
});
}

Expand All @@ -205,12 +167,9 @@ class _RealmCore {
_realmLib.realm_scheduler_perform_work(schedulerHandle._pointer);
}

void setScheduler(Configuration config, SchedulerHandle schedulerHandle) {
_realmLib.realm_config_set_scheduler(config.handle._pointer, schedulerHandle._pointer);
}

RealmHandle openRealm(Configuration config) {
final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_open(config.handle._pointer), "Error opening realm at path ${config.path}");
RealmHandle openRealm(Configuration config, Scheduler scheduler) {
final configHandle = _createConfig(config, scheduler.handle);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_open(configHandle._pointer), "Error opening realm at path ${config.path}");
return RealmHandle._(realmPtr);
}

Expand Down Expand Up @@ -539,7 +498,6 @@ class _RealmCore {

bool objectEquals(RealmObject first, RealmObject second) => _equals(first.handle, second.handle);
bool realmEquals(Realm first, Realm second) => _equals(first.handle, second.handle);
bool configurationEquals(Configuration first, Configuration second) => _equals(first.handle, second.handle);

RealmResultsHandle resultsSnapshot(RealmResults results) {
final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_snapshot(results.handle._pointer));
Expand Down
13 changes: 3 additions & 10 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,10 @@ class Realm {

/// Opens a `Realm` using a [Configuration] object.
Realm(Configuration config) : _config = config {
if (_config.isInUse) {
throw RealmStateError("A Realm instance for this configuraiton object already exists.");
}
_scheduler = Scheduler(_config, close);
_scheduler = Scheduler(close);

try {
_handle = realmCore.openRealm(_config);
_handle = realmCore.openRealm(_config, _scheduler);

for (var realmClass in _config.schema) {
final classMeta = realmCore.getClassMetadata(this, realmClass.name, realmClass.type);
Expand All @@ -71,7 +68,6 @@ class Realm {
_scheduler.stop();
rethrow;
}
_config.isInUse = true;
}

/// Deletes all files associated with a `Realm` located at given [path]
Expand Down Expand Up @@ -192,7 +188,6 @@ class Realm {
void close() {
realmCore.closeRealm(this);
_scheduler.stop();
_config.isInUse = false;
}

/// Checks whether the `Realm` is closed.
Expand Down Expand Up @@ -259,7 +254,7 @@ class Scheduler {
final void Function() onFinalize;
final RawReceivePort receivePort = RawReceivePort();

Scheduler(Configuration config, this.onFinalize) {
Scheduler(this.onFinalize) {
receivePort.handler = (dynamic message) {
if (message == SCHEDULER_FINALIZE_OR_PROCESS_EXIT) {
onFinalize();
Expand All @@ -272,8 +267,6 @@ class Scheduler {

final sendPort = receivePort.sendPort;
handle = realmCore.createScheduler(Isolate.current.hashCode, sendPort.nativePort);

realmCore.setScheduler(config, handle);
}

void stop() {
Expand Down
28 changes: 13 additions & 15 deletions test/configuration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,24 @@ Future<void> main([List<String>? args]) async {
});

test('Configuration get/set path', () {
Configuration config = Configuration([Car.schema]);
final config = Configuration([Car.schema]);
expect(config.path, endsWith('.realm'));

const path = "my/path/default.realm";
config.path = path;
expect(config.path, equals(path));
final explicitPathConfig = Configuration([Car.schema], path: path);
expect(explicitPathConfig.path, equals(path));
});

test('Configuration get/set schema version', () {
Configuration config = Configuration([Car.schema]);
final config = Configuration([Car.schema]);
expect(config.schemaVersion, equals(0));

config.schemaVersion = 3;
expect(config.schemaVersion, equals(3));
final explicitSchemaConfig = Configuration([Car.schema], schemaVersion: 3);
expect(explicitSchemaConfig.schemaVersion, equals(3));
});

test('Configuration readOnly - opening non existing realm throws', () {
Configuration config = Configuration([Car.schema], readOnly: true);
Configuration config = Configuration([Car.schema], isReadOnly: true);
expect(() => getRealm(config), throws<RealmException>("at path '${config.path}' does not exist"));
});

Expand All @@ -81,7 +81,7 @@ Future<void> main([List<String>? args]) async {
realm.close();

// Open an existing realm as readonly.
config = Configuration([Car.schema], readOnly: true);
config = Configuration([Car.schema], isReadOnly: true);
realm = getRealm(config);
});

Expand All @@ -91,8 +91,7 @@ Future<void> main([List<String>? args]) async {
realm.write(() => realm.add(Car("Mustang")));
realm.close();

config = Configuration([Car.schema]);
config.isReadOnly = true;
config = Configuration([Car.schema], isReadOnly: true);
realm = getRealm(config);
var cars = realm.all<Car>();
expect(cars.length, 1);
Expand All @@ -103,26 +102,25 @@ Future<void> main([List<String>? args]) async {
var realm = getRealm(config);
realm.close();

config = Configuration([Car.schema], readOnly: true);
config = Configuration([Car.schema], isReadOnly: true);
realm = getRealm(config);
expect(() => realm.write(() {}), throws<RealmException>("Can't perform transactions on read-only Realms."));
});

test('Configuration inMemory - no files after closing realm', () {
Configuration config = Configuration([Car.schema], inMemory: true);
Configuration config = Configuration([Car.schema], isInMemory: true);
var realm = getRealm(config);
realm.write(() => realm.add(Car('Tesla')));
realm.close();
expect(Realm.existsSync(config.path), false);
});

test('Configuration inMemory can not be readOnly', () {
Configuration config = Configuration([Car.schema], inMemory: true);
Configuration config = Configuration([Car.schema], isInMemory: true);
final realm = getRealm(config);

expect(() {
config = Configuration([Car.schema]);
config.isReadOnly = true;
config = Configuration([Car.schema], isReadOnly: true);
getRealm(config);
}, throws<RealmException>("Realm at path '${config.path}' already opened with different read permissions"));
});
Expand Down
Loading