From 51ecafbed42cdebabef570771e6f1a4b00c5514d Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 20 Jul 2022 14:57:05 +0200 Subject: [PATCH 1/4] feat: Add where(arrayContains) support --- .../example/lib/movie.g.dart | 3 ++ .../collection_reference_test.dart | 53 +++++++++++++++++++ .../lib/simple.g.dart | 15 ++++++ .../lib/src/collection_generator.dart | 10 +++- .../lib/src/templates/query_reference.dart | 15 +++++- .../test/document_reference_test.dart | 32 +++++++++++ 6 files changed, 125 insertions(+), 3 deletions(-) diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm/example/lib/movie.g.dart b/packages/cloud_firestore_odm/cloud_firestore_odm/example/lib/movie.g.dart index df7403a706f4..9f3a88bc3d93 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm/example/lib/movie.g.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm/example/lib/movie.g.dart @@ -396,6 +396,7 @@ abstract class MovieQuery implements QueryReference { List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + String? arrayContains, List? arrayContainsAny, }); @@ -837,6 +838,7 @@ class _$MovieQuery extends QueryReference List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + String? arrayContains, List? arrayContainsAny, }) { return _$MovieQuery( @@ -849,6 +851,7 @@ class _$MovieQuery extends QueryReference isGreaterThan: isGreaterThan, isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, isNull: isNull, + arrayContains: arrayContains, arrayContainsAny: arrayContainsAny, ), _collection, diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm/example/test_driver/collection_reference_test.dart b/packages/cloud_firestore_odm/cloud_firestore_odm/example/test_driver/collection_reference_test.dart index b0ac822b168c..43f8193537b0 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm/example/test_driver/collection_reference_test.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm/example/test_driver/collection_reference_test.dart @@ -349,6 +349,59 @@ void main() { ); }); + group('arrayContains', () { + test('supports whereFieldPath', () async { + final collection = await initializeTest(MovieCollectionReference()); + + await collection.add( + createMovie(title: 'A', genre: ['foo', 'unrelated']), + ); + await collection.add( + createMovie(title: 'B', genre: ['bar', 'unrelated']), + ); + await collection.add( + createMovie(title: 'C', genre: ['bar', 'unrelated']), + ); + + final querySnap = await collection + .whereFieldPath( + FieldPath.fromString('genre'), + arrayContains: 'bar', + ) + .orderByTitle() + .get(); + + expect( + querySnap.docs.map((e) => e.data.title), + ['B', 'C'], + ); + }); + + test('supports whereProperty', () async { + final collection = await initializeTest(MovieCollectionReference()); + + await collection.add( + createMovie(title: 'A', genre: ['foo', 'unrelated']), + ); + await collection.add( + createMovie(title: 'B', genre: ['bar', 'unrelated']), + ); + await collection.add( + createMovie(title: 'C', genre: ['bar', 'unrelated']), + ); + + final querySnap = await collection + .whereGenre(arrayContains: 'bar') + .orderByTitle() + .get(); + + expect( + querySnap.docs.map((e) => e.data.title), + ['B', 'C'], + ); + }); + }); + test('whereFieldPath', () async { final collection = await initializeTest(MovieCollectionReference()); diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart index 954223856474..0c3246a62081 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart @@ -953,6 +953,7 @@ abstract class NestedQuery implements QueryReference { List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + bool? arrayContains, List? arrayContainsAny, }); NestedQuery whereStringList({ @@ -963,6 +964,7 @@ abstract class NestedQuery implements QueryReference { List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + String? arrayContains, List? arrayContainsAny, }); NestedQuery whereNumList({ @@ -973,6 +975,7 @@ abstract class NestedQuery implements QueryReference { List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + num? arrayContains, List? arrayContainsAny, }); NestedQuery whereObjectList({ @@ -983,6 +986,7 @@ abstract class NestedQuery implements QueryReference { List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + Object? arrayContains, List? arrayContainsAny, }); NestedQuery whereDynamicList({ @@ -993,6 +997,7 @@ abstract class NestedQuery implements QueryReference { List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + dynamic arrayContains, List? arrayContainsAny, }); @@ -1242,6 +1247,7 @@ class _$NestedQuery extends QueryReference List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + bool? arrayContains, List? arrayContainsAny, }) { return _$NestedQuery( @@ -1254,6 +1260,7 @@ class _$NestedQuery extends QueryReference isGreaterThan: isGreaterThan, isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, isNull: isNull, + arrayContains: arrayContains, arrayContainsAny: arrayContainsAny, ), _collection, @@ -1268,6 +1275,7 @@ class _$NestedQuery extends QueryReference List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + String? arrayContains, List? arrayContainsAny, }) { return _$NestedQuery( @@ -1280,6 +1288,7 @@ class _$NestedQuery extends QueryReference isGreaterThan: isGreaterThan, isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, isNull: isNull, + arrayContains: arrayContains, arrayContainsAny: arrayContainsAny, ), _collection, @@ -1294,6 +1303,7 @@ class _$NestedQuery extends QueryReference List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + num? arrayContains, List? arrayContainsAny, }) { return _$NestedQuery( @@ -1306,6 +1316,7 @@ class _$NestedQuery extends QueryReference isGreaterThan: isGreaterThan, isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, isNull: isNull, + arrayContains: arrayContains, arrayContainsAny: arrayContainsAny, ), _collection, @@ -1320,6 +1331,7 @@ class _$NestedQuery extends QueryReference List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + Object? arrayContains, List? arrayContainsAny, }) { return _$NestedQuery( @@ -1332,6 +1344,7 @@ class _$NestedQuery extends QueryReference isGreaterThan: isGreaterThan, isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, isNull: isNull, + arrayContains: arrayContains, arrayContainsAny: arrayContainsAny, ), _collection, @@ -1346,6 +1359,7 @@ class _$NestedQuery extends QueryReference List? isGreaterThan, List? isGreaterThanOrEqualTo, bool? isNull, + dynamic arrayContains, List? arrayContainsAny, }) { return _$NestedQuery( @@ -1358,6 +1372,7 @@ class _$NestedQuery extends QueryReference isGreaterThan: isGreaterThan, isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, isNull: isNull, + arrayContains: arrayContains, arrayContainsAny: arrayContainsAny, ), _collection, diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/collection_generator.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/collection_generator.dart index 6af16788e66f..081b0e3b7468 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/collection_generator.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/collection_generator.dart @@ -40,6 +40,7 @@ class CollectionData { required this.queryableFields, required this.fromJson, required this.toJson, + required this.libraryElement, }) : collectionName = collectionName ?? ReCase(path.split('/').last).camelCase; @@ -47,6 +48,7 @@ class CollectionData { final String collectionName; final String path; final List queryableFields; + final LibraryElement libraryElement; late final updatableFields = queryableFields.where((element) => element.updatable).toList(); @@ -126,9 +128,13 @@ class CollectionGenerator extends ParserGenerator { void globalData, Element element, ) async { + final library = await buildStep.inputLibrary; final collectionAnnotations = const TypeChecker.fromRuntime(Collection) .annotationsOf(element) - .map((annotation) => _parseCollectionAnnotation(annotation, element)) + .map( + (annotation) => + _parseCollectionAnnotation(library, annotation, element), + ) .toList(); final roots = collectionAnnotations.where((collection) { @@ -211,6 +217,7 @@ class CollectionGenerator extends ParserGenerator { } CollectionData _parseCollectionAnnotation( + LibraryElement libraryElement, DartObject object, Element annotatedElement, ) { @@ -305,6 +312,7 @@ class CollectionGenerator extends ParserGenerator { type: type, path: path, collectionName: name, + libraryElement: libraryElement, fromJson: (json) { if (fromJson != null) return '$type.fromJson($json)'; return '_\$${type.toString().public}FromJson($json)'; diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/templates/query_reference.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/templates/query_reference.dart index 4734f84dab60..edbd37f8faf0 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/templates/query_reference.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/lib/src/templates/query_reference.dart @@ -1,4 +1,6 @@ import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/dart/element/type_provider.dart'; import '../collection_generator.dart'; import 'template.dart'; @@ -345,8 +347,8 @@ class ${data.queryReferenceImplName} 'isGreaterThanOrEqualTo': nullableType, 'isNull': 'bool?', if (field.type.isDartCoreList) ...{ - // TODO support arrayContains - // 'arrayContains': nullableType, + 'arrayContains': data.libraryElement.typeProvider + .asNullable((field.type as InterfaceType).typeArguments.first), 'arrayContainsAny': nullableType, } else ...{ 'whereIn': 'List<${field.type}>?', @@ -401,3 +403,12 @@ class ${data.queryReferenceImplName} '''; } } + +extension on TypeProvider { + DartType asNullable(DartType type) { + final typeSystem = nullType.element.library.typeSystem; + if (typeSystem.isNullable(type)) return type; + + return typeSystem.leastUpperBound(type, nullType); + } +} diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart index fe5649e18f90..d84ff659bfda 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart @@ -8,6 +8,38 @@ Future main() async { path: 'lib/__test__.dart', ); + group('where(arrayContains)', () { + test(' is typed', () { + expect( + library.withCode( + ''' +import 'simple.dart'; + +void main() { + // Does not arrayContains for non-list + nestedRef.whereValue( + // expect-error: UNDEFINED_NAMED_PARAMETER + arrayContains: null, + ); + // No arrayContains for complex objects + nestedRef.whereValueList( + // expect-error: UNDEFINED_NAMED_PARAMETER + arrayContains: null, + ); + + nestedRef.whereNumList(arrayContains: 42); + nestedRef.whereNumList( + // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE + arrayContains: 'string', + ); +} +''', + ), + compiles, + ); + }); + }); + group('update', () { test('rejects complex object list but allows primitive lists', () { expect( From fd478380e77a71790e441820e7beb42f24f1d7a8 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 20 Jul 2022 15:13:33 +0200 Subject: [PATCH 2/4] Fix test --- .../test/document_reference_test.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart index d84ff659bfda..684516183aec 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart @@ -17,10 +17,8 @@ import 'simple.dart'; void main() { // Does not arrayContains for non-list - nestedRef.whereValue( - // expect-error: UNDEFINED_NAMED_PARAMETER - arrayContains: null, - ); + // expect-error: UNDEFINED_METHOD + nestedRef.whereValue(); // No arrayContains for complex objects nestedRef.whereValueList( // expect-error: UNDEFINED_NAMED_PARAMETER From a801d0f1b9d425c25f6b69413ae75e9971e9dcd8 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 20 Jul 2022 15:16:27 +0200 Subject: [PATCH 3/4] Add test for generated `where` with no `arrayContains` --- .../lib/simple.dart | 2 + .../lib/simple.g.dart | 100 ++++++++++++++++++ .../test/document_reference_test.dart | 4 + 3 files changed, 106 insertions(+) diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.dart index 2d5a188f9c45..d6b4faa6ee19 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.dart @@ -16,6 +16,7 @@ class Model { class Nested { Nested({ required this.value, + required this.simple, required this.valueList, required this.boolList, required this.stringList, @@ -29,6 +30,7 @@ class Nested { Map toJson() => _$NestedToJson(this); final Nested? value; + final int? simple; final List? valueList; final List? boolList; final List? stringList; diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart index 0c3246a62081..95d74187a907 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/cloud_firestore_odm_generator_integration_test/lib/simple.g.dart @@ -759,6 +759,7 @@ abstract class NestedDocumentReference Future delete(); Future update({ + int? simple, List? boolList, List? stringList, List? numList, @@ -808,6 +809,7 @@ class _$NestedDocumentReference } Future update({ + Object? simple = _sentinel, Object? boolList = _sentinel, Object? stringList = _sentinel, Object? numList = _sentinel, @@ -815,6 +817,7 @@ class _$NestedDocumentReference Object? dynamicList = _sentinel, }) async { final json = { + if (simple != _sentinel) "simple": simple as int?, if (boolList != _sentinel) "boolList": boolList as List?, if (stringList != _sentinel) "stringList": stringList as List?, if (numList != _sentinel) "numList": numList as List?, @@ -945,6 +948,17 @@ abstract class NestedQuery implements QueryReference { List? whereIn, List? whereNotIn, }); + NestedQuery whereSimple({ + int? isEqualTo, + int? isNotEqualTo, + int? isLessThan, + int? isLessThanOrEqualTo, + int? isGreaterThan, + int? isGreaterThanOrEqualTo, + bool? isNull, + List? whereIn, + List? whereNotIn, + }); NestedQuery whereBoolList({ List? isEqualTo, List? isNotEqualTo, @@ -1013,6 +1027,18 @@ abstract class NestedQuery implements QueryReference { NestedDocumentSnapshot? startAfterDocument, }); + NestedQuery orderBySimple({ + bool descending = false, + int? startAt, + int? startAfter, + int? endAt, + int? endBefore, + NestedDocumentSnapshot? startAtDocument, + NestedDocumentSnapshot? endAtDocument, + NestedDocumentSnapshot? endBeforeDocument, + NestedDocumentSnapshot? startAfterDocument, + }); + NestedQuery orderByBoolList({ bool descending = false, List? startAt, @@ -1239,6 +1265,34 @@ class _$NestedQuery extends QueryReference ); } + NestedQuery whereSimple({ + int? isEqualTo, + int? isNotEqualTo, + int? isLessThan, + int? isLessThanOrEqualTo, + int? isGreaterThan, + int? isGreaterThanOrEqualTo, + bool? isNull, + List? whereIn, + List? whereNotIn, + }) { + return _$NestedQuery( + reference.where( + _$NestedFieldMap["simple"]!, + isEqualTo: isEqualTo, + isNotEqualTo: isNotEqualTo, + isLessThan: isLessThan, + isLessThanOrEqualTo: isLessThanOrEqualTo, + isGreaterThan: isGreaterThan, + isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, + isNull: isNull, + whereIn: whereIn, + whereNotIn: whereNotIn, + ), + _collection, + ); + } + NestedQuery whereBoolList({ List? isEqualTo, List? isNotEqualTo, @@ -1421,6 +1475,49 @@ class _$NestedQuery extends QueryReference return _$NestedQuery(query, _collection); } + NestedQuery orderBySimple({ + bool descending = false, + Object? startAt = _sentinel, + Object? startAfter = _sentinel, + Object? endAt = _sentinel, + Object? endBefore = _sentinel, + NestedDocumentSnapshot? startAtDocument, + NestedDocumentSnapshot? endAtDocument, + NestedDocumentSnapshot? endBeforeDocument, + NestedDocumentSnapshot? startAfterDocument, + }) { + var query = + reference.orderBy(_$NestedFieldMap["simple"]!, descending: descending); + + if (startAtDocument != null) { + query = query.startAtDocument(startAtDocument.snapshot); + } + if (startAfterDocument != null) { + query = query.startAfterDocument(startAfterDocument.snapshot); + } + if (endAtDocument != null) { + query = query.endAtDocument(endAtDocument.snapshot); + } + if (endBeforeDocument != null) { + query = query.endBeforeDocument(endBeforeDocument.snapshot); + } + + if (startAt != _sentinel) { + query = query.startAt([startAt]); + } + if (startAfter != _sentinel) { + query = query.startAfter([startAfter]); + } + if (endAt != _sentinel) { + query = query.endAt([endAt]); + } + if (endBefore != _sentinel) { + query = query.endBefore([endBefore]); + } + + return _$NestedQuery(query, _collection); + } + NestedQuery orderByBoolList({ bool descending = false, Object? startAt = _sentinel, @@ -8224,6 +8321,7 @@ Nested _$NestedFromJson(Map json) => Nested( value: json['value'] == null ? null : Nested.fromJson(json['value'] as Map), + simple: json['simple'] as int?, valueList: (json['valueList'] as List?) ?.map((e) => Nested.fromJson(e as Map)) .toList(), @@ -8240,6 +8338,7 @@ Nested _$NestedFromJson(Map json) => Nested( const _$NestedFieldMap = { 'value': 'value', + 'simple': 'simple', 'valueList': 'valueList', 'boolList': 'boolList', 'stringList': 'stringList', @@ -8250,6 +8349,7 @@ const _$NestedFieldMap = { Map _$NestedToJson(Nested instance) => { 'value': instance.value, + 'simple': instance.simple, 'valueList': instance.valueList, 'boolList': instance.boolList, 'stringList': instance.stringList, diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart index 684516183aec..c086beed729f 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart @@ -19,6 +19,10 @@ void main() { // Does not arrayContains for non-list // expect-error: UNDEFINED_METHOD nestedRef.whereValue(); + nestedRef.whereSimple( + // expect-error: UNDEFINED_NAMED_PARAMETER + arrayContains: null, + ); // No arrayContains for complex objects nestedRef.whereValueList( // expect-error: UNDEFINED_NAMED_PARAMETER From 7f56a143f5ddcc046b888e6d353d41db5740a5d2 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Wed, 20 Jul 2022 15:36:16 +0200 Subject: [PATCH 4/4] Fix test --- .../test/document_reference_test.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart index c086beed729f..ebf7b2bab00d 100644 --- a/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart +++ b/packages/cloud_firestore_odm/cloud_firestore_odm_generator/test/document_reference_test.dart @@ -23,11 +23,8 @@ void main() { // expect-error: UNDEFINED_NAMED_PARAMETER arrayContains: null, ); - // No arrayContains for complex objects - nestedRef.whereValueList( - // expect-error: UNDEFINED_NAMED_PARAMETER - arrayContains: null, - ); + // expect-error: UNDEFINED_METHOD + nestedRef.whereValueList(); nestedRef.whereNumList(arrayContains: 42); nestedRef.whereNumList(