diff --git a/CHANGELOG.md b/CHANGELOG.md index efb05ee43..99a08f0bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Added support binary data type. ([#1320](https://github.com/realm/realm-dart/pull/1320)) * Extended `ClientResetError` to return the `backupFilePath` where the backup copy of the realm will be placed once the client reset process has completed. ([#1291](https://github.com/realm/realm-dart/pull/1291)) * Added `CompensatingWriteError` containing detailed error information about the writes that have been reverted by the server due to permissions or subscription view restrictions. The `Configuration.flexibleSync.syncErrorHandler` will be invoked with this error type when this error occurs ([#1291](https://github.com/realm/realm-dart/pull/1291)). +* Improve performance of elementAt, first, single and last on RealmResults ([#1261](https://github.com/realm/realm-dart/issues/1261), [#1262](https://github.com/realm/realm-dart/pull/1262), [#1267](https://github.com/realm/realm-dart/pull/1267)). ### Fixed * The constructors of all `SyncError` types are deprecated. The sync errors will be created only internally ([#1291](https://github.com/realm/realm-dart/pull/1291)). diff --git a/lib/src/results.dart b/lib/src/results.dart index a90a440fe..51df26588 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -16,7 +16,6 @@ // //////////////////////////////////////////////////////////////////////////////// import 'dart:async'; -import 'dart:collection' as collection; import 'dart:ffi'; import 'collections.dart'; @@ -28,7 +27,7 @@ import 'realm_object.dart'; /// added to or deleted from the Realm that match the underlying query. /// /// {@category Realm} -class RealmResults extends collection.IterableBase with RealmEntity implements Finalizable { +class RealmResults extends Iterable with RealmEntity implements Finalizable { final RealmObjectMetadata? _metadata; final RealmResultsHandle _handle; @@ -75,6 +74,34 @@ class RealmResults extends collection.IterableBase with Re @override int get length => realmCore.getResultsCount(this); + @override + T get first { + if (length == 0) { + throw RealmStateError('No element'); + } + return this[0]; + } + + @override + T get last { + if (length == 0) { + throw RealmStateError('No element'); + } + return this[length - 1]; + } + + @override + T get single { + final l = length; + if (l > 1) { + throw RealmStateError('Too many elements'); + } + if (l == 0) { + throw RealmStateError('No element'); + } + return this[0]; + } + /// Creates a frozen snapshot of this query. RealmResults freeze() { if (isFrozen) { diff --git a/test/results_test.dart b/test/results_test.dart index 92ad74d5e..d75b75599 100644 --- a/test/results_test.dart +++ b/test/results_test.dart @@ -741,4 +741,53 @@ Future main([List? args]) async { final items = realm.query(ids); expect(items.length, 0); }); + + test('RealmResults.first throws on empty', () async { + final config = Configuration.local([Dog.schema, Person.schema]); + final realm = getRealm(config); + + final empty = Iterable.empty(); + // To illustrate the behavior of Iterable.single on a regular iterable + expect(() => empty.first, throws('No element')); + + expect(() => realm.all().first, throws('No element')); + expect(() => realm.all().first, throws('No element')); + }); + + test('RealmResults.last throws on empty', () async { + final config = Configuration.local([Dog.schema, Person.schema]); + final realm = getRealm(config); + + final empty = Iterable.empty(); + // To illustrate the behavior of Iterable.single on a regular iterable + expect(() => empty.last, throws('No element')); + + expect(() => realm.all().last, throws('No element')); + expect(() => realm.all().last, throws('No element')); + }); + + test('RealmResult.single throws on empty', () { + final config = Configuration.local([Dog.schema, Person.schema]); + final realm = getRealm(config); + + final empty = Iterable.empty(); + // To illustrate the behavior of Iterable.single on a regular iterable + expect(() => empty.single, throws('No element')); + + expect(() => realm.all().single, throws('No element')); + expect(() => realm.all().single, throws('No element')); + }); + + test('RealmResult.single throws on two items', () { + final config = Configuration.local([Dog.schema, Person.schema]); + final realm = getRealm(config); + + final twoItems = [1, 2]; + // To illustrate the behavior of Iterable.single on a regular list + expect(() => twoItems.single, throws('Too many elements')); + + realm.write(() => realm.addAll([Dog('Fido'), Dog('Spot')])); + expect(() => realm.all().single, throws('Too many elements')); + expect(() => realm.all().single, throws('Too many elements')); + }); }