From 33f790561328cd11ba0c723ca0aa886d4d95992a Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 2 Dec 2021 22:13:30 -0500 Subject: [PATCH 1/4] fix: when transactions enabled throw errors when needed --- ParseSwift.playground/Sources/Common.swift | 2 +- .../Objects/ParseInstallation+async.swift | 4 +- .../Objects/ParseInstallation+combine.swift | 4 +- .../Objects/ParseInstallation.swift | 44 ++-- .../Objects/ParseObject+async.swift | 4 +- .../Objects/ParseObject+combine.swift | 4 +- Sources/ParseSwift/Objects/ParseObject.swift | 78 ++++--- .../ParseSwift/Objects/ParseUser+async.swift | 4 +- .../Objects/ParseUser+combine.swift | 4 +- Sources/ParseSwift/Objects/ParseUser.swift | 48 ++-- Sources/ParseSwift/Parse.swift | 48 ++-- .../ParseObjectBatchTests.swift | 211 +++++++++++++++++- 12 files changed, 332 insertions(+), 123 deletions(-) diff --git a/ParseSwift.playground/Sources/Common.swift b/ParseSwift.playground/Sources/Common.swift index 36dd3c522..0c1bf8205 100644 --- a/ParseSwift.playground/Sources/Common.swift +++ b/ParseSwift.playground/Sources/Common.swift @@ -6,7 +6,7 @@ public func initializeParse() { clientKey: "clientKey", masterKey: "masterKey", serverURL: URL(string: "http://localhost:1337/1")!, - useTransactionsInternally: false) + useTransactions: false) } public func initializeParseCustomObjectId() { diff --git a/Sources/ParseSwift/Objects/ParseInstallation+async.swift b/Sources/ParseSwift/Objects/ParseInstallation+async.swift index 7dc99df74..0b5fbcd19 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+async.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+async.swift @@ -110,7 +110,7 @@ public extension Sequence where Element: ParseInstallation { the transactions can fail. */ func saveAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) async throws -> [(Result)] { try await withCheckedThrowingContinuation { continuation in @@ -138,7 +138,7 @@ public extension Sequence where Element: ParseInstallation { the transactions can fail. */ func deleteAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) async throws -> [(Result)] { try await withCheckedThrowingContinuation { continuation in self.deleteAll(batchLimit: limit, diff --git a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift index 968a31b29..5919a4b84 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift @@ -99,7 +99,7 @@ public extension Sequence where Element: ParseInstallation { the transactions can fail. */ func saveAllPublisher(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in @@ -126,7 +126,7 @@ public extension Sequence where Element: ParseInstallation { the transactions can fail. */ func deleteAllPublisher(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in self.deleteAll(batchLimit: limit, diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 34a0dab7a..a44c81bc4 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -747,7 +747,7 @@ public extension Sequence where Element: ParseInstallation { desires a different policy, it should be inserted in `options`. */ func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) throws -> [(Result)] { var options = options @@ -799,12 +799,8 @@ public extension Sequence where Element: ParseInstallation { let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { let currentBatch = try API.Command @@ -850,7 +846,7 @@ public extension Sequence where Element: ParseInstallation { */ func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, @@ -874,7 +870,9 @@ public extension Sequence where Element: ParseInstallation { let group = DispatchGroup() group.enter() installation - .ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + .ensureDeepSave(options: options, + // swiftlint:disable:next line_length + isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in //If an error occurs, everything should be skipped if parseError != nil { error = parseError @@ -917,12 +915,8 @@ public extension Sequence where Element: ParseInstallation { let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 for batch in batches { @@ -1093,18 +1087,14 @@ public extension Sequence where Element: ParseInstallation { desires a different policy, it should be inserted in `options`. */ func deleteAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) throws -> [(Result)] { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) var returnBatch = [(Result)]() let commands = try map { try $0.deleteCommand() } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { let currentBatch = try API.Command)> @@ -1146,7 +1136,7 @@ public extension Sequence where Element: ParseInstallation { */ func deleteAll( batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void @@ -1156,12 +1146,8 @@ public extension Sequence where Element: ParseInstallation { do { var returnBatch = [(Result)]() let commands = try map({ try $0.deleteCommand() }) - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 for batch in batches { diff --git a/Sources/ParseSwift/Objects/ParseObject+async.swift b/Sources/ParseSwift/Objects/ParseObject+async.swift index 3482110d8..0f73d525f 100644 --- a/Sources/ParseSwift/Objects/ParseObject+async.swift +++ b/Sources/ParseSwift/Objects/ParseObject+async.swift @@ -109,7 +109,7 @@ public extension Sequence where Element: ParseObject { the transactions can fail. */ func saveAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) async throws -> [(Result)] { try await withCheckedThrowingContinuation { continuation in @@ -137,7 +137,7 @@ public extension Sequence where Element: ParseObject { the transactions can fail. */ func deleteAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) async throws -> [(Result)] { try await withCheckedThrowingContinuation { continuation in self.deleteAll(batchLimit: limit, diff --git a/Sources/ParseSwift/Objects/ParseObject+combine.swift b/Sources/ParseSwift/Objects/ParseObject+combine.swift index cf8da99f1..3f66b7e7a 100644 --- a/Sources/ParseSwift/Objects/ParseObject+combine.swift +++ b/Sources/ParseSwift/Objects/ParseObject+combine.swift @@ -110,7 +110,7 @@ public extension Sequence where Element: ParseObject { client-side checks are disabled. Developers are responsible for handling such cases. */ func saveAllPublisher(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in @@ -137,7 +137,7 @@ public extension Sequence where Element: ParseObject { the transactions can fail. */ func deleteAllPublisher(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in self.deleteAll(batchLimit: limit, diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 36fd6a341..e647c4a1a 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -83,6 +83,22 @@ public extension ParseObject { // MARK: Batch Support public extension Sequence where Element: ParseObject { + internal func canSendTransactions(_ isUsingTransactions: Bool, + objectCount: Int, + batchLimit: Int) throws { + if isUsingTransactions { + if objectCount > batchLimit { + let error = ParseError(code: .unknownError, + message: """ +The amount of objects (\(objectCount)) can't exceed the batch size(\(batchLimit)). +Either decrease the amount of objects, increase the batch size, or disable +transactions for this call. +""") + throw error + } + } + } + /** Saves a collection of objects *synchronously* all at once and throws an error if necessary. - 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 { desires a different policy, it should be inserted in `options`. */ func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) throws -> [(Result)] { var options = options @@ -126,7 +142,9 @@ public extension Sequence where Element: ParseObject { for object in objects { let group = DispatchGroup() group.enter() - object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + object.ensureDeepSave(options: options, + // swiftlint:disable:next line_length + isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in //If an error occurs, everything should be skipped if parseError != nil { error = parseError @@ -163,12 +181,8 @@ public extension Sequence where Element: ParseObject { var returnBatch = [(Result)]() let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit ?? ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { let currentBatch = try API.Command @@ -212,7 +226,7 @@ public extension Sequence where Element: ParseObject { */ func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, @@ -236,7 +250,9 @@ public extension Sequence where Element: ParseObject { for object in objects { let group = DispatchGroup() group.enter() - object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + object.ensureDeepSave(options: options, + // swiftlint:disable:next line_length + isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in //If an error occurs, everything should be skipped if parseError != nil { error = parseError @@ -279,12 +295,8 @@ public extension Sequence where Element: ParseObject { let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit ?? ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 for batch in batches { @@ -447,18 +459,14 @@ public extension Sequence where Element: ParseObject { desires a different policy, it should be inserted in `options`. */ func deleteAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) throws -> [(Result)] { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) var returnBatch = [(Result)]() let commands = try map { try $0.deleteCommand() } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit ?? ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { let currentBatch = try API.Command)> @@ -497,7 +505,7 @@ public extension Sequence where Element: ParseObject { */ func deleteAll( batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void @@ -507,12 +515,8 @@ public extension Sequence where Element: ParseObject { options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) var returnBatch = [(Result)]() let commands = try map({ try $0.deleteCommand() }) - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit ?? ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 for batch in batches { @@ -755,6 +759,7 @@ extension ParseObject { // swiftlint:disable:next function_body_length internal func ensureDeepSave(options: API.Options = [], + isShouldReturnIfChildObjectsFound: Bool = false, completion: @escaping ([String: PointerType], [UUID: ParseFile], ParseError?) -> Void) { let uuid = UUID() @@ -779,7 +784,16 @@ extension ParseObject { filesSavedBeforeThisOne: nil) var waitingToBeSaved = object.unsavedChildren - + if isShouldReturnIfChildObjectsFound && waitingToBeSaved.count > 0 { + let error = ParseError(code: .unknownError, + message: """ + When using transactions, all child ParseObjects have to originally + be saved to the Parse Server. Either save all child objects first + or disable transactions for this call. + """) + completion([String: PointerType](), [UUID: ParseFile](), error) + return + } while waitingToBeSaved.count > 0 { var savableObjects = [ParseType]() var savableFiles = [ParseFile]() @@ -848,7 +862,7 @@ extension ParseObject { // MARK: Savable Encodable Version internal extension ParseType { func saveAll(objects: [ParseType], - transaction: Bool = ParseSwift.configuration.useTransactionsInternally, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) throws -> [(Result)] { try API.NonParseBodyCommand .batch(objects: objects, diff --git a/Sources/ParseSwift/Objects/ParseUser+async.swift b/Sources/ParseSwift/Objects/ParseUser+async.swift index 7a1bd1d58..411d1a536 100644 --- a/Sources/ParseSwift/Objects/ParseUser+async.swift +++ b/Sources/ParseSwift/Objects/ParseUser+async.swift @@ -246,7 +246,7 @@ public extension Sequence where Element: ParseUser { */ @MainActor func saveAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) async throws -> [(Result)] { try await withCheckedThrowingContinuation { continuation in @@ -275,7 +275,7 @@ public extension Sequence where Element: ParseUser { */ @MainActor func deleteAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) async throws -> [(Result)] { try await withCheckedThrowingContinuation { continuation in self.deleteAll(batchLimit: limit, diff --git a/Sources/ParseSwift/Objects/ParseUser+combine.swift b/Sources/ParseSwift/Objects/ParseUser+combine.swift index 2d5e62b79..09768c61c 100644 --- a/Sources/ParseSwift/Objects/ParseUser+combine.swift +++ b/Sources/ParseSwift/Objects/ParseUser+combine.swift @@ -241,7 +241,7 @@ public extension Sequence where Element: ParseUser { client-side checks are disabled. Developers are responsible for handling such cases. */ func saveAllPublisher(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in @@ -268,7 +268,7 @@ public extension Sequence where Element: ParseUser { the transactions can fail. */ func deleteAllPublisher(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in self.deleteAll(batchLimit: limit, diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index abc7f8404..538a1a4e7 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -1141,7 +1141,7 @@ public extension Sequence where Element: ParseUser { desires a different policy, it should be inserted in `options`. */ func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = []) throws -> [(Result)] { var childObjects = [String: PointerType]() @@ -1153,7 +1153,9 @@ public extension Sequence where Element: ParseUser { for user in users { let group = DispatchGroup() group.enter() - user.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + user.ensureDeepSave(options: options, + // swiftlint:disable:next line_length + isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in //If an error occurs, everything should be skipped if parseError != nil { error = parseError @@ -1192,12 +1194,8 @@ public extension Sequence where Element: ParseUser { let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { let currentBatch = try API.Command @@ -1243,7 +1241,7 @@ public extension Sequence where Element: ParseUser { */ func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, isIgnoreCustomObjectIdConfig: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, @@ -1266,7 +1264,9 @@ public extension Sequence where Element: ParseUser { for user in users { let group = DispatchGroup() group.enter() - user.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + user.ensureDeepSave(options: options, + // swiftlint:disable:next line_length + isShouldReturnIfChildObjectsFound: true) { (savedChildObjects, savedChildFiles, parseError) -> Void in //If an error occurs, everything should be skipped if parseError != nil { error = parseError @@ -1309,12 +1309,8 @@ public extension Sequence where Element: ParseUser { let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 for batch in batches { @@ -1483,18 +1479,14 @@ public extension Sequence where Element: ParseUser { desires a different policy, it should be inserted in `options`. */ func deleteAll(batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = []) throws -> [(Result)] { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) var returnBatch = [(Result)]() let commands = try map { try $0.deleteCommand() } - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { let currentBatch = try API.Command @@ -1535,7 +1527,7 @@ public extension Sequence where Element: ParseUser { */ func deleteAll( batchLimit limit: Int? = nil, - transaction: Bool = false, + transaction: Bool = ParseSwift.configuration.useTransactions, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void @@ -1545,12 +1537,8 @@ public extension Sequence where Element: ParseUser { do { var returnBatch = [(Result)]() let commands = try map({ try $0.deleteCommand() }) - let batchLimit: Int! - if transaction { - batchLimit = commands.count - } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - } + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + try canSendTransactions(transaction, objectCount: commands.count, batchLimit: batchLimit) let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 for batch in batches { diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 0323134d0..03b63c30d 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -9,56 +9,60 @@ import FoundationNetworking public struct ParseConfiguration { /// The application id of your Parse application. - var applicationId: String + public internal(set) var applicationId: String /// The master key of your Parse application. - var masterKey: String? // swiftlint:disable:this inclusive_language + public internal(set) var masterKey: String? // swiftlint:disable:this inclusive_language /// The client key of your Parse application. - var clientKey: String? + public internal(set) var clientKey: String? /// The server URL to connect to Parse Server. - var serverURL: URL + public internal(set) var serverURL: URL /// The live query server URL to connect to Parse Server. - var liveQuerysServerURL: URL? + public internal(set) var liveQuerysServerURL: URL? /// Allows objectIds to be created on the client. - var allowCustomObjectId = false + public internal(set) var allowCustomObjectId = false /// Use transactions inside the Client SDK. - /// - warning: This is experimental and known not to work with mongoDB. - var useTransactionsInternally = false + /// - warning: This is experimental. + public internal(set) var useTransactionsInternally = false + + /// Use transactions when saving/updating multiple objects. + /// - warning: This is experimental. + public internal(set) var useTransactions = false /// The default caching policy for all http requests that determines when to /// return a response from the cache. Defaults to `useProtocolCachePolicy`. /// See Apple's [documentation](https://developer.apple.com/documentation/foundation/url_loading_system/accessing_cached_data) /// for more info. - var requestCachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy + public internal(set) var requestCachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy /// A dictionary of additional headers to send with requests. See Apple's /// [documentation](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders) /// for more info. - var httpAdditionalHeaders: [String: String]? + public internal(set) var httpAdditionalHeaders: [String: String]? /// The memory capacity of the cache, in bytes. Defaults to 512KB. - var cacheMemoryCapacity = 512_000 + public internal(set) var cacheMemoryCapacity = 512_000 /// The disk capacity of the cache, in bytes. Defaults to 10MB. - var cacheDiskCapacity = 10_000_000 + public internal(set) var cacheDiskCapacity = 10_000_000 /// If your app previously used the iOS Objective-C SDK, setting this value /// to `true` will attempt to migrate relevant data stored in the Keychain to /// ParseSwift. Defaults to `false`. - var migrateFromObjcSDK: Bool = false + public internal(set) var migrateFromObjcSDK: Bool = false /// Deletes the Parse Keychain when the app is running for the first time. /// Defaults to `false`. - var deleteKeychainIfNeeded: Bool = false + public internal(set) var deleteKeychainIfNeeded: Bool = false /// Maximum number of times to try to connect to Parse Server. /// Defaults to 5. - var maxConnectionAttempts: Int = 5 + public internal(set) var maxConnectionAttempts: Int = 5 internal var authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, @@ -76,6 +80,7 @@ public struct ParseConfiguration { - parameter allowCustomObjectId: Allows objectIds to be created on the client. side for each object. Must be enabled on the server to work. - parameter useTransactionsInternally: Use transactions inside the Client SDK. + - parameter useTransactions: Use transactions when saving/updating multiple objects. - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this this is the only store available since there is no Keychain. Linux users should replace this store with an @@ -99,7 +104,7 @@ public struct ParseConfiguration { It should have the following argument signature: `(challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void`. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. - - warning: `useTransactionsInternally` is experimental and known not to work with mongoDB. + - warning: `useTransactions` and `useTransactionsInternally` is experimental. */ public init(applicationId: String, clientKey: String? = nil, @@ -108,6 +113,7 @@ public struct ParseConfiguration { liveQueryServerURL: URL? = nil, allowCustomObjectId: Bool = false, useTransactionsInternally: Bool = false, + useTransactions: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheMemoryCapacity: Int = 512_000, @@ -126,6 +132,7 @@ public struct ParseConfiguration { self.liveQuerysServerURL = liveQueryServerURL self.allowCustomObjectId = allowCustomObjectId self.useTransactionsInternally = useTransactionsInternally + self.useTransactions = useTransactions self.mountPath = "/" + serverURL.pathComponents .filter { $0 != "/" } .joined(separator: "/") @@ -146,7 +153,7 @@ public struct ParseConfiguration { */ public struct ParseSwift { - static var configuration: ParseConfiguration! + public internal(set) static var configuration: ParseConfiguration! static var sessionDelegate: ParseURLSessionDelegate! /** @@ -237,6 +244,7 @@ public struct ParseSwift { - parameter allowCustomObjectId: Allows objectIds to be created on the client. side for each object. Must be enabled on the server to work. - parameter useTransactionsInternally: Use transactions inside the Client SDK. + - parameter useTransactions: Use transactions when saving/updating multiple objects. - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this this is the only store available since there is no Keychain. Linux users should replace this store with an @@ -258,7 +266,7 @@ public struct ParseSwift { It should have the following argument signature: `(challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void`. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. - - warning: `useTransactionsInternally` is experimental and known not to work with mongoDB. + - warning: `useTransactions` and `useTransactionsInternally` is experimental. */ static public func initialize( applicationId: String, @@ -268,6 +276,7 @@ public struct ParseSwift { liveQueryServerURL: URL? = nil, allowCustomObjectId: Bool = false, useTransactionsInternally: Bool = false, + useTransactions: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheMemoryCapacity: Int = 512_000, @@ -287,6 +296,7 @@ public struct ParseSwift { liveQueryServerURL: liveQueryServerURL, allowCustomObjectId: allowCustomObjectId, useTransactionsInternally: useTransactionsInternally, + useTransactions: useTransactions, keyValueStore: keyValueStore, requestCachePolicy: requestCachePolicy, cacheMemoryCapacity: cacheMemoryCapacity, @@ -305,6 +315,7 @@ public struct ParseSwift { liveQueryServerURL: URL? = nil, allowCustomObjectId: Bool = false, useTransactionsInternally: Bool = false, + useTransactions: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheMemoryCapacity: Int = 512_000, @@ -324,6 +335,7 @@ public struct ParseSwift { liveQueryServerURL: liveQueryServerURL, allowCustomObjectId: allowCustomObjectId, useTransactionsInternally: useTransactionsInternally, + useTransactions: useTransactions, keyValueStore: keyValueStore, requestCachePolicy: requestCachePolicy, cacheMemoryCapacity: cacheMemoryCapacity, diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index ca4ba2d36..8f753fccc 100644 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -21,6 +21,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le // Custom properties var score: Int = 0 + var other: Game2? //custom initializers init() { @@ -35,6 +36,18 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } } + struct Game2: ParseObject { + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + + //: Your own properties + var name = "Hello" + var profilePicture: ParseFile? + } + override func setUpWithError() throws { try super.setUpWithError() guard let url = URL(string: "http://localhost:1337/1") else { @@ -225,6 +238,126 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } } + func testSaveAllTransactions() { // swiftlint:disable:this function_body_length cyclomatic_complexity + let score = GameScore(score: 10) + let score2 = GameScore(score: 20) + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + var scoreOnServer2 = score2 + scoreOnServer2.objectId = "yolo" + scoreOnServer2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + scoreOnServer2.updatedAt = scoreOnServer2.createdAt + scoreOnServer2.ACL = nil + + let response = [BatchResponseItem(success: scoreOnServer, error: nil), + BatchResponseItem(success: scoreOnServer2, error: nil)] + let encoded: Data! + do { + encoded = try scoreOnServer.getJSONEncoder().encode(response) + //Get dates in correct format from ParseDecoding strategy + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) + scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) + + } catch { + XCTFail("Should have encoded/decoded. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + + let saved = try [score, score2].saveAll(transaction: true) + + XCTAssertEqual(saved.count, 2) + switch saved[0] { + + case .success(let first): + XCTAssert(first.hasSameObjectId(as: scoreOnServer)) + guard let savedCreatedAt = first.createdAt, + let savedUpdatedAt = first.updatedAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = scoreOnServer.createdAt, + let originalUpdatedAt = scoreOnServer.updatedAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(savedCreatedAt, originalCreatedAt) + XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) + XCTAssertNil(first.ACL) + case .failure(let error): + XCTFail(error.localizedDescription) + } + + switch saved[1] { + + case .success(let second): + XCTAssert(second.hasSameObjectId(as: scoreOnServer2)) + guard let savedCreatedAt = second.createdAt, + let savedUpdatedAt = second.updatedAt else { + XCTFail("Should unwrap dates") + return + } + guard let originalCreatedAt = scoreOnServer2.createdAt, + let originalUpdatedAt = scoreOnServer2.updatedAt else { + XCTFail("Should unwrap dates") + return + } + XCTAssertEqual(savedCreatedAt, originalCreatedAt) + XCTAssertEqual(savedUpdatedAt, originalUpdatedAt) + XCTAssertNil(second.ACL) + case .failure(let error): + XCTFail(error.localizedDescription) + } + + } catch { + XCTFail(error.localizedDescription) + } + } + + func testSaveAllTransactionsErrorTooMany() { + let score = GameScore(score: 10) + let score2 = GameScore(score: 20) + do { + _ = try [score, score2].saveAll(batchLimit: 1, transaction: true) + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Error should have casted to ParseError") + return + } + XCTAssertEqual(parseError.code, .unknownError) + XCTAssertTrue(parseError.message.contains("exceed")) + } + } + + func testSaveAllTransactionsErrorChild() { + let score = GameScore(score: 10) + var score2 = GameScore(score: 20) + score2.other = Game2() + do { + _ = try [score, score2].saveAll(transaction: true) + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Error should have casted to ParseError") + return + } + XCTAssertEqual(parseError.code, .unknownError) + XCTAssertTrue(parseError.message.contains("originally")) + } + } + func testSaveAllErrorIncorrectServerResponse() { let score = GameScore(score: 10) let score2 = GameScore(score: 20) @@ -601,6 +734,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } func saveAllAsync(scores: [GameScore], // swiftlint:disable:this function_body_length cyclomatic_complexity + transaction: Bool = false, scoresOnServer: [GameScore], callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Save object1") @@ -611,7 +745,8 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le return } - scores.saveAll(callbackQueue: callbackQueue) { result in + scores.saveAll(transaction: transaction, + callbackQueue: callbackQueue) { result in switch result { @@ -828,6 +963,80 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le callbackQueue: .main) } + func testSaveAllAsyncTransactions() { // swiftlint:disable:this function_body_length cyclomatic_complexity + let score = GameScore(score: 10) + let score2 = GameScore(score: 20) + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + var scoreOnServer2 = score2 + scoreOnServer2.objectId = "yolo" + scoreOnServer2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + scoreOnServer2.updatedAt = scoreOnServer2.createdAt + scoreOnServer2.ACL = nil + + let response = [BatchResponseItem(success: scoreOnServer, error: nil), + BatchResponseItem(success: scoreOnServer2, error: nil)] + let encoded: Data! + do { + encoded = try scoreOnServer.getJSONEncoder().encode(response) + //Get dates in correct format from ParseDecoding strategy + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) + scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) + + } catch { + XCTFail("Should have encoded/decoded. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + self.saveAllAsync(scores: [score, score2], + transaction: true, + scoresOnServer: [scoreOnServer, scoreOnServer2], + callbackQueue: .main) + } + + func testSaveAllAsyncTransactionsErrorTooMany() { + let score = GameScore(score: 10) + let score2 = GameScore(score: 20) + let expectation1 = XCTestExpectation(description: "Save object1") + [score, score2].saveAll(batchLimit: 1, transaction: true) { result in + if case .failure(let error) = result { + XCTAssertEqual(error.code, .unknownError) + XCTAssertTrue(error.message.contains("exceed")) + } else { + XCTFail("Should have received error") + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testSaveAllAsyncTransactionsErrorChild() { + let score = GameScore(score: 10) + var score2 = GameScore(score: 20) + score2.other = Game2() + let expectation1 = XCTestExpectation(description: "Save object1") + [score, score2].saveAll(transaction: true) { result in + if case .failure(let error) = result { + XCTAssertEqual(error.code, .unknownError) + XCTAssertTrue(error.message.contains("originally")) + } else { + XCTFail("Should have received error") + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + /* Note, the current batchCommand for updateAll returns the original object that was updated as opposed to the latestUpdated. The objective c one just returns true/false */ // swiftlint:disable:next function_body_length cyclomatic_complexity From acdab7dbc0a9ebe9cbbc31d48f61b9dd7689ab35 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 2 Dec 2021 22:33:03 -0500 Subject: [PATCH 2/4] add transaction deleteAll tests --- .../ParseObjectBatchTests.swift | 132 ++++++++++++++++-- 1 file changed, 124 insertions(+), 8 deletions(-) diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index 8f753fccc..df53bd6c5 100644 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -238,7 +238,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } } - func testSaveAllTransactions() { // swiftlint:disable:this function_body_length cyclomatic_complexity + func testSaveAllTransaction() { // swiftlint:disable:this function_body_length cyclomatic_complexity let score = GameScore(score: 10) let score2 = GameScore(score: 20) @@ -325,7 +325,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } } - func testSaveAllTransactionsErrorTooMany() { + func testSaveAllTransactionErrorTooMany() { let score = GameScore(score: 10) let score2 = GameScore(score: 20) do { @@ -341,7 +341,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } } - func testSaveAllTransactionsErrorChild() { + func testSaveAllTransactionErrorChild() { let score = GameScore(score: 10) var score2 = GameScore(score: 20) score2.other = Game2() @@ -963,7 +963,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le callbackQueue: .main) } - func testSaveAllAsyncTransactions() { // swiftlint:disable:this function_body_length cyclomatic_complexity + func testSaveAllAsyncTransaction() { // swiftlint:disable:this function_body_length cyclomatic_complexity let score = GameScore(score: 10) let score2 = GameScore(score: 20) @@ -1004,7 +1004,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le callbackQueue: .main) } - func testSaveAllAsyncTransactionsErrorTooMany() { + func testSaveAllAsyncTransactionErrorTooMany() { let score = GameScore(score: 10) let score2 = GameScore(score: 20) let expectation1 = XCTestExpectation(description: "Save object1") @@ -1020,7 +1020,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le wait(for: [expectation1], timeout: 20.0) } - func testSaveAllAsyncTransactionsErrorChild() { + func testSaveAllAsyncTransactionErrorChild() { let score = GameScore(score: 10) var score2 = GameScore(score: 20) score2.other = Game2() @@ -1579,6 +1579,89 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } } + func testDeleteAllTransaction() { + let response = [BatchResponseItem(success: NoBody(), error: nil), + BatchResponseItem(success: NoBody(), error: nil)] + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(response) + } catch { + XCTFail("Should have encoded/decoded. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + let deleted = try [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")].deleteAll(transaction: true) + + XCTAssertEqual(deleted.count, 2) + guard let firstObject = deleted.first else { + XCTFail("Should unwrap") + return + } + + if case let .failure(error) = firstObject { + XCTFail(error.localizedDescription) + } + + guard let lastObject = deleted.last else { + XCTFail("Should unwrap") + return + } + + if case let .failure(error) = lastObject { + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + + do { + let deleted = try [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")] + .deleteAll(transaction: true) + + XCTAssertEqual(deleted.count, 2) + guard let firstObject = deleted.first else { + XCTFail("Should unwrap") + return + } + + if case let .failure(error) = firstObject { + XCTFail(error.localizedDescription) + } + + guard let lastObject = deleted.last else { + XCTFail("Should unwrap") + return + } + + if case let .failure(error) = lastObject { + XCTFail(error.localizedDescription) + } + } catch { + XCTFail(error.localizedDescription) + } + } + + func testDeleteAllTransactionErrorTooMany() { + do { + _ = try [GameScore(objectId: "yarr"), + GameScore(objectId: "yolo")].deleteAll(batchLimit: 1, + transaction: true) + XCTFail("Should have thrown error") + } catch { + guard let parseError = error as? ParseError else { + XCTFail("Error should have casted to ParseError") + return + } + XCTAssertEqual(parseError.code, .unknownError) + XCTAssertTrue(parseError.message.contains("exceed")) + } + } + #if !os(Linux) && !os(Android) && !os(Windows) func testDeleteAllError() { let parseError = ParseError(code: .objectNotFound, message: "Object not found") @@ -1627,13 +1710,13 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } #endif - func deleteAllAsync(callbackQueue: DispatchQueue) { + func deleteAllAsync(transaction: Bool = false, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Delete object1") let expectation2 = XCTestExpectation(description: "Delete object2") [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")] - .deleteAll(callbackQueue: callbackQueue) { result in + .deleteAll(transaction: transaction, callbackQueue: callbackQueue) { result in switch result { @@ -1719,6 +1802,39 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le self.deleteAllAsync(callbackQueue: .main) } + func testDeleteAllAsyncMainQueueTransaction() { + let response = [BatchResponseItem(success: NoBody(), error: nil), + BatchResponseItem(success: NoBody(), error: nil)] + + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + } catch { + XCTFail("Should have encoded/decoded. Error \(error)") + return + } + + self.deleteAllAsync(transaction: true, callbackQueue: .main) + } + + func testDeleteAllAsyncTransactionErrorTooMany() { + let expectation1 = XCTestExpectation(description: "Save object1") + [GameScore(objectId: "yarr"), + GameScore(objectId: "yolo")].deleteAll(batchLimit: 1, + transaction: true) { result in + if case .failure(let error) = result { + XCTAssertEqual(error.code, .unknownError) + XCTAssertTrue(error.message.contains("exceed")) + } else { + XCTFail("Should have received error") + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + func deleteAllAsyncError(parseError: ParseError, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Delete object1") From fe145d1d3f296122f6ef0055466bf4acef51d777 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 2 Dec 2021 23:15:40 -0500 Subject: [PATCH 3/4] nits --- .codecov.yml | 2 +- Sources/ParseSwift/Parse.swift | 17 ++--------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 6899e9b41..7009a7cb2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,7 +6,7 @@ coverage: status: patch: default: - target: 74 + target: auto changes: false project: default: diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 03b63c30d..5d211f23c 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -26,10 +26,6 @@ public struct ParseConfiguration { /// Allows objectIds to be created on the client. public internal(set) var allowCustomObjectId = false - /// Use transactions inside the Client SDK. - /// - warning: This is experimental. - public internal(set) var useTransactionsInternally = false - /// Use transactions when saving/updating multiple objects. /// - warning: This is experimental. public internal(set) var useTransactions = false @@ -79,7 +75,6 @@ public struct ParseConfiguration { - parameter liveQueryServerURL: The live query server URL to connect to Parse Server. - parameter allowCustomObjectId: Allows objectIds to be created on the client. side for each object. Must be enabled on the server to work. - - parameter useTransactionsInternally: Use transactions inside the Client SDK. - parameter useTransactions: Use transactions when saving/updating multiple objects. - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this @@ -104,7 +99,7 @@ public struct ParseConfiguration { It should have the following argument signature: `(challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void`. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. - - warning: `useTransactions` and `useTransactionsInternally` is experimental. + - warning: `useTransactions` is experimental. */ public init(applicationId: String, clientKey: String? = nil, @@ -112,7 +107,6 @@ public struct ParseConfiguration { serverURL: URL, liveQueryServerURL: URL? = nil, allowCustomObjectId: Bool = false, - useTransactionsInternally: Bool = false, useTransactions: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, @@ -131,7 +125,6 @@ public struct ParseConfiguration { self.serverURL = serverURL self.liveQuerysServerURL = liveQueryServerURL self.allowCustomObjectId = allowCustomObjectId - self.useTransactionsInternally = useTransactionsInternally self.useTransactions = useTransactions self.mountPath = "/" + serverURL.pathComponents .filter { $0 != "/" } @@ -243,7 +236,6 @@ public struct ParseSwift { - parameter liveQueryServerURL: The live query server URL to connect to Parse Server. - parameter allowCustomObjectId: Allows objectIds to be created on the client. side for each object. Must be enabled on the server to work. - - parameter useTransactionsInternally: Use transactions inside the Client SDK. - parameter useTransactions: Use transactions when saving/updating multiple objects. - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this @@ -266,7 +258,7 @@ public struct ParseSwift { It should have the following argument signature: `(challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void`. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. - - warning: `useTransactions` and `useTransactionsInternally` is experimental. + - warning: `useTransactions` is experimental. */ static public func initialize( applicationId: String, @@ -275,7 +267,6 @@ public struct ParseSwift { serverURL: URL, liveQueryServerURL: URL? = nil, allowCustomObjectId: Bool = false, - useTransactionsInternally: Bool = false, useTransactions: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, @@ -295,7 +286,6 @@ public struct ParseSwift { serverURL: serverURL, liveQueryServerURL: liveQueryServerURL, allowCustomObjectId: allowCustomObjectId, - useTransactionsInternally: useTransactionsInternally, useTransactions: useTransactions, keyValueStore: keyValueStore, requestCachePolicy: requestCachePolicy, @@ -314,7 +304,6 @@ public struct ParseSwift { serverURL: URL, liveQueryServerURL: URL? = nil, allowCustomObjectId: Bool = false, - useTransactionsInternally: Bool = false, useTransactions: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, @@ -333,8 +322,6 @@ public struct ParseSwift { masterKey: masterKey, serverURL: serverURL, liveQueryServerURL: liveQueryServerURL, - allowCustomObjectId: allowCustomObjectId, - useTransactionsInternally: useTransactionsInternally, useTransactions: useTransactions, keyValueStore: keyValueStore, requestCachePolicy: requestCachePolicy, From 7c26b7376cf6c8ac6b74f1b6d8b07fea7224729c Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 2 Dec 2021 23:24:48 -0500 Subject: [PATCH 4/4] revert --- Sources/ParseSwift/Parse.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 5d211f23c..c3e1b72c8 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -322,6 +322,7 @@ public struct ParseSwift { masterKey: masterKey, serverURL: serverURL, liveQueryServerURL: liveQueryServerURL, + allowCustomObjectId: allowCustomObjectId, useTransactions: useTransactions, keyValueStore: keyValueStore, requestCachePolicy: requestCachePolicy,