Skip to content

Commit

Permalink
Query lists arguments (#1346)
Browse files Browse the repository at this point in the history
* Query list parameter implemented
Fixes #755
Fixes #1084
  • Loading branch information
desistefanova authored Aug 3, 2023
1 parent e61a738 commit 553740f
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 40 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

### Enhancements
* Support ReamSet.freeze() ([#1342](https://github.com/realm/realm-dart/pull/1342))
* Added support for query on `RealmSet`. ([#1346](https://github.com/realm/realm-dart/pull/1346))
* Support for passing `List`, `Set` or `Iterable` arguments to queries with `IN`-operators. ([#1346](https://github.com/realm/realm-dart/pull/1346))


### Fixed
* Fixed an early unlock race condition during client reset callbacks. ([#1335](https://github.com/realm/realm-dart/pull/1335))
Expand Down
2 changes: 1 addition & 1 deletion lib/src/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ extension RealmListOfObject<T extends RealmObjectBase> 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 []]) {
RealmResults<T> query(String query, [List<Object?> arguments = const []]) {
final handle = realmCore.queryList(asManaged(), query, arguments);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
}
Expand Down
98 changes: 65 additions & 33 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ class _RealmCore {
}
}

RealmResultsHandle queryList(RealmList target, String query, List<Object> args) {
RealmResultsHandle queryList(RealmList target, String query, List<Object?> args) {
return using((arena) {
final length = args.length;
final argsPointer = arena<realm_query_arg_t>(length);
Expand All @@ -1122,6 +1122,27 @@ class _RealmCore {
});
}

RealmResultsHandle querySet(RealmSet target, String query, List<Object?> args) {
return using((arena) {
final length = args.length;
final argsPointer = arena<realm_query_arg_t>(length);
for (var i = 0; i < length; ++i) {
_intoRealmQueryArg(args[i], argsPointer.elementAt(i), arena);
}
final queryHandle = _RealmQueryHandle._(
_realmLib.invokeGetPointer(
() => _realmLib.realm_query_parse_for_set(
target.handle._pointer,
query.toCharPtr(arena),
length,
argsPointer,
),
),
target.realm.handle);
return _queryFindAll(queryHandle);
});
}

RealmResultsHandle resultsFromList(RealmList list) {
final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_list_to_results(list.handle._pointer));
return RealmResultsHandle._(pointer, list.realm.handle);
Expand Down Expand Up @@ -2893,79 +2914,90 @@ extension _RealmLibraryEx on RealmLibrary {

Pointer<realm_value_t> _toRealmValue(Object? value, Allocator allocator) {
final realm_value = allocator<realm_value_t>();
_intoRealmValue(value, realm_value, allocator);
_intoRealmValue(value, realm_value.ref, allocator);
return realm_value;
}

const int _microsecondsPerSecond = 1000 * 1000;
const int _nanosecondsPerMicrosecond = 1000;

void _intoRealmQueryArg(Object? value, Pointer<realm_query_arg_t> realm_query_arg, Allocator allocator) {
realm_query_arg.ref.arg = allocator<realm_value_t>();
realm_query_arg.ref.nb_args = 1;
realm_query_arg.ref.is_list = false;
_intoRealmValue(value, realm_query_arg.ref.arg, allocator);
if (value is Iterable) {
realm_query_arg.ref.nb_args = value.length;
realm_query_arg.ref.is_list = true;
realm_query_arg.ref.arg = allocator<realm_value>(value.length);
int i = 0;
for (var item in value) {
_intoRealmValue(item, realm_query_arg.ref.arg[i], allocator);
i++;
}
} else {
realm_query_arg.ref.arg = allocator<realm_value_t>();
realm_query_arg.ref.nb_args = 1;
realm_query_arg.ref.is_list = false;
_intoRealmValue(value, realm_query_arg.ref.arg.ref, allocator);
}
}

void _intoRealmValue(Object? value, Pointer<realm_value_t> realm_value, Allocator allocator) {
void _intoRealmValue(Object? value, realm_value realm_value, Allocator allocator) {
if (value == null) {
realm_value.ref.type = realm_value_type.RLM_TYPE_NULL;
realm_value.type = realm_value_type.RLM_TYPE_NULL;
} else if (value is RealmObjectBase) {
// when converting a RealmObjectBase to realm_value.link we assume the object is managed
final link = realmCore._getObjectAsLink(value);
realm_value.ref.values.link.target = link.targetKey;
realm_value.ref.values.link.target_table = link.classKey;
realm_value.ref.type = realm_value_type.RLM_TYPE_LINK;
realm_value.values.link.target = link.targetKey;
realm_value.values.link.target_table = link.classKey;
realm_value.type = realm_value_type.RLM_TYPE_LINK;
} else if (value is int) {
realm_value.ref.values.integer = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_INT;
realm_value.values.integer = value;
realm_value.type = realm_value_type.RLM_TYPE_INT;
} else if (value is bool) {
realm_value.ref.values.boolean = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_BOOL;
realm_value.values.boolean = value;
realm_value.type = realm_value_type.RLM_TYPE_BOOL;
} else if (value is String) {
String string = value;
final units = utf8.encode(string);
final result = allocator<Uint8>(units.length);
final Uint8List nativeString = result.asTypedList(units.length);
nativeString.setAll(0, units);
realm_value.ref.values.string.data = result.cast();
realm_value.ref.values.string.size = units.length;
realm_value.ref.type = realm_value_type.RLM_TYPE_STRING;
realm_value.values.string.data = result.cast();
realm_value.values.string.size = units.length;
realm_value.type = realm_value_type.RLM_TYPE_STRING;
} else if (value is double) {
realm_value.ref.values.dnum = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_DOUBLE;
realm_value.values.dnum = value;
realm_value.type = realm_value_type.RLM_TYPE_DOUBLE;
} else if (value is ObjectId) {
final bytes = value.bytes;
for (var i = 0; i < 12; i++) {
realm_value.ref.values.object_id.bytes[i] = bytes[i];
realm_value.values.object_id.bytes[i] = bytes[i];
}
realm_value.ref.type = realm_value_type.RLM_TYPE_OBJECT_ID;
realm_value.type = realm_value_type.RLM_TYPE_OBJECT_ID;
} else if (value is Uuid) {
final bytes = value.bytes.asUint8List();
for (var i = 0; i < 16; i++) {
realm_value.ref.values.uuid.bytes[i] = bytes[i];
realm_value.values.uuid.bytes[i] = bytes[i];
}
realm_value.ref.type = realm_value_type.RLM_TYPE_UUID;
realm_value.type = realm_value_type.RLM_TYPE_UUID;
} else if (value is DateTime) {
final microseconds = value.toUtc().microsecondsSinceEpoch;
final seconds = microseconds ~/ _microsecondsPerSecond;
int nanoseconds = _nanosecondsPerMicrosecond * (microseconds % _microsecondsPerSecond);
if (microseconds < 0 && nanoseconds != 0) {
nanoseconds = nanoseconds - _nanosecondsPerMicrosecond * _microsecondsPerSecond;
}
realm_value.ref.values.timestamp.seconds = seconds;
realm_value.ref.values.timestamp.nanoseconds = nanoseconds;
realm_value.ref.type = realm_value_type.RLM_TYPE_TIMESTAMP;
realm_value.values.timestamp.seconds = seconds;
realm_value.values.timestamp.nanoseconds = nanoseconds;
realm_value.type = realm_value_type.RLM_TYPE_TIMESTAMP;
} else if (value is RealmValue) {
return _intoRealmValue(value.value, realm_value, allocator);
} else if (value is Decimal128) {
realm_value.ref.values.decimal128 = value.value;
realm_value.ref.type = realm_value_type.RLM_TYPE_DECIMAL128;
realm_value.values.decimal128 = value.value;
realm_value.type = realm_value_type.RLM_TYPE_DECIMAL128;
} else if (value is Uint8List) {
realm_value.ref.type = realm_value_type.RLM_TYPE_BINARY;
realm_value.ref.values.binary.size = value.length;
realm_value.ref.values.binary.data = allocator<Uint8>(value.length);
realm_value.ref.values.binary.data.asTypedList(value.length).setAll(0, value);
realm_value.type = realm_value_type.RLM_TYPE_BINARY;
realm_value.values.binary.size = value.length;
realm_value.values.binary.data = allocator<Uint8>(value.length);
realm_value.values.binary.data.asTypedList(value.length).setAll(0, value);
} else {
throw RealmException("Property type ${value.runtimeType} not supported");
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export "configuration.dart"
SyncSessionError;
export 'credentials.dart' show AuthProviderType, Credentials, EmailPasswordAuthProvider;
export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges, ListExtension;
export 'set.dart' show RealmSet, RealmSetChanges;
export 'set.dart' show RealmSet, RealmSetChanges, RealmSetOfObject;
export 'migration.dart' show Migration;
export 'realm_object.dart'
show
Expand Down
16 changes: 16 additions & 0 deletions lib/src/set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,19 @@ class RealmSetNotificationsController<T extends Object?> extends NotificationsCo
streamController.addError(error);
}
}

// The query operations on sets, as well as the ability to subscribe for notifications,
// only work for sets of objects (core restriction), so we add these as an extension methods
// to allow the compiler to prevent misuse.
extension RealmSetOfObject<T extends RealmObjectBase> on RealmSet<T> {
/// Filters the set and returns a new [RealmResults] according to the provided [query] (with optional [arguments]).
///
/// Only works for sets of [RealmObject]s or [EmbeddedObject]s.
///
/// 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 handle = realmCore.querySet(asManaged(), query, arguments);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
}
}
17 changes: 17 additions & 0 deletions test/embedded_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:typed_data';

import 'package:test/test.dart' hide test, throws;

import '../lib/realm.dart';
Expand Down Expand Up @@ -850,6 +852,21 @@ Future<void> main([List<String>? args]) async {

expect(parent.recursiveList[0].parent, null);
});

test('Query embedded objects list with list argument with different type of values', () {
final realm = getLocalRealm();
final realmObject = realm.write(() {
return realm.add(ObjectWithEmbedded('', list: [
AllTypesEmbedded('text1', false, DateTime.now(), 1.1, ObjectId(), Uuid.v4(), 1, Decimal128.one, nullableDecimalProp: Decimal128.fromDouble(3.3)),
AllTypesEmbedded('text2', true, DateTime.now(), 2.2, ObjectId(), Uuid.v4(), 2, Decimal128.ten),
AllTypesEmbedded('text3', true, DateTime.now(), 3.3, ObjectId(), Uuid.v4(), 3, Decimal128.infinity),
]));
});
final results = realmObject.list.query(r"nullableDecimalProp IN $0 || stringProp IN $0", [
['text1', null, 2.2, 3] // Searching by different type of values and null
]);
expect(results.length, 3);
});
}

extension on RealmObjectBase {
Expand Down
30 changes: 30 additions & 0 deletions test/list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1238,4 +1238,34 @@ Future<void> main([List<String>? args]) async {
realm.write(() => team.players.clear());
expect(playersAsResults.length, 0);
});

test('Query on RealmList with IN-operator', () {
final config = Configuration.local([Team.schema, Person.schema]);
final realm = getRealm(config);

final team = realm.write(() => realm.add(Team('team', players: [
Person('Paul'),
Person('John'),
Person('Alex'),
])));

final result = team.players.query(r'name IN $0', [
['Paul', 'Alex']
]);
expect(result.length, 2);
});

test('Query on RealmList allows null in arguments', () {
final config = Configuration.local([School.schema, Student.schema]);
final realm = getRealm(config);

final school = realm.write(() => realm.add(School('primary school 1', branches: [
School('131', city: "NY city"),
School('144'),
School('128'),
])));

final result = school.branches.query(r'city = $0', [null]);
expect(result.length, 2);
});
}
31 changes: 31 additions & 0 deletions test/realm_set_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ List<Type> supportedTypes = [
class _Car {
@PrimaryKey()
late String make;
late String? color;
}

@RealmModel()
Expand Down Expand Up @@ -800,4 +801,34 @@ Future<void> main([List<String>? args]) async {
if (count > 1) fail('Should only receive one event');
}
});

test('Query on RealmSet with IN-operator', () {
var config = Configuration.local([TestRealmSets.schema, Car.schema]);
var realm = getRealm(config);

final cars = [Car("Tesla"), Car("Ford"), Car("Audi")];
final testSets = TestRealmSets(1)..objectsSet.addAll(cars);

realm.write(() {
realm.add(testSets);
});
final result = testSets.objectsSet.query(r'make IN $0', [
['Tesla', 'Audi']
]);
expect(result.length, 2);
});

test('Query on RealmSet allows null in arguments', () {
var config = Configuration.local([TestRealmSets.schema, Car.schema]);
var realm = getRealm(config);

final cars = [Car("Tesla", color: "black"), Car("Ford"), Car("Audi")];
final testSets = TestRealmSets(1)..objectsSet.addAll(cars);

realm.write(() {
realm.add(testSets);
});
var result = testSets.objectsSet.query(r'color = $0', [null]);
expect(result.length, 2);
});
}
12 changes: 10 additions & 2 deletions test/realm_set_test.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 27 additions & 3 deletions test/realm_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,6 @@ Future<void> main([List<String>? args]) async {
Decimal128.fromInt(128),
];

final config = Configuration.local([AnythingGoes.schema, Stuff.schema]);
final realm = getRealm(config);

test('Roundtrip', () {
final config = Configuration.local([AnythingGoes.schema, Stuff.schema, TuckedIn.schema]);
final realm = getRealm(config);
Expand All @@ -234,4 +231,31 @@ Future<void> main([List<String>? args]) async {
expect(something.manyAny, values.map(RealmValue.from));
});
});

test('Query with list of realm values in arguments', () {
final now = DateTime.now().toUtc();
final values = <Object?>[
null,
true,
'text',
42,
3.14,
AnythingGoes(),
Stuff(),
now,
ObjectId.fromTimestamp(now),
Uuid.v4(),
Decimal128.fromInt(128),
];
final config = Configuration.local([AnythingGoes.schema, Stuff.schema]);
final realm = getRealm(config);
final realmValues = values.map(RealmValue.from);
realm.write(() => realm.add(AnythingGoes(manyAny: realmValues, oneAny: realmValues.last)));

var results = realm.query<AnythingGoes>("manyAny IN \$0", [values]);
expect(results.first.manyAny, realmValues);

results = realm.query<AnythingGoes>("oneAny IN \$0", [values]);
expect(results.first.oneAny, realmValues.last);
});
}
Loading

0 comments on commit 553740f

Please sign in to comment.