Skip to content

Commit

Permalink
Allow results of primitives.
Browse files Browse the repository at this point in the history
Moves results of objects specific functionality to extension method
to avoid misuse.
  • Loading branch information
nielsenko committed Oct 3, 2022
1 parent afd9a7a commit 5ea1310
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 46 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"dart.lineLength": 160,
"cSpell.words": [
"apikeys",
"BEGINSWITH",
"bson",
"deallocated",
"deleter",
Expand Down
21 changes: 13 additions & 8 deletions lib/src/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ abstract class RealmList<T extends Object?> with RealmEntityMixin implements Lis
/// and it's parent object hasn't been deleted.
bool get isValid;

RealmResults<T> get asResults;

factory RealmList._(RealmListHandle handle, Realm realm, RealmObjectMetadata? metadata) => ManagedRealmList._(handle, realm, metadata);
factory RealmList(Iterable<T> items) => UnmanagedRealmList(items);

Expand Down Expand Up @@ -164,6 +166,9 @@ class ManagedRealmList<T extends Object?> with RealmEntityMixin, ListMixin<T> im
final frozenRealm = realm.freeze();
return frozenRealm.resolveList(this)!;
}

@override
RealmResults<T> get asResults => RealmResultsInternal.createFromList(handle, realm, _metadata);
}

class UnmanagedRealmList<T extends Object?> extends collection.DelegatingList<T> with RealmEntityMixin implements RealmList<T> {
Expand All @@ -177,6 +182,9 @@ class UnmanagedRealmList<T extends Object?> extends collection.DelegatingList<T>

@override
RealmList<T> freeze() => throw RealmStateError("Unmanaged lists can't be frozen");

@override
RealmResults<T> get asResults => throw RealmStateError("Unmanaged lists can't be converted to results");
}

// The query operations on lists, as well as the ability to subscribe for notifications,
Expand All @@ -190,8 +198,7 @@ extension RealmListOfObject<T extends RealmObject<T>> on RealmList<T> {
/// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://academy.realm.io/posts/nspredicate-cheatsheet/)
/// and [Predicate Programming Guide.](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001789)
RealmResults<T> query(String query, [List<Object> arguments = const []]) {
final managedList = asManaged();
final handle = realmCore.queryList(managedList, query, arguments);
final handle = realmCore.queryList(asManaged, query, arguments);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
}

Expand All @@ -200,9 +207,7 @@ extension RealmListOfObject<T extends RealmObject<T>> on RealmList<T> {
if (isFrozen) {
throw RealmStateError('List is frozen and cannot emit changes');
}

final managedList = asManaged();
final controller = ListNotificationsController<T>(managedList);
final controller = ListNotificationsController<T>(asManaged);
return controller.createStream();
}
}
Expand All @@ -218,11 +223,11 @@ extension RealmListInternal<T extends Object?> on RealmList<T> {
}
}

ManagedRealmList<T> asManaged() => this is ManagedRealmList<T> ? this as ManagedRealmList<T> : throw RealmStateError('$this is not managed');
ManagedRealmList<T> get asManaged => this is ManagedRealmList<T> ? this as ManagedRealmList<T> : throw RealmStateError('$this is not managed');

RealmListHandle get handle => asManaged()._handle;
RealmListHandle get handle => asManaged._handle;

RealmObjectMetadata? get metadata => asManaged()._metadata;
RealmObjectMetadata? get metadata => asManaged._metadata;

static RealmList<T> create<T extends Object?>(RealmListHandle handle, Realm realm, RealmObjectMetadata? metadata) => RealmList<T>._(handle, realm, metadata);

Expand Down
15 changes: 14 additions & 1 deletion lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,20 @@ class _RealmCore {
});
}

RealmObjectHandle getObjectAt(RealmResults results, int index) {
RealmResultsHandle resultsFromList(RealmListHandle listHandle) {
final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_list_to_results(listHandle._pointer));
return RealmResultsHandle._(pointer);
}

Object? resultsGetElementAt(RealmResults results, int index) {
return using((Arena arena) {
final realm_value = arena<realm_value_t>();
_realmLib.invokeGetBool(() => _realmLib.realm_results_get(results.handle._pointer, index, realm_value));
return realm_value.toDartValue(results.realm);
});
}

