From 68815609ab1173ec36e4978b2a060ff7c0a8a013 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 29 Nov 2015 01:54:06 -0700 Subject: [PATCH 01/13] Add connection pool for concurrent access Uses WAL mode to support multiple reads and a single writer. --- SQLite.xcodeproj/project.pbxproj | 28 +++ SQLite/Core/Connection.swift | 256 +++++++++++++++++++++++--- SQLite/Core/ConnectionPool.swift | 144 +++++++++++++++ SQLite/Core/Dispatcher.swift | 55 ++++++ SQLiteTests/ConnectionPoolTests.swift | 57 ++++++ 5 files changed, 511 insertions(+), 29 deletions(-) create mode 100644 SQLite/Core/ConnectionPool.swift create mode 100644 SQLite/Core/Dispatcher.swift create mode 100644 SQLiteTests/ConnectionPoolTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d2ac9df4..edc79e3f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,17 @@ 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 */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -162,6 +173,9 @@ 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; path = ConnectionPool.swift; sourceTree = ""; }; + 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 = ""; }; @@ -388,6 +402,7 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( + AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -411,6 +426,8 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( + AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */, + AA780B3C1CC201A700E0E95E /* Dispatcher.swift */, EE91808D1C46E5230038162A /* SQLite-Bridging.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, @@ -780,9 +797,11 @@ 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 */, ); runOnlyForDeploymentPostprocessing = 0; @@ -792,6 +811,7 @@ 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 */, @@ -813,6 +833,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */, + AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -834,9 +856,11 @@ 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 */, ); runOnlyForDeploymentPostprocessing = 0; @@ -846,6 +870,7 @@ 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 */, @@ -881,9 +906,11 @@ 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 */, ); runOnlyForDeploymentPostprocessing = 0; @@ -893,6 +920,7 @@ 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 7d67b051..1b7b3d59 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -25,8 +25,207 @@ import Dispatch import CSQLite + +/// 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" + +} + + +/// Protocol to an SQLite connection +public protocol ConnectionType { + + /// Whether or not the database was opened in a read-only state. + var readonly : Bool { get } + + /// The last rowid inserted into the database via this connection. + var lastInsertRowid : Int64? { get } + + /// The last number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + var changes : Int { get } + + /// The total number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + var totalChanges : Int { get } + + // MARK: - Execute + + /// Executes a batch of SQL statements. + /// + /// - Parameter SQL: A batch of zero or more semicolon-separated SQL + /// statements. + /// + /// - Throws: `Result.Error` if query execution fails. + func execute(SQL: String) throws + + // MARK: - Prepare + + /// Prepares a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result func prepare(statement: String, _ bindings: Binding?...) throws -> Statement + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement + + // MARK: - Run + + /// Runs a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + func run(statement: String, _ bindings: Binding?...) throws -> Statement + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + func run(statement: String, _ bindings: [Binding?]) throws -> Statement + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement + + // MARK: - Scalar + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? + + // MARK: - Transactions + + // TODO: Consider not requiring a throw to roll back? + /// Runs a transaction with the given mode. + /// + /// - Note: Transactions cannot be nested. To nest transactions, see + /// `savepoint()`, instead. + /// + /// - Parameters: + /// + /// - mode: The mode in which a transaction acquires a lock. + /// + /// Default: `.Deferred` + /// + /// - block: A closure to run SQL statements within the transaction. + /// The transaction will be committed when the block returns. The block + /// must throw to roll the transaction back. + /// + /// - Throws: `Result.Error`, and rethrows. + func transaction(mode: TransactionMode, block: () throws -> Void) throws + + // 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 + /// generate a UUID). + /// + /// - SeeAlso: `transaction()`. + /// + /// - Parameters: + /// + /// - savepointName: A unique identifier for the savepoint (optional). + /// + /// - block: A closure to run SQL statements within the transaction. + /// The savepoint will be released (committed) when the block returns. + /// The block must throw to roll the savepoint back. + /// + /// - Throws: `SQLite.Result.Error`, and rethrows. + func savepoint(name: String, block: () throws -> Void) throws + +} + + /// A connection to SQLite. -public final class Connection { +public final class Connection : ConnectionType, Equatable { /// The location of a SQLite database. public enum Location { @@ -67,10 +266,27 @@ public final class Connection { /// Default: `false`. /// /// - Returns: A new database connection. - public init(_ location: Location = .InMemory, readonly: Bool = false) throws { + public convenience init(_ location: Location = .InMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) - dispatch_queue_set_specific(queue, Connection.queueKey, queueContext, nil) + try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection")) + } + + /// Initializes a new SQLite connection. + /// + /// - Parameters: + /// + /// - location: The location of the database. Creates a new database if it + /// doesn’t already exist (unless in read-only mode). + /// + /// - flags: SQLite open flags + /// + /// - dispatcher: Dispatcher synchronization blocks + /// + /// - Returns: A new database connection. + public init(_ location: Location, flags: Int32, dispatcher: Dispatcher) throws { + self.dispatcher = dispatcher + try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) + try check(sqlite3_extended_result_codes(handle, 1)) } /// Initializes a new connection to a database. @@ -265,20 +481,6 @@ public final class Connection { // 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. /// @@ -577,11 +779,7 @@ public final class Connection { } } - if dispatch_get_specific(Connection.queueKey) == queueContext { - box() - } else { - dispatch_sync(queue, box) // FIXME: rdar://problem/21389236 - } + dispatcher.dispatch(box) if let failure = failure { try { () -> Void in throw failure }() @@ -597,12 +795,8 @@ public final class Connection { throw error } - - 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) + + private var dispatcher : Dispatcher } @@ -629,6 +823,10 @@ 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 new file mode 100644 index 00000000..5a6ee5b9 --- /dev/null +++ b/SQLite/Core/ConnectionPool.swift @@ -0,0 +1,144 @@ +// +// 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 + + +/// Connection pool delegate +public protocol ConnectionPoolDelegate { + + func pool(pool: ConnectionPool, shouldAddConnection: Connection) + func pool(pool: ConnectionPool, didAddConnection: Connection) + +} + + +// 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! + + public init(_ location: Connection.Location) throws { + self.location = location + self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool", DISPATCH_QUEUE_SERIAL) + + try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") + } + + // Connection that automatically returns itself + // to the pool when it goes out of scope + private class BorrowedConnection : ConnectionType, Equatable { + + let pool : ConnectionPool + let connection : Connection + + init(pool: ConnectionPool, connection: Connection) { + self.pool = pool + self.connection = connection + } + + deinit { + dispatch_sync(pool.lockQueue) { + if let index = self.pool.unavailableReadConnections.indexOf(self.connection) { + self.pool.unavailableReadConnections.removeAtIndex(index) + } + self.pool.availableReadConnections.append(self.connection) + } + } + + var readonly : Bool { return connection.readonly } + var lastInsertRowid : Int64? { return connection.lastInsertRowid } + var changes : Int { return connection.changes } + var totalChanges : Int { return connection.totalChanges } + + func execute(SQL: String) throws { return try connection.execute(SQL) } + @warn_unused_result func prepare(statement: String, _ bindings: Binding?...) throws -> Statement { return try connection.prepare(statement, bindings) } + @warn_unused_result func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement { return try connection.prepare(statement, bindings) } + @warn_unused_result func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try connection.prepare(statement, bindings) } + + func run(statement: String, _ bindings: Binding?...) throws -> Statement { return try connection.run(statement, bindings) } + func run(statement: String, _ bindings: [Binding?]) throws -> Statement { return try connection.run(statement, bindings) } + func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try connection.run(statement, bindings) } + + @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? { return connection.scalar(statement, bindings) } + @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { return connection.scalar(statement, bindings) } + @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { return connection.scalar(statement, bindings) } + + func transaction(mode: TransactionMode, block: () throws -> Void) throws { return try connection.transaction(mode, block: block) } + func savepoint(name: String, block: () throws -> Void) throws { return try connection.savepoint(name, block: block) } + + } + + + // Acquires a read/write connection to the database + public var writable : Connection { + + var writeConnectionInit = dispatch_once_t() + dispatch_once(&writeConnectionInit) { + let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL + self.writeConnection = try! Connection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) + self.writeConnection.busyTimeout = 2 + } + + return writeConnection + } + + // Acquires a read only connection to the database + public var readable : ConnectionType { + + var borrowed : BorrowedConnection! + + dispatch_sync(lockQueue) { + + let connection : Connection + + if let availableConnection = self.availableReadConnections.popLast() { + connection = availableConnection + } + else { + let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL + connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + connection.busyTimeout = 2 + } + + self.unavailableReadConnections.append(connection) + + borrowed = BorrowedConnection(pool: self, connection: connection) + } + + return borrowed + } + +} + + +private func ==(lhs: ConnectionPool.BorrowedConnection, rhs: ConnectionPool.BorrowedConnection) -> Bool { + return lhs.connection == rhs.connection +} diff --git a/SQLite/Core/Dispatcher.swift b/SQLite/Core/Dispatcher.swift new file mode 100644 index 00000000..b5010488 --- /dev/null +++ b/SQLite/Core/Dispatcher.swift @@ -0,0 +1,55 @@ +// +// Dispatcher.swift +// SQLite +// +// Created by Kevin Wooten on 11/28/15. +// Copyright © 2015 stephencelis. All rights reserved. +// + +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/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift new file mode 100644 index 00000000..e79d3a8c --- /dev/null +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -0,0 +1,57 @@ +import XCTest +import SQLite + +class ConnectionPoolTests : SQLiteTestCase { + + override func setUp() { + super.setUp() + } + + func testConcurrentAccess() { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + + let conn = pool.writable + try! conn.execute("CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY, name TEXT)") + try! conn.execute("DELETE FROM test") + try! conn.execute("INSERT INTO test(id,name) VALUES(0, 'test0')") + try! conn.execute("INSERT INTO test(id,name) VALUES(1, 'test1')") + try! conn.execute("INSERT INTO test(id,name) VALUES(2, 'test2')") + try! conn.execute("INSERT INTO test(id,name) VALUES(3, 'test3')") + try! conn.execute("INSERT INTO test(id,name) VALUES(4, 'test4')") + + var quit = false + let queue = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT) + for x in 0..<5 { + var reads = 0 + let ex = expectationWithDescription("thread" + String(x)) + dispatch_async(queue) { + print("started", x) + let conn = pool.readable + var curr = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + while !quit { + let now = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + if now != curr { + print(now) + curr = now + } + reads += 1 + } + print("ended at", reads, "reads") + ex.fulfill() + } + } + + for x in 10..<500 { + let name = "test" + String(x) + let idx = Int(rand()) % 5 + try! conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + usleep(15000) + } + + quit = true + waitForExpectationsWithTimeout(1000, handler: nil) + } + +} From 42ebd18a06e06319933ce866dd9fbc84faf2e03a Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 29 Nov 2015 03:06:14 -0700 Subject: [PATCH 02/13] Add delegate to connection pool Allows customizing new connections once created --- SQLite/Core/ConnectionPool.swift | 53 +++++++++++++++++++-------- SQLiteTests/ConnectionPoolTests.swift | 38 +++++++++++++++++-- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 5a6ee5b9..d4a0c3f5 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -28,7 +28,7 @@ import Foundation /// Connection pool delegate public protocol ConnectionPoolDelegate { - func pool(pool: ConnectionPool, shouldAddConnection: Connection) + func poolShouldAddConnection(pool: ConnectionPool) -> Bool func pool(pool: ConnectionPool, didAddConnection: Connection) } @@ -45,6 +45,8 @@ public final class ConnectionPool { private let lockQueue : dispatch_queue_t private var writeConnection : Connection! + public var delegate : ConnectionPoolDelegate? + public init(_ location: Connection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool", DISPATCH_QUEUE_SERIAL) @@ -52,6 +54,14 @@ public final class ConnectionPool { try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") } + public var totalReadableConnectionCount : Int { + return availableReadConnections.count + unavailableReadConnections.count + } + + public var availableReadableConnectionCount : Int { + return availableReadConnections.count + } + // Connection that automatically returns itself // to the pool when it goes out of scope private class BorrowedConnection : ConnectionType, Equatable { @@ -98,7 +108,7 @@ public final class ConnectionPool { // Acquires a read/write connection to the database - public var writable : Connection { + public var writable : ConnectionType { var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { @@ -115,23 +125,34 @@ public final class ConnectionPool { var borrowed : BorrowedConnection! - dispatch_sync(lockQueue) { - - let connection : Connection + repeat { - if let availableConnection = self.availableReadConnections.popLast() { - connection = availableConnection - } - else { - let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL - connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) - connection.busyTimeout = 2 + dispatch_sync(lockQueue) { + + let connection : Connection + + if let availableConnection = self.availableReadConnections.popLast() { + connection = availableConnection + } + else if self.delegate?.poolShouldAddConnection(self) ?? true { + + let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL + connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + connection.busyTimeout = 2 + + self.delegate?.pool(self, didAddConnection: connection) + + } + else { + return + } + + self.unavailableReadConnections.append(connection) + + borrowed = BorrowedConnection(pool: self, connection: connection) } - self.unavailableReadConnections.append(connection) - - borrowed = BorrowedConnection(pool: self, connection: connection) - } + } while borrowed == nil return borrowed } diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index e79d3a8c..12e6402f 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -9,7 +9,7 @@ class ConnectionPoolTests : SQLiteTestCase { func testConcurrentAccess() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite") + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) let conn = pool.writable @@ -25,33 +25,63 @@ class ConnectionPoolTests : SQLiteTestCase { let queue = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT) for x in 0..<5 { var reads = 0 + let ex = expectationWithDescription("thread" + String(x)) + dispatch_async(queue) { + print("started", x) + let conn = pool.readable - var curr = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + + let stmt = conn.prepare("SELECT name FROM test WHERE id = ?") + var curr = stmt.scalar(x) as! String while !quit { - let now = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + + let now = stmt.scalar(x) as! String if now != curr { print(now) curr = now } reads += 1 } + print("ended at", reads, "reads") + ex.fulfill() } + } for x in 10..<500 { + let name = "test" + String(x) let idx = Int(rand()) % 5 - try! conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + + do { + try conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + } + catch let error { + XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") + } + usleep(15000) } quit = true waitForExpectationsWithTimeout(1000, handler: nil) } + + func testAutoRelease() { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + + do { + try! pool.readable.execute("SELECT 1") + } + + XCTAssertEqual(pool.totalReadableConnectionCount, pool.availableReadableConnectionCount) + } } From 68f46d5069e0e146055103c6501cc36369181484 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Fri, 15 Apr 2016 21:46:45 -0700 Subject: [PATCH 03/13] Better names for protocols and classes ConnectionType is now Connection and Connection is now DBConnection --- SQLite/Core/Connection.swift | 38 +++++++++++++------------- SQLite/Core/ConnectionPool.swift | 39 +++++++++++++++------------ SQLite/Core/Statement.swift | 4 +-- SQLite/Extensions/FTS4.swift | 2 +- SQLite/Typed/CustomFunctions.swift | 2 +- SQLite/Typed/Query.swift | 2 +- SQLiteTests/ConnectionPoolTests.swift | 8 +++--- SQLiteTests/ConnectionTests.swift | 38 +++++++++++++------------- SQLiteTests/TestHelpers.swift | 2 +- 9 files changed, 71 insertions(+), 64 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 1b7b3d59..e1b82b76 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -42,7 +42,7 @@ public enum TransactionMode : String { /// Protocol to an SQLite connection -public protocol ConnectionType { +public protocol Connection { /// Whether or not the database was opened in a read-only state. var readonly : Bool { get } @@ -201,8 +201,8 @@ public protocol ConnectionType { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - func transaction(mode: TransactionMode, block: () throws -> Void) throws - + func transaction(mode: TransactionMode, block: (Connection) throws -> Void) throws + // 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 @@ -219,13 +219,15 @@ public protocol ConnectionType { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - func savepoint(name: String, block: () throws -> Void) throws + func savepoint(name: String, block: (Connection) throws -> Void) throws + func sync(block: () throws -> T) rethrows -> T + } /// A connection to SQLite. -public final class Connection : ConnectionType, Equatable { +public final class DBConnection : Connection, Equatable { /// The location of a SQLite database. public enum Location { @@ -498,10 +500,10 @@ public final class Connection : ConnectionType, Equatable { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(mode: TransactionMode = .Deferred, block: () throws -> Void) throws { + public func transaction(mode: TransactionMode = .Deferred, block: (Connection) 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 @@ -518,18 +520,18 @@ public final class Connection : ConnectionType, Equatable { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - public func savepoint(name: String = NSUUID().UUIDString, block: () throws -> Void) throws { + public func savepoint(name: String = NSUUID().UUIDString, block: (Connection) 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: () throws -> Void, _ commit: String, or rollback: String) throws { + + private func transaction(begin: String, _ block: (Connection) throws -> Void, _ commit: String, or rollback: String) throws { return try sync { try self.run(begin) do { - try block() + try block(self) } catch { try self.run(rollback) throw error @@ -537,7 +539,7 @@ public final class Connection : ConnectionType, Equatable { try self.run(commit) } } - + /// Interrupts any long-running queries. public func interrupt() { sqlite3_interrupt(handle) @@ -767,7 +769,7 @@ public final class Connection : ConnectionType, Equatable { // MARK: - Error Handling - func sync(block: () throws -> T) rethrows -> T { + public func sync(block: () throws -> T) rethrows -> T { var success: T? var failure: ErrorType? @@ -800,7 +802,7 @@ public final class Connection : ConnectionType, Equatable { } -extension Connection : CustomStringConvertible { +extension DBConnection : CustomStringConvertible { public var description: String { return String.fromCString(sqlite3_db_filename(handle, nil))! @@ -808,7 +810,7 @@ extension Connection : CustomStringConvertible { } -extension Connection.Location : CustomStringConvertible { +extension DBConnection.Location : CustomStringConvertible { public var description: String { switch self { @@ -823,7 +825,7 @@ extension Connection.Location : CustomStringConvertible { } -public func ==(lhs: Connection, rhs: Connection) -> Bool { +public func ==(lhs: DBConnection, rhs: DBConnection) -> Bool { return lhs === rhs } @@ -860,7 +862,7 @@ public enum Result : ErrorType { case Error(message: String, code: Int32, statement: Statement?) - init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { + init?(errorCode: Int32, connection: DBConnection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String.fromCString(sqlite3_errmsg(connection.handle))! diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index d4a0c3f5..87c12845 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -39,18 +39,19 @@ public protocol ConnectionPoolDelegate { // WAL mode. public final class ConnectionPool { - private let location : Connection.Location - private var availableReadConnections = [Connection]() - private var unavailableReadConnections = [Connection]() + private let location : DBConnection.Location + private var availableReadConnections = [DBConnection]() + private var unavailableReadConnections = [DBConnection]() private let lockQueue : dispatch_queue_t - private var writeConnection : Connection! + private var writeConnection : DBConnection! + private let writeQueue : dispatch_queue_t public var delegate : ConnectionPoolDelegate? - public init(_ location: Connection.Location) throws { + public init(_ location: DBConnection.Location) throws { self.location = location - self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool", DISPATCH_QUEUE_SERIAL) - + self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) + self.writeQueue = dispatch_queue_create("SQLite.ConnectionPool.Write", DISPATCH_QUEUE_SERIAL) try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") } @@ -64,12 +65,12 @@ public final class ConnectionPool { // Connection that automatically returns itself // to the pool when it goes out of scope - private class BorrowedConnection : ConnectionType, Equatable { + private class BorrowedConnection : Connection, Equatable { let pool : ConnectionPool - let connection : Connection + let connection : DBConnection - init(pool: ConnectionPool, connection: Connection) { + init(pool: ConnectionPool, connection: DBConnection) { self.pool = pool self.connection = connection } @@ -101,19 +102,23 @@ public final class ConnectionPool { @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { return connection.scalar(statement, bindings) } @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { return connection.scalar(statement, bindings) } - func transaction(mode: TransactionMode, block: () throws -> Void) throws { return try connection.transaction(mode, block: block) } - func savepoint(name: String, block: () throws -> Void) throws { return try connection.savepoint(name, block: block) } + func transaction(mode: TransactionMode, block: (Connection) throws -> Void) throws { return try connection.transaction(mode, block: block) } + func savepoint(name: String, block: (Connection) throws -> Void) throws { return try connection.savepoint(name, block: block) } + + func sync(block: () throws -> T) rethrows -> T { return try connection.sync(block) } + func check(resultCode: Int32, statement: Statement? = nil) throws -> Int32 { return try connection.check(resultCode, statement: statement) } } // Acquires a read/write connection to the database - public var writable : ConnectionType { + public var writable : DBConnection { + var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL - self.writeConnection = try! Connection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) + self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) self.writeConnection.busyTimeout = 2 } @@ -121,7 +126,7 @@ public final class ConnectionPool { } // Acquires a read only connection to the database - public var readable : ConnectionType { + public var readable : Connection { var borrowed : BorrowedConnection! @@ -129,7 +134,7 @@ public final class ConnectionPool { dispatch_sync(lockQueue) { - let connection : Connection + let connection : DBConnection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection @@ -137,7 +142,7 @@ public final class ConnectionPool { else if self.delegate?.poolShouldAddConnection(self) ?? true { let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL - connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) connection.busyTimeout = 2 self.delegate?.pool(self, didAddConnection: connection) diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 39fb000d..07942ae0 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -29,9 +29,9 @@ public final class Statement { private var handle: COpaquePointer = nil - private let connection: Connection + private let connection: DBConnection - init(_ connection: Connection, _ SQL: String) throws { + init(_ connection: DBConnection, _ SQL: String) throws { self.connection = connection try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index e5a1d815..351bb721 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -140,7 +140,7 @@ extension Tokenizer : CustomStringConvertible { } -extension Connection { +extension DBConnection { public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in diff --git a/SQLite/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift index 068d0340..e32ab3c4 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public extension Connection { +public extension DBConnection { /// Creates or redefines a custom SQL function. /// diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index e45034ca..9f966250 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -1051,7 +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 index 12e6402f..349891ec 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -10,7 +10,7 @@ class ConnectionPoolTests : SQLiteTestCase { func testConcurrentAccess() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) let conn = pool.writable try! conn.execute("CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY, name TEXT)") @@ -53,7 +53,7 @@ class ConnectionPoolTests : SQLiteTestCase { } - for x in 10..<500 { + for x in 10..<50000 { let name = "test" + String(x) let idx = Int(rand()) % 5 @@ -75,7 +75,7 @@ class ConnectionPoolTests : SQLiteTestCase { func testAutoRelease() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) do { try! pool.readable.execute("SELECT 1") @@ -83,5 +83,5 @@ class ConnectionPoolTests : SQLiteTestCase { XCTAssertEqual(pool.totalReadableConnectionCount, pool.availableReadableConnectionCount) } - + } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index aeec9b72..ab43c52d 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -10,27 +10,27 @@ class ConnectionTests : SQLiteTestCase { } func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! Connection(.InMemory) + let db = try! DBConnection(.InMemory) XCTAssertEqual("", db.description) } func test_init_returnsInMemoryByDefault() { - let db = try! Connection() + let db = try! DBConnection() XCTAssertEqual("", db.description) } func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! Connection(.Temporary) + let db = try! DBConnection(.Temporary) XCTAssertEqual("", db.description) } func test_init_withURI_returnsURIConnection() { - let db = try! Connection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + let db = try! DBConnection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } func test_init_withString_returnsURIConnection() { - let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + let db = try! DBConnection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } @@ -39,7 +39,7 @@ class ConnectionTests : SQLiteTestCase { } func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = try! Connection(readonly: true) + let db = try! DBConnection(readonly: true) XCTAssertTrue(db.readonly) } @@ -95,19 +95,19 @@ class ConnectionTests : SQLiteTestCase { } func test_transaction_executesBeginDeferred() { - try! db.transaction(.Deferred) {} + try! db.transaction(.Deferred) {_ in } AssertSQL("BEGIN DEFERRED TRANSACTION") } func test_transaction_executesBeginImmediate() { - try! db.transaction(.Immediate) {} + try! db.transaction(.Immediate) {_ in } AssertSQL("BEGIN IMMEDIATE TRANSACTION") } func test_transaction_executesBeginExclusive() { - try! db.transaction(.Exclusive) {} + try! db.transaction(.Exclusive) {_ in } AssertSQL("BEGIN EXCLUSIVE TRANSACTION") } @@ -115,7 +115,7 @@ class ConnectionTests : SQLiteTestCase { func test_transaction_beginsAndCommitsTransactions() { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") - try! db.transaction { + try! db.transaction {_ in try stmt.run() } @@ -129,7 +129,7 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { - try db.transaction { + try db.transaction {_ in try stmt.run() try stmt.run() } @@ -145,8 +145,8 @@ class ConnectionTests : SQLiteTestCase { func test_savepoint_beginsAndCommitsSavepoints() { let db = self.db - try! db.savepoint("1") { - try db.savepoint("2") { + try! db.savepoint("1") {_ in + try db.savepoint("2") {_ in try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") } } @@ -165,13 +165,13 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { - try db.savepoint("1") { - try db.savepoint("2") { + try db.savepoint("1") {_ in + try db.savepoint("2") {_ in try stmt.run() try stmt.run() try stmt.run() } - try db.savepoint("2") { + try db.savepoint("2") {_ in try stmt.run() try stmt.run() try stmt.run() @@ -235,7 +235,7 @@ class ConnectionTests : SQLiteTestCase { db.commitHook { done() } - try! db.transaction { + try! db.transaction {_ in try self.InsertUser("alice") } XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) @@ -246,7 +246,7 @@ class ConnectionTests : SQLiteTestCase { async { done in db.rollbackHook(done) do { - try db.transaction { + try db.transaction {_ in try self.InsertUser("alice") try self.InsertUser("alice") // throw } @@ -263,7 +263,7 @@ class ConnectionTests : SQLiteTestCase { } db.rollbackHook(done) do { - try db.transaction { + try db.transaction {_ in try self.InsertUser("alice") } } catch { diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 464b9c27..0854b410 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -5,7 +5,7 @@ class SQLiteTestCase : XCTestCase { var trace = [String: Int]() - let db = try! Connection() + let db = try! DBConnection() let users = Table("users") From 66b82b0e0ac957518ce8afe115538c41a080f47f Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Fri, 15 Apr 2016 23:25:26 -0700 Subject: [PATCH 04/13] Use vfs for exclusivity vfs seems to be a more reliable handling of exclusive mode. --- SQLite/Core/Connection.swift | 13 +++++++++---- SQLite/Core/ConnectionPool.swift | 22 ++++++++++++++-------- SQLiteTests/ConnectionPoolTests.swift | 4 ++-- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index e1b82b76..a2574af2 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -268,9 +268,9 @@ public final class DBConnection : Connection, Equatable { /// Default: `false`. /// /// - Returns: A new database connection. - public convenience init(_ location: Location = .InMemory, readonly: Bool = false) throws { + public convenience init(_ location: Location = .InMemory, readonly: Bool = false, vfsName: String? = nil) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection")) + try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection"), vfsName: vfsName) } /// Initializes a new SQLite connection. @@ -285,9 +285,14 @@ public final class DBConnection : Connection, Equatable { /// - dispatcher: Dispatcher synchronization blocks /// /// - Returns: A new database connection. - public init(_ location: Location, flags: Int32, dispatcher: Dispatcher) throws { + public init(_ location: Location, flags: Int32, dispatcher: Dispatcher, vfsName: String? = nil) throws { self.dispatcher = dispatcher - try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) + 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)) } diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 87c12845..c231f4ce 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -25,6 +25,9 @@ import Foundation +private let vfsName = "unix-excl" + + /// Connection pool delegate public protocol ConnectionPoolDelegate { @@ -44,15 +47,13 @@ public final class ConnectionPool { private var unavailableReadConnections = [DBConnection]() private let lockQueue : dispatch_queue_t private var writeConnection : DBConnection! - private let writeQueue : dispatch_queue_t public var delegate : ConnectionPoolDelegate? public init(_ location: DBConnection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) - self.writeQueue = dispatch_queue_create("SQLite.ConnectionPool.Write", DISPATCH_QUEUE_SERIAL) - try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") + try writable.execute("PRAGMA journal_mode = WAL;") } public var totalReadableConnectionCount : Int { @@ -114,12 +115,16 @@ public final class ConnectionPool { // Acquires a read/write connection to the database public var writable : DBConnection { - var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { - let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL - self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) + + let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX + self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) self.writeConnection.busyTimeout = 2 + + if let delegate = self.delegate { + delegate.pool(self, didAddConnection: self.writeConnection) + } } return writeConnection @@ -141,8 +146,9 @@ public final class ConnectionPool { } else if self.delegate?.poolShouldAddConnection(self) ?? true { - let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL - connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX + + connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) connection.busyTimeout = 2 self.delegate?.pool(self, didAddConnection: connection) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 349891ec..47ee1f31 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -34,7 +34,7 @@ class ConnectionPoolTests : SQLiteTestCase { let conn = pool.readable - let stmt = conn.prepare("SELECT name FROM test WHERE id = ?") + let stmt = try! conn.prepare("SELECT name FROM test WHERE id = ?") var curr = stmt.scalar(x) as! String while !quit { @@ -65,7 +65,7 @@ class ConnectionPoolTests : SQLiteTestCase { XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") } - usleep(15000) + usleep(1500) } quit = true From ccc20e574a6bab6be91f452f0ecf141b18011deb Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sat, 16 Apr 2016 01:15:49 -0700 Subject: [PATCH 05/13] Added alternate concurrency test --- SQLiteTests/ConnectionPoolTests.swift | 52 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 47ee1f31..ce460fe5 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -7,13 +7,13 @@ class ConnectionPoolTests : SQLiteTestCase { super.setUp() } - func testConcurrentAccess() { + func testConcurrentAccess2() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) let conn = pool.writable - try! conn.execute("CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY, name TEXT)") + try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") try! conn.execute("DELETE FROM test") try! conn.execute("INSERT INTO test(id,name) VALUES(0, 'test0')") try! conn.execute("INSERT INTO test(id,name) VALUES(1, 'test1')") @@ -40,7 +40,7 @@ class ConnectionPoolTests : SQLiteTestCase { let now = stmt.scalar(x) as! String if now != curr { - print(now) + //print(now) curr = now } reads += 1 @@ -53,7 +53,7 @@ class ConnectionPoolTests : SQLiteTestCase { } - for x in 10..<50000 { + for x in 10..<5000 { let name = "test" + String(x) let idx = Int(rand()) % 5 @@ -65,13 +65,55 @@ class ConnectionPoolTests : SQLiteTestCase { XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") } - usleep(1500) + usleep(500) } quit = true waitForExpectationsWithTimeout(1000, handler: nil) } + func testConcurrentAccess() throws { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) + + try! pool.writable.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") + try! pool.writable.run("INSERT INTO test(value) VALUES(?)", 0) + + let q = dispatch_queue_create("Readers/Writers", DISPATCH_QUEUE_CONCURRENT); + var finished = false + + for _ in 0..<5 { + + dispatch_async(q) { + + while !finished { + + let val = pool.readable.scalar("SELECT value FROM test") + assert(val != nil, "DB query returned nil result set") + + } + + } + + } + + for c in 0..<5000 { + + try pool.writable.run("INSERT INTO test(value) VALUES(?)", c) + + usleep(100); + + } + + finished = true + + // Wait for readers to finish + dispatch_barrier_sync(q) { + } + + } + func testAutoRelease() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") From 18f612fd112bdedc6fecec558fb27cd119a2c59e Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sat, 16 Apr 2016 01:23:10 -0700 Subject: [PATCH 06/13] Fix imports in ConnectionPool.swift --- SQLite/Core/ConnectionPool.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index c231f4ce..5166e6eb 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -22,7 +22,8 @@ // THE SOFTWARE. // -import Foundation +import Dispatch +import CSQLite private let vfsName = "unix-excl" From 2a5908b513d5b44b1070f2667011f1972482bbd4 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 17 Apr 2016 15:55:59 -0700 Subject: [PATCH 07/13] Rename `DBConnection` to `DirectConnection` --- SQLite.xcodeproj/project.pbxproj | 12 ++++++------ SQLite/Core/Connection.swift | 10 +++++----- SQLite/Core/ConnectionPool.swift | 22 +++++++++++----------- SQLite/Core/Statement.swift | 4 ++-- SQLite/Extensions/FTS4.swift | 2 +- SQLite/Typed/CustomFunctions.swift | 2 +- SQLiteTests/ConnectionTests.swift | 12 ++++++------ SQLiteTests/TestHelpers.swift | 2 +- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index edc79e3f..6b145ca1 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -173,7 +173,7 @@ 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; path = ConnectionPool.swift; sourceTree = ""; }; + 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; }; @@ -182,19 +182,19 @@ 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; path = Connection.swift; sourceTree = ""; }; + EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Connection.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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; path = Statement.swift; sourceTree = ""; }; + EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Statement.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; - EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; + EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = FTS4.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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; path = CustomFunctions.swift; sourceTree = ""; }; + EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CustomFunctions.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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 = ""; }; @@ -204,7 +204,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; path = ConnectionTests.swift; sourceTree = ""; }; + EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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 = ""; }; diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index a2574af2..4ae468dd 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -227,7 +227,7 @@ public protocol Connection { /// A connection to SQLite. -public final class DBConnection : Connection, Equatable { +public final class DirectConnection : Connection, Equatable { /// The location of a SQLite database. public enum Location { @@ -807,7 +807,7 @@ public final class DBConnection : Connection, Equatable { } -extension DBConnection : CustomStringConvertible { +extension DirectConnection : CustomStringConvertible { public var description: String { return String.fromCString(sqlite3_db_filename(handle, nil))! @@ -815,7 +815,7 @@ extension DBConnection : CustomStringConvertible { } -extension DBConnection.Location : CustomStringConvertible { +extension DirectConnection.Location : CustomStringConvertible { public var description: String { switch self { @@ -830,7 +830,7 @@ extension DBConnection.Location : CustomStringConvertible { } -public func ==(lhs: DBConnection, rhs: DBConnection) -> Bool { +public func ==(lhs: DirectConnection, rhs: DirectConnection) -> Bool { return lhs === rhs } @@ -867,7 +867,7 @@ public enum Result : ErrorType { case Error(message: String, code: Int32, statement: Statement?) - init?(errorCode: Int32, connection: DBConnection, statement: Statement? = nil) { + init?(errorCode: Int32, connection: DirectConnection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String.fromCString(sqlite3_errmsg(connection.handle))! diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 5166e6eb..931e5874 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -43,15 +43,15 @@ public protocol ConnectionPoolDelegate { // WAL mode. public final class ConnectionPool { - private let location : DBConnection.Location - private var availableReadConnections = [DBConnection]() - private var unavailableReadConnections = [DBConnection]() + private let location : DirectConnection.Location + private var availableReadConnections = [DirectConnection]() + private var unavailableReadConnections = [DirectConnection]() private let lockQueue : dispatch_queue_t - private var writeConnection : DBConnection! + private var writeConnection : DirectConnection! public var delegate : ConnectionPoolDelegate? - public init(_ location: DBConnection.Location) throws { + public init(_ location: DirectConnection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) try writable.execute("PRAGMA journal_mode = WAL;") @@ -70,9 +70,9 @@ public final class ConnectionPool { private class BorrowedConnection : Connection, Equatable { let pool : ConnectionPool - let connection : DBConnection + let connection : DirectConnection - init(pool: ConnectionPool, connection: DBConnection) { + init(pool: ConnectionPool, connection: DirectConnection) { self.pool = pool self.connection = connection } @@ -114,13 +114,13 @@ public final class ConnectionPool { // Acquires a read/write connection to the database - public var writable : DBConnection { + public var writable : DirectConnection { var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) + self.writeConnection = try! DirectConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) self.writeConnection.busyTimeout = 2 if let delegate = self.delegate { @@ -140,7 +140,7 @@ public final class ConnectionPool { dispatch_sync(lockQueue) { - let connection : DBConnection + let connection : DirectConnection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection @@ -149,7 +149,7 @@ public final class ConnectionPool { let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) + connection = try! DirectConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) connection.busyTimeout = 2 self.delegate?.pool(self, didAddConnection: connection) diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 07942ae0..d7652c1a 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -29,9 +29,9 @@ public final class Statement { private var handle: COpaquePointer = nil - private let connection: DBConnection + private let connection: DirectConnection - init(_ connection: DBConnection, _ SQL: String) throws { + init(_ connection: DirectConnection, _ SQL: String) throws { self.connection = connection try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index 351bb721..045e5303 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -140,7 +140,7 @@ extension Tokenizer : CustomStringConvertible { } -extension DBConnection { +extension DirectConnection { public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in diff --git a/SQLite/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift index e32ab3c4..d004f75e 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public extension DBConnection { +public extension DirectConnection { /// Creates or redefines a custom SQL function. /// diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index ab43c52d..bbad19a8 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -10,27 +10,27 @@ class ConnectionTests : SQLiteTestCase { } func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! DBConnection(.InMemory) + let db = try! DirectConnection(.InMemory) XCTAssertEqual("", db.description) } func test_init_returnsInMemoryByDefault() { - let db = try! DBConnection() + let db = try! DirectConnection() XCTAssertEqual("", db.description) } func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! DBConnection(.Temporary) + let db = try! DirectConnection(.Temporary) XCTAssertEqual("", db.description) } func test_init_withURI_returnsURIConnection() { - let db = try! DBConnection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + let db = try! DirectConnection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } func test_init_withString_returnsURIConnection() { - let db = try! DBConnection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + let db = try! DirectConnection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } @@ -39,7 +39,7 @@ class ConnectionTests : SQLiteTestCase { } func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = try! DBConnection(readonly: true) + let db = try! DirectConnection(readonly: true) XCTAssertTrue(db.readonly) } diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 0854b410..855bf7bd 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -5,7 +5,7 @@ class SQLiteTestCase : XCTestCase { var trace = [String: Int]() - let db = try! DBConnection() + let db = try! DirectConnection() let users = Table("users") From 9548d172655ed841be1ba31600cf47417c58389c Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Mon, 18 Apr 2016 14:41:19 -0700 Subject: [PATCH 08/13] Replace pool delegate with setup closures --- SQLite/Core/ConnectionPool.swift | 58 ++++++++++++++++++--------- SQLiteTests/ConnectionPoolTests.swift | 13 ++++++ 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 931e5874..d430d7cf 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -29,15 +29,6 @@ import CSQLite private let vfsName = "unix-excl" -/// Connection pool delegate -public protocol ConnectionPoolDelegate { - - func poolShouldAddConnection(pool: ConnectionPool) -> Bool - func pool(pool: ConnectionPool, didAddConnection: Connection) - -} - - // Connection pool for accessing an SQLite database // with multiple readers & a single writer. Utilizes // WAL mode. @@ -49,12 +40,29 @@ public final class ConnectionPool { private let lockQueue : dispatch_queue_t private var writeConnection : DirectConnection! - public var delegate : ConnectionPoolDelegate? + 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]() public init(_ location: DirectConnection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) - try writable.execute("PRAGMA journal_mode = WAL;") + self.internalSetup[.WriteAheadLogging] = { try $0.execute("PRAGMA journal_mode = WAL;") } } public var totalReadableConnectionCount : Int { @@ -123,9 +131,14 @@ public final class ConnectionPool { self.writeConnection = try! DirectConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) self.writeConnection.busyTimeout = 2 - if let delegate = self.delegate { - delegate.pool(self, didAddConnection: self.writeConnection) + for setupProcessor in self.internalSetup.values { + try! setupProcessor(self.writeConnection) } + + for setupProcessor in self.setup { + try! setupProcessor(self.writeConnection) + } + } return writeConnection @@ -140,23 +153,32 @@ public final class ConnectionPool { dispatch_sync(lockQueue) { + // Ensure database is open + self.writable + let connection : DirectConnection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection } - else if self.delegate?.poolShouldAddConnection(self) ?? true { + else { let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX connection = try! DirectConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) connection.busyTimeout = 2 - self.delegate?.pool(self, didAddConnection: connection) + for (type, setupProcessor) in self.internalSetup { + if type == .WriteAheadLogging { + continue + } + try! setupProcessor(connection) + } + + for setupProcessor in self.setup { + try! setupProcessor(connection) + } - } - else { - return } self.unavailableReadConnections.append(connection) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index ce460fe5..b621396c 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -6,6 +6,19 @@ class ConnectionPoolTests : SQLiteTestCase { override func setUp() { super.setUp() } + + func testConnectionSetupClosures() { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) + + pool.foreignKeys = true + pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } + + XCTAssertTrue(try pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) + try! pool.writable.execute("INSERT INTO test(value) VALUES (1)") + try! pool.readable.execute("SELECT value FROM test") + } func testConcurrentAccess2() { From ffb92745ad76b7a5eb2e3e293da755fa8d760a7a Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Mon, 18 Apr 2016 17:18:22 -0700 Subject: [PATCH 09/13] Fix writable connection initialization Was using incorrect dispatch_once token. --- SQLite/Core/ConnectionPool.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index d430d7cf..dd644304 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -122,9 +122,11 @@ public final class ConnectionPool { // Acquires a read/write connection to the database + + var writeConnectionInit = dispatch_once_t() + public var writable : DirectConnection { - - var writeConnectionInit = dispatch_once_t() + dispatch_once(&writeConnectionInit) { let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX From b7a0eb1a58936071a6d171036f3e36f75af8f401 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Mon, 18 Apr 2016 17:51:38 -0700 Subject: [PATCH 10/13] Cleanup connection pool tests # Conflicts: # SQLiteTests/ConnectionPoolTests.swift --- SQLiteTests/ConnectionPoolTests.swift | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index b621396c..74351527 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -3,28 +3,25 @@ import SQLite class ConnectionPoolTests : SQLiteTestCase { + var pool : ConnectionPool! + override func setUp() { - super.setUp() + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) } func testConnectionSetupClosures() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - pool.foreignKeys = true pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } - XCTAssertTrue(try pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) + XCTAssertTrue(pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) try! pool.writable.execute("INSERT INTO test(value) VALUES (1)") try! pool.readable.execute("SELECT value FROM test") } func testConcurrentAccess2() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - let conn = pool.writable try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") try! conn.execute("DELETE FROM test") @@ -45,7 +42,7 @@ class ConnectionPoolTests : SQLiteTestCase { print("started", x) - let conn = pool.readable + let conn = self.pool.readable let stmt = try! conn.prepare("SELECT name FROM test WHERE id = ?") var curr = stmt.scalar(x) as! String @@ -87,9 +84,6 @@ class ConnectionPoolTests : SQLiteTestCase { func testConcurrentAccess() throws { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - try! pool.writable.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") try! pool.writable.run("INSERT INTO test(value) VALUES(?)", 0) @@ -102,7 +96,7 @@ class ConnectionPoolTests : SQLiteTestCase { while !finished { - let val = pool.readable.scalar("SELECT value FROM test") + let val = self.pool.readable.scalar("SELECT value FROM test") assert(val != nil, "DB query returned nil result set") } @@ -129,9 +123,6 @@ class ConnectionPoolTests : SQLiteTestCase { func testAutoRelease() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - do { try! pool.readable.execute("SELECT 1") } From b9247db36ddec4bd3304551a2ad003018e412a6f Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 17 Apr 2016 11:49:42 -0700 Subject: [PATCH 11/13] Convert fatalErrors to Swift error handling Most fatalErrors have been replaced with proper Swift error handling. Aside from being more "correct" Swift code this also allows errors during binding to be thrown from external code and handled in places like custom function invocation. --- SQLite.xcodeproj/project.pbxproj | 4 ++ SQLite/Core/Connection.swift | 73 +++++++++++++++++------------- SQLite/Core/Errors.swift | 58 ++++++++++++++++++++++++ SQLite/Core/Statement.swift | 36 +++++++-------- SQLite/Core/Value.swift | 16 +++---- SQLite/Foundation.swift | 12 ++--- SQLite/Helpers.swift | 8 ++-- SQLite/Typed/CoreFunctions.swift | 8 ++-- SQLite/Typed/CustomFunctions.swift | 68 ++++++++++++++-------------- SQLite/Typed/Expression.swift | 12 ++--- SQLite/Typed/Operators.swift | 16 +++---- SQLite/Typed/Query.swift | 60 ++++++++++++------------ SQLite/Typed/Setter.swift | 8 ++-- 13 files changed, 226 insertions(+), 153 deletions(-) create mode 100644 SQLite/Core/Errors.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 6b145ca1..a4f6b7a9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 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 */; }; + AA58505F1CC40BA70034C46D /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA58505E1CC40BA70034C46D /* Errors.swift */; }; 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 */; }; @@ -174,6 +175,7 @@ 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; }; + AA58505E1CC40BA70034C46D /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 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; }; @@ -435,6 +437,7 @@ EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, + AA58505E1CC40BA70034C46D /* Errors.swift */, ); path = Core; sourceTree = ""; @@ -855,6 +858,7 @@ EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */, EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, + AA58505F1CC40BA70034C46D /* Errors.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, AA780B3E1CC201A700E0E95E /* Dispatcher.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 4ae468dd..1ff8a0dc 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -701,48 +701,59 @@ public final class DirectConnection : Connection, Equatable { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - public func createFunction(function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { + public func createFunction(function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: (args: [Binding?]) throws -> Binding?) throws { let argc = argumentCount.map { Int($0) } ?? -1 let box: Function = { context, argc, argv in - let arguments: [Binding?] = (0...self), { context, argc, value in + try check(sqlite3_create_function_v2(handle, function, Int32(argc), flags, unsafeBitCast(box, UnsafeMutablePointer.self), { context, argc, value in unsafeBitCast(sqlite3_user_data(context), Function.self)(context, argc, value) - }, nil, nil, nil) + }, nil, nil, nil)) if functions[function] == nil { self.functions[function] = [:] } functions[function]?[argc] = box } diff --git a/SQLite/Core/Errors.swift b/SQLite/Core/Errors.swift new file mode 100644 index 00000000..f50fdb74 --- /dev/null +++ b/SQLite/Core/Errors.swift @@ -0,0 +1,58 @@ +// +// Errors.swift +// SQLite +// +// Created by Kevin Wooten on 4/17/16. +// +// + +import Foundation + + +public enum FunctionError : ErrorType, CustomStringConvertible { + case UnsupportedArgumentType(type: Int32) + case UnsupportedResultType(value: Binding) + + public var description : String { + switch self { + case UnsupportedArgumentType(let type): + return "Unsupported argument type: \(type)" + case UnsupportedResultType(let value): + return "Unsupported result type for value: \(value)" + } + } +} + +public enum BindingError : ErrorType, CustomStringConvertible { + case UnsupportedType(value: Binding) + case ParameterNotFound(name: String) + case IncorrectParameterCount(expected: Int, provided: Int) + + public var description : String { + switch self { + case UnsupportedType(let value): + return "Unsupported type for value: \(value)" + case ParameterNotFound(let name): + return "Parameter not found: \(name)" + case let IncorrectParameterCount(expected, provided): + return "Incorrect parameter count, \(provided) provided with \(expected) expected" + } + } +} + +public enum QueryError : ErrorType, CustomStringConvertible { + case NoSuchTable(name: String) + case NoSuchColumn(name: String) + case AmbiguousColumn(name: String) + + public var description : String { + switch self { + case NoSuchTable(let name): + return "No such table: \(name)" + case NoSuchColumn(let name): + return "No such column: \(name)" + case AmbiguousColumn(let name): + return "Ambiguous column: \(name)" + } + } +} diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index d7652c1a..459fcf3d 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -54,8 +54,8 @@ public final class Statement { /// - Parameter values: A list of parameters to bind to the statement. /// /// - Returns: The statement object (useful for chaining). - public func bind(values: Binding?...) -> Statement { - return bind(values) + public func bind(values: Binding?...) throws -> Statement { + return try bind(values) } /// Binds a list of parameters to a statement. @@ -63,13 +63,13 @@ public final class Statement { /// - Parameter values: A list of parameters to bind to the statement. /// /// - Returns: The statement object (useful for chaining). - public func bind(values: [Binding?]) -> Statement { + public func bind(values: [Binding?]) throws -> Statement { if values.isEmpty { return self } reset() guard values.count == Int(sqlite3_bind_parameter_count(handle)) else { - fatalError("\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed") + throw BindingError.IncorrectParameterCount(expected: Int(sqlite3_bind_parameter_count(handle)), provided: values.count) } - for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) } + for idx in 1...values.count { try bind(values[idx - 1], atIndex: idx) } return self } @@ -79,19 +79,19 @@ public final class Statement { /// statement. /// /// - Returns: The statement object (useful for chaining). - public func bind(values: [String: Binding?]) -> Statement { + public func bind(values: [String: Binding?]) throws -> Statement { reset() for (name, value) in values { let idx = sqlite3_bind_parameter_index(handle, name) guard idx > 0 else { - fatalError("parameter not found: \(name)") + throw BindingError.ParameterNotFound(name: name) } - bind(value, atIndex: Int(idx)) + try bind(value, atIndex: Int(idx)) } return self } - private func bind(value: Binding?, atIndex idx: Int) { + private func bind(value: Binding?, atIndex idx: Int) throws { if value == nil { sqlite3_bind_null(handle, Int32(idx)) } else if let value = value as? Blob { @@ -103,11 +103,11 @@ public final class Statement { } else if let value = value as? String { sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT) } else if let value = value as? Int { - self.bind(value.datatypeValue, atIndex: idx) + try self.bind(try value.datatypeValue(), atIndex: idx) } else if let value = value as? Bool { - self.bind(value.datatypeValue, atIndex: idx) + try self.bind(try value.datatypeValue(), atIndex: idx) } else if let value = value { - fatalError("tried to bind unexpected value \(value)") + throw BindingError.UnsupportedType(value: value) } } @@ -148,9 +148,9 @@ public final class Statement { /// - Parameter bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: Binding?...) -> Binding? { + @warn_unused_result public func scalar(bindings: Binding?...) throws -> Binding? { guard bindings.isEmpty else { - return scalar(bindings) + return try scalar(bindings) } reset(clearBindings: false) @@ -161,8 +161,8 @@ public final class Statement { /// - Parameter bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: [Binding?]) -> Binding? { - return bind(bindings).scalar() + @warn_unused_result public func scalar(bindings: [Binding?]) throws -> Binding? { + return try bind(bindings).scalar() } @@ -170,8 +170,8 @@ public final class Statement { /// statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: [String: Binding?]) -> Binding? { - return bind(bindings).scalar() + @warn_unused_result public func scalar(bindings: [String: Binding?]) throws -> Binding? { + return try bind(bindings).scalar() } public func step() throws -> Bool { diff --git a/SQLite/Core/Value.swift b/SQLite/Core/Value.swift index 2cfb45ed..f95ee2fd 100644 --- a/SQLite/Core/Value.swift +++ b/SQLite/Core/Value.swift @@ -39,9 +39,9 @@ public protocol Value : Expressible { // extensions cannot have inheritance clau static var declaredDatatype: String { get } - static func fromDatatypeValue(datatypeValue: Datatype) -> ValueType + static func fromDatatypeValue(datatypeValue: Datatype) throws -> ValueType - var datatypeValue: Datatype { get } + func datatypeValue() throws -> Datatype } @@ -53,7 +53,7 @@ extension Double : Number, Value { return datatypeValue } - public var datatypeValue: Double { + public func datatypeValue() throws -> Double { return self } @@ -67,7 +67,7 @@ extension Int64 : Number, Value { return datatypeValue } - public var datatypeValue: Int64 { + public func datatypeValue() throws -> Int64 { return self } @@ -81,7 +81,7 @@ extension String : Binding, Value { return datatypeValue } - public var datatypeValue: String { + public func datatypeValue() throws -> String { return self } @@ -95,7 +95,7 @@ extension Blob : Binding, Value { return datatypeValue } - public var datatypeValue: Blob { + public func datatypeValue() throws -> Blob { return self } @@ -111,7 +111,7 @@ extension Bool : Binding, Value { return datatypeValue != 0 } - public var datatypeValue: Int64 { + public func datatypeValue() throws -> Int64 { return self ? 1 : 0 } @@ -125,7 +125,7 @@ extension Int : Number, Value { return Int(datatypeValue) } - public var datatypeValue: Int64 { + public func datatypeValue() throws -> Int64 { return Int64(self) } diff --git a/SQLite/Foundation.swift b/SQLite/Foundation.swift index 52b2ea02..27fabb1a 100644 --- a/SQLite/Foundation.swift +++ b/SQLite/Foundation.swift @@ -34,7 +34,7 @@ extension NSData : Value { return NSData(bytes: dataValue.bytes, length: dataValue.bytes.count) } - public var datatypeValue: Blob { + public func datatypeValue() throws -> Blob { return Blob(bytes: bytes, length: length) } @@ -50,7 +50,7 @@ extension NSDate : Value { return dateFormatter.dateFromString(stringValue)! } - public var datatypeValue: String { + public func datatypeValue() throws -> String { return dateFormatter.stringFromDate(self) } @@ -90,17 +90,17 @@ extension QueryType { extension Row { public subscript(column: Expression) -> NSData { - return get(column) + return try! get(column) } public subscript(column: Expression) -> NSData? { - return get(column) + return try! get(column) } public subscript(column: Expression) -> NSDate { - return get(column) + return try! get(column) } public subscript(column: Expression) -> NSDate? { - return get(column) + return try! get(column) } } diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index 33fc6a62..c25f3ac9 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -115,10 +115,10 @@ extension String { } } -@warn_unused_result func value(v: Binding) -> A { - return A.fromDatatypeValue(v as! A.Datatype) as! A +@warn_unused_result func value(v: Binding) throws -> A { + return try A.fromDatatypeValue(v as! A.Datatype) as! A } -@warn_unused_result func value(v: Binding?) -> A { - return value(v!) +@warn_unused_result func value(v: Binding?) throws -> A { + return try value(v!) } diff --git a/SQLite/Typed/CoreFunctions.swift b/SQLite/Typed/CoreFunctions.swift index b929e40a..8bebc4c2 100644 --- a/SQLite/Typed/CoreFunctions.swift +++ b/SQLite/Typed/CoreFunctions.swift @@ -597,9 +597,9 @@ extension CollectionType where Generator.Element : Value, Index.Distance == Int /// /// - Returns: A copy of the expression prepended with an `IN` check against /// the collection. - @warn_unused_result public func contains(expression: Expression) -> Expression { + @warn_unused_result public func contains(expression: Expression) throws -> Expression { let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") - return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return "IN".infix(expression, Expression("(\(templates))", try map { try $0.datatypeValue() })) } /// Builds a copy of the expression prepended with an `IN` check against the @@ -613,9 +613,9 @@ extension CollectionType where Generator.Element : Value, Index.Distance == Int /// /// - Returns: A copy of the expression prepended with an `IN` check against /// the collection. - @warn_unused_result public func contains(expression: Expression) -> Expression { + @warn_unused_result public func contains(expression: Expression) throws -> Expression { let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") - return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return "IN".infix(expression, Expression("(\(templates))", try map { try $0.datatypeValue() })) } } diff --git a/SQLite/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift index d004f75e..2dc570cd 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -39,94 +39,94 @@ public extension DirectConnection { /// The assigned types must be explicit. /// /// - Returns: A closure returning an SQL expression to call the function. - public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z) throws -> (() -> Expression) { - let fn = try createFunction(function, 0, deterministic) { _ in block() } + public func createFunction(function: String, deterministic: Bool = false, _ block: () throws -> Z) throws -> (() -> Expression) { + let fn = try createFunction(function, 0, deterministic) { _ in try block() } return { fn([]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z?) throws -> (() -> Expression) { - let fn = try createFunction(function, 0, deterministic) { _ in block() } + public func createFunction(function: String, deterministic: Bool = false, _ block: () throws -> Z?) throws -> (() -> Expression) { + let fn = try createFunction(function, 0, deterministic) { _ in try block() } return { fn([]) } } // MARK: - - public func createFunction(function: String, deterministic: Bool = false, _ block: A -> Z) throws -> (Expression -> Expression) { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } + public func createFunction(function: String, deterministic: Bool = false, _ block: A throws -> Z) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in try block(try value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z) throws -> (Expression -> Expression) { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } + public func createFunction(function function: String, deterministic: Bool = false, _ block: A? throws -> Z) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in try block(try args[0].map(value)) } return { arg in fn([arg]) } } - public func createFunction(function function: String, deterministic: Bool = false, _ block: A -> Z?) throws -> (Expression -> Expression) { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } + public func createFunction(function function: String, deterministic: Bool = false, _ block: A throws -> Z?) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in try block(try value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z?) throws -> (Expression -> Expression) { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } + public func createFunction(function function: String, deterministic: Bool = false, _ block: A? throws -> Z?) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in try block(try args[0].map(value)) } return { arg in fn([arg]) } } // MARK: - - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) throws -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try value(args[0]), try value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) throws -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try args[0].map(value), try value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) throws -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try value(args[0]), try args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) throws -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try value(args[0]), try value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) throws -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try args[0].map(value), try args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) throws -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try args[0].map(value), try value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) throws -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try value(args[0]), try args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) throws -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in try block(try args[0].map(value), try args[1].map(value)) } return { a, b in fn([a, b]) } } // MARK: - - private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z) throws -> ([Expressible] -> Expression) { - createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in - block(arguments).datatypeValue + private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] throws -> Z) throws -> ([Expressible] -> Expression) { + try createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in + try block(arguments).datatypeValue() } return { arguments in function.quote().wrap(", ".join(arguments)) } } - private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z?) throws -> ([Expressible] -> Expression) { - createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in - block(arguments)?.datatypeValue + private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] throws -> Z?) throws -> ([Expressible] -> Expression) { + try createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in + try block(arguments)?.datatypeValue() } return { arguments in function.quote().wrap(", ".join(arguments)) diff --git a/SQLite/Typed/Expression.swift b/SQLite/Typed/Expression.swift index 2e71b970..5fb8a335 100644 --- a/SQLite/Typed/Expression.swift +++ b/SQLite/Typed/Expression.swift @@ -110,8 +110,8 @@ extension ExpressionType { extension ExpressionType where UnderlyingType : Value { - public init(value: UnderlyingType) { - self.init("?", [value.datatypeValue]) + public init(value: UnderlyingType) throws { + self.init("?", [try value.datatypeValue()]) } } @@ -119,11 +119,11 @@ extension ExpressionType where UnderlyingType : Value { extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { public static var null: Self { - return self.init(value: nil) + return try! self.init(value: nil) } - public init(value: UnderlyingType.WrappedType?) { - self.init("?", [value?.datatypeValue]) + public init(value: UnderlyingType.WrappedType?) throws { + self.init("?", [try value?.datatypeValue()]) } } @@ -131,7 +131,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension Value { public var expression: Expression { - return Expression(value: self).expression + return try! Expression(value: self).expression } } diff --git a/SQLite/Typed/Operators.swift b/SQLite/Typed/Operators.swift index 816560a5..a7f96816 100644 --- a/SQLite/Typed/Operators.swift +++ b/SQLite/Typed/Operators.swift @@ -335,15 +335,15 @@ public func ==(lhs: Expression, rhs: public func ==(lhs: Expression, rhs: V) -> Expression { return "=".infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V?) -> Expression { - guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } +public func ==(lhs: Expression, rhs: V?) throws -> Expression { + guard let rhs = rhs else { return "IS".infix(lhs, try Expression(value: nil)) } return "=".infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression { return "=".infix(lhs, rhs) } -public func ==(lhs: V?, rhs: Expression) -> Expression { - guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } +public func ==(lhs: V?, rhs: Expression) throws -> Expression { + guard let lhs = lhs else { return "IS".infix(try Expression(value: nil), rhs) } return "=".infix(lhs, rhs) } @@ -362,15 +362,15 @@ public func !=(lhs: Expression, rhs: public func !=(lhs: Expression, rhs: V) -> Expression { return infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V?) -> Expression { - guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } +public func !=(lhs: Expression, rhs: V?) throws -> Expression { + guard let rhs = rhs else { return "IS NOT".infix(lhs, try Expression(value: nil)) } return infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression { return infix(lhs, rhs) } -public func !=(lhs: V?, rhs: Expression) -> Expression { - guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } +public func !=(lhs: V?, rhs: Expression) throws -> Expression { + guard let lhs = lhs else { return "IS NOT".infix(try Expression(value: nil), rhs) } return infix(lhs, rhs) } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 9f966250..a3889f79 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -913,7 +913,7 @@ extension Connection { continue column } } - fatalError("no such table: \(namespace)") + throw QueryError.NoSuchTable(name: namespace) } for q in queries { try expandGlob(query.clauses.join.count > 0)(q) @@ -932,26 +932,26 @@ extension Connection { } } - public func scalar(query: ScalarQuery) -> V { + public func scalar(query: ScalarQuery) throws -> V { let expression = query.expression - return value(scalar(expression.template, expression.bindings)) + return try value(scalar(expression.template, expression.bindings)) } - public func scalar(query: ScalarQuery) -> V.ValueType? { + public func scalar(query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) + return try V.fromDatatypeValue(value) } - public func scalar(query: Select) -> V { + public func scalar(query: Select) throws -> V { let expression = query.expression - return value(scalar(expression.template, expression.bindings)) + return try value(scalar(expression.template, expression.bindings)) } - public func scalar(query: Select) -> V.ValueType? { + public func scalar(query: Select) throws -> V.ValueType? { let expression = query.expression guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) + return try V.fromDatatypeValue(value) } public func pluck(query: QueryType) -> Row? { @@ -1025,13 +1025,13 @@ public struct Row { /// - Parameter column: An expression representing a column selected in a Query. /// /// - Returns: The value for the given column. - public func get(column: Expression) -> V { - return get(Expression(column))! + public func get(column: Expression) throws -> V { + return try get(Expression(column))! } - public func get(column: Expression) -> V? { - func valueAtIndex(idx: Int) -> V? { + public func get(column: Expression) throws -> V? { + func valueAtIndex(idx: Int) throws -> V? { guard let value = values[idx] as? V.Datatype else { return nil } - return (V.fromDatatypeValue(value) as? V)! + return (try V.fromDatatypeValue(value) as? V)! } guard let idx = columnNames[column.template] else { @@ -1039,59 +1039,59 @@ public struct Row { switch similar.count { case 0: - fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sort())") + throw QueryError.NoSuchColumn(name: column.template) case 1: - return valueAtIndex(columnNames[similar[0]]!) + return try valueAtIndex(columnNames[similar[0]]!) default: - fatalError("ambiguous column '\(column.template)' (please disambiguate: \(similar))") + throw QueryError.AmbiguousColumn(name: column.template) } } - return valueAtIndex(idx) + return try valueAtIndex(idx) } // FIXME: rdar://problem/18673897 // subscript… public subscript(column: Expression) -> Blob { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Blob? { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Bool { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Bool? { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Double { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Double? { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Int { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Int? { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Int64 { - return get(column) + return try! get(column) } public subscript(column: Expression) -> Int64? { - return get(column) + return try! get(column) } public subscript(column: Expression) -> String { - return get(column) + return try! get(column) } public subscript(column: Expression) -> String? { - return get(column) + return try! get(column) } } diff --git a/SQLite/Typed/Setter.swift b/SQLite/Typed/Setter.swift index d8bf775a..cf3babc6 100644 --- a/SQLite/Typed/Setter.swift +++ b/SQLite/Typed/Setter.swift @@ -53,9 +53,9 @@ public struct Setter { self.value = value } - private init(column: Expression, value: V?) { + private init(column: Expression, value: V?) throws { self.column = column - self.value = Expression(value: value) + self.value = try Expression(value: value) } } @@ -80,8 +80,8 @@ public func <-(column: Expression, value: Expression) -> Sette public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: V?) -> Setter { - return Setter(column: column, value: value) +public func <-(column: Expression, value: V?) throws -> Setter { + return try Setter(column: column, value: value) } public func +=(column: Expression, value: Expression) -> Setter { From 612a99f60bf63fff6ac54d8d94ee41e0ae84e8fe Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 17 Apr 2016 12:27:25 -0700 Subject: [PATCH 12/13] Add Errors.swift to proper targets --- SQLite.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a4f6b7a9..137490e0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; AA58505F1CC40BA70034C46D /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA58505E1CC40BA70034C46D /* Errors.swift */; }; + AA5850601CC4154B0034C46D /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA58505E1CC40BA70034C46D /* Errors.swift */; }; + AA5850611CC4154B0034C46D /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA58505E1CC40BA70034C46D /* Errors.swift */; }; 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 */; }; @@ -56,8 +58,6 @@ 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 */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -428,16 +428,16 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( + EE247AEF1C3F06E900AE3E12 /* Connection.swift */, AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */, AA780B3C1CC201A700E0E95E /* Dispatcher.swift */, - EE91808D1C46E5230038162A /* SQLite-Bridging.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, - EE247AEF1C3F06E900AE3E12 /* Connection.swift */, - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, - EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, AA58505E1CC40BA70034C46D /* Errors.swift */, + EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, + EE91808D1C46E5230038162A /* SQLite-Bridging.h */, + EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, ); path = Core; sourceTree = ""; @@ -799,6 +799,7 @@ 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */, 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */, + AA5850611CC4154B0034C46D /* Errors.swift in Sources */, 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */, AA780B471CC202F400E0E95E /* Dispatcher.swift in Sources */, 03A65E841C6BB2FB0062603F /* Query.swift in Sources */, @@ -836,8 +837,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */, - AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -909,6 +908,7 @@ EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */, EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */, + AA5850601CC4154B0034C46D /* Errors.swift in Sources */, EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */, AA780B451CC202F300E0E95E /* Dispatcher.swift in Sources */, EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */, From ea4c49bffc718d90637d0fc42aec1a711be38a78 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 17 Apr 2016 14:38:22 -0700 Subject: [PATCH 13/13] Cleanup `throws` and `try`s It didn't make a lot of sense to being requiring `try` when calling operators that were building queries. Other functions like `scalar` and `pluck` are now correctly throwing errors --- SQLite/Core/Connection.swift | 22 ++++++++-------- SQLite/Core/ConnectionPool.swift | 6 ++--- SQLite/Core/Statement.swift | 2 +- SQLite/Typed/Expression.swift | 12 ++++----- SQLite/Typed/Operators.swift | 16 ++++++------ SQLite/Typed/Query.swift | 8 +++--- SQLite/Typed/Setter.swift | 8 +++--- SQLiteTests/ConnectionPoolTests.swift | 8 +++--- SQLiteTests/ConnectionTests.swift | 36 +++++++++++++-------------- SQLiteTests/CoreFunctionsTests.swift | 4 +-- SQLiteTests/FTS4Tests.swift | 2 +- SQLiteTests/QueryTests.swift | 8 +++--- SQLiteTests/TestHelpers.swift | 6 ++--- 13 files changed, 69 insertions(+), 69 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 1ff8a0dc..117ceabb 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -156,7 +156,7 @@ public protocol Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? + @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) throws -> Binding? /// Runs a single SQL statement (with optional parameter bindings), /// returning the first value of the first row. @@ -168,7 +168,7 @@ public protocol Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? + @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) throws -> Binding? /// Runs a single SQL statement (with optional parameter bindings), /// returning the first value of the first row. @@ -180,7 +180,7 @@ public protocol Connection { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? + @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) throws -> Binding? // MARK: - Transactions @@ -454,8 +454,8 @@ public final class DirectConnection : Connection, Equatable { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { - return scalar(statement, bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) throws -> Binding? { + return try scalar(statement, bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -468,8 +468,8 @@ public final class DirectConnection : Connection, Equatable { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { - return try! prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) throws -> Binding? { + return try prepare(statement).scalar(bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -482,8 +482,8 @@ public final class DirectConnection : Connection, Equatable { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { - return try! prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) throws -> Binding? { + return try prepare(statement).scalar(bindings) } // MARK: - Transactions @@ -771,11 +771,11 @@ public final class DirectConnection : Connection, Equatable { /// /// - block: A collation function that takes two strings and returns the /// comparison result. - public func createCollation(collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) { + public func createCollation(collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) throws { let box: Collation = { lhs, rhs in Int32(block(lhs: String.fromCString(UnsafePointer(lhs))!, rhs: String.fromCString(UnsafePointer(rhs))!).rawValue) } - try! check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, UnsafeMutablePointer.self), { callback, _, lhs, _, rhs in + try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, UnsafeMutablePointer.self), { callback, _, lhs, _, rhs in unsafeBitCast(callback, Collation.self)(lhs, rhs) }, nil)) collations[collation] = box diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index dd644304..1c58dbb8 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -108,9 +108,9 @@ public final class ConnectionPool { func run(statement: String, _ bindings: [Binding?]) throws -> Statement { return try connection.run(statement, bindings) } func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try connection.run(statement, bindings) } - @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? { return connection.scalar(statement, bindings) } - @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { return connection.scalar(statement, bindings) } - @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { return connection.scalar(statement, bindings) } + @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) throws -> Binding? { return try connection.scalar(statement, bindings) } + @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) throws -> Binding? { return try connection.scalar(statement, bindings) } + @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) throws -> Binding? { return try connection.scalar(statement, bindings) } func transaction(mode: TransactionMode, block: (Connection) throws -> Void) throws { return try connection.transaction(mode, block: block) } func savepoint(name: String, block: (Connection) throws -> Void) throws { return try connection.savepoint(name, block: block) } diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 459fcf3d..6adf9c76 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -154,7 +154,7 @@ public final class Statement { } reset(clearBindings: false) - try! step() + try step() return row[0] } diff --git a/SQLite/Typed/Expression.swift b/SQLite/Typed/Expression.swift index 5fb8a335..87c61a2c 100644 --- a/SQLite/Typed/Expression.swift +++ b/SQLite/Typed/Expression.swift @@ -110,8 +110,8 @@ extension ExpressionType { extension ExpressionType where UnderlyingType : Value { - public init(value: UnderlyingType) throws { - self.init("?", [try value.datatypeValue()]) + public init(value: UnderlyingType) { + self.init("?", [try! value.datatypeValue()]) } } @@ -119,11 +119,11 @@ extension ExpressionType where UnderlyingType : Value { extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { public static var null: Self { - return try! self.init(value: nil) + return self.init(value: nil) } - public init(value: UnderlyingType.WrappedType?) throws { - self.init("?", [try value?.datatypeValue()]) + public init(value: UnderlyingType.WrappedType?) { + self.init("?", [try! value?.datatypeValue()]) } } @@ -131,7 +131,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension Value { public var expression: Expression { - return try! Expression(value: self).expression + return Expression(value: self).expression } } diff --git a/SQLite/Typed/Operators.swift b/SQLite/Typed/Operators.swift index a7f96816..816560a5 100644 --- a/SQLite/Typed/Operators.swift +++ b/SQLite/Typed/Operators.swift @@ -335,15 +335,15 @@ public func ==(lhs: Expression, rhs: public func ==(lhs: Expression, rhs: V) -> Expression { return "=".infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V?) throws -> Expression { - guard let rhs = rhs else { return "IS".infix(lhs, try Expression(value: nil)) } +public func ==(lhs: Expression, rhs: V?) -> Expression { + guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } return "=".infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression { return "=".infix(lhs, rhs) } -public func ==(lhs: V?, rhs: Expression) throws -> Expression { - guard let lhs = lhs else { return "IS".infix(try Expression(value: nil), rhs) } +public func ==(lhs: V?, rhs: Expression) -> Expression { + guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } return "=".infix(lhs, rhs) } @@ -362,15 +362,15 @@ public func !=(lhs: Expression, rhs: public func !=(lhs: Expression, rhs: V) -> Expression { return infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V?) throws -> Expression { - guard let rhs = rhs else { return "IS NOT".infix(lhs, try Expression(value: nil)) } +public func !=(lhs: Expression, rhs: V?) -> Expression { + guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression { return infix(lhs, rhs) } -public func !=(lhs: V?, rhs: Expression) throws -> Expression { - guard let lhs = lhs else { return "IS NOT".infix(try Expression(value: nil), rhs) } +public func !=(lhs: V?, rhs: Expression) -> Expression { + guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return infix(lhs, rhs) } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index a3889f79..c1ed7211 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -939,7 +939,7 @@ extension Connection { public func scalar(query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression - guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return try V.fromDatatypeValue(value) } @@ -950,12 +950,12 @@ extension Connection { public func scalar(query: Select) throws -> V.ValueType? { let expression = query.expression - guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return try V.fromDatatypeValue(value) } - public func pluck(query: QueryType) -> Row? { - return try! prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + public func pluck(query: QueryType) throws -> Row? { + return try prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() } /// Runs an `Insert` query. diff --git a/SQLite/Typed/Setter.swift b/SQLite/Typed/Setter.swift index cf3babc6..d8bf775a 100644 --- a/SQLite/Typed/Setter.swift +++ b/SQLite/Typed/Setter.swift @@ -53,9 +53,9 @@ public struct Setter { self.value = value } - private init(column: Expression, value: V?) throws { + private init(column: Expression, value: V?) { self.column = column - self.value = try Expression(value: value) + self.value = Expression(value: value) } } @@ -80,8 +80,8 @@ public func <-(column: Expression, value: Expression) -> Sette public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: V?) throws -> Setter { - return try Setter(column: column, value: value) +public func <-(column: Expression, value: V?) -> Setter { + return Setter(column: column, value: value) } public func +=(column: Expression, value: Expression) -> Setter { diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 74351527..e10b8457 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -15,7 +15,7 @@ class ConnectionPoolTests : SQLiteTestCase { pool.foreignKeys = true pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } - XCTAssertTrue(pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) + XCTAssertTrue(try pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) try! pool.writable.execute("INSERT INTO test(value) VALUES (1)") try! pool.readable.execute("SELECT value FROM test") } @@ -45,10 +45,10 @@ class ConnectionPoolTests : SQLiteTestCase { let conn = self.pool.readable let stmt = try! conn.prepare("SELECT name FROM test WHERE id = ?") - var curr = stmt.scalar(x) as! String + var curr = try! stmt.scalar(x) as! String while !quit { - let now = stmt.scalar(x) as! String + let now = try! stmt.scalar(x) as! String if now != curr { //print(now) curr = now @@ -96,7 +96,7 @@ class ConnectionPoolTests : SQLiteTestCase { while !finished { - let val = self.pool.readable.scalar("SELECT value FROM test") + let val = try! self.pool.readable.scalar("SELECT value FROM test") assert(val != nil, "DB query returned nil result set") } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index bbad19a8..4f7036f9 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -87,10 +87,10 @@ class ConnectionTests : SQLiteTestCase { } func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } @@ -238,7 +238,7 @@ class ConnectionTests : SQLiteTestCase { try! db.transaction {_ in try self.InsertUser("alice") } - XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -252,7 +252,7 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -268,41 +268,41 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } func test_createFunction_withArrayArguments() { - db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } + try! db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as? String) - XCTAssert(db.scalar("SELECT hello(NULL)") == nil) + XCTAssertEqual("Hello, world!", try! db.scalar("SELECT hello('world')") as? String) + XCTAssert(try! db.scalar("SELECT hello(NULL)") == nil) } func test_createFunction_createsQuotableFunction() { - db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } + try! db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as? String) - XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) + XCTAssertEqual("Hello, world!", try! db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(try! db.scalar("SELECT \"hello world\"(NULL)") == nil) } func test_createCollation_createsCollation() { - db.createCollation("NODIACRITIC") { lhs, rhs in + try! db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } func test_createCollation_createsQuotableCollation() { - db.createCollation("NO DIACRITIC") { lhs, rhs in + try! db.createCollation("NO DIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } func test_interrupt_interruptsLongRunningQuery() { try! InsertUsers("abcdefghijklmnopqrstuvwxyz".characters.map { String($0) }) - db.createFunction("sleep") { args in + try! db.createFunction("sleep") { args in usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) return nil } diff --git a/SQLiteTests/CoreFunctionsTests.swift b/SQLiteTests/CoreFunctionsTests.swift index 8f7460d5..6f456328 100644 --- a/SQLiteTests/CoreFunctionsTests.swift +++ b/SQLiteTests/CoreFunctionsTests.swift @@ -129,8 +129,8 @@ class CoreFunctionsTests : XCTestCase { } func test_contains_buildsExpressionWithInOperator() { - AssertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string)) - AssertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional)) + AssertSQL("(\"string\" IN ('hello', 'world'))", try ["hello", "world"].contains(string)) + AssertSQL("(\"stringOptional\" IN ('hello', 'world'))", try ["hello", "world"].contains(stringOptional)) } } diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index a94d9073..c36c5739 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -71,7 +71,7 @@ class FTS4IntegrationTests : SQLiteTestCase { AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") try! db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, db.scalar(emails.filter(emails.match("aun")).count)) + XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) } } diff --git a/SQLiteTests/QueryTests.swift b/SQLiteTests/QueryTests.swift index b76da61e..fab35211 100644 --- a/SQLiteTests/QueryTests.swift +++ b/SQLiteTests/QueryTests.swift @@ -306,16 +306,16 @@ class QueryIntegrationTests : SQLiteTestCase { } func test_scalar() { - XCTAssertEqual(0, db.scalar(users.count)) - XCTAssertEqual(false, db.scalar(users.exists)) + XCTAssertEqual(0, try db.scalar(users.count)) + XCTAssertEqual(false, try db.scalar(users.exists)) try! InsertUsers("alice") - XCTAssertEqual(1, db.scalar(users.select(id.average))) + XCTAssertEqual(1, try db.scalar(users.select(id.average))) } func test_pluck() { let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, db.pluck(users)![id]) + XCTAssertEqual(rowid, try! db.pluck(users)![id]) } func test_insert() { diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 855bf7bd..e3cf38f7 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -43,7 +43,7 @@ class SQLiteTestCase : XCTestCase { func InsertUser(name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { return try db.run( "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - "\(name)@example.com", age?.datatypeValue, admin.datatypeValue + "\(name)@example.com", try age?.datatypeValue(), try admin.datatypeValue() ) } @@ -96,8 +96,8 @@ let int64Optional = Expression("int64Optional") let string = Expression("string") let stringOptional = Expression("stringOptional") -func AssertSQL(@autoclosure expression1: () -> String, @autoclosure _ expression2: () -> Expressible, file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) +func AssertSQL(@autoclosure expression1: () -> String, @autoclosure _ expression2: () throws -> Expressible, file: StaticString = #file, line: UInt = #line) { + XCTAssertEqual(expression1(), try! expression2().asSQL(), file: file, line: line) } func AssertThrows(@autoclosure expression: () throws -> T, file: StaticString = #file, line: UInt = #line) {