Skip to content

Commit 677cbd1

Browse files
authored
fix(datastore): Add DateTime, Date, Time, Int, Float, Enum field in indexes to GraphQL input correctly for mutations (#1612)
* fix(datastore): Add DateTime field in indexes to GraphQL input correctly for mutations * Address review comments * Address review comments * Address review comments
1 parent 9e3e8c4 commit 677cbd1

File tree

9 files changed

+407
-3
lines changed

9 files changed

+407
-3
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@
438438
97DC3D5024C1FCEE00D79E24 /* LogEntryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DC3D4F24C1FCEE00D79E24 /* LogEntryRow.swift */; };
439439
97DC3D5224C1FE4100D79E24 /* LogViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DC3D5124C1FE4100D79E24 /* LogViewer.swift */; };
440440
97DC3D5424C200A600D79E24 /* LogEntryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DC3D5324C200A600D79E24 /* LogEntryHelper.swift */; };
441+
97F793B427A9CC7E000153D6 /* GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F793B327A9CC7E000153D6 /* GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift */; };
442+
97F793B627AA0619000153D6 /* CustomerWithMultipleFieldsinPK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F793B527AA0619000153D6 /* CustomerWithMultipleFieldsinPK.swift */; };
443+
97F793B827AA0667000153D6 /* CustomerWithMultipleFieldsinPK+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F793B727AA0667000153D6 /* CustomerWithMultipleFieldsinPK+Schema.swift */; };
444+
97F793BA27AA0767000153D6 /* Priority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F793B927AA0767000153D6 /* Priority.swift */; };
441445
A5BD5B125AE6AAD604092274 /* Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5009041CDAAB46551245D381 /* Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework */; };
442446
B4251A0124250369007F59EF /* AuthConfirmResetPasswordRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4251A0024250369007F59EF /* AuthConfirmResetPasswordRequest.swift */; };
443447
B4251A05242503F6007F59EF /* AuthConfirmResetPasswordOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4251A04242503F6007F59EF /* AuthConfirmResetPasswordOperation.swift */; };
@@ -1358,6 +1362,10 @@
13581362
97DC3D4F24C1FCEE00D79E24 /* LogEntryRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEntryRow.swift; sourceTree = "<group>"; };
13591363
97DC3D5124C1FE4100D79E24 /* LogViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewer.swift; sourceTree = "<group>"; };
13601364
97DC3D5324C200A600D79E24 /* LogEntryHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEntryHelper.swift; sourceTree = "<group>"; };
1365+
97F793B327A9CC7E000153D6 /* GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift; sourceTree = "<group>"; };
1366+
97F793B527AA0619000153D6 /* CustomerWithMultipleFieldsinPK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerWithMultipleFieldsinPK.swift; sourceTree = "<group>"; };
1367+
97F793B727AA0667000153D6 /* CustomerWithMultipleFieldsinPK+Schema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomerWithMultipleFieldsinPK+Schema.swift"; sourceTree = "<group>"; };
1368+
97F793B927AA0767000153D6 /* Priority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Priority.swift; sourceTree = "<group>"; };
13611369
AF4FA69A23AEB42CF7BB8336 /* Pods-Amplify-AmplifyTestConfigs-AmplifyTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Amplify-AmplifyTestConfigs-AmplifyTests.debug.xcconfig"; path = "Target Support Files/Pods-Amplify-AmplifyTestConfigs-AmplifyTests/Pods-Amplify-AmplifyTestConfigs-AmplifyTests.debug.xcconfig"; sourceTree = "<group>"; };
13621370
B4251A0024250369007F59EF /* AuthConfirmResetPasswordRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmResetPasswordRequest.swift; sourceTree = "<group>"; };
13631371
B4251A04242503F6007F59EF /* AuthConfirmResetPasswordOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmResetPasswordOperation.swift; sourceTree = "<group>"; };
@@ -1972,6 +1980,7 @@
19721980
6BEE08182533CAA600133961 /* GraphQLRequestOwnerAndGroupTests.swift */,
19731981
97943A06265B7A5F0097DBBA /* GraphQLRequestSyncCustomPrimaryKeyTests.swift */,
19741982
6B9F7C5125267E1500F1F71C /* MockAWSAuthUser.swift */,
1983+
97F793B327A9CC7E000153D6 /* GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift */,
19751984
);
19761985
path = GraphQLRequest;
19771986
sourceTree = "<group>";
@@ -2202,6 +2211,9 @@
22022211
218F78C9275AC6B30042334B /* TodoCustomTimestampV2+Schema.swift */,
22032212
218F78C4275AC6B30042334B /* TodoWithDefaultValueV2.swift */,
22042213
218F78C5275AC6B30042334B /* TodoWithDefaultValueV2+Schema.swift */,
2214+
97F793B527AA0619000153D6 /* CustomerWithMultipleFieldsinPK.swift */,
2215+
97F793B727AA0667000153D6 /* CustomerWithMultipleFieldsinPK+Schema.swift */,
2216+
97F793B927AA0767000153D6 /* Priority.swift */,
22052217
);
22062218
path = TransformerV2;
22072219
sourceTree = "<group>";
@@ -4984,6 +4996,7 @@
49844996
6B5087C7256638EA000AB673 /* QueryPredicateEvaluateGeneratedDateTests.swift in Sources */,
49854997
21DDCDF8272C3D8300D9B297 /* AuthRuleExtensionTests.swift in Sources */,
49864998
6BBECD7423ADA9D100C8DFBE /* AmplifyAWSServiceConfigurationTests.swift in Sources */,
4999+
97F793B427A9CC7E000153D6 /* GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift in Sources */,
49875000
6BAF4F38256893B900A811BA /* QueryPredicateEvaluateGenerator.swift in Sources */,
49885001
6B9F7C5225267E1500F1F71C /* MockAWSAuthUser.swift in Sources */,
49895002
6B5087CD25673AC8000AB673 /* QueryPredicateEvaluateGeneratedTimeTests.swift in Sources */,
@@ -5656,6 +5669,7 @@
56565669
214F497C2486D8A200DA616C /* UserFollowers.swift in Sources */,
56575670
217D5EB62577F9DF009F0639 /* Comment3+Schema.swift in Sources */,
56585671
215D40AA277231920077E7DA /* Attendee8V2.swift in Sources */,
5672+
97F793B827AA0667000153D6 /* CustomerWithMultipleFieldsinPK+Schema.swift in Sources */,
56595673
B9521835237E21BA00F53237 /* Comment.swift in Sources */,
56605674
21AD4257249BFFE00016FE95 /* DeprecatedTodo.swift in Sources */,
56615675
6B7743E325906FD3001469F5 /* Restaurant+Schema.swift in Sources */,
@@ -5683,6 +5697,7 @@
56835697
214F49782486D8A200DA616C /* UserFollowing.swift in Sources */,
56845698
6BEE08242533D30800133961 /* OGCScenarioBMGroupPost.swift in Sources */,
56855699
217D5EC22577F9DF009F0639 /* User5+Schema.swift in Sources */,
5700+
97F793B627AA0619000153D6 /* CustomerWithMultipleFieldsinPK.swift in Sources */,
56865701
21FDBB642587D7A30086FCDC /* Post6+Schema.swift in Sources */,
56875702
218F78E6275AD0310042334B /* Team4aV2+Schema.swift in Sources */,
56885703
217D5EB12577F9DF009F0639 /* Team2+Schema.swift in Sources */,
@@ -5699,6 +5714,7 @@
56995714
21A7C90225ACC4D1004355D6 /* MockDataStoreResponders.swift in Sources */,
57005715
218F784A275A631A0042334B /* Comment4V2+Schema.swift in Sources */,
57015716
218F78DD275AD0280042334B /* Team4bV2.swift in Sources */,
5717+
97F793BA27AA0767000153D6 /* Priority.swift in Sources */,
57025718
218F789A275AB1F60042334B /* Post3aV2.swift in Sources */,
57035719
218F7899275AB1F60042334B /* Comment3aV2.swift in Sources */,
57045720
FA32C428264F35390057272F /* Transaction.swift in Sources */,

AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelIdDecorator.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ public struct ModelIdDecorator: ModelBasedGraphQLDocumentDecorator {
1818
var fields = [String: String]()
1919
if let customPrimaryKeys = model.schema.customPrimaryIndexFields {
2020
for key in customPrimaryKeys {
21-
fields[key] = model[key] as? String
21+
if let value = model.graphQLInputForPrimaryKey(modelFieldName: key) {
22+
fields[key] = value
23+
}
2224
}
2325
}
2426
self.init(id: model.id, fields: fields)

AmplifyPlugins/Core/AWSPluginsCore/Model/Support/Model+GraphQL.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,44 @@ extension Model {
115115
return input
116116
}
117117

118+
/// Retrieve the custom primary key's value used for the GraphQL input.
119+
/// Only a subset of data types are applicable as custom indexes such as
120+
/// `date`, `dateTime`, `time`, `enum`, `string`, `double`, and `int`.
121+
func graphQLInputForPrimaryKey(modelFieldName: ModelFieldName) -> String? {
122+
123+
guard let modelField = schema.field(withName: modelFieldName) else {
124+
return nil
125+
}
126+
127+
let fieldValueOptional = getFieldValue(for: modelField.name, modelSchema: schema)
128+
129+
guard let fieldValue = fieldValueOptional else {
130+
return nil
131+
}
132+
133+
// swiftlint:disable:next syntactic_sugar
134+
guard case .some(Optional<Any>.some(let value)) = fieldValue else {
135+
return nil
136+
}
137+
138+
switch modelField.type {
139+
case .date, .dateTime, .time:
140+
if let date = value as? TemporalSpec {
141+
return date.iso8601String
142+
} else {
143+
return nil
144+
}
145+
case .enum:
146+
return (value as? EnumPersistable)?.rawValue
147+
case .model, .embedded, .embeddedCollection:
148+
return nil
149+
case .string, .double, .int:
150+
return String(describing: value)
151+
default:
152+
return nil
153+
}
154+
}
155+
118156
private func getModelId(from value: Any, modelSchema: ModelSchema) -> String? {
119157
if let modelValue = value as? Model {
120158
return modelValue.id
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import XCTest
9+
@testable import Amplify
10+
@testable import AmplifyTestCommon
11+
@testable import AWSPluginsCore
12+
13+
// swiftlint:disable type_name
14+
class GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests: XCTestCase {
15+
16+
override func setUp() {
17+
ModelRegistry.register(modelType: CustomerWithMultipleFieldsinPK.self)
18+
}
19+
20+
override func tearDown() {
21+
ModelRegistry.reset()
22+
}
23+
24+
func testDeleteMutationGraphQLRequestWithDateInPK() throws {
25+
let customer = CustomerWithMultipleFieldsinPK(dob: Temporal.DateTime.now(),
26+
date: Temporal.Date.now(),
27+
time: Temporal.Time.now(),
28+
phoneNumber: 1_234_567,
29+
priority: Priority.high,
30+
height: 6.1,
31+
firstName: "John",
32+
lastName: "Doe")
33+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: customer.modelName,
34+
operationType: .mutation)
35+
documentBuilder.add(decorator: DirectiveNameDecorator(type: .delete))
36+
documentBuilder.add(decorator: ModelIdDecorator(model: customer))
37+
documentBuilder.add(decorator: ConflictResolutionDecorator(version: 1, lastSync: nil))
38+
let document = documentBuilder.build()
39+
let documentStringValue = """
40+
mutation DeleteCustomerWithMultipleFieldsinPK($input: DeleteCustomerWithMultipleFieldsinPKInput!) {
41+
deleteCustomerWithMultipleFieldsinPK(input: $input) {
42+
id
43+
createdAt
44+
date
45+
dob
46+
firstName
47+
height
48+
lastName
49+
phoneNumber
50+
priority
51+
time
52+
updatedAt
53+
__typename
54+
_version
55+
_deleted
56+
_lastChangedAt
57+
}
58+
}
59+
"""
60+
XCTAssertEqual(document.stringValue, documentStringValue)
61+
62+
guard let expectedInput = document.variables?["input"] as? [String: Any] else {
63+
XCTFail("The document variables property doesn't contain a valid input")
64+
return
65+
}
66+
67+
let request = GraphQLRequest<MutationSyncResult>.deleteMutation(of: customer,
68+
modelSchema: customer.schema,
69+
version: 1)
70+
71+
XCTAssertEqual(request.document, document.stringValue)
72+
XCTAssert(request.responseType == MutationSyncResult.self)
73+
74+
guard let variables = request.variables else {
75+
XCTFail("The request doesn't contain variables")
76+
return
77+
}
78+
guard let input = variables["input"] as? [String: Any] else {
79+
XCTFail("The document variables property doesn't contain a valid input")
80+
return
81+
}
82+
83+
XCTAssertEqual(input["id"] as? String, customer.id)
84+
XCTAssertEqual(input["dob"] as? String, customer.dob.iso8601String)
85+
XCTAssertEqual(input["date"] as? String, customer.date.iso8601String)
86+
XCTAssertEqual(input["time"] as? String, customer.time.iso8601String)
87+
XCTAssertEqual(input["phoneNumber"] as? String, String(describing: customer.phoneNumber))
88+
XCTAssertEqual(input["priority"] as? String, customer.priority.rawValue)
89+
XCTAssertEqual(input["height"] as? String, String(describing: customer.height))
90+
XCTAssertEqual(input["_version"] as? Int, 1)
91+
92+
XCTAssertEqual(input["id"] as? String, expectedInput["id"] as? String)
93+
XCTAssertEqual(input["dob"] as? String, expectedInput["dob"] as? String)
94+
XCTAssertEqual(input["date"] as? String, expectedInput["date"] as? String)
95+
XCTAssertEqual(input["time"] as? String, expectedInput["time"] as? String)
96+
XCTAssertEqual(input["phoneNumber"] as? String, expectedInput["phoneNumber"] as? String)
97+
XCTAssertEqual(input["priority"] as? String, expectedInput["priority"] as? String)
98+
XCTAssertEqual(input["height"] as? String, expectedInput["height"] as? String)
99+
XCTAssertEqual(input["_version"] as? Int, expectedInput["_version"] as? Int)
100+
}
101+
102+
func testOnCreateSubscriptionGraphQLRequestWithDateInPK() throws {
103+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: CustomerWithMultipleFieldsinPK.modelName,
104+
operationType: .subscription)
105+
documentBuilder.add(decorator: DirectiveNameDecorator(type: .onCreate))
106+
documentBuilder.add(decorator: ConflictResolutionDecorator())
107+
let document = documentBuilder.build()
108+
let documentStringValue = """
109+
subscription OnCreateCustomerWithMultipleFieldsinPK {
110+
onCreateCustomerWithMultipleFieldsinPK {
111+
id
112+
createdAt
113+
date
114+
dob
115+
firstName
116+
height
117+
lastName
118+
phoneNumber
119+
priority
120+
time
121+
updatedAt
122+
__typename
123+
_version
124+
_deleted
125+
_lastChangedAt
126+
}
127+
}
128+
"""
129+
XCTAssertEqual(document.stringValue, documentStringValue)
130+
131+
let request = GraphQLRequest<MutationSyncResult>.subscription(to: CustomerWithMultipleFieldsinPK.self,
132+
subscriptionType: .onCreate)
133+
XCTAssertEqual(document.stringValue, request.document)
134+
XCTAssertEqual(documentStringValue, request.document)
135+
}
136+
137+
func testSyncQueryGraphQLRequestWithDateInPK() throws {
138+
let nextToken = "nextToken"
139+
let limit = 100
140+
let lastSync = 123
141+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: CustomerWithMultipleFieldsinPK.modelName,
142+
operationType: .query)
143+
documentBuilder.add(decorator: DirectiveNameDecorator(type: .sync))
144+
documentBuilder.add(decorator: PaginationDecorator(limit: limit, nextToken: nextToken))
145+
documentBuilder.add(decorator: ConflictResolutionDecorator(lastSync: lastSync))
146+
let document = documentBuilder.build()
147+
let documentStringValue = """
148+
query SyncCustomerWithMultipleFieldsinPKs($lastSync: AWSTimestamp, $limit: Int, $nextToken: String) {
149+
syncCustomerWithMultipleFieldsinPKs(lastSync: $lastSync, limit: $limit, nextToken: $nextToken) {
150+
items {
151+
id
152+
createdAt
153+
date
154+
dob
155+
firstName
156+
height
157+
lastName
158+
phoneNumber
159+
priority
160+
time
161+
updatedAt
162+
__typename
163+
_version
164+
_deleted
165+
_lastChangedAt
166+
}
167+
nextToken
168+
startedAt
169+
}
170+
}
171+
"""
172+
XCTAssertEqual(document.stringValue, documentStringValue)
173+
174+
let request = GraphQLRequest<SyncQueryResult>.syncQuery(modelSchema: CustomerWithMultipleFieldsinPK.schema,
175+
limit: limit,
176+
nextToken: nextToken,
177+
lastSync: lastSync)
178+
179+
XCTAssertEqual(document.stringValue, request.document)
180+
XCTAssertEqual(documentStringValue, request.document)
181+
XCTAssert(request.responseType == SyncQueryResult.self)
182+
guard let variables = request.variables else {
183+
XCTFail("The request doesn't contain variables")
184+
return
185+
}
186+
XCTAssertNotNil(variables["limit"])
187+
XCTAssertEqual(variables["limit"] as? Int, limit)
188+
XCTAssertNotNil(variables["nextToken"])
189+
XCTAssertEqual(variables["nextToken"] as? String, nextToken)
190+
XCTAssertNotNil(variables["lastSync"])
191+
XCTAssertEqual(variables["lastSync"] as? Int, lastSync)
192+
}
193+
194+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
// swiftlint:disable all
9+
import Amplify
10+
import Foundation
11+
12+
extension CustomerWithMultipleFieldsinPK {
13+
// MARK: - CodingKeys
14+
public enum CodingKeys: String, ModelKey {
15+
case id
16+
case dob
17+
case date
18+
case time
19+
case phoneNumber
20+
case priority
21+
case height
22+
case firstName
23+
case lastName
24+
case createdAt
25+
case updatedAt
26+
}
27+
28+
public static let keys = CodingKeys.self
29+
// MARK: - ModelSchema
30+
31+
public static let schema = defineSchema { model in
32+
let customerWithMultipleFieldsinPK = CustomerWithMultipleFieldsinPK.keys
33+
34+
model.pluralName = "CustomerWithMultipleFieldsinPKs"
35+
36+
model.attributes(
37+
.index(fields: ["id", "dob", "date", "time", "phoneNumber", "priority", "height"], name: nil)
38+
)
39+
40+
model.fields(
41+
.id(),
42+
.field(customerWithMultipleFieldsinPK.dob, is: .required, ofType: .dateTime),
43+
.field(customerWithMultipleFieldsinPK.date, is: .required, ofType: .date),
44+
.field(customerWithMultipleFieldsinPK.time, is: .required, ofType: .time),
45+
.field(customerWithMultipleFieldsinPK.phoneNumber, is: .required, ofType: .int),
46+
.field(customerWithMultipleFieldsinPK.priority, is: .required, ofType: .enum(type: Priority.self)),
47+
.field(customerWithMultipleFieldsinPK.height, is: .required, ofType: .double),
48+
.field(customerWithMultipleFieldsinPK.firstName, is: .optional, ofType: .string),
49+
.field(customerWithMultipleFieldsinPK.lastName, is: .optional, ofType: .string),
50+
.field(customerWithMultipleFieldsinPK.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime),
51+
.field(customerWithMultipleFieldsinPK.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime)
52+
)
53+
}
54+
}

0 commit comments

Comments
 (0)