From 20fa6ca68de34d71668c0fe24101af3c5943005d Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Tue, 19 Jul 2016 15:50:14 -0600 Subject: [PATCH] Revert "Implement connection pool." --- Documentation/Index.md | 30 +--- SQLite.xcodeproj/project.pbxproj | 38 +---- SQLite/Core/Connection.swift | 84 +++++------ SQLite/Core/ConnectionPool.swift | 196 -------------------------- SQLite/Core/Dispatcher.swift | 71 ---------- SQLite/Typed/Query.swift | 1 + SQLiteTests/ConnectionPoolTests.swift | 190 ------------------------- SQLiteTests/ConnectionTests.swift | 26 ++-- 8 files changed, 58 insertions(+), 578 deletions(-) delete mode 100644 SQLite/Core/ConnectionPool.swift delete mode 100644 SQLite/Core/Dispatcher.swift delete mode 100644 SQLiteTests/ConnectionPoolTests.swift diff --git a/Documentation/Index.md b/Documentation/Index.md index a33000e8..0579e6f3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -11,7 +11,6 @@ - [Read-Only Databases](#read-only-databases) - [In-Memory Databases](#in-memory-databases) - [Thread-Safety](#thread-safety) - - [Connection Pools](#connection-pools) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) - [Compound Expressions](#compound-expressions) @@ -252,7 +251,7 @@ Every Connection comes equipped with its own serial queue for statement executio If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: -``` swift +```swift db.busyTimeout = 5 db.busyHandler({ tries in @@ -266,33 +265,6 @@ db.busyHandler({ tries in > _Note:_ The default timeout is 0, so if you see `database is locked` errors, you may be trying to access the same database simultaneously from multiple connections. -### Connection Pools - -Connection pools use SQLite WAL mode to allow concurrent reads and writes, which can increase performance. Connection pools are created similar to connections: - -``` swift -let pool = try ConnectionPool("path/to/db.sqlite3") -``` - -Writes are done inside of a readWrite block: - -``` swift -pool.readWrite { connection in - try connection.run(users.insert(email <- "alice@mac.com", name <- "Alice")) -} -``` - -Reads are done inside of a read block: - -``` swift -pool.read { connection in - for user in try connection.prepare(users) { - print("id: \(user[id]), email: \(user[email]), name: \(user[name])") - } -} -``` - - ## Building Type-Safe SQL SQLite.swift comes with a typed expression layer that directly maps [Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ec4cdfad..88d74dc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,17 +46,6 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; - AA780B3D1CC201A700E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B3E1CC201A700E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; - AA780B411CC202C800E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; - AA780B421CC202C900E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; - AA780B431CC202CA00E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; - AA780B441CC202F300E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B451CC202F300E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; - AA780B461CC202F400E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B471CC202F400E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; - AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; @@ -181,28 +170,25 @@ 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionPool.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - AA780B3C1CC201A700E0E95E /* Dispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatcher.swift; sourceTree = ""; }; - AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPoolTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; - EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Connection.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Statement.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; - EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = FTS4.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; EE247AF61C3F06E900AE3E12 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; EE247AF71C3F06E900AE3E12 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; EE247AF81C3F06E900AE3E12 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; EE247AFB1C3F06E900AE3E12 /* Collation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collation.swift; sourceTree = ""; }; EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctions.swift; sourceTree = ""; }; - EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CustomFunctions.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctions.swift; sourceTree = ""; }; EE247AFE1C3F06E900AE3E12 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; EE247AFF1C3F06E900AE3E12 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; EE247B001C3F06E900AE3E12 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; @@ -212,7 +198,7 @@ EE247B181C3F134A00AE3E12 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; EE247B1B1C3F137700AE3E12 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; - EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; EE247B201C3F137700AE3E12 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; @@ -412,7 +398,6 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( - AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -437,8 +422,6 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */, - AA780B3C1CC201A700E0E95E /* Dispatcher.swift */, EE91808D1C46E5230038162A /* SQLite-Bridging.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, @@ -809,11 +792,9 @@ 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */, 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */, - AA780B471CC202F400E0E95E /* Dispatcher.swift in Sources */, 03A65E841C6BB2FB0062603F /* Query.swift in Sources */, 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */, 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, - AA780B461CC202F400E0E95E /* ConnectionPool.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, ); @@ -824,7 +805,6 @@ buildActionMask = 2147483647; files = ( 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, - AA780B431CC202CA00E0E95E /* ConnectionPoolTests.swift in Sources */, 03A65E901C6BB3030062603F /* R*TreeTests.swift in Sources */, 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */, @@ -847,8 +827,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */, - AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -870,11 +848,9 @@ EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, - AA780B3E1CC201A700E0E95E /* Dispatcher.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, - AA780B3D1CC201A700E0E95E /* ConnectionPool.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, ); @@ -885,7 +861,6 @@ buildActionMask = 2147483647; files = ( EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, - AA780B411CC202C800E0E95E /* ConnectionPoolTests.swift in Sources */, EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */, @@ -922,11 +897,9 @@ EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */, EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */, - AA780B451CC202F300E0E95E /* Dispatcher.swift in Sources */, EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */, EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */, EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, - AA780B441CC202F300E0E95E /* ConnectionPool.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, ); @@ -937,7 +910,6 @@ buildActionMask = 2147483647; files = ( EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, - AA780B421CC202C900E0E95E /* ConnectionPoolTests.swift in Sources */, EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */, diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index d594a4f5..866ed1bf 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -30,24 +30,8 @@ import sqlite3 import CSQLite #endif - -/// The mode in which a transaction acquires a lock. -public enum TransactionMode : String { - - /// Defers locking the database till the first read/write executes. - case Deferred = "DEFERRED" - - /// Immediately acquires a reserved lock on the database. - case Immediate = "IMMEDIATE" - - /// Immediately acquires an exclusive lock on all databases. - case Exclusive = "EXCLUSIVE" - -} - - /// A connection to SQLite. -public final class Connection : Equatable { +public final class Connection { /// The location of a SQLite database. public enum Location { @@ -88,20 +72,10 @@ public final class Connection : Equatable { /// Default: `false`. /// /// - Returns: A new database connection. - public convenience init(_ location: Location = .InMemory, readonly: Bool = false) throws { + public init(_ location: Location = .InMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection"), vfsName: nil) - } - - init(_ location: Location, flags: Int32, dispatcher: Dispatcher, vfsName: String? = nil) throws { - self.dispatcher = dispatcher - if let vfsName = vfsName { - try check(sqlite3_open_v2(location.description, &_handle, flags, vfsName)) - } - else { - try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) - } - try check(sqlite3_extended_result_codes(handle, 1)) + try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + dispatch_queue_set_specific(queue, Connection.queueKey, queueContext, nil) } /// Initializes a new connection to a database. @@ -296,6 +270,20 @@ public final class Connection : Equatable { // MARK: - Transactions + /// The mode in which a transaction acquires a lock. + public enum TransactionMode : String { + + /// Defers locking the database till the first read/write executes. + case Deferred = "DEFERRED" + + /// Immediately acquires a reserved lock on the database. + case Immediate = "IMMEDIATE" + + /// Immediately acquires an exclusive lock on all databases. + case Exclusive = "EXCLUSIVE" + + } + // TODO: Consider not requiring a throw to roll back? /// Runs a transaction with the given mode. /// @@ -313,10 +301,10 @@ public final class Connection : Equatable { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(mode: TransactionMode = .Deferred, block: (Connection) throws -> Void) throws { + public func transaction(mode: TransactionMode = .Deferred, block: () throws -> Void) throws { try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") } - + // TODO: Consider not requiring a throw to roll back? // TODO: Consider removing ability to set a name? /// Runs a transaction with the given savepoint name (if omitted, it will @@ -333,18 +321,18 @@ public final class Connection : Equatable { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - public func savepoint(name: String = NSUUID().UUIDString, block: (Connection) throws -> Void) throws { + public func savepoint(name: String = NSUUID().UUIDString, block: () throws -> Void) throws { let name = name.quote("'") let savepoint = "SAVEPOINT \(name)" - + try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") } - - private func transaction(begin: String, _ block: (Connection) throws -> Void, _ commit: String, or rollback: String) throws { + + private func transaction(begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { return try sync { try self.run(begin) do { - try block(self) + try block() } catch { try self.run(rollback) throw error @@ -352,7 +340,7 @@ public final class Connection : Equatable { try self.run(commit) } } - + /// Interrupts any long-running queries. public func interrupt() { sqlite3_interrupt(handle) @@ -582,7 +570,7 @@ public final class Connection : Equatable { // MARK: - Error Handling - public func sync(block: () throws -> T) rethrows -> T { + func sync(block: () throws -> T) rethrows -> T { var success: T? var failure: ErrorType? @@ -594,7 +582,11 @@ public final class Connection : Equatable { } } - dispatcher.dispatch(box) + if dispatch_get_specific(Connection.queueKey) == queueContext { + box() + } else { + dispatch_sync(queue, box) // FIXME: rdar://problem/21389236 + } if let failure = failure { try { () -> Void in throw failure }() @@ -610,8 +602,12 @@ public final class Connection : Equatable { throw error } - - private var dispatcher: Dispatcher + + private var queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) + + private static let queueKey = unsafeBitCast(Connection.self, UnsafePointer.self) + + private lazy var queueContext: UnsafeMutablePointer = unsafeBitCast(self, UnsafeMutablePointer.self) } @@ -638,10 +634,6 @@ extension Connection.Location : CustomStringConvertible { } -public func == (lhs: Connection, rhs: Connection) -> Bool { - return lhs === rhs -} - /// An SQL operation passed to update callbacks. public enum Operation { diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift deleted file mode 100644 index 91c3e366..00000000 --- a/SQLite/Core/ConnectionPool.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Dispatch -#if SQLITE_SWIFT_STANDALONE -import sqlite3 -#else -import CSQLite -#endif - - -private let vfsName = "unix-excl" - - -// Connection pool for accessing an SQLite database -// with multiple readers & a single writer. Utilizes -// WAL mode. -public final class ConnectionPool { - - private let location: Connection.Location - private var availableReadConnections = [Connection]() - private var unavailableReadConnections = [Connection]() - private let lockQueue: dispatch_queue_t - private var writeConnection: Connection! - private let connectionSemaphore = dispatch_semaphore_create(5) - - public var foreignKeys : Bool { - get { - return internalSetup[.ForeignKeys] != nil - } - set { - internalSetup[.ForeignKeys] = newValue ? { try $0.execute("PRAGMA foreign_keys = ON;") } : nil - } - } - - public typealias ConnectionProcessor = Connection throws -> Void - public var setup = [ConnectionProcessor]() - - private enum InternalOption { - case WriteAheadLogging - case ForeignKeys - } - - private var internalSetup = [InternalOption: ConnectionProcessor]() - - /// Initializes a new SQLite connection pool. - /// - /// - Parameters: - /// - /// - location: The location of the database. Creates a new database if it - /// doesn’t already exist. - /// - /// Default: `.InMemory`. - /// - /// - Throws: `Result.Error` iff a connection cannot be established. - /// - /// - Returns: A new connection pool. - public init(_ location: Connection.Location = .InMemory) throws { - self.location = location - self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) - self.internalSetup[.WriteAheadLogging] = { try $0.execute("PRAGMA journal_mode = WAL;") } - } - - /// Initializes a new connection to a database. - /// - /// - Parameters: - /// - /// - filename: The location of the database. Creates a new database if - /// it doesn’t already exist (unless in read-only mode). - /// - /// - Throws: `Result.Error` iff a connection cannot be established. - /// - /// - Returns: A new database connection pool. - public convenience init(_ filename: String) throws { - try self.init(.URI(filename)) - } - - public var totalReadableConnectionCount : Int { - return availableReadConnections.count + unavailableReadConnections.count - } - - public var availableReadableConnectionCount : Int { - return availableReadConnections.count - } - - /// Calls `readBlock` with an available read connection from the connection pool, - /// after which the connection is made available again. - public func read(readBlock: (connection: Connection) -> Void) { - let connection = readable - readBlock(connection: connection) - - dispatch_sync(lockQueue) { - if let index = self.unavailableReadConnections.indexOf(connection) { - self.unavailableReadConnections.removeAtIndex(index) - } - self.availableReadConnections.append(connection) - dispatch_semaphore_signal(self.connectionSemaphore) - } - } - - /// Calls `readWriteBlock` with a writeable connection - public func readWrite(readWriteBlock: (connection: Connection) -> Void) { - let connection = writable - readWriteBlock(connection: connection) - } - - // Acquires a read/write connection to the database - var writeConnectionInit = dispatch_once_t() - - private var writable: Connection { - - dispatch_once(&writeConnectionInit) { - - let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - self.writeConnection = try! Connection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) - self.writeConnection.busyTimeout = 2 - - for setupProcessor in self.internalSetup.values { - try! setupProcessor(self.writeConnection) - } - - for setupProcessor in self.setup { - try! setupProcessor(self.writeConnection) - } - - } - - return writeConnection - } - - // Acquires a read only connection to the database - private var readable: Connection { - - var borrowed: Connection! - - dispatch_semaphore_wait(connectionSemaphore, DISPATCH_TIME_FOREVER) - dispatch_sync(lockQueue) { - - // Ensure database is open - self.writable - - let connection: Connection - - if let availableConnection = self.availableReadConnections.popLast() { - connection = availableConnection - } - else { - - let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - - connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) - connection.busyTimeout = 2 - - for (type, setupProcessor) in self.internalSetup { - if type == .WriteAheadLogging { - continue - } - try! setupProcessor(connection) - } - - for setupProcessor in self.setup { - try! setupProcessor(connection) - } - - } - - self.unavailableReadConnections.append(connection) - - borrowed = connection - } - - return borrowed - } - -} diff --git a/SQLite/Core/Dispatcher.swift b/SQLite/Core/Dispatcher.swift deleted file mode 100644 index f6cff005..00000000 --- a/SQLite/Core/Dispatcher.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Foundation - - -/// Block dispatch method -public protocol Dispatcher { - - /// Dispatches the provided block - func dispatch(block: dispatch_block_t) - -} - - -/// Dispatches block immediately on current thread -public final class ImmediateDispatcher : Dispatcher { - - public func dispatch(block: dispatch_block_t) { - block() - } - -} - - -/// Synchronously dispatches block on a serial -/// queue. Specifically allows reentrant calls -public final class ReentrantDispatcher : Dispatcher { - - static let queueKey = unsafeBitCast(ReentrantDispatcher.self, UnsafePointer.self) - - let queue : dispatch_queue_t - - let queueContext : UnsafeMutablePointer - - public init(_ name: String) { - queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL) - queueContext = unsafeBitCast(queue, UnsafeMutablePointer.self) - dispatch_queue_set_specific(queue, ReentrantDispatcher.queueKey, queueContext, nil) - } - - public func dispatch(block: dispatch_block_t) { - if dispatch_get_specific(ReentrantDispatcher.queueKey) == self.queueContext { - block() - } else { - dispatch_sync(self.queue, block) // FIXME: rdar://problem/21389236 - } - } - -} diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 30a57337..e45034ca 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -1051,6 +1051,7 @@ public struct Row { } // FIXME: rdar://problem/18673897 // subscript… + public subscript(column: Expression) -> Blob { return get(column) } diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift deleted file mode 100644 index 5a20485b..00000000 --- a/SQLiteTests/ConnectionPoolTests.swift +++ /dev/null @@ -1,190 +0,0 @@ -import XCTest -import SQLite - -class ConnectionPoolTests : SQLiteTestCase { - - var pool : ConnectionPool! - - override func setUp() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - pool = try! ConnectionPool("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - } - - func testConnectionSetupClosures() { - - pool.foreignKeys = true - pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } - pool.read { conn in - XCTAssertTrue(conn.scalar("PRAGMA foreign_keys") as! Int64 == 1) - } - - pool.readWrite { conn in - try! conn.execute("INSERT INTO test(value) VALUES (1)") - try! conn.execute("SELECT value FROM test") - } - - } - - func testConcurrentAccess2() { - - let threadCount = 20 - pool.readWrite { conn in - try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") - try! conn.execute("DELETE FROM test") - for threadNumber in 0..