diff options
author | August Mueller <gus@flyingmeat.com> | 2021-12-24 04:07:33 +0300 |
---|---|---|
committer | August Mueller <gus@flyingmeat.com> | 2021-12-24 04:07:33 +0300 |
commit | 589c05e7848e75675450b5121531d11b79a06098 (patch) | |
tree | 7627c9f67edb0c33f054e0539fe82e8b817777e0 | |
parent | 7f0098b2a28e2e90aad502819262a34dd1ae3189 (diff) |
Fleshing out some more of the queueFMDB4-Swift
-rw-r--r-- | FMDBSwift/Sources/FMDB/FMDatabase.swift | 6 | ||||
-rw-r--r-- | FMDBSwift/Sources/FMDB/FMDatabaseQueue.swift | 252 | ||||
-rw-r--r-- | FMDBSwift/Tests/FMDBTests/FMDatabaseQueueTests.swift | 57 |
3 files changed, 312 insertions, 3 deletions
diff --git a/FMDBSwift/Sources/FMDB/FMDatabase.swift b/FMDBSwift/Sources/FMDB/FMDatabase.swift index 116340b..385398a 100644 --- a/FMDBSwift/Sources/FMDB/FMDatabase.swift +++ b/FMDBSwift/Sources/FMDB/FMDatabase.swift @@ -35,7 +35,7 @@ public class FMDatabase : NSObject { private var _db : OpaquePointer? var _databasePath : String? - private var _isOpen : Bool + internal var _isOpen : Bool private var _maxBusyRetryTimeInterval : TimeInterval = 2 private var _startBusyRetryTime : TimeInterval = 0 public var logsErrors : Bool @@ -207,7 +207,7 @@ public class FMDatabase : NSObject { } - public func openWithFlags(flags : Int32, vfsName : String?) throws -> Bool { + @discardableResult public func openWithFlags(flags : Int32, vfsName : String?) throws -> Bool { if (_isOpen) { return true @@ -823,7 +823,7 @@ public class FMDatabase : NSObject { _isInTransaction } - func interrupt() -> Bool { + @discardableResult func interrupt() -> Bool { if (_db != nil) { sqlite3_interrupt(_db); return true; diff --git a/FMDBSwift/Sources/FMDB/FMDatabaseQueue.swift b/FMDBSwift/Sources/FMDB/FMDatabaseQueue.swift index 9ed432c..96aec5a 100644 --- a/FMDBSwift/Sources/FMDB/FMDatabaseQueue.swift +++ b/FMDBSwift/Sources/FMDB/FMDatabaseQueue.swift @@ -4,4 +4,256 @@ import SQLite3 public class FMDatabaseQueue : NSObject { + enum FMDBTransaction : Int32 { + case FMDBTransactionExclusive = 1, + FMDBTransactionDeferred = 2, + FMDBTransactionImmediate = 3 + } + + + + var _databasePath : String? + var _openFlags : Int32 = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE + var _vfsName : String? + private var _db : FMDatabase? + + static var savePointIdx : CUnsignedLong = 0 + + public let queue = DispatchQueue(label: "fmdb.\(String(describing: self))") + + private let queueKey = DispatchSpecificKey<FMDatabaseQueue>() + + public override init() { + super.init() + + queue.setSpecific(key:queueKey, value:self) + + print(queue) + } + + deinit { + + if let _db = _db { + queue.sync { + if (!_db.close()) { + print("Could not close database") + } + } + } + + + } + + public func interrupt() { + _db?.interrupt() + } + + // FIXME: What would the swift version of this be? + /* + + (Class)databaseClass { + return [FMDatabase class]; + } + */ + + + + + + + static func queue(with filePath : String) -> FMDatabaseQueue { + + let q = FMDatabaseQueue() + + q._databasePath = filePath + + return q + } + + static func queue(with fileURL : URL) -> FMDatabaseQueue { + return FMDatabaseQueue.queue(with: fileURL.path) + } + + func database() -> FMDatabase? { + + if (_db == nil || !(_db!._isOpen)) { + + if (_db == nil) { + + _db = FMDatabase.database(with: _databasePath!) + + } + + do { + try _db?.openWithFlags(flags: _openFlags, vfsName: _vfsName) + } + catch { + print(error) + abort() + } + } + + return _db + + } + + func inDatabase(_ f: (FMDatabase) -> ()) { + + /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue + * and then check it against self to make sure we're not about to deadlock. */ + + let currentSyncQueue = queue.getSpecific(key: queueKey) + assert(currentSyncQueue != self, "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock") + + + queue.sync { + + let db = database() + + f(db!) + + + if (db!.hasOpenResultSets()) { + print("Warning: there is at least one open result set around after performing FMDatabaseQueue.inDatabase()") + } + } + } + + + func beginTransaction(transaction: FMDBTransaction, f: (FMDatabase, inout Bool) -> ()) { + + + queue.sync { + + if let db = database() { + + var shouldRollback = false + + do { + + switch (transaction) { + + case .FMDBTransactionExclusive: + try db.beginExclusiveTransaction() + break + + case .FMDBTransactionDeferred: + try db.beginDeferredTransaction() + break + + case .FMDBTransactionImmediate: + try db.beginImmediateTransaction() + break + + } + + f(db, &shouldRollback) + + if (shouldRollback) { + try db.rollback(); + } + else { + try db.commit(); + } + } + catch { + print(error) + } + } + } + + } + + + func inTransaction(_ f: (FMDatabase, inout Bool) -> ()) { + beginTransaction(transaction: .FMDBTransactionExclusive, f: f) + } + + func inDeferredTransaction(_ f: (FMDatabase, inout Bool) -> ()) { + beginTransaction(transaction: .FMDBTransactionDeferred, f: f) + } + + func inExclusiveTransaction(_ f: (FMDatabase, inout Bool) -> ()) { + beginTransaction(transaction: .FMDBTransactionExclusive, f: f) + } + + func inImmediateTransaction(_ f: (FMDatabase, inout Bool) -> ()) { + beginTransaction(transaction: .FMDBTransactionImmediate, f: f) + } + + + func inSavePoint(_ f: (FMDatabase, inout Bool) -> ()) { + + + queue.sync { + + #warning("Need to implement startSavePointWithName in FMDatabase before the Queue can do inSavePoint:") + FMDatabaseQueue.savePointIdx = FMDatabaseQueue.savePointIdx + 1 + +// let name = "savePoint\(FMDatabaseQueue.savePointIdx)" +// var shouldRollback = false +// + } + + } + + + /* + + - (NSError*)inSavePoint:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block { + #if SQLITE_VERSION_NUMBER >= 3007000 + static unsigned long savePointIdx = 0; + __block NSError *err = 0x00; + FMDBRetain(self); + dispatch_sync(_queue, ^() { + + NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; + + BOOL shouldRollback = NO; + + if ([[self database] startSavePointWithName:name error:&err]) { + + block([self database], &shouldRollback); + + if (shouldRollback) { + // We need to rollback and release this savepoint to remove it + [[self database] rollbackToSavePointWithName:name error:&err]; + } + [[self database] releaseSavePointWithName:name error:&err]; + + } + }); + FMDBRelease(self); + return err; + #else + NSString *errorMessage = NSLocalizedStringFromTable(@"Save point functions require SQLite 3.7", @"FMDB", nil); + if (_db.logsErrors) NSLog(@"%@", errorMessage); + return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; + #endif + } + + - (BOOL)checkpoint:(FMDBCheckpointMode)mode error:(NSError * __autoreleasing *)error + { + return [self checkpoint:mode name:nil logFrameCount:NULL checkpointCount:NULL error:error]; + } + + - (BOOL)checkpoint:(FMDBCheckpointMode)mode name:(NSString *)name error:(NSError * __autoreleasing *)error + { + return [self checkpoint:mode name:name logFrameCount:NULL checkpointCount:NULL error:error]; + } + + - (BOOL)checkpoint:(FMDBCheckpointMode)mode name:(NSString *)name logFrameCount:(int * _Nullable)logFrameCount checkpointCount:(int * _Nullable)checkpointCount error:(NSError * __autoreleasing _Nullable * _Nullable)error + { + __block BOOL result; + + FMDBRetain(self); + dispatch_sync(_queue, ^() { + result = [self.database checkpoint:mode name:name logFrameCount:logFrameCount checkpointCount:checkpointCount error:error]; + }); + FMDBRelease(self); + + return result; + } + + */ + } diff --git a/FMDBSwift/Tests/FMDBTests/FMDatabaseQueueTests.swift b/FMDBSwift/Tests/FMDBTests/FMDatabaseQueueTests.swift new file mode 100644 index 0000000..2a66a93 --- /dev/null +++ b/FMDBSwift/Tests/FMDBTests/FMDatabaseQueueTests.swift @@ -0,0 +1,57 @@ +import XCTest +import SQLite3 +@testable import FMDB + +final class FMDatabaseQueueTests: FMDBTempDBTests { + + let tempPath = "/tmp/FMDatabaseQueueTests.db" + + override func setUp() { + + } + + /* + func testURLOpenNoPath() throws { + + do { + let q = FMDatabaseQueue() + XCTAssert(q != nil, "Database queue should be returned") + } + + catch { + print(error) + XCTAssert(false) + } + }*/ + + func testSimpleSelect() throws { + + let q = FMDatabaseQueue.queue(with: tempPath) + + var worked = false + + q.inDatabase({ db in + + do { + let rs = try db.executeQuery("select 'hello'") + try rs.next() + + XCTAssertTrue(rs.string(0) == "hello") + rs.close() + worked = true + } + + catch { + print(error) + XCTAssert(false) + } + + }) + + XCTAssert(worked) + } + + + + +} |