diff --git a/packages/amplify_core/lib/src/types/query/query_field.dart b/packages/amplify_core/lib/src/types/query/query_field.dart index 3d119488f3..b5658c8f94 100644 --- a/packages/amplify_core/lib/src/types/query/query_field.dart +++ b/packages/amplify_core/lib/src/types/query/query_field.dart @@ -255,6 +255,27 @@ class QueryField { QueryPredicateOperation beginsWith(String value) => QueryPredicateOperation(fieldName, BeginsWithQueryOperator(value)); + /// An **attribute exists** operation. + /// + /// Matches models whether the given field exists or not. + /// + /// ### Example: + /// The example returns Blog where the optional Author attribute exists. + /// + /// ```dart + /// ModelQueries.list( + /// Blog.classType, + /// where: Blog.AUTHOR.attributeExists(), + /// ); + /// ``` + QueryPredicateOperation attributeExists({bool exists = true}) => + QueryPredicateOperation( + fieldName, + AttributeExistsQueryOperator( + exists: exists, + ), + ); + /// Sorts models by the given field in ascending order /// /// ### Example: diff --git a/packages/amplify_core/lib/src/types/query/query_field_operators.dart b/packages/amplify_core/lib/src/types/query/query_field_operators.dart index 0eb923050f..ff972ef7a9 100644 --- a/packages/amplify_core/lib/src/types/query/query_field_operators.dart +++ b/packages/amplify_core/lib/src/types/query/query_field_operators.dart @@ -14,7 +14,8 @@ enum QueryFieldOperatorType { greater_than, contains, between, - begins_with + begins_with, + attribute_exists, } extension QueryFieldOperatorTypeExtension on QueryFieldOperatorType { @@ -257,3 +258,26 @@ class BeginsWithQueryOperator extends QueryFieldOperatorSingleValue { return other.startsWith(value); } } + +class AttributeExistsQueryOperator extends QueryFieldOperator { + const AttributeExistsQueryOperator({this.exists = true}) + : super(QueryFieldOperatorType.attribute_exists); + + final bool exists; + + @override + bool evaluate(T? other) { + if (exists == true) { + return other != null; + } + return other == null; + } + + @override + Map serializeAsMap() { + return { + 'operatorName': QueryFieldOperatorType.attribute_exists.toShortString(), + 'exists': this.exists, + }; + } +} diff --git a/packages/amplify_datastore/example/integration_test/observe_test.dart b/packages/amplify_datastore/example/integration_test/observe_test.dart index 5dc6977570..d533e90c0d 100644 --- a/packages/amplify_datastore/example/integration_test/observe_test.dart +++ b/packages/amplify_datastore/example/integration_test/observe_test.dart @@ -124,5 +124,36 @@ void main() { await Amplify.DataStore.delete(updatedBlog); await Amplify.DataStore.save(otherBlog); }); + + testWidgets( + 'observe with attribute exists query predicate filters out non matches', + (WidgetTester tester) async { + HasOneChild hasAttribute = HasOneChild(name: 'name - ${uuid()}'); + HasOneChild hasNoAttribute = HasOneChild(); + + var hasAttributeStream = Amplify.DataStore.observe(HasOneChild.classType, + where: Blog.NAME.attributeExists()) + .map((event) => event.item); + expectLater( + hasAttributeStream, + emitsInOrder( + [hasAttribute], + ), + ); + + var hasNoAttributeStream = Amplify.DataStore.observe( + HasOneChild.classType, + where: Blog.NAME.attributeExists(exists: false)) + .map((event) => event.item); + expectLater( + hasNoAttributeStream, + emitsInOrder( + [hasNoAttribute], + ), + ); + + await Amplify.DataStore.save(hasAttribute); + await Amplify.DataStore.save(hasNoAttribute); + }); }); } diff --git a/packages/amplify_datastore/test/query_predicate_test.dart b/packages/amplify_datastore/test/query_predicate_test.dart index 0579525313..7f0ae9c07b 100644 --- a/packages/amplify_datastore/test/query_predicate_test.dart +++ b/packages/amplify_datastore/test/query_predicate_test.dart @@ -315,6 +315,12 @@ void main() { expect(testPredicate.evaluate(post2), isTrue); }); + test('attributeExists', () { + QueryPredicate testPredicate = Post.LIKECOUNT.attributeExists(); + expect(testPredicate.evaluate(post4), isFalse); + expect(testPredicate.evaluate(post2), isTrue); + }); + test('Temporal type', () { QueryPredicate testPredicate = Post.CREATED.lt(TemporalDateTime( DateTime(2020, 01, 01, 12, 00), diff --git a/packages/api/amplify_api/example/amplify/backend/api/apiintegmultiauth/schema.graphql b/packages/api/amplify_api/example/amplify/backend/api/apiintegmultiauth/schema.graphql index d6da35c282..08c9b23e36 100644 --- a/packages/api/amplify_api/example/amplify/backend/api/apiintegmultiauth/schema.graphql +++ b/packages/api/amplify_api/example/amplify/backend/api/apiintegmultiauth/schema.graphql @@ -1,21 +1,29 @@ -type Blog @model @auth(rules: [ - { allow: public, operations: [read], provider: apiKey}, - { allow: public, operations: [read], provider: iam}, - { allow: private, operations: [read], provider: iam}, - { allow: private, operations: [read], provider: userPools}, - { allow: owner, operations: [create, read, update, delete] } -]) { +type Blog + @model + @auth( + rules: [ + { allow: public, operations: [read], provider: apiKey } + { allow: public, operations: [read], provider: iam } + { allow: private, operations: [read], provider: iam } + { allow: private, operations: [read], provider: userPools } + { allow: owner, operations: [create, read, update, delete] } + ] + ) { id: ID! name: String! posts: [Post] @hasMany(indexName: "byBlog", fields: ["id"]) } -type Post @model @auth(rules: [ - { allow: public, operations: [read], provider: iam}, - { allow: private, operations: [read], provider: iam}, - { allow: private, operations: [read], provider: userPools}, - { allow: owner, operations: [create, read, update, delete] } -]) { +type Post + @model + @auth( + rules: [ + { allow: public, operations: [read], provider: iam } + { allow: private, operations: [read], provider: iam } + { allow: private, operations: [read], provider: userPools } + { allow: owner, operations: [create, read, update, delete] } + ] + ) { id: ID! title: String! rating: Int! @@ -24,37 +32,41 @@ type Post @model @auth(rules: [ comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"]) } -type Comment @model @auth(rules: [ - { allow: private, operations: [read], provider: iam}, - { allow: private, operations: [read], provider: userPools}, - { allow: owner, operations: [create, read, update, delete] } -]) { +type Comment + @model + @auth( + rules: [ + { allow: private, operations: [read], provider: iam } + { allow: private, operations: [read], provider: userPools } + { allow: owner, operations: [create, read, update, delete] } + ] + ) { id: ID! postID: ID! @index(name: "byPost") post: Post @belongsTo(fields: ["postID"]) content: String! } -type CpkOneToOneBidirectionalParentCD @model @auth(rules: [ - { allow: private, provider: iam} -]) { +type CpkOneToOneBidirectionalParentCD + @model + @auth(rules: [{ allow: private, provider: iam }]) { customId: ID! @primaryKey(sortKeyFields: ["name"]) name: String! implicitChild: CpkOneToOneBidirectionalChildImplicitCD @hasOne explicitChild: CpkOneToOneBidirectionalChildExplicitCD @hasOne } -type CpkOneToOneBidirectionalChildImplicitCD @model @auth(rules: [ - { allow: private, provider: iam} -]) { +type CpkOneToOneBidirectionalChildImplicitCD + @model + @auth(rules: [{ allow: private, provider: iam }]) { id: ID! @primaryKey(sortKeyFields: ["name"]) name: String! belongsToParent: CpkOneToOneBidirectionalParentCD @belongsTo } -type CpkOneToOneBidirectionalChildExplicitCD @model @auth(rules: [ - { allow: private, provider: iam} -]) { +type CpkOneToOneBidirectionalChildExplicitCD + @model + @auth(rules: [{ allow: private, provider: iam }]) { id: ID! @primaryKey(sortKeyFields: ["name"]) name: String! belongsToParentID: ID @@ -63,22 +75,41 @@ type CpkOneToOneBidirectionalChildExplicitCD @model @auth(rules: [ @belongsTo(fields: ["belongsToParentID", "belongsToParentName"]) } -type OwnerOnly @model @auth(rules: [{allow: owner}]) { +type OwnerOnly @model @auth(rules: [{ allow: owner }]) { id: ID! name: String! -} +} type lowerCase @model @auth( rules: [ - { allow: public, operations: [read], provider: apiKey }, - { allow: public, operations: [read], provider: iam }, - { allow: private, operations: [read], provider: iam }, - { allow: private, operations: [read], provider: userPools }, + { allow: public, operations: [read], provider: apiKey } + { allow: public, operations: [read], provider: iam } + { allow: private, operations: [read], provider: iam } + { allow: private, operations: [read], provider: userPools } { allow: owner, operations: [create, read, update, delete] } ] ) { id: ID! name: String! -} \ No newline at end of file +} + +type Sample + @model + @auth( + rules: [ + { allow: public, operations: [read], provider: apiKey } + { allow: public, operations: [read], provider: iam } + { allow: private, operations: [read], provider: iam } + { allow: private, operations: [read], provider: userPools } + { allow: owner, operations: [create, read, update, delete] } + ] + ) { + id: ID! + name: String + number: Int + flag: Boolean + date: AWSTime + rootbeer: Float +} diff --git a/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart b/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart index 39f2a1d17e..23cc06a74e 100644 --- a/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart +++ b/packages/api/amplify_api/example/integration_test/graphql/iam_test.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:async'; import 'dart:convert'; +import 'dart:math'; import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_api_example/models/ModelProvider.dart'; @@ -20,6 +21,8 @@ import '../util.dart'; /// increase past the default limit. const _limit = 10000; +const _max = 10000; + void main({bool useExistingTestUser = false}) { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -188,6 +191,51 @@ void main({bool useExistingTestUser = false}) { expect(postFromResponse?.title, title); }); + testWidgets('should return model if attribute exists', + (WidgetTester tester) async { + // Use same name to scope the query to the created model. + final name = 'Lorem Ipsum Test Sample: ${uuid()}'; + final number = Random().nextInt(_max); + await addSamplePartial( + name, + number: number, + ); + await addSamplePartial(name); + + final existsRequest = ModelQueries.list( + Sample.classType, + where: Sample.NUMBER.attributeExists().and(Sample.NAME.eq(name)), + limit: _limit, + ); + + final existsResponse = await Amplify.API + .query( + request: existsRequest, + ) + .response; + + final existsData = existsResponse.data; + expect(existsData?.items.length, 1); + expect(existsData?.items[0]?.number, number); + + final doesNotExistRequest = ModelQueries.list( + Sample.classType, + where: Sample.NUMBER + .attributeExists(exists: false) + .and(Sample.NAME.eq(name)), + limit: _limit, + ); + final doesNotExistResponse = await Amplify.API + .query( + request: doesNotExistRequest, + ) + .response; + + final doesNotExistData = doesNotExistResponse.data; + expect(doesNotExistData?.items.length, 1); + expect(doesNotExistData?.items[0]?.number, null); + }); + testWidgets('should copyWith request', (WidgetTester tester) async { final title = 'Lorem Ipsum Test Post: ${uuid()}'; const rating = 0; diff --git a/packages/api/amplify_api/example/integration_test/util.dart b/packages/api/amplify_api/example/integration_test/util.dart index a1d1647cbb..312bea821c 100644 --- a/packages/api/amplify_api/example/integration_test/util.dart +++ b/packages/api/amplify_api/example/integration_test/util.dart @@ -23,6 +23,7 @@ final lowerCaseCache = []; final cpkParentCache = []; final cpkExplicitChildCache = []; final cpkImplicitChildCache = []; +final sampleCache = []; class TestUser { TestUser({ @@ -219,6 +220,34 @@ Future addPostAndBlog( return addPost(title, rating, createdBlog); } +Future addSamplePartial(String name, {int? number}) async { + const document = r''' + mutation CreatePartialSample($name: String, $number: Int) { + createSample(input: {name: $name, number: $number}) { + id + name + number + } + } + '''; + final variables = {'name': name}; + if (number != null) { + variables['number'] = number; + } + final request = GraphQLRequest( + document: document, + variables: variables, + authorizationMode: APIAuthorizationType.userPools, + decodePath: 'createSample', + modelType: Sample.classType, + ); + final response = await Amplify.API.mutate(request: request).response; + expect(response, hasNoGraphQLErrors); + final sampleFromResponse = response.data!; + sampleCache.add(sampleFromResponse); + return sampleFromResponse; +} + Future deleteBlog(Blog blog) async { final request = ModelMutations.deleteById( Blog.classType, @@ -312,6 +341,18 @@ Future deleteLowerCase(lowerCase model) async { return res.data; } +Future deleteSample(Sample sample) async { + final request = ModelMutations.deleteById( + Sample.classType, + sample.modelIdentifier, + authorizationMode: APIAuthorizationType.userPools, + ); + final response = await Amplify.API.mutate(request: request).response; + expect(response, hasNoGraphQLErrors); + sampleCache.removeWhere((sampleFromCache) => sampleFromCache.id == sample.id); + return sample; +} + Future deleteTestModels() async { await Future.wait(blogCache.map(deleteBlog)); await Future.wait(postCache.map(deletePost)); @@ -319,6 +360,7 @@ Future deleteTestModels() async { await Future.wait(cpkImplicitChildCache.map(deleteCpkImplicitChild)); await Future.wait(ownerOnlyCache.map(deleteOwnerOnly)); await Future.wait(lowerCaseCache.map(deleteLowerCase)); + await Future.wait(sampleCache.map(deleteSample)); } /// Wait for subscription established for given request. diff --git a/packages/api/amplify_api/example/lib/models/ModelProvider.dart b/packages/api/amplify_api/example/lib/models/ModelProvider.dart index e306481b65..782d646ceb 100644 --- a/packages/api/amplify_api/example/lib/models/ModelProvider.dart +++ b/packages/api/amplify_api/example/lib/models/ModelProvider.dart @@ -27,6 +27,7 @@ import 'CpkOneToOneBidirectionalChildImplicitCD.dart'; import 'CpkOneToOneBidirectionalParentCD.dart'; import 'OwnerOnly.dart'; import 'Post.dart'; +import 'Sample.dart'; import 'lowerCase.dart'; export 'Blog.dart'; @@ -36,11 +37,12 @@ export 'CpkOneToOneBidirectionalChildImplicitCD.dart'; export 'CpkOneToOneBidirectionalParentCD.dart'; export 'OwnerOnly.dart'; export 'Post.dart'; +export 'Sample.dart'; export 'lowerCase.dart'; class ModelProvider implements amplify_core.ModelProviderInterface { @override - String version = "76a7a7d8d3182c2fe17550068e585db7"; + String version = "9b304310f45499a1a0cd1d36e4665dcd"; @override List modelSchemas = [ Blog.schema, @@ -50,6 +52,7 @@ class ModelProvider implements amplify_core.ModelProviderInterface { CpkOneToOneBidirectionalParentCD.schema, OwnerOnly.schema, Post.schema, + Sample.schema, lowerCase.schema ]; @override @@ -74,6 +77,8 @@ class ModelProvider implements amplify_core.ModelProviderInterface { return OwnerOnly.classType; case "Post": return Post.classType; + case "Sample": + return Sample.classType; case "lowerCase": return lowerCase.classType; default: diff --git a/packages/api/amplify_api/example/lib/models/Sample.dart b/packages/api/amplify_api/example/lib/models/Sample.dart new file mode 100644 index 0000000000..9db8e79be7 --- /dev/null +++ b/packages/api/amplify_api/example/lib/models/Sample.dart @@ -0,0 +1,365 @@ +/* +* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +// NOTE: This file is generated and may not follow lint rules defined in your app +// Generated files can be excluded from analysis in analysis_options.yaml +// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis + +// ignore_for_file: public_member_api_docs, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, override_on_non_overriding_member, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously + +import 'ModelProvider.dart'; +import 'package:amplify_core/amplify_core.dart' as amplify_core; + +/** This is an auto generated class representing the Sample type in your schema. */ +class Sample extends amplify_core.Model { + static const classType = const _SampleModelType(); + final String id; + final String? _name; + final int? _number; + final bool? _flag; + final amplify_core.TemporalTime? _date; + final double? _rootbeer; + final amplify_core.TemporalDateTime? _createdAt; + final amplify_core.TemporalDateTime? _updatedAt; + + @override + getInstanceType() => classType; + + @Deprecated( + '[getId] is being deprecated in favor of custom primary key feature. Use getter [modelIdentifier] to get model identifier.') + @override + String getId() => id; + + SampleModelIdentifier get modelIdentifier { + return SampleModelIdentifier(id: id); + } + + String? get name { + return _name; + } + + int? get number { + return _number; + } + + bool? get flag { + return _flag; + } + + amplify_core.TemporalTime? get date { + return _date; + } + + double? get rootbeer { + return _rootbeer; + } + + amplify_core.TemporalDateTime? get createdAt { + return _createdAt; + } + + amplify_core.TemporalDateTime? get updatedAt { + return _updatedAt; + } + + const Sample._internal( + {required this.id, + name, + number, + flag, + date, + rootbeer, + createdAt, + updatedAt}) + : _name = name, + _number = number, + _flag = flag, + _date = date, + _rootbeer = rootbeer, + _createdAt = createdAt, + _updatedAt = updatedAt; + + factory Sample( + {String? id, + String? name, + int? number, + bool? flag, + amplify_core.TemporalTime? date, + double? rootbeer}) { + return Sample._internal( + id: id == null ? amplify_core.UUID.getUUID() : id, + name: name, + number: number, + flag: flag, + date: date, + rootbeer: rootbeer); + } + + bool equals(Object other) { + return this == other; + } + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Sample && + id == other.id && + _name == other._name && + _number == other._number && + _flag == other._flag && + _date == other._date && + _rootbeer == other._rootbeer; + } + + @override + int get hashCode => toString().hashCode; + + @override + String toString() { + var buffer = new StringBuffer(); + + buffer.write("Sample {"); + buffer.write("id=" + "$id" + ", "); + buffer.write("name=" + "$_name" + ", "); + buffer.write( + "number=" + (_number != null ? _number!.toString() : "null") + ", "); + buffer.write("flag=" + (_flag != null ? _flag!.toString() : "null") + ", "); + buffer.write("date=" + (_date != null ? _date!.format() : "null") + ", "); + buffer.write("rootbeer=" + + (_rootbeer != null ? _rootbeer!.toString() : "null") + + ", "); + buffer.write("createdAt=" + + (_createdAt != null ? _createdAt!.format() : "null") + + ", "); + buffer.write( + "updatedAt=" + (_updatedAt != null ? _updatedAt!.format() : "null")); + buffer.write("}"); + + return buffer.toString(); + } + + Sample copyWith( + {String? name, + int? number, + bool? flag, + amplify_core.TemporalTime? date, + double? rootbeer}) { + return Sample._internal( + id: id, + name: name ?? this.name, + number: number ?? this.number, + flag: flag ?? this.flag, + date: date ?? this.date, + rootbeer: rootbeer ?? this.rootbeer); + } + + Sample copyWithModelFieldValues( + {ModelFieldValue? name, + ModelFieldValue? number, + ModelFieldValue? flag, + ModelFieldValue? date, + ModelFieldValue? rootbeer}) { + return Sample._internal( + id: id, + name: name == null ? this.name : name.value, + number: number == null ? this.number : number.value, + flag: flag == null ? this.flag : flag.value, + date: date == null ? this.date : date.value, + rootbeer: rootbeer == null ? this.rootbeer : rootbeer.value); + } + + Sample.fromJson(Map json) + : id = json['id'], + _name = json['name'], + _number = (json['number'] as num?)?.toInt(), + _flag = json['flag'], + _date = json['date'] != null + ? amplify_core.TemporalTime.fromString(json['date']) + : null, + _rootbeer = (json['rootbeer'] as num?)?.toDouble(), + _createdAt = json['createdAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['createdAt']) + : null, + _updatedAt = json['updatedAt'] != null + ? amplify_core.TemporalDateTime.fromString(json['updatedAt']) + : null; + + Map toJson() => { + 'id': id, + 'name': _name, + 'number': _number, + 'flag': _flag, + 'date': _date?.format(), + 'rootbeer': _rootbeer, + 'createdAt': _createdAt?.format(), + 'updatedAt': _updatedAt?.format() + }; + + Map toMap() => { + 'id': id, + 'name': _name, + 'number': _number, + 'flag': _flag, + 'date': _date, + 'rootbeer': _rootbeer, + 'createdAt': _createdAt, + 'updatedAt': _updatedAt + }; + + static final amplify_core.QueryModelIdentifier + MODEL_IDENTIFIER = + amplify_core.QueryModelIdentifier(); + static final ID = amplify_core.QueryField(fieldName: "id"); + static final NAME = amplify_core.QueryField(fieldName: "name"); + static final NUMBER = amplify_core.QueryField(fieldName: "number"); + static final FLAG = amplify_core.QueryField(fieldName: "flag"); + static final DATE = amplify_core.QueryField(fieldName: "date"); + static final ROOTBEER = amplify_core.QueryField(fieldName: "rootbeer"); + static var schema = amplify_core.Model.defineSchema( + define: (amplify_core.ModelSchemaDefinition modelSchemaDefinition) { + modelSchemaDefinition.name = "Sample"; + modelSchemaDefinition.pluralName = "Samples"; + + modelSchemaDefinition.authRules = [ + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PUBLIC, + provider: amplify_core.AuthRuleProvider.APIKEY, + operations: const [amplify_core.ModelOperation.READ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PUBLIC, + provider: amplify_core.AuthRuleProvider.IAM, + operations: const [amplify_core.ModelOperation.READ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PRIVATE, + provider: amplify_core.AuthRuleProvider.IAM, + operations: const [amplify_core.ModelOperation.READ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.PRIVATE, + provider: amplify_core.AuthRuleProvider.USERPOOLS, + operations: const [amplify_core.ModelOperation.READ]), + amplify_core.AuthRule( + authStrategy: amplify_core.AuthStrategy.OWNER, + ownerField: "owner", + identityClaim: "cognito:username", + provider: amplify_core.AuthRuleProvider.USERPOOLS, + operations: const [ + amplify_core.ModelOperation.CREATE, + amplify_core.ModelOperation.READ, + amplify_core.ModelOperation.UPDATE, + amplify_core.ModelOperation.DELETE + ]) + ]; + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.id()); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: Sample.NAME, + isRequired: false, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.string))); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: Sample.NUMBER, + isRequired: false, + ofType: + amplify_core.ModelFieldType(amplify_core.ModelFieldTypeEnum.int))); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: Sample.FLAG, + isRequired: false, + ofType: + amplify_core.ModelFieldType(amplify_core.ModelFieldTypeEnum.bool))); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: Sample.DATE, + isRequired: false, + ofType: + amplify_core.ModelFieldType(amplify_core.ModelFieldTypeEnum.time))); + + modelSchemaDefinition.addField(amplify_core.ModelFieldDefinition.field( + key: Sample.ROOTBEER, + isRequired: false, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.double))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'createdAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + + modelSchemaDefinition.addField( + amplify_core.ModelFieldDefinition.nonQueryField( + fieldName: 'updatedAt', + isRequired: false, + isReadOnly: true, + ofType: amplify_core.ModelFieldType( + amplify_core.ModelFieldTypeEnum.dateTime))); + }); +} + +class _SampleModelType extends amplify_core.ModelType { + const _SampleModelType(); + + @override + Sample fromJson(Map jsonData) { + return Sample.fromJson(jsonData); + } + + @override + String modelName() { + return 'Sample'; + } +} + +/** + * This is an auto generated class representing the model identifier + * of [Sample] in your schema. + */ +class SampleModelIdentifier implements amplify_core.ModelIdentifier { + final String id; + + /** Create an instance of SampleModelIdentifier using [id] the primary key. */ + const SampleModelIdentifier({required this.id}); + + @override + Map serializeAsMap() => ({'id': id}); + + @override + List> serializeAsList() => serializeAsMap() + .entries + .map((entry) => ({entry.key: entry.value})) + .toList(); + + @override + String serializeAsString() => serializeAsMap().values.join('#'); + + @override + String toString() => 'SampleModelIdentifier(id: $id)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + return other is SampleModelIdentifier && id == other.id; + } + + @override + int get hashCode => id.hashCode; +} diff --git a/packages/api/amplify_api_dart/lib/src/graphql/factories/graphql_request_factory.dart b/packages/api/amplify_api_dart/lib/src/graphql/factories/graphql_request_factory.dart index 2f5539a810..36ac085546 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/factories/graphql_request_factory.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/factories/graphql_request_factory.dart @@ -447,6 +447,11 @@ Map _queryFieldOperatorToPartialGraphQLFilter( ], }; } + if (queryFieldOperator is AttributeExistsQueryOperator) { + return { + filterExpression: _getSerializedValue(queryFieldOperator.exists), + }; + } throw ApiOperationException( 'Unable to translate the QueryFieldOperator ${queryFieldOperator.type} to a GraphQL filter.', @@ -464,6 +469,7 @@ String _getGraphQLFilterExpression(QueryFieldOperatorType operatorType) { QueryFieldOperatorType.between: 'between', QueryFieldOperatorType.contains: 'contains', QueryFieldOperatorType.begins_with: 'beginsWith', + QueryFieldOperatorType.attribute_exists: 'attributeExists', }; final result = dictionary[operatorType]; if (result == null) {