From 790454f514d0e28b7905cfa85a77d3e4468301d5 Mon Sep 17 00:00:00 2001 From: Tommaso Piazza Date: Tue, 27 Jun 2017 13:58:16 +0200 Subject: [PATCH 1/4] Add optional && and || operators to Where clause --- CoreStoreTests/WhereTests.swift | 30 +++++++++++++++ Sources/Where.swift | 68 +++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/CoreStoreTests/WhereTests.swift b/CoreStoreTests/WhereTests.swift index c8f826e..307e404 100644 --- a/CoreStoreTests/WhereTests.swift +++ b/CoreStoreTests/WhereTests.swift @@ -256,6 +256,21 @@ final class WhereTests: XCTestCase { XCTAssertEqual(andWhere.predicate, andPredicate) XCTAssertEqual(andWhere, whereClause1 && whereClause2 && whereClause3) } + do { + + let andWhere = whereClause1 && whereClause2 && whereClause3 + let noneWhere: Where? = nil + let someWhere: Where? = Where("key4", isEqualTo: "value4") + + + let finalNoneWhere = andWhere && noneWhere + let finalSomeWhere = andWhere && someWhere + let unwrappedFinalSomeWhere = andWhere && someWhere! + + + XCTAssertEqual(andWhere.predicate, finalNoneWhere.predicate) + XCTAssertEqual(finalSomeWhere.predicate, unwrappedFinalSomeWhere.predicate) + } do { let orWhere = whereClause1 || whereClause2 || whereClause3 @@ -272,6 +287,21 @@ final class WhereTests: XCTestCase { XCTAssertEqual(orWhere.predicate, orPredicate) XCTAssertEqual(orWhere, whereClause1 || whereClause2 || whereClause3) } + do { + + let orWhere = whereClause1 || whereClause2 || whereClause3 + let noneWhere: Where? = nil + let someWhere: Where? = Where("key4", isEqualTo: "value4") + + + let finalNoneWhere = orWhere && noneWhere + let finalSomeWhere = orWhere && someWhere + let unwrappedFinalSomeWhere = orWhere && someWhere! + + XCTAssertEqual(orWhere.predicate, finalNoneWhere.predicate) + XCTAssertEqual(finalSomeWhere.predicate, unwrappedFinalSomeWhere.predicate) + } + } @objc diff --git a/Sources/Where.swift b/Sources/Where.swift index f9fe369..06aff22 100644 --- a/Sources/Where.swift +++ b/Sources/Where.swift @@ -42,6 +42,40 @@ public struct Where: FetchClause, QueryClause, DeleteClause, Hashable { return Where(NSCompoundPredicate(type: .and, subpredicates: [left.predicate, right.predicate])) } + /** + Combines two `Where` predicates together using `AND` operator. + - parameter left: the left hand side `Where` clause + - parameter right: the right hand side `Where` clause + - returns: Return `left` unchanged if `right` is nil + */ + public static func && (left: Where, right: Where?) -> Where { + + if right != nil { + return left && right! + } + else { + + return left + } + } + + /** + Combines two `Where` predicates together using `AND` operator. + - parameter left: the left hand side `Where` clause + - parameter right: the right hand side `Where` clause + - returns: Returns `right` unchanged if `left` is nil + */ + public static func && (left: Where?, right: Where) -> Where { + + if left != nil { + return left && right + } + else { + + return right + } + } + /** Combines two `Where` predicates together using `OR` operator */ @@ -50,6 +84,40 @@ public struct Where: FetchClause, QueryClause, DeleteClause, Hashable { return Where(NSCompoundPredicate(type: .or, subpredicates: [left.predicate, right.predicate])) } + /** + Combines two `Where` predicates together using `OR` operator. + - parameter left: the left hand side `Where` clause + - parameter right: the right hand side `Where` clause + - returns: Returns `left` unchanged if `right` is nil + */ + public static func || (left: Where, right: Where?) -> Where { + + if right != nil { + return left || right! + } + else { + + return left + } + } + + /** + Combines two `Where` predicates together using `OR` operator. + - parameter left: the left hand side `Where` clause + - parameter right: the right hand side `Where` clause + - returns: Return `right` unchanged if `left` is nil + */ + public static func || (left: Where?, right: Where) -> Where { + + if left != nil { + return left || right + } + else { + + return right + } + } + /** Inverts the predicate of a `Where` clause using `NOT` operator */ From 809aa4ff96f25aa302c06c1ea676dced95fae459 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 1 Jul 2017 11:50:23 +0900 Subject: [PATCH 2/4] allow querying file size on SQLiteStore --- .../CoreStore+CustomDebugStringConvertible.swift | 3 ++- Sources/SQLiteStore.swift | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Sources/CoreStore+CustomDebugStringConvertible.swift b/Sources/CoreStore+CustomDebugStringConvertible.swift index dc197e3..a1f5e80 100644 --- a/Sources/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/CoreStore+CustomDebugStringConvertible.swift @@ -868,7 +868,8 @@ extension SQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringConvert ("storeOptions", self.storeOptions as Any), ("fileURL", self.fileURL), ("migrationMappingProviders", self.migrationMappingProviders), - ("localStorageOptions", self.localStorageOptions) + ("localStorageOptions", self.localStorageOptions), + ("fileSize", self.fileSize() as Any) ) } } diff --git a/Sources/SQLiteStore.swift b/Sources/SQLiteStore.swift index fff62ff..42c6505 100644 --- a/Sources/SQLiteStore.swift +++ b/Sources/SQLiteStore.swift @@ -117,6 +117,20 @@ public final class SQLiteStore: LocalStorage { ) } + /** + Queries the file size (in bytes) of the store, or `nil` if the file does not exist yet + */ + public func fileSize() -> UInt64? { + + guard let attribute = try? FileManager.default.attributesOfItem(atPath: self.fileURL.path), + let sizeAttribute = attribute[.size], + let fileSize = sizeAttribute as? NSNumber else { + + return nil + } + return fileSize.uint64Value + } + // MARK: StorageInterface From 3096cb784c0670221bac83cc5cde6dddf4f9bca1 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 1 Jul 2017 16:45:11 +0900 Subject: [PATCH 3/4] add internal utilities to force checkpoint operations on SQLite --- Sources/LegacySQLiteStore.swift | 5 +++++ Sources/SQLiteStore.swift | 16 ++++++++++++++++ Sources/StorageInterface.swift | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/Sources/LegacySQLiteStore.swift b/Sources/LegacySQLiteStore.swift index 27eed59..23daff9 100644 --- a/Sources/LegacySQLiteStore.swift +++ b/Sources/LegacySQLiteStore.swift @@ -86,6 +86,11 @@ public final class LegacySQLiteStore: LocalStorage { public var localStorageOptions: LocalStorageOptions + public func cs_finalizeStorageAndWait(soureModelHint: NSManagedObjectModel) throws { + + fatalError() + } + public func cs_eraseStorageAndWait(metadata: [String: Any], soureModelHint: NSManagedObjectModel?) throws { fatalError() diff --git a/Sources/SQLiteStore.swift b/Sources/SQLiteStore.swift index 42c6505..b50c29f 100644 --- a/Sources/SQLiteStore.swift +++ b/Sources/SQLiteStore.swift @@ -205,6 +205,22 @@ public final class SQLiteStore: LocalStorage { return storeOptions } + /** + Called by the `DataStack` to perform checkpoint operations on the storage. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE to force a checkpoint. + */ + public func cs_finalizeStorageAndWait(soureModelHint: NSManagedObjectModel) throws { + + _ = try withExtendedLifetime(NSPersistentStoreCoordinator(managedObjectModel: soureModelHint)) { (coordinator: NSPersistentStoreCoordinator) in + + try coordinator.addPersistentStore( + ofType: type(of: self).storeType, + configurationName: self.configuration, + at: fileURL, + options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]] + ) + } + } + /** Called by the `DataStack` to perform actual deletion of the store file from disk. Do not call directly! The `sourceModel` argument is a hint for the existing store's model version. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE before deleting the file. */ diff --git a/Sources/StorageInterface.swift b/Sources/StorageInterface.swift index 86de874..3ddaa0a 100644 --- a/Sources/StorageInterface.swift +++ b/Sources/StorageInterface.swift @@ -141,6 +141,11 @@ public protocol LocalStorage: StorageInterface { */ func dictionary(forOptions options: LocalStorageOptions) -> [AnyHashable: Any]? + /** + Called by the `DataStack` to perform checkpoint operations on the storage. (SQLite stores for example, can convert the database's WAL journaling mode to DELETE to force a checkpoint) + */ + func cs_finalizeStorageAndWait(soureModelHint: NSManagedObjectModel) throws + /** Called by the `DataStack` to perform actual deletion of the store file from disk. **Do not call directly!** The `sourceModel` argument is a hint for the existing store's model version. Implementers can use the `sourceModel` to perform necessary store operations. (SQLite stores for example, can convert WAL journaling mode to DELETE before deleting) */ From 961f39a806077ac51aa40054030ffd34c2eebbcd Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 1 Jul 2017 17:05:10 +0900 Subject: [PATCH 4/4] add internal utilities to force checkpoint operations on SQLite --- Sources/DataStack+Migration.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/DataStack+Migration.swift b/Sources/DataStack+Migration.swift index 8d22919..48823da 100644 --- a/Sources/DataStack+Migration.swift +++ b/Sources/DataStack+Migration.swift @@ -707,6 +707,7 @@ public extension DataStack { do { + try storage.cs_finalizeStorageAndWait(soureModelHint: sourceModel) try migrationManager.migrateStore( from: fileURL, sourceType: type(of: storage).storeType, @@ -716,6 +717,13 @@ public extension DataStack { destinationType: type(of: storage).storeType, destinationOptions: nil ) + let temporaryStorage = SQLiteStore( + fileURL: temporaryFileURL, + configuration: storage.configuration, + migrationMappingProviders: storage.migrationMappingProviders, + localStorageOptions: storage.localStorageOptions + ) + try temporaryStorage.cs_finalizeStorageAndWait(soureModelHint: destinationModel) } catch {