Skip to content

Commit

Permalink
Implement indexOf using realm_list_find (#911)
Browse files Browse the repository at this point in the history
* Update realm-core to v12.7.0

Includes: 4d21f9a87 Expose `list_find` in the c api (#5848)

* Update bindings

* Add indexOf tests

* Implement UnmanagedRealmList using DelegatingList from collection

This both simplifies and optimize the implementation of UnmanagedRealmList,
as it delegates all operation to the backing list.

* Implement indexOf using newly exposed realm_list_find function on C-api

* Tweak indexOf tests. Realm lists throws, if type don't match

* Update CHANGELOG

* Error to call indexOf on a managed list with an unmanaged object

* More precise comment

* Refactor handling of state error in indexOf

* Revive dead test code

* Fail hard if trying to use unmanaged object as managed

* Just throw _CastError..
  • Loading branch information
nielsenko authored Sep 21, 2022
1 parent cfddb4d commit e80758f
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 31 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
**This project is in the Beta stage. The API should be quite stable, but occasional breaking changes may be made.**

### Enhancements
* Added support for "frozen objects" - these are objects, queries, lists, or Realms that have been "frozen" at a specific version. All frozen objects can be accessed and queried as normal, but attempting to mutate them or add change listeners will throw an exception. `Realm`, `RealmObject`, `RealmList`, and `RealmResults` now have a method `freeze()` which returns an immutable version of the object, as well as an `isFrozen` property which can be used to check whether an object is frozen. (Issue [#56](https://github.com/realm/realm-dart/issues/56))
* Added support for "frozen objects" - these are objects, queries, lists, or Realms that have been "frozen" at a specific version. All frozen objects can be accessed and queried as normal, but attempting to mutate them or add change listeners will throw an exception. `Realm`, `RealmObject`, `RealmList`, and `RealmResults` now have a method `freeze()` which returns an immutable version of the object, as well as an `isFrozen` property which can be used to check whether an object is frozen. ([#56](https://github.com/realm/realm-dart/issues/56))
* You can now set a realm property of type `T` to any object `o` where `o is T`. Previously it was required that `o.runtimeType == T`. ([#904](https://github.com/realm/realm-dart/issues/904))
* Performance of indexOf on realm lists has been improved. It now uses realm-core instead of the generic version from ListMixin. ([#911](https://github.com/realm/realm-dart/pull/911)).
* Added support for migrations for local Realms. You can now construct a configuration with a migration callback that will be invoked if the schema version of the file on disk is lower than the schema version supplied by the callback. (Issue [#70](https://github.com/realm/realm-dart/issues/70))

A minimal example looks like this:
Expand Down
41 changes: 16 additions & 25 deletions lib/src/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
////////////////////////////////////////////////////////////////////////////////
import 'dart:async';
import 'dart:collection' as collection;
import 'dart:collection';
import 'dart:ffi';

import 'package:collection/collection.dart' as collection;

import 'collections.dart';
import 'native/realm_core.dart';
import 'realm_class.dart';
Expand Down Expand Up @@ -47,7 +49,7 @@ abstract class RealmList<T extends Object?> with RealmEntity implements List<T>,
RealmList<T> freeze();
}

class ManagedRealmList<T extends Object?> extends collection.ListBase<T> with RealmEntity implements RealmList<T> {
class ManagedRealmList<T extends Object?> with RealmEntity, ListMixin<T> implements RealmList<T> {
final RealmListHandle _handle;

@override
Expand Down Expand Up @@ -127,6 +129,16 @@ class ManagedRealmList<T extends Object?> extends collection.ListBase<T> with Re
@override
void clear() => realmCore.listClear(handle);

@override
int indexOf(covariant T element, [int start = 0]) {
if (element is RealmObject && !element.isManaged) {
throw RealmStateError('Cannot call indexOf on a managed list with an element that is an unmanaged object');
}
if (start < 0) start = 0;
final index = realmCore.listFind(this, element);
return index < start ? -1 : index; // to align with dart list semantics
}

@override
bool get isValid => realmCore.listIsValid(this);

Expand All @@ -141,36 +153,15 @@ class ManagedRealmList<T extends Object?> extends collection.ListBase<T> with Re
}
}

class UnmanagedRealmList<T extends Object?> extends collection.ListBase<T> with RealmEntity implements RealmList<T> {
final _unmanaged = <T?>[]; // use T? for length=

UnmanagedRealmList([Iterable<T>? items]) {
if (items != null) {
_unmanaged.addAll(items);
}
}
class UnmanagedRealmList<T extends Object?> extends collection.DelegatingList<T> with RealmEntity implements RealmList<T> {
UnmanagedRealmList([Iterable<T>? items]) : super(List<T>.from(items ?? <T>[]));

@override
RealmObjectMetadata? get _metadata => throw RealmException("Unmanaged lists don't have metadata associated with them.");

@override
set _metadata(RealmObjectMetadata? _) => throw RealmException("Unmanaged lists don't have metadata associated with them.");

@override
int get length => _unmanaged.length;

@override
set length(int length) => _unmanaged.length = length;

@override
T operator [](int index) => _unmanaged[index] as T;

@override
void operator []=(int index, T value) => _unmanaged[index] = value;

@override
void clear() => _unmanaged.clear();

@override
bool get isValid => true;

Expand Down
17 changes: 17 additions & 0 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,23 @@ class _RealmCore {
_realmLib.invokeGetBool(() => _realmLib.realm_list_remove_all(list.handle._pointer));
}

int listFind(RealmList list, Object? value) {
return using((Arena arena) {
final out_index = arena<Size>();
final out_found = arena<Bool>();
final realm_value = _toRealmValue(value, arena);
_realmLib.invokeGetBool(
() => _realmLib.realm_list_find(
list.handle._pointer,
realm_value,
out_index,
out_found,
),
);
return out_found.value ? out_index.value : -1;
});
}

void resultsDeleteAll(RealmResults results) {
_realmLib.invokeGetBool(() => _realmLib.realm_results_delete_all(results.handle._pointer));
}
Expand Down
6 changes: 1 addition & 5 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -485,11 +485,7 @@ abstract class NotificationsController implements Finalizable {
}

void stop() {
if (handle == null) {
return;
}

handle!.release();
handle?.release();
handle = null;
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/realm_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:io';

import 'list.dart';
import 'native/realm_core.dart';
Expand Down Expand Up @@ -403,7 +404,9 @@ extension RealmObjectInternal on RealmObject {
return object;
}

// if we ever see a _CastError here, we forgot to guard against misuse further up the call-stack
RealmObjectHandle get handle => _handle!;

RealmAccessor get accessor => _accessor;
}

Expand Down
40 changes: 40 additions & 0 deletions test/list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -806,4 +806,44 @@ Future<void> main([List<String>? args]) async {
expect(team.players.query('TRUEPREDICATE'), isNot(realm.all<Person>()));
expect(team.players.query('TRUEPREDICATE'), [alice, bob]);
});

test('ManagedRealmList.indexOf', () {
final config = Configuration.local([Team.schema, Person.schema]);
final realm = getRealm(config);

final team = Team('sad team', players: [for (int i = 0; i < 100; ++i) Person('$i')]);
realm.write(() => realm.add(team));
final players = team.players;

expect(players, isA<RealmList<Person>>());
expect(players.isManaged, isTrue);
expect(players.indexOf(players.first, -1), 0); // okay to start from negative index
expect(players.indexOf(players.first, 1), -1); // start respected
expect(players.indexOf(players.first, 101), -1); // okay to start from non-existent index

var index = 0;
final r = Random(42); // deterministic
for (final p in players) {
expect(players.indexOf(p, r.nextInt(index + 1) - 1), index++);
}

// List.indexOf with wrong type of element, just returns -1.
// Proof:
final dartList = <int>[1, 2, 3];
expect((dartList as List<Object>).indexOf("abc"), -1); // ignore: unnecessary_cast

// .. but realm list behaves differently in this regard.
expect(() => (players as List<Object>).indexOf(1), throwsA(isA<TypeError>())); // ignore: unnecessary_cast

// .. Also it is a state error to lookup an unmanaged object in a managed list,
// even if the static type is right.
expect(
() => players.indexOf(Person('10')),
throwsA(isA<RealmStateError>().having(
(e) => e.message,
'message',
'Cannot call indexOf on a managed list with an element that is an unmanaged object',
)),
);
});
}

0 comments on commit e80758f

Please sign in to comment.