Skip to content

Commit 6d30310

Browse files
thisisabhashdiegocstnlawmicha
committed
feat(datastore): support mutation (deletion) with custom primary keys (#1228)
* feat(datastore): [WIP] support for @key directive * feat(datastore): [WIP] support for @key directive * feat(datastore): [WIP] support for @key directive - fix pbxproj and generated models * Datastore changes to support deletion with custom primary keys * Add pbxproj files * Update AWSPluginCoreTests * Add unit test for ModelDeleteDecorator * Update ModelDeleteDecorator as per review comments * Update unit tests and add changes to ModelIdDecorator * fix: Extended ModelIdDecorator with backwards compatibility * Ignore id in fields when decoration is performed Co-authored-by: Diego Costantino <[email protected]> Co-authored-by: Michael <[email protected]>
1 parent 2ba2dbf commit 6d30310

File tree

12 files changed

+389
-18
lines changed

12 files changed

+389
-18
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@
248248
762C978E26210FF100798FA3 /* RecordCover+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 762C978D26210FF100798FA3 /* RecordCover+Schema.swift */; };
249249
7678B38426017D5300B4917F /* AppSyncErrorTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7678B38326017D5300B4917F /* AppSyncErrorTypeTests.swift */; };
250250
7678B38526017D5300B4917F /* AppSyncErrorTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7678B38326017D5300B4917F /* AppSyncErrorTypeTests.swift */; };
251+
767F85FC2649FF540076D633 /* CustomerOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 767F85FB2649FF540076D633 /* CustomerOrder.swift */; };
252+
767F85FE2649FFCC0076D633 /* CustomerOrder+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 767F85FD2649FFCC0076D633 /* CustomerOrder+Schema.swift */; };
251253
7D5ED6C78E25246DDAF2F2EC /* Pods_Amplify.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F3A76FB68CEFA45F4BB1BB /* Pods_Amplify.framework */; platformFilter = ios; };
252254
7F27B1DCE59C1E674172CCD6 /* Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 976D972EC2BBCAAD023694EB /* Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework */; };
253255
881246F5DCC59436DC932469 /* Pods_Amplify_AWSPluginsCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 35D92182B8445C8F9B0FAE94 /* Pods_Amplify_AWSPluginsCore.framework */; };
@@ -328,6 +330,7 @@
328330
976D4D5324DC90610083F5AC /* PersistentLogWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976D4D5224DC90610083F5AC /* PersistentLogWrapperTests.swift */; };
329331
976D4D5524DCA3F80083F5AC /* PersistentLoggingPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976D4D5424DCA3F80083F5AC /* PersistentLoggingPluginTests.swift */; };
330332
977007E324C1585100848468 /* LogEntryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 977007E224C1585100848468 /* LogEntryItem.swift */; };
333+
97943A2E265B7CAB0097DBBA /* GraphQLRequestSyncCustomPrimaryKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97943A06265B7A5F0097DBBA /* GraphQLRequestSyncCustomPrimaryKeyTests.swift */; };
331334
97CB85FD24AA451B000C65FB /* PersistentLoggingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97CB85F524AA451B000C65FB /* PersistentLoggingPlugin.swift */; };
332335
97CB85FE24AA451B000C65FB /* AmplifyDevMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97CB85F624AA451B000C65FB /* AmplifyDevMenu.swift */; };
333336
97CB85FF24AA451B000C65FB /* DevMenuList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97CB85F824AA451B000C65FB /* DevMenuList.swift */; };
@@ -1130,6 +1133,8 @@
11301133
762C978426210F6400798FA3 /* RecordCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordCover.swift; sourceTree = "<group>"; };
11311134
762C978D26210FF100798FA3 /* RecordCover+Schema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecordCover+Schema.swift"; sourceTree = "<group>"; };
11321135
7678B38326017D5300B4917F /* AppSyncErrorTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncErrorTypeTests.swift; sourceTree = "<group>"; };
1136+
767F85FB2649FF540076D633 /* CustomerOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerOrder.swift; sourceTree = "<group>"; };
1137+
767F85FD2649FFCC0076D633 /* CustomerOrder+Schema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomerOrder+Schema.swift"; sourceTree = "<group>"; };
11331138
77A2D125114EA0FFA11A7EFD /* Pods-AmplifyTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AmplifyTests.debug.xcconfig"; path = "Target Support Files/Pods-AmplifyTests/Pods-AmplifyTests.debug.xcconfig"; sourceTree = "<group>"; };
11341139
7A238096BAB328655B5E388F /* Pods-Amplify-AWSPluginsCore-CoreMLPredictionsPlugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Amplify-AWSPluginsCore-CoreMLPredictionsPlugin.debug.xcconfig"; path = "Target Support Files/Pods-Amplify-AWSPluginsCore-CoreMLPredictionsPlugin/Pods-Amplify-AWSPluginsCore-CoreMLPredictionsPlugin.debug.xcconfig"; sourceTree = "<group>"; };
11351140
7A7FD9B3CFF63FD16788A990 /* Pods-Amplify-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Amplify-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests.release.xcconfig"; path = "Target Support Files/Pods-Amplify-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests/Pods-Amplify-AmplifyAWSPlugins-AWSPluginsCore-AWSPinpointAnalyticsPlugin-AWSPinpointAnalyticsPluginTests.release.xcconfig"; sourceTree = "<group>"; };
@@ -1232,6 +1237,7 @@
12321237
976D4D5424DCA3F80083F5AC /* PersistentLoggingPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentLoggingPluginTests.swift; sourceTree = "<group>"; };
12331238
976D972EC2BBCAAD023694EB /* Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Amplify_AmplifyTestConfigs_AmplifyTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
12341239
977007E224C1585100848468 /* LogEntryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEntryItem.swift; sourceTree = "<group>"; };
1240+
97943A06265B7A5F0097DBBA /* GraphQLRequestSyncCustomPrimaryKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequestSyncCustomPrimaryKeyTests.swift; sourceTree = "<group>"; };
12351241
97CB85F524AA451B000C65FB /* PersistentLoggingPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentLoggingPlugin.swift; sourceTree = "<group>"; };
12361242
97CB85F624AA451B000C65FB /* AmplifyDevMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmplifyDevMenu.swift; sourceTree = "<group>"; };
12371243
97CB85F824AA451B000C65FB /* DevMenuList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DevMenuList.swift; sourceTree = "<group>"; };
@@ -1899,6 +1905,7 @@
18991905
2129BE322394828B006363A1 /* GraphQLRequestModelTests.swift */,
19001906
214F497D2486DA5000DA616C /* GraphQLRequestOptionalAssociationTests.swift */,
19011907
6BEE08182533CAA600133961 /* GraphQLRequestOwnerAndGroupTests.swift */,
1908+
97943A06265B7A5F0097DBBA /* GraphQLRequestSyncCustomPrimaryKeyTests.swift */,
19021909
6B9F7C5125267E1500F1F71C /* MockAWSAuthUser.swift */,
19031910
);
19041911
path = GraphQLRequest;
@@ -2931,6 +2938,8 @@
29312938
214F49CC24898E8500DA616C /* Article+Schema.swift */,
29322939
B9521830237E21B900F53237 /* Comment.swift */,
29332940
B952182E237E21B900F53237 /* Comment+Schema.swift */,
2941+
767F85FB2649FF540076D633 /* CustomerOrder.swift */,
2942+
767F85FD2649FFCC0076D633 /* CustomerOrder+Schema.swift */,
29342943
21AD4255249BFFDF0016FE95 /* DeprecatedTodo.swift */,
29352944
FAA2E8BB239FFC7700E420EA /* MockModels.swift */,
29362945
6BEE08222533D30800133961 /* OGCScenarioBMGroupPost.swift */,
@@ -2963,6 +2972,7 @@
29632972
6B7743D625906F7E001469F5 /* Restaurant */,
29642973
21A9051E2616442000EC141D /* Scalar */,
29652974
6BE9D6E725A6620B00AB5C9A /* TeamProject */,
2975+
767F85FD2649FFCC0076D633 /* CustomerOrder+Schema.swift */,
29662976
);
29672977
path = Models;
29682978
sourceTree = "<group>";
@@ -4768,6 +4778,7 @@
47684778
isa = PBXSourcesBuildPhase;
47694779
buildActionMask = 2147483647;
47704780
files = (
4781+
97943A2E265B7CAB0097DBBA /* GraphQLRequestSyncCustomPrimaryKeyTests.swift in Sources */,
47714782
6BEE0817253114FD00133961 /* AWSAuthServiceTests.swift in Sources */,
47724783
6B9F7C59252687CC00F1F71C /* GraphQLRequestAuthIdentityClaimTests.swift in Sources */,
47734784
2183A56923EA4A9100232880 /* GraphQLUpdateMutationTests.swift in Sources */,
@@ -5335,6 +5346,7 @@
53355346
6B7743E125906FD3001469F5 /* MenuType.swift in Sources */,
53365347
FACA361C2327FC7D000E74F6 /* MockHubCategoryPlugin.swift in Sources */,
53375348
FAD3937F23820DAE00463F5E /* MockDataStoreCategoryPlugin.swift in Sources */,
5349+
767F85FC2649FF540076D633 /* CustomerOrder.swift in Sources */,
53385350
217D5EBB2577F9DF009F0639 /* Team1.swift in Sources */,
53395351
6BEE08252533D30800133961 /* OGCScenarioBMGroupPost+Schema.swift in Sources */,
53405352
21FDBB662587D7A30086FCDC /* Blog6+Schema.swift in Sources */,
@@ -5429,6 +5441,7 @@
54295441
217D5EBA2577F9DF009F0639 /* Post4+Schema.swift in Sources */,
54305442
21A7C90225ACC4D1004355D6 /* MockDataStoreResponders.swift in Sources */,
54315443
FA32C428264F35390057272F /* Transaction.swift in Sources */,
5444+
767F85FE2649FFCC0076D633 /* CustomerOrder+Schema.swift in Sources */,
54325445
217D5EBD2577F9DF009F0639 /* Post5.swift in Sources */,
54335446
6B7743E425906FD3001469F5 /* Restaurant.swift in Sources */,
54345447
FA1846EE23998E44009B9D01 /* MockAPIResponders.swift in Sources */,

Amplify/Categories/DataStore/Model/Internal/Schema/ModelSchema.swift

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77

88
/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used
99
/// directly by host applications. The behavior of this may change without warning.
10-
public enum ModelAttribute {
10+
public typealias ModelFieldName = String
11+
12+
/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used
13+
/// directly by host applications. The behavior of this may change without warning.
14+
public enum ModelAttribute: Equatable {
1115

1216
/// Represents a database index, often used for frequent query optimizations.
13-
case index
17+
case index(fields: [ModelFieldName], name: String?)
1418

1519
/// This model is used by the Amplify system or a plugin, and should not be used by the app developer
1620
case isSystem
@@ -26,7 +30,7 @@ public enum ModelFieldAttribute {
2630
/// directly by host applications. The behavior of this may change without warning.
2731
public struct ModelField {
2832

29-
public let name: String
33+
public let name: ModelFieldName
3034
public let type: ModelFieldType
3135
public let isRequired: Bool
3236
public let isReadOnly: Bool
@@ -105,6 +109,37 @@ public struct ModelSchema {
105109

106110
}
107111

112+
// MARK: - ModelAttribute + Index
113+
114+
/// - Warning: Although this has `public` access, it is intended for internal & codegen use and should not be used
115+
/// directly by host applications. The behavior of this may change without warning.
116+
public extension ModelSchema {
117+
var indexes: [ModelAttribute] {
118+
attributes.filter {
119+
switch $0 {
120+
case .index:
121+
return true
122+
default:
123+
return false
124+
}
125+
}
126+
}
127+
128+
/// Returns the list of fields that make up the primary key for the model.
129+
/// In case of a custom primary key, the model has a `@key` directive
130+
/// without a name and at least 1 field
131+
var customPrimaryIndexFields: [ModelFieldName]? {
132+
attributes.compactMap {
133+
if case let .index(fields, name) = $0, name == nil, fields.count >= 1 {
134+
return fields
135+
}
136+
return nil
137+
}.first
138+
}
139+
}
140+
141+
// MARK: - Dictionary + ModelField
142+
108143
extension Dictionary where Key == String, Value == ModelField {
109144

110145
/// Returns an array of the values sorted by some pre-defined rules:

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,21 @@ import Amplify
1212
public struct ModelIdDecorator: ModelBasedGraphQLDocumentDecorator {
1313

1414
private let id: Model.Identifier
15+
private let fields: [String: String]?
16+
17+
public init(model: Model) {
18+
var fields = [String: String]()
19+
if let customPrimaryKeys = model.schema.customPrimaryIndexFields {
20+
for key in customPrimaryKeys {
21+
fields[key] = model[key] as? String
22+
}
23+
}
24+
self.init(id: model.id, fields: fields)
25+
}
1526

16-
public init(id: Model.Identifier) {
27+
public init(id: Model.Identifier, fields: [String: String]? = nil) {
1728
self.id = id
29+
self.fields = fields
1830
}
1931

2032
public func decorate(_ document: SingleDirectiveGraphQLDocument,
@@ -27,10 +39,23 @@ public struct ModelIdDecorator: ModelBasedGraphQLDocumentDecorator {
2739
var inputs = document.inputs
2840

2941
if case .mutation = document.operationType {
42+
var objectMap = [String: String]()
43+
if let fields = fields {
44+
for (fieldName, value) in fields where fieldName != "id" {
45+
objectMap[fieldName] = value
46+
}
47+
}
48+
objectMap["id"] = id
3049
inputs["input"] = GraphQLDocumentInput(type: "\(document.name.pascalCased())Input!",
31-
value: .object(["id": id]))
50+
value: .object(objectMap))
3251
} else if case .query = document.operationType {
3352
inputs["id"] = GraphQLDocumentInput(type: "ID!", value: .scalar(id))
53+
54+
if let fields = fields {
55+
for (fieldName, value) in fields where fieldName != "id" {
56+
inputs[fieldName] = GraphQLDocumentInput(type: "String!", value: .scalar(value))
57+
}
58+
}
3459
}
3560

3661
return document.copy(inputs: inputs)

AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ protocol ModelSyncGraphQLRequestFactory {
2626
where filter: GraphQLFilter?,
2727
version: Int?) -> GraphQLRequest<MutationSyncResult>
2828

29-
static func deleteMutation(modelName: String,
30-
id: Model.Identifier,
29+
static func deleteMutation(of model: Model,
30+
modelSchema: ModelSchema,
3131
where filter: GraphQLFilter?,
3232
version: Int?) -> GraphQLRequest<MutationSyncResult>
3333

@@ -110,13 +110,13 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
110110
createOrUpdateMutation(of: model, modelSchema: modelSchema, where: filter, type: .update, version: version)
111111
}
112112

113-
public static func deleteMutation(modelName: String,
114-
id: Model.Identifier,
113+
public static func deleteMutation(of model: Model,
114+
modelSchema: ModelSchema,
115115
where filter: GraphQLFilter? = nil,
116116
version: Int? = nil) -> GraphQLRequest<MutationSyncResult> {
117-
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: modelName, operationType: .mutation)
117+
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: model.modelName, operationType: .mutation)
118118
documentBuilder.add(decorator: DirectiveNameDecorator(type: .delete))
119-
documentBuilder.add(decorator: ModelIdDecorator(id: id))
119+
documentBuilder.add(decorator: ModelIdDecorator(model: model))
120120
if let filter = filter {
121121
documentBuilder.add(decorator: FilterDecorator(filter: filter))
122122
}

AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+Model.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ extension GraphQLRequest: ModelGraphQLRequestFactory {
182182
case .create:
183183
documentBuilder.add(decorator: ModelDecorator(model: model))
184184
case .delete:
185-
documentBuilder.add(decorator: ModelIdDecorator(id: model.id))
185+
documentBuilder.add(decorator: ModelIdDecorator(model: model))
186186
if let predicate = predicate {
187187
documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter(for: modelSchema)))
188188
}

AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import XCTest
1010
@testable import AmplifyTestCommon
1111
@testable import AWSPluginsCore
1212

13+
// swiftlint:disable type_body_length
1314
class GraphQLRequestAnyModelWithSyncTests: XCTestCase {
1415

1516
override func setUp() {
1617
ModelRegistry.register(modelType: Comment.self)
1718
ModelRegistry.register(modelType: Post.self)
19+
1820
}
1921

2022
override func tearDown() {
@@ -172,7 +174,7 @@ class GraphQLRequestAnyModelWithSyncTests: XCTestCase {
172174
}
173175
"""
174176

175-
let request = GraphQLRequest<MutationSyncResult>.deleteMutation(modelName: post.modelName, id: post.id)
177+
let request = GraphQLRequest<MutationSyncResult>.deleteMutation(of: post, modelSchema: post.schema)
176178

177179
XCTAssertEqual(document.stringValue, request.document)
178180
XCTAssertEqual(documentStringValue, request.document)

AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAuthRuleTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class GraphQLRequestAuthRuleTests: XCTestCase {
161161
}
162162
"""
163163

164-
let request = GraphQLRequest<MutationSyncResult>.deleteMutation(modelName: article.modelName, id: article.id)
164+
let request = GraphQLRequest<MutationSyncResult>.deleteMutation(of: article, modelSchema: article.schema)
165165

166166
XCTAssertEqual(document.stringValue, request.document)
167167
XCTAssertEqual(documentStringValue, request.document)

0 commit comments

Comments
 (0)