diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d2ac9df4..137490e0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,18 @@ 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 */; }; + 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 */; }; + 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 */; }; 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,25 +174,29 @@ 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionPool.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 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; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; - EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 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 = ""; }; @@ -190,7 +206,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 = ""; }; @@ -388,6 +404,7 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( + AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -411,13 +428,16 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - EE91808D1C46E5230038162A /* SQLite-Bridging.h */, - EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, - EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, + AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */, + AA780B3C1CC201A700E0E95E /* Dispatcher.swift */, + EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, + AA58505E1CC40BA70034C46D /* Errors.swift */, + EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, + EE91808D1C46E5230038162A /* SQLite-Bridging.h */, + EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, ); path = Core; sourceTree = ""; @@ -779,10 +799,13 @@ 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 */, 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */, 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, + AA780B461CC202F400E0E95E /* ConnectionPool.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -792,6 +815,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 */, @@ -833,10 +857,13 @@ 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 */, 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 +873,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 */, @@ -880,10 +908,13 @@ 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 */, EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */, EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, + AA780B441CC202F300E0E95E /* ConnectionPool.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -893,6 +924,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..117ceabb 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -25,8 +25,209 @@ 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 Connection { + + /// 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?...) throws -> 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?]) throws -> 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?]) throws -> 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: (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 + /// 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: (Connection) throws -> Void) throws + + func sync(block: () throws -> T) rethrows -> T + +} + + /// A connection to SQLite. -public final class Connection { +public final class DirectConnection : Connection, Equatable { /// The location of a SQLite database. public enum Location { @@ -67,10 +268,32 @@ 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, vfsName: String? = nil) 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"), vfsName: vfsName) + } + + /// 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, vfsName: String? = nil) throws { + self.dispatcher = dispatcher + if let vfsName = vfsName { + try check(sqlite3_open_v2(location.description, &_handle, flags, vfsName)) + } + else { + try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) + } + try check(sqlite3_extended_result_codes(handle, 1)) } /// Initializes a new connection to a database. @@ -231,8 +454,8 @@ public final class Connection { /// - 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), @@ -245,8 +468,8 @@ public final class Connection { /// - 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), @@ -259,26 +482,12 @@ public final class Connection { /// - 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 - /// 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. /// @@ -296,10 +505,10 @@ public final class Connection { /// 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 @@ -316,18 +525,18 @@ public final class Connection { /// 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 @@ -335,7 +544,7 @@ public final class Connection { try self.run(commit) } } - + /// Interrupts any long-running queries. public func interrupt() { sqlite3_interrupt(handle) @@ -492,48 +701,59 @@ public final class Connection { /// - 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 } @@ -551,11 +771,11 @@ public final class Connection { /// /// - 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 @@ -565,7 +785,7 @@ public final class Connection { // MARK: - Error Handling - func sync(block: () throws -> T) rethrows -> T { + public func sync(block: () throws -> T) rethrows -> T { var success: T? var failure: ErrorType? @@ -577,11 +797,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,16 +813,12 @@ 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 } -extension Connection : CustomStringConvertible { +extension DirectConnection : CustomStringConvertible { public var description: String { return String.fromCString(sqlite3_db_filename(handle, nil))! @@ -614,7 +826,7 @@ extension Connection : CustomStringConvertible { } -extension Connection.Location : CustomStringConvertible { +extension DirectConnection.Location : CustomStringConvertible { public var description: String { switch self { @@ -629,6 +841,10 @@ extension Connection.Location : CustomStringConvertible { } +public func ==(lhs: DirectConnection, rhs: DirectConnection) -> Bool { + return lhs === rhs +} + /// An SQL operation passed to update callbacks. public enum Operation { @@ -662,7 +878,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: 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 new file mode 100644 index 00000000..1c58dbb8 --- /dev/null +++ b/SQLite/Core/ConnectionPool.swift @@ -0,0 +1,201 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Dispatch +import CSQLite + + +private let vfsName = "unix-excl" + + +// Connection pool for accessing an SQLite database +// with multiple readers & a single writer. Utilizes +// WAL mode. +public final class ConnectionPool { + + private let location : DirectConnection.Location + private var availableReadConnections = [DirectConnection]() + private var unavailableReadConnections = [DirectConnection]() + private let lockQueue : dispatch_queue_t + private var writeConnection : DirectConnection! + + 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) + self.internalSetup[.WriteAheadLogging] = { try $0.execute("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 : Connection, Equatable { + + let pool : ConnectionPool + let connection : DirectConnection + + init(pool: ConnectionPool, connection: DirectConnection) { + 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?...) 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) } + + 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 + + var writeConnectionInit = dispatch_once_t() + + public var writable : DirectConnection { + + dispatch_once(&writeConnectionInit) { + + let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX + self.writeConnection = try! DirectConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) + self.writeConnection.busyTimeout = 2 + + for setupProcessor in self.internalSetup.values { + try! setupProcessor(self.writeConnection) + } + + for setupProcessor in self.setup { + try! setupProcessor(self.writeConnection) + } + + } + + return writeConnection + } + + // Acquires a read only connection to the database + public var readable : Connection { + + var borrowed : BorrowedConnection! + + repeat { + + dispatch_sync(lockQueue) { + + // Ensure database is open + self.writable + + let connection : DirectConnection + + if let availableConnection = self.availableReadConnections.popLast() { + connection = availableConnection + } + 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 + + for (type, setupProcessor) in self.internalSetup { + if type == .WriteAheadLogging { + continue + } + try! setupProcessor(connection) + } + + for setupProcessor in self.setup { + try! setupProcessor(connection) + } + + } + + self.unavailableReadConnections.append(connection) + + borrowed = BorrowedConnection(pool: self, connection: connection) + } + + } while borrowed == nil + + 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/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 39fb000d..6adf9c76 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: DirectConnection - init(_ connection: Connection, _ SQL: String) throws { + init(_ connection: DirectConnection, _ SQL: String) throws { self.connection = connection try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } @@ -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,21 +148,21 @@ 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) - try! step() + try step() return row[0] } /// - 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/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index e5a1d815..045e5303 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -140,7 +140,7 @@ extension Tokenizer : CustomStringConvertible { } -extension Connection { +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/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 068d0340..2dc570cd 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public extension Connection { +public extension DirectConnection { /// Creates or redefines a custom SQL function. /// @@ -39,94 +39,94 @@ public extension Connection { /// 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..87c61a2c 100644 --- a/SQLite/Typed/Expression.swift +++ b/SQLite/Typed/Expression.swift @@ -111,7 +111,7 @@ extension ExpressionType { extension ExpressionType where UnderlyingType : Value { public init(value: UnderlyingType) { - self.init("?", [value.datatypeValue]) + self.init("?", [try! value.datatypeValue()]) } } @@ -123,7 +123,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr } public init(value: UnderlyingType.WrappedType?) { - self.init("?", [value?.datatypeValue]) + self.init("?", [try! value?.datatypeValue()]) } } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index e45034ca..c1ed7211 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,30 +932,30 @@ 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) + guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + 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) + 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. @@ -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/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift new file mode 100644 index 00000000..e10b8457 --- /dev/null +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -0,0 +1,133 @@ +import XCTest +import SQLite + +class ConnectionPoolTests : SQLiteTestCase { + + var pool : ConnectionPool! + + override func setUp() { + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) + } + + func testConnectionSetupClosures() { + + pool.foreignKeys = true + pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } + + 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() { + + 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") + 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 = self.pool.readable + + let stmt = try! conn.prepare("SELECT name FROM test WHERE id = ?") + var curr = try! stmt.scalar(x) as! String + while !quit { + + let now = try! 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..<5000 { + + let name = "test" + String(x) + let idx = Int(rand()) % 5 + + do { + try conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + } + catch let error { + XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") + } + + usleep(500) + } + + quit = true + waitForExpectationsWithTimeout(1000, handler: nil) + } + + func testConcurrentAccess() throws { + + 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 = try! self.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() { + + do { + try! pool.readable.execute("SELECT 1") + } + + XCTAssertEqual(pool.totalReadableConnectionCount, pool.availableReadableConnectionCount) + } + +} diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index aeec9b72..4f7036f9 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! DirectConnection(.InMemory) XCTAssertEqual("", db.description) } func test_init_returnsInMemoryByDefault() { - let db = try! Connection() + let db = try! DirectConnection() XCTAssertEqual("", db.description) } func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! Connection(.Temporary) + let db = try! DirectConnection(.Temporary) XCTAssertEqual("", db.description) } func test_init_withURI_returnsURIConnection() { - let db = try! Connection(.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! Connection("\(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! Connection(readonly: true) + let db = try! DirectConnection(readonly: true) XCTAssertTrue(db.readonly) } @@ -87,27 +87,27 @@ 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) } 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,10 +235,10 @@ 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) + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -246,13 +246,13 @@ 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 } } catch { } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -263,46 +263,46 @@ class ConnectionTests : SQLiteTestCase { } db.rollbackHook(done) do { - try db.transaction { + try db.transaction {_ in try self.InsertUser("alice") } } 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 464b9c27..e3cf38f7 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! DirectConnection() let users = Table("users") @@ -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) {