RealmObjectHandle resultsGetObjectAt(RealmResults results, int index) {
final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_get_object(results.handle._pointer, index));
return RealmObjectHandle._(pointer);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export 'credentials.dart' show Credentials, AuthProviderType, EmailPasswordAuthP
export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges;
export 'realm_object.dart' show RealmEntityMixin, RealmObjectMixin, RealmException, UserCallbackException, RealmObject, RealmObjectChanges, DynamicRealmObject;
export 'realm_property.dart';
export 'results.dart' show RealmResults, RealmResultsChanges;
export 'results.dart' show RealmResults, RealmResultsChanges, RealmResultsOfObject;
export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress, ConnectionStateChange;
export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet;
export 'user.dart' show User, UserState, UserIdentity;
Expand Down Expand Up @@ -580,7 +580,7 @@ class DynamicRealm {
RealmResults<RealmObject<dynamic>> all(String className) {
final metadata = _realm._metadata.getByName(className);
final handle = realmCore.findAll(_realm, metadata.key);
return RealmResultsInternal.create<RealmObject>(handle, _realm, metadata);
return RealmResultsInternal.create<RealmObject<dynamic>>(handle, _realm, metadata);
}

/// Fast lookup for a [RealmObject] of type [className] with the specified [primaryKey].
Expand Down
82 changes: 54 additions & 28 deletions lib/src/results.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:async';
import 'dart:collection' as collection;
import 'dart:ffi';

import 'package:realm_common/realm_common.dart';

import 'collections.dart';
import 'native/realm_core.dart';
import 'realm_class.dart';
import 'realm_object.dart';
import 'type_utils.dart';

/// Instances of this class are live collections and will update as new elements are either
/// added to or deleted from the Realm that match the underlying query.
Expand All @@ -33,25 +35,21 @@ class RealmResults<T extends Object?> extends collection.IterableBase<T> with Re
final RealmObjectMetadata? _metadata;
final RealmResultsHandle _handle;

final _supportsSnapshot = <T>[] is List<RealmObject?>;
final _supportsSnapshot = isSubtype<T, RealmObject<T>>();

RealmResults._(this._handle, Realm realm, this._metadata) {
setRealm(realm);
}

/// Returns the element of type `T` at the specified [index].
T operator [](int index) {
final handle = realmCore.getObjectAt(this, index);
return RealmObjectInternal.create<T>(realm, handle, _metadata!);
}

/// Returns a new [RealmResults] filtered according to the provided query.
///
/// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://academy.realm.io/posts/nspredicate-cheatsheet/)
/// and [Predicate Programming Guide.](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001789)
RealmResults<T> query(String query, [List<Object> args = const []]) {
final handle = realmCore.queryResults(this, query, args);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
final meta = _metadata;
if (meta != null) {
final handle = realmCore.resultsGetObjectAt(this, index);
return RealmObjectInternal.create<T>(realm, handle, meta);
} else {
return realmCore.resultsGetElementAt(this, index) as T;
}
}

/// `true` if the `Results` collection is empty.
Expand All @@ -73,24 +71,42 @@ class RealmResults<T extends Object?> extends collection.IterableBase<T> with Re
@override
int get length => realmCore.getResultsCount(this);

/// Allows listening for changes when the contents of this collection changes.
Stream<RealmResultsChanges<T>> get changes {
/// Creates a frozen snapshot of this query.
RealmResults<T> freeze() {
if (isFrozen) {
throw RealmStateError('Results are frozen and cannot emit changes');
return this;
}
final frozenRealm = realm.freeze();
return frozenRealm.resolveResults(this);
}
}

final controller = ResultsNotificationsController<T>(this);
return controller.createStream();
// The query operations on results, as well as the ability to subscribe for notifications,
// only work for results of objects (core restriction), so we add these as an extension methods
// to allow the compiler to prevent misuse.
extension RealmResultsOfObject<T extends RealmObjectMarker> on RealmResults<T> {
RealmResults<T> snapshot() {
final handle = realmCore.resultsSnapshot(this);
return RealmResults<T>._(handle, realm, _metadata);
}

/// Creates a frozen snapshot of this query.
RealmResults<T> freeze() {
/// Returns a new [RealmResults] filtered according to the provided query.
///
/// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://academy.realm.io/posts/nspredicate-cheatsheet/)
/// and [Predicate Programming Guide.](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001789)
RealmResults<T> query(String query, [List<Object> args = const []]) {
final handle = realmCore.queryResults(this, query, args);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
}

/// Allows listening for changes when the contents of this collection changes.
Stream<RealmResultsChanges<T>> get changes {
if (isFrozen) {
return this;
throw RealmStateError('Results are frozen and cannot emit changes');
}

final frozenRealm = realm.freeze();
return frozenRealm.resolveResults(this);
final controller = ResultsNotificationsController<T>(this);
return controller.createStream();
}
}

Expand All @@ -104,11 +120,21 @@ extension RealmResultsInternal on RealmResults {

RealmResultsHandle get handle => _handle;

RealmObjectMetadata? get metadata => _metadata;

static RealmResults<T> create<T extends Object?>(RealmResultsHandle handle, Realm realm, RealmObjectMetadata? metadata) {
return RealmResults<T>._(handle, realm, metadata);
}
RealmObjectMetadata get metadata => _metadata!;

static RealmResults<T> create<T extends Object?>(
RealmResultsHandle handle,
Realm realm,
RealmObjectMetadata? metadata,
) =>
RealmResults<T>._(handle, realm, metadata);

static RealmResults<T> createFromList<T extends Object?>(
RealmListHandle handle,
Realm realm,
RealmObjectMetadata? metadata,
) =>
RealmResults<T>._(realmCore.resultsFromList(handle), realm, metadata);
}

/// Describes the changes in a Realm results collection since the last time the notification callback was invoked.
Expand Down
12 changes: 12 additions & 0 deletions test/results_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,16 @@ Future<void> main([List<String>? args]) async {
expect(realm.query<Person>("name CONTAINS 'a'").query("name CONTAINS 'l'"), isNot([alice, carol]));
expect(realm.query<Person>("name CONTAINS 'a'").query("name CONTAINS 'l'"), [carol]);
});

test('Results of primitives', () {
var config = Configuration.local([Player.schema, Game.schema]);
var realm = getRealm(config);

final scores = [-1, null, 0, 1];
final alice = Player('Alice', scoresByRound: scores);
realm.write(() => realm.add(alice));

expect(alice.scoresByRound, scores);
expect(alice.scoresByRound.asResults, scores);
});
}
14 changes: 7 additions & 7 deletions test/subscription_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -295,18 +295,18 @@ Future<void> main([List<String>? args]) async {

ObjectId newOid() => ObjectId.fromBytes(randomBytes(12));

final oids = <ObjectId>{};
final objectIds = <ObjectId>{};
const max = 1000;
subscriptions.update((mutableSubscriptions) {
oids.addAll([
objectIds.addAll([
for (int i = 0; i < max; ++i) mutableSubscriptions.add(realm.query<Task>(r'_id == $0', [newOid()])).id
]);
});
expect(oids.length, max); // no collisions
expect(objectIds.length, max); // no collisions
expect(subscriptions.length, max);

for (final sub in subscriptions) {
expect(sub.id, isIn(oids));
expect(sub.id, isIn(objectIds));
}
});

Expand Down Expand Up @@ -499,7 +499,7 @@ Future<void> main([List<String>? args]) async {
expect(() => realm.write(() => realm.add(Task(ObjectId()))), throws<RealmException>("no flexible sync subscription has been created"));
});

testSubscriptions('Subscription on unqueryable field sould throw', (realm) async {
testSubscriptions('Subscription on non-queryable field should throw', (realm) async {
realm.subscriptions.update((mutableSubscriptions) {
mutableSubscriptions.add(realm.all<Event>());
});
Expand All @@ -513,7 +513,7 @@ Future<void> main([List<String>? args]) async {
isCompleted: false,
durationInMinutes: 10,
),
Event(ObjectId(), name: "Some other eveent", isCompleted: true, durationInMinutes: 60),
Event(ObjectId(), name: "Some other event", isCompleted: true, durationInMinutes: 60),
]);
});

Expand Down Expand Up @@ -550,7 +550,7 @@ Future<void> main([List<String>? args]) async {
realm.addAll([
Event(ObjectId(), name: "NPMG Event", isCompleted: true, durationInMinutes: 30),
Event(ObjectId(), name: "NPMG Meeting", isCompleted: false, durationInMinutes: 10),
Event(ObjectId(), name: "Some other eveent", isCompleted: true, durationInMinutes: 60),
Event(ObjectId(), name: "Some other event", isCompleted: true, durationInMinutes: 60),
]);
});

Expand Down

0 comments on commit 5ea1310

Please sign in to comment.