From e825ff2880ad72f2e09a02938c2cb2f87aa946df Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 10 Apr 2021 19:18:40 -0400 Subject: [PATCH 1/6] Add findAll to query --- .../Objects/ParseInstallation.swift | 8 ++- Sources/ParseSwift/Objects/ParseObject.swift | 25 ++++--- Sources/ParseSwift/Objects/ParseUser.swift | 8 ++- Sources/ParseSwift/Types/Query.swift | 69 +++++++++++++++++++ 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 60f185053..19b59fd1e 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -742,8 +742,12 @@ public extension Sequence where Element: ParseInstallation { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - let queue = DispatchQueue(label: "com.parse.saveAll", qos: .default, - attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + let uuid = UUID() + let queue = DispatchQueue(label: "com.parse.saveAll.\(uuid)", + qos: .default, + attributes: .concurrent, + autoreleaseFrequency: .inherit, + target: nil) queue.sync { var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index a2c476c4a..69262a843 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -126,7 +126,7 @@ public extension Sequence where Element: ParseObject { if transaction { batchLimit = commands.count } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + batchLimit = limit ?? ParseConstants.batchLimit } let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { @@ -163,8 +163,12 @@ public extension Sequence where Element: ParseObject { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - let queue = DispatchQueue(label: "com.parse.saveAll", qos: .default, - attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + let uuid = UUID() + let queue = DispatchQueue(label: "com.parse.saveAll.\(uuid)", + qos: .default, + attributes: .concurrent, + autoreleaseFrequency: .inherit, + target: nil) queue.sync { var childObjects = [String: PointerType]() @@ -220,7 +224,7 @@ public extension Sequence where Element: ParseObject { if transaction { batchLimit = commands.count } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + batchLimit = limit ?? ParseConstants.batchLimit } let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 @@ -390,7 +394,7 @@ public extension Sequence where Element: ParseObject { if transaction { batchLimit = commands.count } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + batchLimit = limit ?? ParseConstants.batchLimit } let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) try batches.forEach { @@ -439,7 +443,7 @@ public extension Sequence where Element: ParseObject { if transaction { batchLimit = commands.count } else { - batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + batchLimit = limit ?? ParseConstants.batchLimit } let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) var completed = 0 @@ -633,9 +637,12 @@ extension ParseObject { internal func ensureDeepSave(options: API.Options = [], completion: @escaping ([String: PointerType], [UUID: ParseFile], ParseError?) -> Void) { - - let queue = DispatchQueue(label: "com.parse.deepSave", qos: .default, - attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + let uuid = UUID() + let queue = DispatchQueue(label: "com.parse.deepSave.\(uuid)", + qos: .default, + attributes: .concurrent, + autoreleaseFrequency: .inherit, + target: nil) queue.sync { var objectsFinishedSaving = [String: PointerType]() diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 79494a8db..a895bfc49 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -1065,8 +1065,12 @@ public extension Sequence where Element: ParseUser { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - let queue = DispatchQueue(label: "com.parse.saveAll", qos: .default, - attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + let uuid = UUID() + let queue = DispatchQueue(label: "com.parse.saveAll.\(uuid)", + qos: .default, + attributes: .concurrent, + autoreleaseFrequency: .inherit, + target: nil) queue.sync { var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index b5f953915..3408f6d65 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -907,6 +907,75 @@ extension Query: Queryable { } } + /** + Finds objects *asynchronously* and calls the given block with the results. + + - parameter hint: String or Object of index that should be used when executing query. + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default value of .main. + - parameter completion: The block to execute. + It should have the following argument signature: `(Result<[Decodable], ParseError>)`. + */ + public func findAll(hint: String? = nil, + batchLimit limit: Int? = nil, + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result<[ResultType], ParseError>) -> Void) { + if order != nil || skip > 0 || limit != 100 { + let error = ParseError(code: .unknownError, + message: "Cannot iterate on a query with sort, skip, or limit.") + completion(.failure(error)) + return + } + let uuid = UUID() + let queue = DispatchQueue(label: "com.parse.findAll.\(uuid)", + qos: .default, + attributes: .concurrent, + autoreleaseFrequency: .inherit, + target: nil) + queue.sync { + + var query = self + .order([.ascending("objectId")]) + query.limit = limit ?? ParseConstants.batchLimit + var results = [ResultType]() + var finished = false + + while !finished { + do { + let currentResults: [ResultType] = try query.findCommand(explain: false, + hint: hint).execute(options: options) + if currentResults.count >= query.limit { + guard let lastObjectId = results[results.count - 1].objectId else { + throw ParseError(code: .unknownError, message: "Last object should have an id.") + } + query.where.add("objectId" > lastObjectId) + results.append(contentsOf: currentResults) + } else { + finished = true + } + } catch { + callbackQueue.async { + guard let parseError = error as? ParseError else { + completion(.failure(ParseError(code: .unknownError, + message: error.localizedDescription))) + return + } + completion(.failure(parseError)) + } + return + } + } + + callbackQueue.async { + completion(.success(results)) + } + } + } + /** Gets an object *synchronously* based on the constructed query and sets an error if any occurred. From 93f03377784f3123378695629bbf8d7cae075d55 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 10 Apr 2021 21:58:03 -0400 Subject: [PATCH 2/6] add testcases --- CHANGELOG.md | 6 +- .../Contents.swift | 11 ++ ParseSwift.podspec | 2 +- ParseSwift.xcodeproj/project.pbxproj | 16 +-- Scripts/jazzy.sh | 2 +- .../Objects/ParseInstallation+combine.swift | 4 +- .../Objects/ParseInstallation.swift | 8 +- .../Objects/ParseObject+combine.swift | 4 +- Sources/ParseSwift/Objects/ParseObject.swift | 8 +- .../Objects/ParseUser+combine.swift | 4 +- Sources/ParseSwift/Objects/ParseUser.swift | 8 +- Sources/ParseSwift/ParseConstants.swift | 2 +- Sources/ParseSwift/Types/Query+combine.swift | 21 +++ Sources/ParseSwift/Types/Query.swift | 12 +- .../ParseQueryCombineTests.swift | 43 +++++- Tests/ParseSwiftTests/ParseQueryTests.swift | 136 ++++++++++++++++++ 16 files changed, 251 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77229badb..a256448cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Parse-Swift Changelog ### main -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.3.0...main) +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.3.1...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +### 1.3.1 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.3.0...1.3.1) + __New features__ +- Add findAll query to find all objects ([#118](https://github.com/parse-community/Parse-Swift/pull/118)), thanks to [Corey Baker](https://github.com/cbaker6). - Migrate installationId from obj-c SDK ([#117](https://github.com/parse-community/Parse-Swift/pull/117)), thanks to [Corey Baker](https://github.com/cbaker6). __Improvements__ diff --git a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift index c73a4c290..96629049c 100644 --- a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift @@ -173,6 +173,17 @@ query7.find { results in } } +//: Find all GameScores. +let query8 = GameScore.query() +query8.findAll { (result: Result<[GameScore], ParseError>) in + switch result { + case .success(let scores): + print(scores) + case .failure(let error): + print(error.localizedDescription) + } +} + //: Explain the previous query. let explain: AnyDecodable = try query2.first(explain: true) print(explain) diff --git a/ParseSwift.podspec b/ParseSwift.podspec index 3a7a453b3..3915a85f1 100644 --- a/ParseSwift.podspec +++ b/ParseSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ParseSwift" - s.version = "1.3.0" + s.version = "1.3.1" s.summary = "Parse Pure Swift SDK" s.homepage = "https://github.com/parse-community/Parse-Swift" s.authors = { diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 281038be4..2e7314162 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -2329,7 +2329,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SKIP_INSTALL = YES; @@ -2353,7 +2353,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SKIP_INSTALL = YES; @@ -2419,7 +2419,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SDKROOT = macosx; @@ -2445,7 +2445,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SDKROOT = macosx; @@ -2592,7 +2592,7 @@ INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS"; @@ -2621,7 +2621,7 @@ INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS"; PRODUCT_NAME = ParseSwift; @@ -2648,7 +2648,7 @@ INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS"; @@ -2676,7 +2676,7 @@ INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS"; PRODUCT_NAME = ParseSwift; diff --git a/Scripts/jazzy.sh b/Scripts/jazzy.sh index 030a5ddbe..307c00176 100755 --- a/Scripts/jazzy.sh +++ b/Scripts/jazzy.sh @@ -5,7 +5,7 @@ bundle exec jazzy \ --author_url http://parseplatform.org \ --github_url https://github.com/parse-community/Parse-Swift \ --root-url http://parseplatform.org/Parse-Swift/api/ \ - --module-version 1.3.0 \ + --module-version 1.3.1 \ --theme fullwidth \ --skip-undocumented \ --output ./docs/api \ diff --git a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift index e0f65c307..f9e7c500d 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift @@ -88,7 +88,7 @@ public extension Sequence where Element: ParseInstallation { /** Saves a collection of installations *asynchronously* and publishes when complete. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -113,7 +113,7 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations *asynchronously* and publishes when complete. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 19b59fd1e..7e5864d33 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -637,7 +637,7 @@ public extension Sequence where Element: ParseInstallation { /** Saves a collection of installations *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 + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -721,7 +721,7 @@ public extension Sequence where Element: ParseInstallation { /** Saves a collection of installations all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -951,7 +951,7 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations *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 + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -998,7 +998,7 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that diff --git a/Sources/ParseSwift/Objects/ParseObject+combine.swift b/Sources/ParseSwift/Objects/ParseObject+combine.swift index 3abb33e04..fbd7fd069 100644 --- a/Sources/ParseSwift/Objects/ParseObject+combine.swift +++ b/Sources/ParseSwift/Objects/ParseObject+combine.swift @@ -85,7 +85,7 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects *asynchronously* and publishes when complete. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -110,7 +110,7 @@ public extension Sequence where Element: ParseObject { /** Deletes a collection of objects *asynchronously* and publishes when complete. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 69262a843..aeea0a504 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -61,7 +61,7 @@ public extension Sequence where Element: ParseObject { /** 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 + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -143,7 +143,7 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -365,7 +365,7 @@ public extension Sequence where Element: ParseObject { /** Deletes 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 + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -408,7 +408,7 @@ public extension Sequence where Element: ParseObject { /** Deletes a collection of objects all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that diff --git a/Sources/ParseSwift/Objects/ParseUser+combine.swift b/Sources/ParseSwift/Objects/ParseUser+combine.swift index 77b7e92a5..82e0d68d4 100644 --- a/Sources/ParseSwift/Objects/ParseUser+combine.swift +++ b/Sources/ParseSwift/Objects/ParseUser+combine.swift @@ -210,7 +210,7 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users *asynchronously* and publishes when complete. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -235,7 +235,7 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users *asynchronously* and publishes when complete. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index a895bfc49..ca562ef4f 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -961,7 +961,7 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users *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 + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -1044,7 +1044,7 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -1271,7 +1271,7 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users *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 + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that @@ -1317,7 +1317,7 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index c4e3e35fa..23b6d35d8 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -9,7 +9,7 @@ import Foundation enum ParseConstants { - static let parseVersion = "1.3.0" + static let parseVersion = "1.3.1" static let hashingKey = "parseSwift" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" diff --git a/Sources/ParseSwift/Types/Query+combine.swift b/Sources/ParseSwift/Types/Query+combine.swift index 5e4960bdd..9f8e74945 100644 --- a/Sources/ParseSwift/Types/Query+combine.swift +++ b/Sources/ParseSwift/Types/Query+combine.swift @@ -46,6 +46,27 @@ public extension Query { } } + /** + Retrieves *asynchronously* a complete list of `ParseObject`'s that satisfy this query + and publishes when complete. + - parameter hint: String or Object of index that should be used when executing query. + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - warning: The items are processed in an unspecified order. The query may not have any sort + order, and may not use limit or skip. + */ + func findAllPublisher(hint: String? = nil, + batchLimit: Int? = nil, + options: API.Options = []) -> Future<[ResultType], ParseError> { + Future { promise in + self.findAll(hint: hint, + batchLimit: batchLimit, + options: options, + completion: promise) + } + } + /** Gets an object *asynchronously* and publishes when complete. - parameter options: A set of header options sent to the server. Defaults to an empty set. diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 3408f6d65..ae5c7daca 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -908,23 +908,25 @@ extension Query: Queryable { } /** - Finds objects *asynchronously* and calls the given block with the results. - + Retrieves *asynchronously* a complete list of `ParseObject`'s that satisfy this query. + - parameter hint: String or Object of index that should be used when executing query. - - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched. is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: The block to execute. It should have the following argument signature: `(Result<[Decodable], ParseError>)`. + - warning: The items are processed in an unspecified order. The query may not have any sort + order, and may not use limit or skip. */ public func findAll(hint: String? = nil, batchLimit limit: Int? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[ResultType], ParseError>) -> Void) { - if order != nil || skip > 0 || limit != 100 { + if order != nil || skip > 0 || self.limit != 100 { let error = ParseError(code: .unknownError, message: "Cannot iterate on a query with sort, skip, or limit.") completion(.failure(error)) @@ -948,12 +950,12 @@ extension Query: Queryable { do { let currentResults: [ResultType] = try query.findCommand(explain: false, hint: hint).execute(options: options) + results.append(contentsOf: currentResults) if currentResults.count >= query.limit { guard let lastObjectId = results[results.count - 1].objectId else { throw ParseError(code: .unknownError, message: "Last object should have an id.") } query.where.add("objectId" > lastObjectId) - results.append(contentsOf: currentResults) } else { finished = true } diff --git a/Tests/ParseSwiftTests/ParseQueryCombineTests.swift b/Tests/ParseSwiftTests/ParseQueryCombineTests.swift index db28e621c..23f2ddfd7 100644 --- a/Tests/ParseSwiftTests/ParseQueryCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryCombineTests.swift @@ -73,7 +73,7 @@ class ParseQueryCombineTests: XCTestCase { // swiftlint:disable:this type_body_l func testFind() { var subscriptions = Set() - let expectation1 = XCTestExpectation(description: "Save") + let expectation1 = XCTestExpectation(description: "Find") var scoreOnServer = GameScore(score: 10) scoreOnServer.score = 11 @@ -115,6 +115,47 @@ class ParseQueryCombineTests: XCTestCase { // swiftlint:disable:this type_body_l wait(for: [expectation1], timeout: 20.0) } + func testFindAll() { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "FindAll") + + var scoreOnServer = GameScore(score: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = AnyResultsResponse(results: [scoreOnServer]) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + let query = GameScore.query() + let publisher = query.findAllPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { found in + + guard let object = found.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object.hasSameObjectId(as: scoreOnServer)) + }) + publisher.store(in: &subscriptions) + + wait(for: [expectation1], timeout: 20.0) + } + func testFindExplain() { var subscriptions = Set() let expectation1 = XCTestExpectation(description: "Save") diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index 1340d0e59..8f6acae5b 100644 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -376,6 +376,142 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length findAsync(scoreOnServer: scoreOnServer, callbackQueue: .main) } + func testFindAllAsync() { + var scoreOnServer = GameScore(score: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = AnyResultsResponse(results: [scoreOnServer]) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + let query = GameScore.query() + let expectation = XCTestExpectation(description: "Count object1") + query.findAll { result in + + switch result { + + case .success(let found): + guard let score = found.first else { + XCTFail("Should unwrap score count") + expectation.fulfill() + return + } + XCTAssert(score.hasSameObjectId(as: scoreOnServer)) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 20.0) + } + + func testFindAllAsyncErrorSkip() { + var scoreOnServer = GameScore(score: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = AnyResultsResponse(results: [scoreOnServer]) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + var query = GameScore.query() + query.skip = 10 + let expectation = XCTestExpectation(description: "Count object1") + query.findAll { result in + + switch result { + + case .success: + XCTFail("Should have failed") + case .failure(let error): + XCTAssertTrue(error.message.contains("Cannot iterate")) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 20.0) + } + + func testFindAllAsyncErrorOrder() { + var scoreOnServer = GameScore(score: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = AnyResultsResponse(results: [scoreOnServer]) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + let query = GameScore.query() + .order([.ascending("score")]) + let expectation = XCTestExpectation(description: "Count object1") + query.findAll { result in + + switch result { + + case .success: + XCTFail("Should have failed") + case .failure(let error): + XCTAssertTrue(error.message.contains("Cannot iterate")) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 20.0) + } + + func testFindAllAsyncErrorLimit() { + var scoreOnServer = GameScore(score: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = AnyResultsResponse(results: [scoreOnServer]) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + var query = GameScore.query() + query.limit = 10 + let expectation = XCTestExpectation(description: "Count object1") + query.findAll { result in + + switch result { + + case .success: + XCTFail("Should have failed") + case .failure(let error): + XCTAssertTrue(error.message.contains("Cannot iterate")) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 20.0) + } + func testFirst() { var scoreOnServer = GameScore(score: 10) scoreOnServer.objectId = "yarr" From 60750b9900706f7d1318ca8105b4c9ddca41b139 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 10 Apr 2021 22:26:56 -0400 Subject: [PATCH 3/6] Allow deletion of Objc Keychain --- Sources/ParseSwift/Parse.swift | 44 ++++++++++++------- .../ParseSwiftTests/InitializeSDKTests.swift | 31 +++++++++++++ 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 3a23a7f1f..802ccb85d 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -170,21 +170,6 @@ public struct ParseSwift { migrateFromObjcSDK: migrateFromObjcSDK) } - /** - Update the authentication callback. - - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. - Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. - 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. - */ - static func updateAuthentication(_ authentication: ((URLAuthenticationChallenge, - (URLSession.AuthChallengeDisposition, - URLCredential?) -> Void) -> Void)?) { - Self.sessionDelegate = ParseURLSessionDelegate(callbackQueue: .main, - authentication: authentication) - } - internal static func initialize(applicationId: String, clientKey: String? = nil, masterKey: String? = nil, @@ -208,4 +193,33 @@ public struct ParseSwift { migrateFromObjcSDK: migrateFromObjcSDK) Self.configuration.isTestingSDK = testing } + + /** + Update the authentication callback. + - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. + Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. + 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. + */ + static public func updateAuthentication(_ authentication: ((URLAuthenticationChallenge, + (URLSession.AuthChallengeDisposition, + URLCredential?) -> Void) -> Void)?) { + Self.sessionDelegate = ParseURLSessionDelegate(callbackQueue: .main, + authentication: authentication) + } + + #if !os(Linux) && !os(Android) + /** + Delete the Parse iOS Objective-C SDK Keychain from the device. + - note: ParseSwift uses a different Keychain. After migration, the iOS Objective-C SDK Keychain is no longer needed. + - warning: The keychain cannot be recovered after deletion. + */ + static public func deleteObjectiveCKeychain() throws { + if let identifier = Bundle.main.bundleIdentifier { + let objcParseKeychain = KeychainStore(service: "\(identifier).com.parse.sdk") + try objcParseKeychain.deleteAll() + } + } + #endif } diff --git a/Tests/ParseSwiftTests/InitializeSDKTests.swift b/Tests/ParseSwiftTests/InitializeSDKTests.swift index 89ec0e553..b68ea2e2a 100644 --- a/Tests/ParseSwiftTests/InitializeSDKTests.swift +++ b/Tests/ParseSwiftTests/InitializeSDKTests.swift @@ -172,6 +172,37 @@ class InitializeSDKTests: XCTestCase { XCTAssertEqual(Installation.currentInstallationContainer.installationId, objcInstallationId) } + func testDeleteObjcSDKKeychain() throws { + + //Set keychain the way objc sets keychain + guard let identifier = Bundle.main.bundleIdentifier else { + XCTFail("Should have unwrapped") + return + } + let objcInstallationId = "helloWorld" + let objcParseKeychain = KeychainStore(service: "\(identifier).com.parse.sdk") + _ = objcParseKeychain.set(object: objcInstallationId, forKey: "installationId") + + guard let retrievedInstallationId: String? = try objcParseKeychain.get(valueFor: "installationId") else { + XCTFail("Should have unwrapped") + return + } + XCTAssertEqual(retrievedInstallationId, objcInstallationId) + XCTAssertNoThrow(try ParseSwift.deleteObjectiveCKeychain()) + let retrievedInstallationId2: String? = try objcParseKeychain.get(valueFor: "installationId") + XCTAssertNil(retrievedInstallationId2) + + //This is needed for tear down + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url) + } + func testMigrateObjcSDKMissingInstallation() { //Set keychain the way objc sets keychain From d9ea8eedf628ed08fc6e1e9b27e2f41fda22b9ac Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 10 Apr 2021 23:05:21 -0400 Subject: [PATCH 4/6] Add relative query playground example --- CHANGELOG.md | 1 + .../Contents.swift | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a256448cc..2691f6a91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ __New features__ - Add findAll query to find all objects ([#118](https://github.com/parse-community/Parse-Swift/pull/118)), thanks to [Corey Baker](https://github.com/cbaker6). +- Can now delete the iOS Objective-C SDK Keychain from app ([#118](https://github.com/parse-community/Parse-Swift/pull/118)), thanks to [Corey Baker](https://github.com/cbaker6). - Migrate installationId from obj-c SDK ([#117](https://github.com/parse-community/Parse-Swift/pull/117)), thanks to [Corey Baker](https://github.com/cbaker6). __Improvements__ diff --git a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift index e86f35b77..7279fb79a 100644 --- a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift @@ -84,6 +84,21 @@ query.first { results in } } +//: Query based on relative time. Have to be using mongoDB. +let queryRelative = GameScore.query(relative(key: "createdAt", + comparator: .lessThan, + time: "10 minutes ago")) +queryRelative.find { results in + switch results { + case .success(let scores): + + print("Found scores using relative time: \(scores)") + + case .failure(let error): + assertionFailure("Error querying: \(error)") + } +} + let querySelect = query.select("score") querySelect.first { results in switch results { From e1ac9a0bcc1f0a171b68a0b4db14941c01b1ce19 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 10 Apr 2021 23:19:39 -0400 Subject: [PATCH 5/6] Simplify findAll in playgrounds --- .../Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift index 96629049c..38194ec52 100644 --- a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift @@ -175,7 +175,7 @@ query7.find { results in //: Find all GameScores. let query8 = GameScore.query() -query8.findAll { (result: Result<[GameScore], ParseError>) in +query8.findAll { result in switch result { case .success(let scores): print(scores) From 90c597beb6a8031510a2c369164fdab00c71b058 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 10 Apr 2021 23:27:57 -0400 Subject: [PATCH 6/6] Update includeAll documentation --- Sources/ParseSwift/Types/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index ae5c7daca..06b0e05bc 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -687,7 +687,7 @@ public struct Query: Encodable, Equatable where T: ParseObject { } /** - Includes all nested `ParseObject`s. + Includes all nested `ParseObject`s one level deep. - warning: Requires Parse Server 3.0.0+ */ public func includeAll() -> Query {