Skip to content

Commit ae6de04

Browse files
authored
fix: when transactions enabled throw errors when needed (#295)
* fix: when transactions enabled throw errors when needed * add transaction deleteAll tests * nits * revert
1 parent 1352a83 commit ae6de04

13 files changed

+448
-135
lines changed

.codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ coverage:
66
status:
77
patch:
88
default:
9-
target: 74
9+
target: auto
1010
changes: false
1111
project:
1212
default:

ParseSwift.playground/Sources/Common.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public func initializeParse() {
66
clientKey: "clientKey",
77
masterKey: "masterKey",
88
serverURL: URL(string: "http://localhost:1337/1")!,
9-
useTransactionsInternally: false)
9+
useTransactions: false)
1010
}
1111

1212
public func initializeParseCustomObjectId() {

Sources/ParseSwift/Objects/ParseInstallation+async.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public extension Sequence where Element: ParseInstallation {
110110
the transactions can fail.
111111
*/
112112
func saveAll(batchLimit limit: Int? = nil,
113-
transaction: Bool = false,
113+
transaction: Bool = ParseSwift.configuration.useTransactions,
114114
isIgnoreCustomObjectIdConfig: Bool = false,
115115
options: API.Options = []) async throws -> [(Result<Self.Element, ParseError>)] {
116116
try await withCheckedThrowingContinuation { continuation in
@@ -138,7 +138,7 @@ public extension Sequence where Element: ParseInstallation {
138138
the transactions can fail.
139139
*/
140140
func deleteAll(batchLimit limit: Int? = nil,
141-
transaction: Bool = false,
141+
transaction: Bool = ParseSwift.configuration.useTransactions,
142142
options: API.Options = []) async throws -> [(Result<Void, ParseError>)] {
143143
try await withCheckedThrowingContinuation { continuation in
144144
self.deleteAll(batchLimit: limit,

Sources/ParseSwift/Objects/ParseInstallation+combine.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public extension Sequence where Element: ParseInstallation {
9999
the transactions can fail.
100100
*/
101101
func saveAllPublisher(batchLimit limit: Int? = nil,
102-
transaction: Bool = false,
102+
transaction: Bool = ParseSwift.configuration.useTransactions,
103103
isIgnoreCustomObjectIdConfig: Bool = false,
104104
options: API.Options = []) -> Future<[(Result<Self.Element, ParseError>)], ParseError> {
105105
Future { promise in
@@ -126,7 +126,7 @@ public extension Sequence where Element: ParseInstallation {
126126
the transactions can fail.
127127
*/
128128
func deleteAllPublisher(batchLimit limit: Int? = nil,
129-
transaction: Bool = false,
129+
transaction: Bool = ParseSwift.configuration.useTransactions,
130130
options: API.Options = []) -> Future<[(Result<Void, ParseError>)], ParseError> {
131131
Future { promise in
132132
self.deleteAll(batchLimit: limit,

Sources/ParseSwift/Objects/ParseInstallation.swift

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ public extension Sequence where Element: ParseInstallation {
747747
desires a different policy, it should be inserted in `options`.
748748
*/
749749
func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length
750-
transaction: Bool = false,
750+
transaction: Bool = ParseSwift.configuration.useTransactions,
751751
isIgnoreCustomObjectIdConfig: Bool = false,
752752
options: API.Options = []) throws -> [(Result<Self.Element, ParseError>)] {
753753
var options = options
@@ -799,12 +799,8 @@ public extension Sequence where Element: ParseInstallation {
799799
let commands = try map {
800800
try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig)
801801
}
802-
let batchLimit: Int!
803-
if transaction {
804-
batchLimit = commands.count
805-
} else {
806-
batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
807-
}
802+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
803+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
808804
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
809805
try batches.forEach {
810806
let currentBatch = try API.Command<Self.Element, Self.Element>
@@ -850,7 +846,7 @@ public extension Sequence where Element: ParseInstallation {
850846
*/
851847
func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity
852848
batchLimit limit: Int? = nil,
853-
transaction: Bool = false,
849+
transaction: Bool = ParseSwift.configuration.useTransactions,
854850
isIgnoreCustomObjectIdConfig: Bool = false,
855851
options: API.Options = [],
856852
callbackQueue: DispatchQueue = .main,
@@ -874,7 +870,9 @@ public extension Sequence where Element: ParseInstallation {
874870
let group = DispatchGroup()
875871
group.enter()
876872
installation
877-
.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in
873+
.ensureDeepSave(options: options,
874+
// swiftlint:disable:next line_length
875+
isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in
878876
//If an error occurs, everything should be skipped
879877
if parseError != nil {
880878
error = parseError
@@ -917,12 +915,8 @@ public extension Sequence where Element: ParseInstallation {
917915
let commands = try map {
918916
try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig)
919917
}
920-
let batchLimit: Int!
921-
if transaction {
922-
batchLimit = commands.count
923-
} else {
924-
batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
925-
}
918+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
919+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
926920
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
927921
var completed = 0
928922
for batch in batches {
@@ -1093,18 +1087,14 @@ public extension Sequence where Element: ParseInstallation {
10931087
desires a different policy, it should be inserted in `options`.
10941088
*/
10951089
func deleteAll(batchLimit limit: Int? = nil,
1096-
transaction: Bool = false,
1090+
transaction: Bool = ParseSwift.configuration.useTransactions,
10971091
options: API.Options = []) throws -> [(Result<Void, ParseError>)] {
10981092
var options = options
10991093
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
11001094
var returnBatch = [(Result<Void, ParseError>)]()
11011095
let commands = try map { try $0.deleteCommand() }
1102-
let batchLimit: Int!
1103-
if transaction {
1104-
batchLimit = commands.count
1105-
} else {
1106-
batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
1107-
}
1096+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
1097+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
11081098
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
11091099
try batches.forEach {
11101100
let currentBatch = try API.Command<Self.Element, (Result<Void, ParseError>)>
@@ -1146,7 +1136,7 @@ public extension Sequence where Element: ParseInstallation {
11461136
*/
11471137
func deleteAll(
11481138
batchLimit limit: Int? = nil,
1149-
transaction: Bool = false,
1139+
transaction: Bool = ParseSwift.configuration.useTransactions,
11501140
options: API.Options = [],
11511141
callbackQueue: DispatchQueue = .main,
11521142
completion: @escaping (Result<[(Result<Void, ParseError>)], ParseError>) -> Void
@@ -1156,12 +1146,8 @@ public extension Sequence where Element: ParseInstallation {
11561146
do {
11571147
var returnBatch = [(Result<Void, ParseError>)]()
11581148
let commands = try map({ try $0.deleteCommand() })
1159-
let batchLimit: Int!
1160-
if transaction {
1161-
batchLimit = commands.count
1162-
} else {
1163-
batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
1164-
}
1149+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
1150+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
11651151
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
11661152
var completed = 0
11671153
for batch in batches {

Sources/ParseSwift/Objects/ParseObject+async.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public extension Sequence where Element: ParseObject {
109109
the transactions can fail.
110110
*/
111111
func saveAll(batchLimit limit: Int? = nil,
112-
transaction: Bool = false,
112+
transaction: Bool = ParseSwift.configuration.useTransactions,
113113
isIgnoreCustomObjectIdConfig: Bool = false,
114114
options: API.Options = []) async throws -> [(Result<Self.Element, ParseError>)] {
115115
try await withCheckedThrowingContinuation { continuation in
@@ -137,7 +137,7 @@ public extension Sequence where Element: ParseObject {
137137
the transactions can fail.
138138
*/
139139
func deleteAll(batchLimit limit: Int? = nil,
140-
transaction: Bool = false,
140+
transaction: Bool = ParseSwift.configuration.useTransactions,
141141
options: API.Options = []) async throws -> [(Result<Void, ParseError>)] {
142142
try await withCheckedThrowingContinuation { continuation in
143143
self.deleteAll(batchLimit: limit,

Sources/ParseSwift/Objects/ParseObject+combine.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public extension Sequence where Element: ParseObject {
110110
client-side checks are disabled. Developers are responsible for handling such cases.
111111
*/
112112
func saveAllPublisher(batchLimit limit: Int? = nil,
113-
transaction: Bool = false,
113+
transaction: Bool = ParseSwift.configuration.useTransactions,
114114
isIgnoreCustomObjectIdConfig: Bool = false,
115115
options: API.Options = []) -> Future<[(Result<Self.Element, ParseError>)], ParseError> {
116116
Future { promise in
@@ -137,7 +137,7 @@ public extension Sequence where Element: ParseObject {
137137
the transactions can fail.
138138
*/
139139
func deleteAllPublisher(batchLimit limit: Int? = nil,
140-
transaction: Bool = false,
140+
transaction: Bool = ParseSwift.configuration.useTransactions,
141141
options: API.Options = []) -> Future<[(Result<Void, ParseError>)], ParseError> {
142142
Future { promise in
143143
self.deleteAll(batchLimit: limit,

Sources/ParseSwift/Objects/ParseObject.swift

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ public extension ParseObject {
8383
// MARK: Batch Support
8484
public extension Sequence where Element: ParseObject {
8585

86+
internal func canSendTransactions(_ isUsingTransactions: Bool,
87+
objectCount: Int,
88+
batchLimit: Int) throws {
89+
if isUsingTransactions {
90+
if objectCount > batchLimit {
91+
let error = ParseError(code: .unknownError,
92+
message: """
93+
The amount of objects (\(objectCount)) can't exceed the batch size(\(batchLimit)).
94+
Either decrease the amount of objects, increase the batch size, or disable
95+
transactions for this call.
96+
""")
97+
throw error
98+
}
99+
}
100+
}
101+
86102
/**
87103
Saves a collection of objects *synchronously* all at once and throws an error if necessary.
88104
- parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched.
@@ -113,7 +129,7 @@ public extension Sequence where Element: ParseObject {
113129
desires a different policy, it should be inserted in `options`.
114130
*/
115131
func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length
116-
transaction: Bool = false,
132+
transaction: Bool = ParseSwift.configuration.useTransactions,
117133
isIgnoreCustomObjectIdConfig: Bool = false,
118134
options: API.Options = []) throws -> [(Result<Self.Element, ParseError>)] {
119135
var options = options
@@ -126,7 +142,9 @@ public extension Sequence where Element: ParseObject {
126142
for object in objects {
127143
let group = DispatchGroup()
128144
group.enter()
129-
object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in
145+
object.ensureDeepSave(options: options,
146+
// swiftlint:disable:next line_length
147+
isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in
130148
//If an error occurs, everything should be skipped
131149
if parseError != nil {
132150
error = parseError
@@ -163,12 +181,8 @@ public extension Sequence where Element: ParseObject {
163181

164182
var returnBatch = [(Result<Self.Element, ParseError>)]()
165183
let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) }
166-
let batchLimit: Int!
167-
if transaction {
168-
batchLimit = commands.count
169-
} else {
170-
batchLimit = limit ?? ParseConstants.batchLimit
171-
}
184+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
185+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
172186
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
173187
try batches.forEach {
174188
let currentBatch = try API.Command<Self.Element, Self.Element>
@@ -212,7 +226,7 @@ public extension Sequence where Element: ParseObject {
212226
*/
213227
func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity
214228
batchLimit limit: Int? = nil,
215-
transaction: Bool = false,
229+
transaction: Bool = ParseSwift.configuration.useTransactions,
216230
isIgnoreCustomObjectIdConfig: Bool = false,
217231
options: API.Options = [],
218232
callbackQueue: DispatchQueue = .main,
@@ -236,7 +250,9 @@ public extension Sequence where Element: ParseObject {
236250
for object in objects {
237251
let group = DispatchGroup()
238252
group.enter()
239-
object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in
253+
object.ensureDeepSave(options: options,
254+
// swiftlint:disable:next line_length
255+
isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in
240256
//If an error occurs, everything should be skipped
241257
if parseError != nil {
242258
error = parseError
@@ -279,12 +295,8 @@ public extension Sequence where Element: ParseObject {
279295
let commands = try map {
280296
try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig)
281297
}
282-
let batchLimit: Int!
283-
if transaction {
284-
batchLimit = commands.count
285-
} else {
286-
batchLimit = limit ?? ParseConstants.batchLimit
287-
}
298+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
299+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
288300
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
289301
var completed = 0
290302
for batch in batches {
@@ -447,18 +459,14 @@ public extension Sequence where Element: ParseObject {
447459
desires a different policy, it should be inserted in `options`.
448460
*/
449461
func deleteAll(batchLimit limit: Int? = nil,
450-
transaction: Bool = false,
462+
transaction: Bool = ParseSwift.configuration.useTransactions,
451463
options: API.Options = []) throws -> [(Result<Void, ParseError>)] {
452464
var options = options
453465
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
454466
var returnBatch = [(Result<Void, ParseError>)]()
455467
let commands = try map { try $0.deleteCommand() }
456-
let batchLimit: Int!
457-
if transaction {
458-
batchLimit = commands.count
459-
} else {
460-
batchLimit = limit ?? ParseConstants.batchLimit
461-
}
468+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
469+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
462470
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
463471
try batches.forEach {
464472
let currentBatch = try API.Command<Self.Element, (Result<Void, ParseError>)>
@@ -497,7 +505,7 @@ public extension Sequence where Element: ParseObject {
497505
*/
498506
func deleteAll(
499507
batchLimit limit: Int? = nil,
500-
transaction: Bool = false,
508+
transaction: Bool = ParseSwift.configuration.useTransactions,
501509
options: API.Options = [],
502510
callbackQueue: DispatchQueue = .main,
503511
completion: @escaping (Result<[(Result<Void, ParseError>)], ParseError>) -> Void
@@ -507,12 +515,8 @@ public extension Sequence where Element: ParseObject {
507515
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
508516
var returnBatch = [(Result<Void, ParseError>)]()
509517
let commands = try map({ try $0.deleteCommand() })
510-
let batchLimit: Int!
511-
if transaction {
512-
batchLimit = commands.count
513-
} else {
514-
batchLimit = limit ?? ParseConstants.batchLimit
515-
}
518+
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
519+
try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit)
516520
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
517521
var completed = 0
518522
for batch in batches {
@@ -755,6 +759,7 @@ extension ParseObject {
755759

756760
// swiftlint:disable:next function_body_length
757761
internal func ensureDeepSave(options: API.Options = [],
762+
isShouldReturnIfChildObjectsFound: Bool = false,
758763
completion: @escaping ([String: PointerType],
759764
[UUID: ParseFile], ParseError?) -> Void) {
760765
let uuid = UUID()
@@ -779,7 +784,16 @@ extension ParseObject {
779784
filesSavedBeforeThisOne: nil)
780785

781786
var waitingToBeSaved = object.unsavedChildren
782-
787+
if isShouldReturnIfChildObjectsFound && waitingToBeSaved.count > 0 {
788+
let error = ParseError(code: .unknownError,
789+
message: """
790+
When using transactions, all child ParseObjects have to originally
791+
be saved to the Parse Server. Either save all child objects first
792+
or disable transactions for this call.
793+
""")
794+
completion([String: PointerType](), [UUID: ParseFile](), error)
795+
return
796+
}
783797
while waitingToBeSaved.count > 0 {
784798
var savableObjects = [ParseType]()
785799
var savableFiles = [ParseFile]()
@@ -848,7 +862,7 @@ extension ParseObject {
848862
// MARK: Savable Encodable Version
849863
internal extension ParseType {
850864
func saveAll(objects: [ParseType],
851-
transaction: Bool = ParseSwift.configuration.useTransactionsInternally,
865+
transaction: Bool = ParseSwift.configuration.useTransactions,
852866
options: API.Options = []) throws -> [(Result<PointerType, ParseError>)] {
853867
try API.NonParseBodyCommand<AnyCodable, PointerType>
854868
.batch(objects: objects,

0 commit comments

Comments
 (0)