From 1b2d648784152f2c998c3e118524fcfc305d90d0 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Tue, 19 Jul 2016 16:05:15 -0600 Subject: [PATCH 01/86] Add test for single connection across multiple threads. --- SQLiteTests/ConnectionTests.swift | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 62203362..4ec50c8a 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -313,5 +313,26 @@ class ConnectionTests : SQLiteTestCase { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) AssertThrows(try stmt.run()) } + + func test_concurrent_access_single_connection() { + let conn = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Connection Tests.sqlite") + try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") + try! conn.run("INSERT INTO test(value) VALUES(?)", 0) + + let q = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT); + var finished = false + + for _ in 0..<100 { + dispatch_async(q) { + while !finished { + _ = try! conn.prepare("SELECT value FROM test") + } + } + } + + // Give the threads some time to conflict + sleep(5) + finished = true + } } From ce1d0f1e5c463b63b5d92f97773429a29e117548 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Wed, 20 Jul 2016 10:45:25 -0600 Subject: [PATCH 02/86] Update test. --- SQLiteTests/ConnectionTests.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 4ec50c8a..a00ab388 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -320,19 +320,22 @@ class ConnectionTests : SQLiteTestCase { try! conn.run("INSERT INTO test(value) VALUES(?)", 0) let q = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT); - var finished = false - for _ in 0..<100 { + var reads = [0, 0, 0, 0, 0] + var finished = false + for index in 0..<5 { dispatch_async(q) { while !finished { _ = try! conn.prepare("SELECT value FROM test") + reads[index] += 1 } } } - // Give the threads some time to conflict - sleep(5) - finished = true + while !finished { + sleep(1) + finished = reads.reduce(true) { $0 && ($1 > 500) } + } } } From 72ee02cd8536c95a7e09604e2a85614a44efb386 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Wed, 20 Jul 2016 11:06:46 -0600 Subject: [PATCH 03/86] Whitespace change to retrigger build. --- SQLiteTests/ConnectionTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index a00ab388..c49e99e3 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -336,6 +336,6 @@ class ConnectionTests : SQLiteTestCase { sleep(1) finished = reads.reduce(true) { $0 && ($1 > 500) } } - } + } } From d5ede257ecba3281272e32232c4306641b2c3629 Mon Sep 17 00:00:00 2001 From: Daniel Alm Date: Mon, 9 May 2016 16:43:57 +0200 Subject: [PATCH 04/86] Fix transactions not being rolled back when the individual statements succeed, but the committing the transaction fails. --- SQLite/Core/Connection.swift | 2 +- SQLiteTests/ConnectionTests.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 04fdd901..62a2b812 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -333,11 +333,11 @@ public final class Connection { try self.run(begin) do { try block() + try self.run(commit) } catch { try self.run(rollback) throw error } - try self.run(commit) } } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 3d7dd3eb..eda4677a 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -125,6 +125,33 @@ class ConnectionTests : SQLiteTestCase { AssertSQL("ROLLBACK TRANSACTION", 0) } + func test_transaction_rollsBackTransactionsIfCommitsFail() { + // This test case needs to emulate an environment where the individual statements succeed, but committing the + // transactuin fails. Using deferred foreign keys is one option to achieve this. + try! db.execute("PRAGMA foreign_keys = ON;") + try! db.execute("PRAGMA defer_foreign_keys = ON;") + let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) + + do { + try db.transaction { + try stmt.run() + } + } catch { + } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email, manager_id) VALUES ('alice@example.com', 100)") + AssertSQL("COMMIT TRANSACTION") + AssertSQL("ROLLBACK TRANSACTION") + + // Run another transaction to ensure that a subsequent transaction does not fail with an "cannot start a + // transaction within a transaction" error. + let stmt2 = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + try! db.transaction { + try stmt2.run() + } + } + func test_transaction_beginsAndRollsTransactionsBack() { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") From 97e6a660e3a41ef4db43a3a33249d2c10f083fc8 Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Mon, 16 Jan 2017 11:28:59 +0200 Subject: [PATCH 05/86] Add possibility to have expression on other side of like --- Sources/SQLite/Typed/CoreFunctions.swift | 50 ++++++++++++++++++++++ Tests/SQLiteTests/CoreFunctionsTests.swift | 6 +++ 2 files changed, 56 insertions(+) diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 9d17a326..dc450abd 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -223,6 +223,31 @@ extension ExpressionType where UnderlyingType == String { return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// let pattern = Expression("pattern") + /// email.like(pattern) + /// // "email" LIKE "pattern" + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + let like: Expression = "LIKE".infix(self, pattern, wrap: false) + return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) + } + /// Builds a copy of the expression appended with a `GLOB` query against the /// given pattern. /// @@ -422,6 +447,31 @@ extension ExpressionType where UnderlyingType == String? { } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// let pattern = Expression("pattern") + /// email.like(pattern) + /// // "email" LIKE "pattern" + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + let like: Expression = "LIKE".infix(self, pattern, wrap: false) + return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) + } /// Builds a copy of the expression appended with a `GLOB` query against the /// given pattern. diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index db37ff7f..82c59c61 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -37,6 +37,12 @@ class CoreFunctionsTests : XCTestCase { AssertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) AssertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) + + AssertSQL("(\"string\" LIKE \"a\")", string.like(Expression("a"))) + AssertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(Expression("a"))) + + AssertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) + AssertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { From 5800d139597b5ffa8ba747d059fbeb9f6dd425eb Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Mon, 16 Jan 2017 12:14:25 +0200 Subject: [PATCH 06/86] Allow use of string on left hand side of like expresssion --- Sources/SQLite/Typed/CoreFunctions.swift | 29 ++++++++++++++++++++++ Tests/SQLiteTests/CoreFunctionsTests.swift | 3 +++ 2 files changed, 32 insertions(+) diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index dc450abd..2e00c44a 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -673,6 +673,35 @@ extension Collection where Iterator.Element : Value, IndexDistance == Int { } +extension String { + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = "some@thing.com" + /// let pattern = Expression("pattern") + /// email.like(pattern) + /// // 'some@thing.com' LIKE "pattern" + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + let like: Expression = "LIKE".infix(self, pattern, wrap: false) + return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) + } + +} + /// Builds a copy of the given expressions wrapped with the `ifnull` function. /// /// let name = Expression("name") diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index 82c59c61..7cf046a3 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -43,6 +43,9 @@ class CoreFunctionsTests : XCTestCase { AssertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) AssertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) + + AssertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) + AssertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { From 8a8da02a15366f5c6c2ff3d2384881f49cb7ab9f Mon Sep 17 00:00:00 2001 From: avinassh Date: Fri, 21 Jul 2017 17:52:08 +0530 Subject: [PATCH 07/86] Fix OS X database path --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 07bb5f33..491a5938 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -251,7 +251,7 @@ On OS X, you can use your app’s **Application Support** directory: ``` swift var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true -).first! + Bundle.main.bundleIdentifier! +).first! + "/" + Bundle.main.bundleIdentifier! // create parent directory iff it doesn’t exist try FileManager.default.createDirectoryAtPath( From 01de138c85b4b2ee7ea6e3ac087cd9827f8fded8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20de=20Oliveira=20S=C3=A1?= Date: Thu, 3 Aug 2017 16:16:29 -0300 Subject: [PATCH 08/86] try was missing before db.scalar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e435bfed..8c3affd9 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) -db.scalar(users.count) // 0 +try db.scalar(users.count) // 0 // SELECT count(*) FROM "users" ``` From 8e3fd559622f96b59f1cc9882dcfd6361635f3de Mon Sep 17 00:00:00 2001 From: Ross MacLeod Date: Thu, 7 Sep 2017 12:06:49 -0400 Subject: [PATCH 09/86] add support for ORDER and LIMIT on UPDATE and DELETE --- Sources/SQLite/Typed/Query.swift | 8 ++++++-- Tests/SQLiteTests/QueryTests.swift | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c9d2ea9c..2ab51e45 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -648,7 +648,9 @@ extension QueryType { tableName(), Expression(literal: "SET"), ", ".join(values.map { " = ".join([$0.column, $0.value]) }), - whereClause + whereClause, + orderClause, + limitOffsetClause ] return Update(" ".join(clauses.flatMap { $0 }).expression) @@ -660,7 +662,9 @@ extension QueryType { let clauses: [Expressible?] = [ Expression(literal: "DELETE FROM"), tableName(), - whereClause + whereClause, + orderClause, + limitOffsetClause ] return Delete(" ".join(clauses.flatMap { $0 }).expression) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2cf164c6..e725105b 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -245,6 +245,13 @@ class QueryTests : XCTestCase { ) } + func test_update_compilesUpdateLimitOrderExpression() { + AssertSQL( + "UPDATE \"users\" SET \"age\" = 30 ORDER BY \"id\" LIMIT 1", + users.order(id).limit(1).update(age <- 30) + ) + } + func test_delete_compilesDeleteExpression() { AssertSQL( "DELETE FROM \"users\" WHERE (\"id\" = 1)", @@ -252,6 +259,13 @@ class QueryTests : XCTestCase { ) } + func test_delete_compilesDeleteLimitOrderExpression() { + AssertSQL( + "DELETE FROM \"users\" ORDER BY \"id\" LIMIT 1", + users.order(id).limit(1).delete() + ) + } + func test_delete_compilesExistsExpression() { AssertSQL( "SELECT EXISTS (SELECT * FROM \"users\")", From 59b8dd7a0ef4d5db96607dcd3a4a11fe05624c45 Mon Sep 17 00:00:00 2001 From: Stephan Heilner Date: Thu, 7 Sep 2017 13:30:03 -0600 Subject: [PATCH 10/86] Added support for the union query clause --- Sources/SQLite/Typed/Query.swift | 37 ++++++++++++++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 22 +++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c9d2ea9c..e13d054d 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -180,6 +180,27 @@ extension QueryType { return query } + // MARK: UNION + + /// Adds a `UNION` clause to the query. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.filter(email == "alice@example.com").union(users.filter(email == "sally@example.com")) + /// // SELECT * FROM "users" WHERE email = 'alice@example.com' UNION SELECT * FROM "users" WHERE email = 'sally@example.com' + /// + /// - Parameters: + /// + /// - table: A query representing the other table. + /// + /// - Returns: A query with the given `UNION` clause applied. + public func union(_ table: QueryType) -> Self { + var query = self + query.clauses.union.append(table) + return query + } + // MARK: JOIN /// Adds a `JOIN` clause to the query. @@ -565,6 +586,19 @@ extension QueryType { Expression(literal: "OFFSET \(offset)") ]) } + + fileprivate var unionClause: Expressible? { + guard !clauses.union.isEmpty else { + return nil + } + + return " ".join(clauses.union.map { query in + " ".join([ + Expression(literal: "UNION"), + query + ]) + }) + } // MARK: - @@ -779,6 +813,7 @@ extension QueryType { joinClause, whereClause, groupByClause, + unionClause, orderClause, limitOffsetClause ] @@ -1154,6 +1189,8 @@ public struct QueryClauses { var order = [Expressible]() var limit: (length: Int, offset: Int?)? + + var union = [QueryType]() fileprivate init(_ name: String, alias: String?, database: String?) { self.from = (name, alias, database) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2cf164c6..cac32b28 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -361,5 +361,25 @@ class QueryIntegrationTests : SQLiteTestCase { let changes = try! db.run(users.delete()) XCTAssertEqual(0, changes) } - + + func test_union() throws { + let expectedIDs = [ + try db.run(users.insert(email <- "alice@example.com")), + try db.run(users.insert(email <- "sally@example.com")) + ] + + let query1 = users.filter(email == "alice@example.com") + let query2 = users.filter(email == "sally@example.com") + + let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } + XCTAssertEqual(expectedIDs, actualIDs) + + let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") + let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") + + print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) + + let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } + XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) + } } From 5dbe70f5003691f5c0a081cfd49ccf7b4acacc77 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:06:10 +0200 Subject: [PATCH 11/86] Update for Swift 4, removing module maps and fixing the map issue --- CocoaPods/appletvos/module.modulemap | 4 --- CocoaPods/appletvsimulator/module.modulemap | 4 --- CocoaPods/iphoneos-10.0/module.modulemap | 4 --- CocoaPods/iphoneos/module.modulemap | 4 --- .../iphonesimulator-10.0/module.modulemap | 4 --- CocoaPods/iphonesimulator/module.modulemap | 4 --- CocoaPods/macosx-10.11/module.modulemap | 4 --- CocoaPods/macosx-10.12/module.modulemap | 4 --- CocoaPods/macosx/module.modulemap | 4 --- CocoaPods/watchos/module.modulemap | 4 --- CocoaPods/watchsimulator/module.modulemap | 4 --- SQLite.xcodeproj/project.pbxproj | 30 ++++++++++++++----- .../xcschemes/SQLite Mac.xcscheme | 2 +- .../xcschemes/SQLite iOS.xcscheme | 2 +- .../xcschemes/SQLite tvOS.xcscheme | 2 +- .../xcschemes/SQLite watchOS.xcscheme | 2 +- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- Sources/SQLite/Typed/Query.swift | 5 ++-- Tests/SQLiteTests/ConnectionTests.swift | 2 +- 21 files changed, 34 insertions(+), 61 deletions(-) delete mode 100644 CocoaPods/appletvos/module.modulemap delete mode 100644 CocoaPods/appletvsimulator/module.modulemap delete mode 100644 CocoaPods/iphoneos-10.0/module.modulemap delete mode 100644 CocoaPods/iphoneos/module.modulemap delete mode 100644 CocoaPods/iphonesimulator-10.0/module.modulemap delete mode 100644 CocoaPods/iphonesimulator/module.modulemap delete mode 100644 CocoaPods/macosx-10.11/module.modulemap delete mode 100644 CocoaPods/macosx-10.12/module.modulemap delete mode 100644 CocoaPods/macosx/module.modulemap delete mode 100644 CocoaPods/watchos/module.modulemap delete mode 100644 CocoaPods/watchsimulator/module.modulemap diff --git a/CocoaPods/appletvos/module.modulemap b/CocoaPods/appletvos/module.modulemap deleted file mode 100644 index 637d9935..00000000 --- a/CocoaPods/appletvos/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/appletvsimulator/module.modulemap b/CocoaPods/appletvsimulator/module.modulemap deleted file mode 100644 index f8b9b671..00000000 --- a/CocoaPods/appletvsimulator/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphoneos-10.0/module.modulemap b/CocoaPods/iphoneos-10.0/module.modulemap deleted file mode 100644 index 67a6c203..00000000 --- a/CocoaPods/iphoneos-10.0/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphoneos/module.modulemap b/CocoaPods/iphoneos/module.modulemap deleted file mode 100644 index 043db6c4..00000000 --- a/CocoaPods/iphoneos/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphonesimulator-10.0/module.modulemap b/CocoaPods/iphonesimulator-10.0/module.modulemap deleted file mode 100644 index c8b84ab8..00000000 --- a/CocoaPods/iphonesimulator-10.0/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.0.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphonesimulator/module.modulemap b/CocoaPods/iphonesimulator/module.modulemap deleted file mode 100644 index a7b14cbb..00000000 --- a/CocoaPods/iphonesimulator/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/macosx-10.11/module.modulemap b/CocoaPods/macosx-10.11/module.modulemap deleted file mode 100644 index 9e091297..00000000 --- a/CocoaPods/macosx-10.11/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/macosx-10.12/module.modulemap b/CocoaPods/macosx-10.12/module.modulemap deleted file mode 100644 index 8fc958e6..00000000 --- a/CocoaPods/macosx-10.12/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/macosx/module.modulemap b/CocoaPods/macosx/module.modulemap deleted file mode 100644 index cc8370ec..00000000 --- a/CocoaPods/macosx/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/watchos/module.modulemap b/CocoaPods/watchos/module.modulemap deleted file mode 100644 index 62a6c4ee..00000000 --- a/CocoaPods/watchos/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/watchsimulator/module.modulemap b/CocoaPods/watchsimulator/module.modulemap deleted file mode 100644 index 086fbab2..00000000 --- a/CocoaPods/watchsimulator/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 78b43249..c4d702c7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -740,7 +740,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0900; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -753,11 +753,11 @@ }; EE247AD21C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; EE247ADC1C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; EE247B3B1C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; @@ -1185,14 +1185,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1237,14 +1243,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1297,7 +1309,8 @@ "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1322,7 +1335,8 @@ "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -1334,7 +1348,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1346,7 +1361,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index 606b5a10..2c3c431f 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ ?, Expression?) -> Expression diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c9d2ea9c..6a8d885b 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -494,8 +494,9 @@ extension QueryType { return nil } - return " ".join(clauses.join.map { type, query, condition in - " ".join([ + return " ".join(clauses.join.map { arg in + let (type, query, condition) = arg + return " ".join([ Expression(literal: "\(type.rawValue) JOIN"), query.tableName(alias: true), Expression(literal: "ON"), diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index d05c5ece..373cba0a 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -6,7 +6,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif SWIFT_PACKAGE || COCOAPODS -import CSQLite +import SQLite3 #endif class ConnectionTests : SQLiteTestCase { From 53f18574ca6500d1ad44a7f083aa179daf9559d4 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:11:16 +0200 Subject: [PATCH 12/86] Remove reference to deleted files --- SQLite.swift.podspec | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 06aa043b..6e9145c8 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -31,19 +31,6 @@ Pod::Spec.new do |s| ss.library = 'sqlite3' ss.preserve_paths = 'CocoaPods/**/*' - ss.pod_target_xcconfig = { - 'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx', - 'SWIFT_INCLUDE_PATHS[sdk=macosx10.11]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.11', - 'SWIFT_INCLUDE_PATHS[sdk=macosx10.12]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.12', - 'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos', - 'SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos-10.0', - 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator', - 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator-10.0', - 'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos', - 'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator', - 'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos', - 'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator' - } end s.subspec 'standalone' do |ss| From 53e768ab0db413a5f0dbbb1166736eb9e89802fa Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:21:17 +0200 Subject: [PATCH 13/86] Remove deleted paths --- SQLite.swift.podspec | 1 - 1 file changed, 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 6e9145c8..808e8a46 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -30,7 +30,6 @@ Pod::Spec.new do |s| ss.private_header_files = 'Sources/SQLiteObjc/*.h' ss.library = 'sqlite3' - ss.preserve_paths = 'CocoaPods/**/*' end s.subspec 'standalone' do |ss| From 034093fc45c638525163c3bbfb0076ca698f73d7 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:27:27 +0200 Subject: [PATCH 14/86] Remove dependency --- SQLite.swift.podspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 808e8a46..ce31843e 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -28,8 +28,6 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' - - ss.library = 'sqlite3' end s.subspec 'standalone' do |ss| From 3033a4ddc6a8ed2d98b7488a51bbb553a7287036 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:32:24 +0200 Subject: [PATCH 15/86] Undo --- SQLite.swift.podspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index ce31843e..808e8a46 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -28,6 +28,8 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' + + ss.library = 'sqlite3' end s.subspec 'standalone' do |ss| From 2211bce8dea454b48b9df94715f1526d3b9276ed Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Fri, 16 Jun 2017 23:52:47 +0200 Subject: [PATCH 16/86] More cleanup --- .swift-version | 2 +- SQLite.xcodeproj/project.pbxproj | 125 +++++++------------------------ 2 files changed, 26 insertions(+), 101 deletions(-) diff --git a/.swift-version b/.swift-version index 9f55b2cc..5186d070 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 +4.0 diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index c4d702c7..30295edb 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; - 03A65E711C6BB2CD0062603F /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; @@ -88,7 +87,6 @@ 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; - 3D67B3FA1DB2470600A4F4C6 /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; @@ -164,10 +162,8 @@ EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - EE91808C1C46E34A0038162A /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE9180901C46E8980038162A /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180911C46E9D30038162A /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ @@ -207,13 +203,6 @@ 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; - 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A691CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A6B1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 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 = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -265,7 +254,6 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SQLite-Bridging.h"; path = "../../SQLiteObjc/include/SQLite-Bridging.h"; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; @@ -331,76 +319,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 39548A611CA63C740003E3B5 /* CocoaPods */ = { - isa = PBXGroup; - children = ( - 39548A621CA63C740003E3B5 /* appletvos */, - 39548A641CA63C740003E3B5 /* appletvsimulator */, - 39548A661CA63C740003E3B5 /* iphoneos */, - 39548A681CA63C740003E3B5 /* iphonesimulator */, - 39548A6A1CA63C740003E3B5 /* macosx */, - 39548A6C1CA63C740003E3B5 /* watchos */, - 39548A6E1CA63C740003E3B5 /* watchsimulator */, - ); - path = CocoaPods; - sourceTree = ""; - }; - 39548A621CA63C740003E3B5 /* appletvos */ = { - isa = PBXGroup; - children = ( - 39548A631CA63C740003E3B5 /* module.modulemap */, - ); - path = appletvos; - sourceTree = ""; - }; - 39548A641CA63C740003E3B5 /* appletvsimulator */ = { - isa = PBXGroup; - children = ( - 39548A651CA63C740003E3B5 /* module.modulemap */, - ); - path = appletvsimulator; - sourceTree = ""; - }; - 39548A661CA63C740003E3B5 /* iphoneos */ = { - isa = PBXGroup; - children = ( - 39548A671CA63C740003E3B5 /* module.modulemap */, - ); - path = iphoneos; - sourceTree = ""; - }; - 39548A681CA63C740003E3B5 /* iphonesimulator */ = { - isa = PBXGroup; - children = ( - 39548A691CA63C740003E3B5 /* module.modulemap */, - ); - path = iphonesimulator; - sourceTree = ""; - }; - 39548A6A1CA63C740003E3B5 /* macosx */ = { - isa = PBXGroup; - children = ( - 39548A6B1CA63C740003E3B5 /* module.modulemap */, - ); - path = macosx; - sourceTree = ""; - }; - 39548A6C1CA63C740003E3B5 /* watchos */ = { - isa = PBXGroup; - children = ( - 39548A6D1CA63C740003E3B5 /* module.modulemap */, - ); - path = watchos; - sourceTree = ""; - }; - 39548A6E1CA63C740003E3B5 /* watchsimulator */ = { - isa = PBXGroup; - children = ( - 39548A6F1CA63C740003E3B5 /* module.modulemap */, - ); - path = watchsimulator; - sourceTree = ""; - }; 3D67B3E41DB2469200A4F4C6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -439,7 +357,6 @@ EE247AD51C3F04ED00AE3E12 /* SQLite */ = { isa = PBXGroup; children = ( - EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */, EE247AD61C3F04ED00AE3E12 /* SQLite.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, @@ -525,7 +442,6 @@ EE247B8A1C3F81D000AE3E12 /* Metadata */ = { isa = PBXGroup; children = ( - 39548A611CA63C740003E3B5 /* CocoaPods */, EE247B771C3F40D700AE3E12 /* README.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, @@ -566,7 +482,6 @@ files = ( 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */, - 03A65E711C6BB2CD0062603F /* usr/include/sqlite3.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -575,7 +490,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3FA1DB2470600A4F4C6 /* usr/include/sqlite3.h in Headers */, 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */, 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, @@ -588,7 +502,6 @@ files = ( EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */, EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, - EE91808C1C46E34A0038162A /* usr/include/sqlite3.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -597,7 +510,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE9180901C46E8980038162A /* usr/include/sqlite3.h in Headers */, EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */, @@ -744,12 +656,15 @@ TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0900; }; 03A65E621C6BB0F60062603F = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0900; }; A121AC441CA35C79005A31D1 = { CreatedOnToolsVersion = 7.3; + LastSwiftMigration = 0900; }; EE247AD21C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; @@ -761,11 +676,11 @@ }; EE247B3B1C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; EE247B441C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; }; }; @@ -1073,7 +988,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1096,7 +1012,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1109,7 +1026,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1122,7 +1040,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1146,7 +1065,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1171,7 +1091,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1389,7 +1310,8 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1416,7 +1338,8 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -1431,7 +1354,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1446,7 +1370,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; From 52e02ae7c3e7ab7be0404c3739ec925def785ab0 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Fri, 30 Jun 2017 19:17:46 +0200 Subject: [PATCH 17/86] Update Travis CI to use Xcode 9 beta 2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2eb85311..5dba809f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c rvm: 2.2 -osx_image: xcode8.3 +osx_image: xcode9 env: global: - IOS_SIMULATOR="iPhone 6s" From f780ba16a2511760b694d7c8a179610e4198614f Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Fri, 30 Jun 2017 23:00:59 +0200 Subject: [PATCH 18/86] Fix for iOS version not found on Travis --- Makefile | 2 +- SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index adab1d81..ebd38494 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 10.3 +IOS_VERSION = 11.0 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme index e6cb416a..fb00f5bb 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES" codeCoverageEnabled = "YES"> @@ -56,6 +57,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" From ede1af7baad71344dc323e3d54b0e22995992694 Mon Sep 17 00:00:00 2001 From: thebluepotato Date: Sat, 1 Jul 2017 01:33:14 +0200 Subject: [PATCH 19/86] Enforce iOS 11.0 after update from master --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5dba809f..62f36185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ osx_image: xcode9 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="10.3.1" + - IOS_VERSION="11.0" matrix: include: - env: BUILD_SCHEME="SQLite iOS" From 0bbb4ca9bcc091a42bc6a60ff6c0410a70b75d18 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 22 Aug 2017 08:45:27 +0200 Subject: [PATCH 20/86] Fix compiler warnings - objc inference - substring(to:) deprecation - redundant conformance constraint --- SQLite.xcodeproj/project.pbxproj | 28 ++++++++++++++-------------- Sources/SQLite/Extensions/FTS4.swift | 15 ++++++++++----- Sources/SQLite/Typed/Operators.swift | 4 ++-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 30295edb..8e3162d9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -988,7 +988,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1012,7 +1012,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1026,7 +1026,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1040,7 +1040,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1065,7 +1065,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; @@ -1091,7 +1091,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; @@ -1230,7 +1230,7 @@ "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1256,7 +1256,7 @@ "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; @@ -1269,7 +1269,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1282,7 +1282,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; @@ -1310,7 +1310,7 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1338,7 +1338,7 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; @@ -1354,7 +1354,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1370,7 +1370,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 3f28d33b..5ef84dd7 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -151,13 +151,18 @@ extension Connection { guard let (token, range) = next(string) else { return nil } - let view = string.utf8 - offset.pointee += Int32(string.substring(to: range.lowerBound).utf8.count) - length.pointee = Int32(view.distance(from: range.lowerBound.samePosition(in: view), to: range.upperBound.samePosition(in: view))) - return token + let view:String.UTF8View = string.utf8 + + if let from = range.lowerBound.samePosition(in: view), + let to = range.upperBound.samePosition(in: view) { + offset.pointee += Int32(string[string.startIndex..(lhs: V, rhs: Expression) -> Expression wher return infix(lhs, rhs) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } From f898813ccf9f4966e6889ae74bb4a67104df7d69 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 14 Sep 2017 20:55:01 +0200 Subject: [PATCH 21/86] replace custom validator w/ new test_specs --- SQLite.swift.podspec | 23 ++++- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 41 ++++---- Tests/CocoaPods/integration_test.rb | 18 ++-- Tests/CocoaPods/test_running_validator.rb | 120 ---------------------- Tests/SQLiteTests/Fixtures.swift | 2 +- 6 files changed, 49 insertions(+), 157 deletions(-) delete mode 100644 Tests/CocoaPods/test_running_validator.rb diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 808e8a46..9ffb885f 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.3" + s.version = "0.11.4" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,26 +21,35 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '3.0', + 'SWIFT_VERSION' => '4.0', } s.subspec 'standard' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' - ss.library = 'sqlite3' + + ss.test_spec 'tests' do |test_spec| + test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.source_files = 'Tests/SQLiteTests/*.swift' + end end s.subspec 'standalone' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' } + ss.dependency 'sqlite3' - ss.dependency 'sqlite3', '>= 3.14.0' + ss.test_spec 'tests' do |test_spec| + test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.source_files = 'Tests/SQLiteTests/*.swift' + end end s.subspec 'SQLCipher' do |ss| @@ -50,7 +59,11 @@ Pod::Spec.new do |s| 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' } - ss.dependency 'SQLCipher', '>= 3.4.0' + + ss.test_spec 'tests' do |test_spec| + test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.source_files = 'Tests/SQLiteTests/*.swift' + end end end diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index e40770e8..04f0155a 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.2.0' +gem 'cocoapods', '~> 1.3.1' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index 869ae3dc..47a2db58 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -2,33 +2,33 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (2.3.5) - activesupport (4.2.8) + activesupport (4.2.9) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - claide (1.0.1) - cocoapods (1.2.0) + claide (1.0.2) + cocoapods (1.3.1) activesupport (>= 4.0.2, < 5) - claide (>= 1.0.1, < 2.0) - cocoapods-core (= 1.2.0) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.3.1) cocoapods-deintegrate (>= 1.0.1, < 2.0) cocoapods-downloader (>= 1.1.3, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.1.2, < 2.0) + cocoapods-trunk (>= 1.2.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) - colored (~> 1.2) + colored2 (~> 3.1) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.5.5) + molinillo (~> 0.5.7) nap (~> 1.0) - ruby-macho (~> 0.2.5) - xcodeproj (>= 1.4.1, < 2.0) - cocoapods-core (1.2.0) - activesupport (>= 4.0.2, < 5) + ruby-macho (~> 1.1) + xcodeproj (>= 1.5.1, < 2.0) + cocoapods-core (1.3.1) + activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.1) @@ -37,37 +37,36 @@ GEM nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.1.2) + cocoapods-trunk (1.2.0) nap (>= 0.8, < 2.0) netrc (= 0.7.8) cocoapods-try (1.1.0) - colored (1.2) + colored2 (3.1.2) escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) gh_inspector (1.0.3) - i18n (0.8.1) + i18n (0.8.6) minitest (5.10.1) molinillo (0.5.7) nanaimo (0.2.3) nap (1.1.0) netrc (0.7.8) - ruby-macho (0.2.6) + ruby-macho (1.1.0) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) - xcodeproj (1.4.2) + xcodeproj (1.5.1) CFPropertyList (~> 2.3.3) - activesupport (>= 3) - claide (>= 1.0.1, < 2.0) - colored (~> 1.2) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) nanaimo (~> 0.2.3) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.2.0) + cocoapods (~> 1.3.1) minitest BUNDLED WITH diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 192aff90..9792b570 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -1,7 +1,8 @@ #!/usr/bin/env ruby +require 'cocoapods' +require 'cocoapods/validator' require 'minitest/autorun' -require_relative 'test_running_validator' class IntegrationTest < Minitest::Test @@ -12,9 +13,7 @@ def test_validate_project private def validator - @validator ||= TestRunningValidator.new(podspec, []).tap do |validator| - validator.test_files = Dir["#{project_test_dir}/**/*.swift"] - validator.test_resources = Dir["#{project_test_dir}/fixtures"] + @validator ||= CustomValidator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| validator.config.verbose = true validator.no_clean = true validator.use_frameworks = true @@ -27,9 +26,6 @@ def validator else validator.only_subspec = subspec end - if ENV['IOS_SIMULATOR'] - validator.ios_simulator = ENV['IOS_SIMULATOR'] - end end end @@ -37,7 +33,11 @@ def podspec File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec') end - def project_test_dir - File.expand_path(File.dirname(__FILE__) + '/../SQLiteTests') + + class CustomValidator < Pod::Validator + def test_pod + # https://github.com/CocoaPods/CocoaPods/issues/7009 + super unless consumer.platform_name == :watchos + end end end diff --git a/Tests/CocoaPods/test_running_validator.rb b/Tests/CocoaPods/test_running_validator.rb deleted file mode 100644 index fcfd3c28..00000000 --- a/Tests/CocoaPods/test_running_validator.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'cocoapods' -require 'cocoapods/validator' -require 'fileutils' - -class TestRunningValidator < Pod::Validator - APP_TARGET = 'App' - TEST_TARGET = 'Tests' - - attr_accessor :test_files - attr_accessor :test_resources - attr_accessor :ios_simulator - attr_accessor :tvos_simulator - attr_accessor :watchos_simulator - - def initialize(spec_or_path, source_urls) - super(spec_or_path, source_urls) - self.test_files = [] - self.test_resources = [] - self.ios_simulator = :oldest - self.tvos_simulator = :oldest - self.watchos_simulator = :oldest - end - - def create_app_project - super - project = Xcodeproj::Project.open(validation_dir + "#{APP_TARGET}.xcodeproj") - create_test_target(project) - project.save - end - - def add_app_project_import - super - project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj') - group = project.new_group(TEST_TARGET) - test_target = project.targets.last - test_target.add_resources(test_resources.map { |resource| group.new_file(resource) }) - test_target.add_file_references(test_files.map { |file| group.new_file(file) }) - add_swift_version(test_target) - project.save - end - - def install_pod - super - if local? - FileUtils.ln_s file.dirname, validation_dir + "Pods/#{spec.name}" - end - end - - def podfile_from_spec(*args) - super(*args).tap do |pod_file| - add_test_target(pod_file) - end - end - - def build_pod - super - Pod::UI.message "\Testing with xcodebuild.\n".yellow do - run_tests - end - end - - private - def create_test_target(project) - test_target = project.new_target(:unit_test_bundle, TEST_TARGET, consumer.platform_name, deployment_target) - create_test_scheme(project, test_target) - end - - def create_test_scheme(project, test_target) - project.recreate_user_schemes - test_scheme = Xcodeproj::XCScheme.new(test_scheme_path(project)) - test_scheme.add_test_target(test_target) - test_scheme.save! - end - - def test_scheme_path(project) - Xcodeproj::XCScheme.user_data_dir(project.path) + "#{TEST_TARGET}.xcscheme" - end - - def add_test_target(pod_file) - app_target = pod_file.target_definitions[APP_TARGET] - Pod::Podfile::TargetDefinition.new(TEST_TARGET, app_target) - end - - def run_tests - command = [ - 'clean', 'build', 'build-for-testing', 'test-without-building', - '-workspace', File.join(validation_dir, "#{APP_TARGET}.xcworkspace"), - '-scheme', TEST_TARGET, - '-configuration', 'Debug' - ] - case consumer.platform_name - when :ios - command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) - command += Fourflusher::SimControl.new.destination(ios_simulator, 'iOS', deployment_target) - when :osx - command += %w(LD_RUNPATH_SEARCH_PATHS=@loader_path/../Frameworks) - when :tvos - command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) - command += Fourflusher::SimControl.new.destination(tvos_simulator, 'tvOS', deployment_target) - when :watchos - # there's no XCTest on watchOS (https://openradar.appspot.com/21760513) - return - else - return - end - - output, status = _xcodebuild(command) - - unless status.success? - message = 'Returned an unsuccessful exit code.' - if config.verbose? - message += "\nXcode output: \n#{output}\n" - else - message += ' You can use `--verbose` for more information.' - end - error('xcodebuild', message) - end - output - end -end diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index 13f83f77..d0683130 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -3,6 +3,6 @@ import Foundation func fixture(_ name: String, withExtension: String?) -> String { let testBundle = Bundle(for: SQLiteTestCase.self) return testBundle.url( - forResource: URL(string: "fixtures")?.appendingPathComponent(name).path, + forResource: name, withExtension: withExtension)!.path } From c7656a6d90caffb92717da735182daa51dabfabd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 15 Sep 2017 17:59:39 +0200 Subject: [PATCH 22/86] Lower deployment target #624, #671, #717 --- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 9ffb885f..4a329338 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/stephencelis' s.module_name = 'SQLite' - s.ios.deployment_target = "9.0" + s.ios.deployment_target = "8.0" s.tvos.deployment_target = "9.1" s.osx.deployment_target = "10.10" s.watchos.deployment_target = "2.2" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 8e3162d9..fc92eeff 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1220,7 +1220,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1247,7 +1247,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; From ac52569861e6ced88f168be1fca2e20bcd1118cd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 09:58:25 +0200 Subject: [PATCH 23/86] fix create/drop index functions --- Sources/SQLite/Typed/Schema.swift | 11 ++--------- Tests/SQLiteTests/SchemaTests.swift | 10 +++++----- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 7bf70fe4..690a26a5 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -127,11 +127,7 @@ extension Table { // MARK: - CREATE INDEX - public func createIndex(_ columns: Expressible...) -> String { - return createIndex(columns) - } - - public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { + public func createIndex(_ columns: Expressible..., unique: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create("INDEX", indexName(columns), unique ? .unique : nil, ifNotExists), Expression(literal: "ON"), @@ -144,11 +140,8 @@ extension Table { // MARK: - DROP INDEX - public func dropIndex(_ columns: Expressible...) -> String { - return dropIndex(columns) - } - public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String { + public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { return drop("INDEX", indexName(columns), ifExists) } diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index e59b569b..34226b3c 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -727,25 +727,25 @@ class SchemaTests : XCTestCase { XCTAssertEqual( "CREATE UNIQUE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")", - table.createIndex([int64], unique: true) + table.createIndex(int64, unique: true) ) XCTAssertEqual( "CREATE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", - table.createIndex([int64], ifNotExists: true) + table.createIndex(int64, ifNotExists: true) ) XCTAssertEqual( "CREATE UNIQUE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", - table.createIndex([int64], unique: true, ifNotExists: true) + table.createIndex(int64, unique: true, ifNotExists: true) ) XCTAssertEqual( "CREATE UNIQUE INDEX IF NOT EXISTS \"main\".\"index_table_on_int64\" ON \"table\" (\"int64\")", - qualifiedTable.createIndex([int64], unique: true, ifNotExists: true) + qualifiedTable.createIndex(int64, unique: true, ifNotExists: true) ) } func test_dropIndex_compilesCreateIndexExpression() { XCTAssertEqual("DROP INDEX \"index_table_on_int64\"", table.dropIndex(int64)) - XCTAssertEqual("DROP INDEX IF EXISTS \"index_table_on_int64\"", table.dropIndex([int64], ifExists: true)) + XCTAssertEqual("DROP INDEX IF EXISTS \"index_table_on_int64\"", table.dropIndex(int64, ifExists: true)) } func test_create_onView_compilesCreateViewExpression() { From 0bb468359a9e8e709741c85ca4daa85067a7a6a6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 09:59:44 +0200 Subject: [PATCH 24/86] Changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6387a56..495034a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +0.11.4 (xx-09-2017), [diff][diff-0.11.4] +======================================== + +* Fix create/drop index functions ([#666][]) +* Set deployment target to 8.0 (#624, #671, #717) +* Added support for the union query clause ([#723][]) +* Add support for ORDER and LIMIT on UPDATE and DELETE ([#722][]) +* Swift 4 support [(#668][]) + 0.11.3 (30-03-2017), [diff][diff-0.11.3] ======================================== @@ -33,6 +42,7 @@ [diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1 [diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 +[diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 @@ -45,3 +55,7 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#666]: https://github.com/stephencelis/SQLite.swift/pull/666 +[#668]: https://github.com/stephencelis/SQLite.swift/pull/668 +[#722]: https://github.com/stephencelis/SQLite.swift/pull/722 +[#723]: https://github.com/stephencelis/SQLite.swift/pull/723 From 3708a962dadcd46dfc9d47f44c826029d4c0c4c1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 12:01:01 +0200 Subject: [PATCH 25/86] Bump version number --- Documentation/Index.md | 10 +++++----- README.md | 8 ++++---- Sources/SQLite/Info.plist | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 491a5938..2255373c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.3 + github "stephencelis/SQLite.swift" ~> 0.11.4 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -113,7 +113,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.3' + pod 'SQLite.swift', '~> 0.11.4' end ``` @@ -126,7 +126,7 @@ install SQLite.swift with Carthage: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.3' + pod 'SQLite.swift/standalone', '~> 0.11.4' end ``` @@ -134,7 +134,7 @@ By default this will use the most recent version of SQLite without any extras. I ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.3' + pod 'SQLite.swift/standalone', '~> 0.11.4' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ subspec in your Podfile: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.3' + pod 'SQLite.swift/SQLCipher', '~> 0.11.4' end ``` diff --git a/README.md b/README.md index 8c3affd9..420da81d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-3-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) +[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-4-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -109,7 +109,7 @@ For a more comprehensive example, see [this article](http://masteringswift.blogs ## Installation -> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8). Use the [swift-4][] branch for Xcode 9 Beta. +> _Note:_ SQLite.swift requires Swift 4 (and [Xcode][] 9). ### Carthage @@ -121,7 +121,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.3 + github "stephencelis/SQLite.swift" ~> 0.11.4 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -157,7 +157,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.3' + pod 'SQLite.swift', '~> 0.11.4' end ``` diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 8c55becd..7347d842 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.3 + 0.11.4 CFBundleSignature ???? CFBundleVersion From 70c06c6624599a9bb78197e100d03a62cafd46cb Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 12:08:31 +0200 Subject: [PATCH 26/86] Fix link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 495034a5..2473b6ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Set deployment target to 8.0 (#624, #671, #717) * Added support for the union query clause ([#723][]) * Add support for ORDER and LIMIT on UPDATE and DELETE ([#722][]) -* Swift 4 support [(#668][]) +* Swift 4 support ([#668][]) 0.11.3 (30-03-2017), [diff][diff-0.11.3] ======================================== From 9d42763509956cea05959f7b16c96bf4428a5202 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 18:16:26 +0200 Subject: [PATCH 27/86] Add ticket --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2473b6ba..b70b1403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) * Added support for the union query clause ([#723][]) -* Add support for ORDER and LIMIT on UPDATE and DELETE ([#722][]) +* Add support for ORDER and LIMIT on UPDATE and DELETE ([#657][], [#722][]) * Swift 4 support ([#668][]) 0.11.3 (30-03-2017), [diff][diff-0.11.3] @@ -55,6 +55,7 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 From b8d6a9cf9495358dd917d5270a1efef345ea0796 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 19:42:31 +0200 Subject: [PATCH 28/86] Generics work as expected now --- Sources/SQLite/Foundation.swift | 38 -------------- Sources/SQLite/Typed/Query.swift | 81 ++---------------------------- Tests/SQLiteTests/QueryTests.swift | 16 ++++++ 3 files changed, 20 insertions(+), 115 deletions(-) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 5e102297..5638bc5b 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -68,41 +68,3 @@ public var dateFormatter: DateFormatter = { formatter.timeZone = TimeZone(secondsFromGMT: 0) return formatter }() - -// FIXME: rdar://problem/18673897 // subscript… - -extension QueryType { - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - -} - -extension Row { - - public subscript(column: Expression) -> Data { - return get(column) - } - public subscript(column: Expression) -> Data? { - return get(column) - } - - public subscript(column: Expression) -> Date { - return get(column) - } - public subscript(column: Expression) -> Date? { - return get(column) - } - -} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 79fdbc66..7c0a055f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -726,47 +726,11 @@ extension QueryType { return Expression(".".join([tableName(), column]).expression) } - // FIXME: rdar://problem/18673897 // subscript… - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } @@ -1102,50 +1066,13 @@ public struct Row { return valueAtIndex(idx) } - // FIXME: rdar://problem/18673897 // subscript… - - public subscript(column: Expression) -> Blob { - return get(column) - } - public subscript(column: Expression) -> Blob? { - return get(column) - } - - public subscript(column: Expression) -> Bool { - return get(column) - } - public subscript(column: Expression) -> Bool? { + public subscript(column: Expression) -> T { return get(column) } - public subscript(column: Expression) -> Double { + public subscript(column: Expression) -> T? { return get(column) } - public subscript(column: Expression) -> Double? { - return get(column) - } - - public subscript(column: Expression) -> Int { - return get(column) - } - public subscript(column: Expression) -> Int? { - return get(column) - } - - public subscript(column: Expression) -> Int64 { - return get(column) - } - public subscript(column: Expression) -> Int64? { - return get(column) - } - - public subscript(column: Expression) -> String { - return get(column) - } - public subscript(column: Expression) -> String? { - return get(column) - } - } /// Determines the join operator for a query’s `JOIN` clause. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 6bb109bb..813173ad 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -348,6 +348,22 @@ class QueryIntegrationTests : SQLiteTestCase { } } + func test_select_optional() { + for _ in try! db.prepare(users) { + // FIXME + } + + let managerId = Expression("manager_id") + let managers = users.alias("managers") + + let alice = try! db.run(users.insert(email <- "alice@example.com")) + _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + + for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + _ = user[users[managerId]] + } + } + func test_scalar() { XCTAssertEqual(0, try! db.scalar(users.count)) XCTAssertEqual(false, try! db.scalar(users.exists)) From bd1fc80414c89b8d7b756899cff2a153d5fd04f1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 20:43:28 +0200 Subject: [PATCH 29/86] Row.get should throw, not crash #649, #413 --- SQLite.xcodeproj/project.pbxproj | 10 ++++++++++ Sources/SQLite/Core/Errors.swift | 18 ++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 19 ++++++++++--------- Tests/SQLiteTests/QueryTests.swift | 14 ++++++++++++++ 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 Sources/SQLite/Core/Errors.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fc92eeff..e246e76d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -51,22 +51,26 @@ 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17FDA323BAFDEC627E76F /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; + 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; @@ -196,6 +200,7 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; @@ -408,6 +413,7 @@ EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, + 19A1710E73A46D5AC721CDA9 /* Errors.swift */, ); path = Core; sourceTree = ""; @@ -787,6 +793,7 @@ 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */, + 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -840,6 +847,7 @@ 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */, 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */, + 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -867,6 +875,7 @@ EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */, + 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -920,6 +929,7 @@ EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */, + 19A17490543609FCED53CACC /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Errors.swift b/Sources/SQLite/Core/Errors.swift new file mode 100644 index 00000000..e069f50e --- /dev/null +++ b/Sources/SQLite/Core/Errors.swift @@ -0,0 +1,18 @@ +import Foundation + +public enum QueryError: Error, CustomStringConvertible { + case noSuchTable(name: String) + case noSuchColumn(name: String, columns: [String]) + case ambiguousColumn(name: String, similar: [String]) + + public var description: String { + switch self { + case .noSuchTable(let name): + return "No such table: \(name)" + case .noSuchColumn(let name, let columns): + return "No such column `\(name)` in columns \(columns)" + case .ambiguousColumn(let name, let similar): + return "Ambiguous column `\(name)` (please disambiguate: \(similar))" + } + } +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 7c0a055f..7bcda40b 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -929,7 +929,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) @@ -1041,13 +1041,14 @@ 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? { + + public func get(_ column: Expression) throws -> V? { func valueAtIndex(_ idx: Int) -> V? { guard let value = values[idx] as? V.Datatype else { return nil } - return (V.fromDatatypeValue(value) as? V)! + return V.fromDatatypeValue(value) as? V } guard let idx = columnNames[column.template] else { @@ -1055,11 +1056,11 @@ public struct Row { switch similar.count { case 0: - fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sorted())") + throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) case 1: return valueAtIndex(columnNames[similar[0]]!) default: - fatalError("ambiguous column '\(column.template)' (please disambiguate: \(similar))") + throw QueryError.ambiguousColumn(name: column.template, similar: similar) } } @@ -1067,11 +1068,11 @@ public struct Row { } public subscript(column: Expression) -> T { - return get(column) + return try! get(column) } public subscript(column: Expression) -> T? { - return get(column) + return try! get(column) } } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 813173ad..3ab0419a 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -412,4 +412,18 @@ class QueryIntegrationTests : SQLiteTestCase { let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) } + + func test_no_such_column() throws { + let doesNotExist = Expression("doesNotExist") + try! InsertUser("alice") + let row = try! db.pluck(users.filter(email == "alice@example.com"))! + + XCTAssertThrowsError(try row.get(doesNotExist)) { error in + if case QueryError.noSuchColumn(let name, _) = error { + XCTAssertEqual("\"doesNotExist\"", name) + } else { + XCTFail("unexpected error: \(error)") + } + } + } } From b286edee9b1244094b130d39e8b634b70d966b08 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 20:50:19 +0200 Subject: [PATCH 30/86] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b70b1403..e59e221c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) * Added support for the union query clause ([#723][]) @@ -55,6 +56,7 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#649]: https://github.com/stephencelis/SQLite.swift/pull/649 [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 From 5d1c7936ba63d68e5aeb3560590d124a6911ee7d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 21:09:11 +0200 Subject: [PATCH 31/86] No longer applies --- Documentation/Index.md | 12 +++--------- README.md | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 2255373c..7d13b5a7 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -93,13 +93,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed and active in the default location (`/Applications/Xcode.app`). - - ```sh - sudo xcode-select --switch /Applications/Xcode.app - ``` - - 2. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). + 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). ``` sh # Using the default Ruby install will require you to use sudo when @@ -107,7 +101,7 @@ install SQLite.swift with Carthage: [sudo] gem install cocoapods ``` - 3. Update your Podfile to include the following: + 2. Update your Podfile to include the following: ``` ruby use_frameworks! @@ -117,7 +111,7 @@ install SQLite.swift with Carthage: end ``` - 4. Run `pod install --repo-update`. + 3. Run `pod install --repo-update`. #### Requiring a specific version of SQLite diff --git a/README.md b/README.md index 420da81d..fe06c6f7 100644 --- a/README.md +++ b/README.md @@ -137,13 +137,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed and active in the default location (`/Applications/Xcode.app`). - - ```sh - sudo xcode-select --switch /Applications/Xcode.app - ``` - - 2. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) ``` sh # Using the default Ruby install will require you to use sudo when @@ -151,7 +145,7 @@ SQLite.swift with CocoaPods: [sudo] gem install cocoapods ``` - 3. Update your Podfile to include the following: + 2. Update your Podfile to include the following: ``` ruby use_frameworks! @@ -161,7 +155,7 @@ SQLite.swift with CocoaPods: end ``` - 4. Run `pod install --repo-update`. + 3. Run `pod install --repo-update`. [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started From dc1b643b509605e90a37ebf687d3905fd06b026b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 08:51:08 +0200 Subject: [PATCH 32/86] Handle null value case --- SQLite.xcodeproj/project.pbxproj | 8 +++ Sources/SQLite/Core/Errors.swift | 3 ++ Sources/SQLite/Typed/Query.swift | 8 ++- Tests/SQLiteTests/RowTests.swift | 88 ++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 Tests/SQLiteTests/RowTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index e246e76d..c033cf3a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -47,8 +47,10 @@ 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; @@ -58,6 +60,7 @@ 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -204,6 +207,7 @@ 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; + 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; @@ -398,6 +402,7 @@ 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, 19A17399EA9E61235D5D77BF /* CipherTests.swift */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, + 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -820,6 +825,7 @@ 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */, 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, + 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -902,6 +908,7 @@ 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */, 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, + 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -956,6 +963,7 @@ 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */, 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, + 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Errors.swift b/Sources/SQLite/Core/Errors.swift index e069f50e..3cd7ae9f 100644 --- a/Sources/SQLite/Core/Errors.swift +++ b/Sources/SQLite/Core/Errors.swift @@ -4,6 +4,7 @@ public enum QueryError: Error, CustomStringConvertible { case noSuchTable(name: String) case noSuchColumn(name: String, columns: [String]) case ambiguousColumn(name: String, similar: [String]) + case unexpectedNullValue(name: String) public var description: String { switch self { @@ -13,6 +14,8 @@ public enum QueryError: Error, CustomStringConvertible { return "No such column `\(name)` in columns \(columns)" case .ambiguousColumn(let name, let similar): return "Ambiguous column `\(name)` (please disambiguate: \(similar))" + case .unexpectedNullValue(let name): + return "Unexpected null value for column `\(name)`" } } } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 7bcda40b..8fa2c539 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1031,7 +1031,7 @@ public struct Row { fileprivate let values: [Binding?] - fileprivate init(_ columnNames: [String: Int], _ values: [Binding?]) { + internal init(_ columnNames: [String: Int], _ values: [Binding?]) { self.columnNames = columnNames self.values = values } @@ -1042,7 +1042,11 @@ public struct Row { /// /// - Returns: The value for the given column. public func get(_ column: Expression) throws -> V { - return try get(Expression(column))! + if let value = try get(Expression(column)) { + return value + } else { + throw QueryError.unexpectedNullValue(name: column.template) + } } public func get(_ column: Expression) throws -> V? { diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift new file mode 100644 index 00000000..4ef5dc2d --- /dev/null +++ b/Tests/SQLiteTests/RowTests.swift @@ -0,0 +1,88 @@ +import XCTest +@testable import SQLite + +class RowTests : XCTestCase { + + public func test_get_value() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = try! row.get(Expression("foo")) + + XCTAssertEqual("value", result) + } + + public func test_get_value_subscript() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = row[Expression("foo")] + + XCTAssertEqual("value", result) + } + + public func test_get_value_optional() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = try! row.get(Expression("foo")) + + XCTAssertEqual("value", result) + } + + public func test_get_value_optional_subscript() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = row[Expression("foo")] + + XCTAssertEqual("value", result) + } + + public func test_get_value_optional_nil() { + let row = Row(["\"foo\"": 0], [nil]) + let result = try! row.get(Expression("foo")) + + XCTAssertNil(result) + } + + public func test_get_value_optional_nil_subscript() { + let row = Row(["\"foo\"": 0], [nil]) + let result = row[Expression("foo")] + + XCTAssertNil(result) + } + + public func test_get_type_mismatch_throws_unexpected_null_value() { + let row = Row(["\"foo\"": 0], ["value"]) + XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + if case QueryError.unexpectedNullValue(let name) = error { + XCTAssertEqual("\"foo\"", name) + } else { + XCTFail("unexpected error: \(error)") + } + } + } + + public func test_get_type_mismatch_optional_returns_nil() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = try! row.get(Expression("foo")) + XCTAssertNil(result) + } + + public func test_get_non_existent_column_throws_no_such_column() { + let row = Row(["\"foo\"": 0], ["value"]) + XCTAssertThrowsError(try row.get(Expression("bar"))) { error in + if case QueryError.noSuchColumn(let name, let columns) = error { + XCTAssertEqual("\"bar\"", name) + XCTAssertEqual(["\"foo\""], columns) + } else { + XCTFail("unexpected error: \(error)") + } + } + } + + public func test_get_ambiguous_column_throws() { + let row = Row(["table1.\"foo\"": 0, "table2.\"foo\"": 1], ["value"]) + XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + if case QueryError.ambiguousColumn(let name, let columns) = error { + XCTAssertEqual("\"foo\"", name) + XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns) + } else { + XCTFail("unexpected error: \(error)") + } + } + } +} From d79d52c74ed138280c36314f0e1e42757679fc6d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:04:20 +0200 Subject: [PATCH 33/86] Simplify `sync` #640 --- Sources/SQLite/Core/Connection.swift | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 51b68ed8..a2e7dcf2 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -626,29 +626,12 @@ public final class Connection { // MARK: - Error Handling - func sync(_ block: @escaping () throws -> T) rethrows -> T { - var success: T? - var failure: Error? - - let box: () -> Void = { - do { - success = try block() - } catch { - failure = error - } - } - + func sync(_ block: () throws -> T) rethrows -> T { if DispatchQueue.getSpecific(key: Connection.queueKey) == queueContext { - box() + return try block() } else { - queue.sync(execute: box) // FIXME: rdar://problem/21389236 - } - - if let failure = failure { - try { () -> Void in throw failure }() + return try queue.sync(execute: block) } - - return success! } @discardableResult func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { From a1dc335fee02985a4af08e2e32421b28e9cfac7b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:08:17 +0200 Subject: [PATCH 34/86] Fix FIXMEs --- Sources/SQLite/Typed/Expression.swift | 3 +-- Tests/SQLiteTests/CoreFunctionsTests.swift | 2 +- Tests/SQLiteTests/QueryTests.swift | 10 +--------- Tests/SQLiteTests/TestHelpers.swift | 2 +- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 3198901c..d26b6692 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -73,8 +73,7 @@ public protocol Expressible { extension Expressible { // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - // FIXME: use @testable and make internal - public func asSQL() -> String { + internal func asSQL() -> String { let expressed = expression var idx = 0 return expressed.template.characters.reduce("") { template, character in diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index db37ff7f..4a2b511e 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class CoreFunctionsTests : XCTestCase { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 3ab0419a..7dde0c53 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class QueryTests : XCTestCase { @@ -333,10 +333,6 @@ class QueryIntegrationTests : SQLiteTestCase { // MARK: - func test_select() { - for _ in try! db.prepare(users) { - // FIXME - } - let managerId = Expression("manager_id") let managers = users.alias("managers") @@ -349,10 +345,6 @@ class QueryIntegrationTests : SQLiteTestCase { } func test_select_optional() { - for _ in try! db.prepare(users) { - // FIXME - } - let managerId = Expression("manager_id") let managers = users.alias("managers") diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 8c33bf6a..b66144b3 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class SQLiteTestCase : XCTestCase { From 9b8cc16114f25a8b8e8541f3524a38434cbdf554 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:12:20 +0200 Subject: [PATCH 35/86] Drop `@escaping` --- Sources/SQLite/Core/Connection.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index a2e7dcf2..0189d300 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -328,7 +328,7 @@ public final class Connection { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(_ mode: TransactionMode = .deferred, block: @escaping () throws -> Void) throws { + public func transaction(_ mode: TransactionMode = .deferred, block: () throws -> Void) throws { try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") } @@ -348,14 +348,14 @@ public final class Connection { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - public func savepoint(_ name: String = UUID().uuidString, block: @escaping () throws -> Void) throws { + public func savepoint(_ name: String = UUID().uuidString, block: () throws -> Void) throws { let name = name.quote("'") let savepoint = "SAVEPOINT \(name)" try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") } - fileprivate func transaction(_ begin: String, _ block: @escaping () throws -> Void, _ commit: String, or rollback: String) throws { + fileprivate func transaction(_ begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { return try sync { try self.run(begin) do { From 5850d8397d846814e58d74cdab5710ebfa639b8a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:41:28 +0200 Subject: [PATCH 36/86] Fix test --- Tests/SQLiteTests/RowTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift index 4ef5dc2d..17873e71 100644 --- a/Tests/SQLiteTests/RowTests.swift +++ b/Tests/SQLiteTests/RowTests.swift @@ -79,7 +79,7 @@ class RowTests : XCTestCase { XCTAssertThrowsError(try row.get(Expression("foo"))) { error in if case QueryError.ambiguousColumn(let name, let columns) = error { XCTAssertEqual("\"foo\"", name) - XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns) + XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns.sorted()) } else { XCTFail("unexpected error: \(error)") } From 9f1735a52898a3cae23020a76e2003357712c38c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 10:31:56 +0200 Subject: [PATCH 37/86] Keep API compatibility --- Sources/SQLite/Typed/Expression.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index d26b6692..33329b73 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -73,7 +73,8 @@ public protocol Expressible { extension Expressible { // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - internal func asSQL() -> String { + // FIXME: make internal (0.12.0) + public func asSQL() -> String { let expressed = expression var idx = 0 return expressed.template.characters.reduce("") { template, character in From feb26b06cd29584bba59c5db5f84b958a476d9bc Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Fri, 5 May 2017 14:48:18 -0600 Subject: [PATCH 38/86] Add cursor type for more safety. --- Sources/SQLite/Core/Statement.swift | 8 ++ Sources/SQLite/Typed/Query.swift | 111 ++++++++++++++++++---------- Tests/SQLiteTests/QueryTests.swift | 10 +++ 3 files changed, 89 insertions(+), 40 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 8c13ff6c..c817e887 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -191,6 +191,14 @@ public final class Statement { } +extension Statement { + + func rowCursorNext() throws -> [Binding?]? { + return try step() ? Array(row) : nil + } + +} + extension Statement : Sequence { public func makeIterator() -> Statement { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 8fa2c539..199e5ef8 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -894,58 +894,89 @@ public struct Delete : ExpressionType { } +public struct RowCursor { + let statement: Statement + let columnNames: [String: Int] + + public func next() throws -> Row? { + return try statement.rowCursorNext().flatMap { Row(columnNames, $0) } + } + + public func map(_ transform: (Row) throws -> T) throws -> [T] { + var elements = [T]() + while true { + if let row = try next() { + elements.append(try transform(row)) + } else { + break + } + } + + return elements + } +} + extension Connection { + + public func prepareCursor(_ query: QueryType) throws -> RowCursor { + let expression = query.expression + let statement = try prepare(expression.template, expression.bindings) + return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) + } public func prepare(_ query: QueryType) throws -> AnySequence { let expression = query.expression let statement = try prepare(expression.template, expression.bindings) - let columnNames: [String: Int] = try { - var (columnNames, idx) = ([String: Int](), 0) - column: for each in query.clauses.select.columns { - var names = each.expression.template.characters.split { $0 == "." }.map(String.init) - let column = names.removeLast() - let namespace = names.joined(separator: ".") - - func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { - return { (query: QueryType) throws -> (Void) in - var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) - q.clauses.select = query.clauses.select - let e = q.expression - var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } - if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } - for name in names { columnNames[name] = idx; idx += 1 } - } - } + let columnNames = try columnNamesForQuery(query) - if column == "*" { - var select = query - select.clauses.select = (false, [Expression(literal: "*") as Expressible]) - let queries = [select] + query.clauses.join.map { $0.query } - if !namespace.isEmpty { - for q in queries { - if q.tableName().expression.template == namespace { - try expandGlob(true)(q) - continue column - } + return AnySequence { + AnyIterator { statement.next().map { Row(columnNames, $0) } } + } + } + + private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { + var (columnNames, idx) = ([String: Int](), 0) + column: for each in query.clauses.select.columns { + var names = each.expression.template.characters.split { $0 == "." }.map(String.init) + let column = names.removeLast() + let namespace = names.joined(separator: ".") + + func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { + return { (query: QueryType) throws -> (Void) in + var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) + q.clauses.select = query.clauses.select + let e = q.expression + var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } + if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } + for name in names { columnNames[name] = idx; idx += 1 } + } + } + + if column == "*" { + var select = query + select.clauses.select = (false, [Expression(literal: "*") as Expressible]) + let queries = [select] + query.clauses.join.map { $0.query } + if !namespace.isEmpty { + for q in queries { + if q.tableName().expression.template == namespace { + try expandGlob(true)(q) + continue column } throw QueryError.noSuchTable(name: namespace) } - for q in queries { - try expandGlob(query.clauses.join.count > 0)(q) - } - continue + fatalError("no such table: \(namespace)") } - - columnNames[each.expression.template] = idx - idx += 1 + for q in queries { + try expandGlob(query.clauses.join.count > 0)(q) + } + continue } - return columnNames - }() - - return AnySequence { - AnyIterator { statement.next().map { Row(columnNames, $0) } } + + columnNames[each.expression.template] = idx + idx += 1 } + return columnNames } public func scalar(_ query: ScalarQuery) throws -> V { @@ -971,7 +1002,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepare(query.limit(1, query.clauses.limit?.offset)).makeIterator().next() + return try prepareCursor(query.limit(1, query.clauses.limit?.offset)).next() } /// Runs an `Insert` query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 7dde0c53..feb0596d 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -343,6 +343,16 @@ class QueryIntegrationTests : SQLiteTestCase { _ = user[users[managerId]] } } + + func test_prepareCursor() { + let names = ["a", "b", "c"] + try! InsertUsers(names) + + let emailColumn = Expression("email") + let emails = try! db.prepareCursor(users).map { $0[emailColumn] } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } func test_select_optional() { let managerId = Expression("manager_id") From 28e2cfa1cc9a0251abdbd707bec11f265a62c0a9 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Wed, 20 Sep 2017 07:53:39 -0600 Subject: [PATCH 39/86] Change fatalError to throw. --- Sources/SQLite/Typed/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 199e5ef8..e9fa941c 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -965,7 +965,7 @@ extension Connection { } throw QueryError.noSuchTable(name: namespace) } - fatalError("no such table: \(namespace)") + throw QueryError.noSuchTable(name: namespace) } for q in queries { try expandGlob(query.clauses.join.count > 0)(q) From 5a9b583d2a28d12bc8523b204e75a0da6c8b04ff Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 24 Sep 2017 22:53:16 +0200 Subject: [PATCH 40/86] Introduce FailableIterator --- Sources/SQLite/Core/Statement.swift | 40 +++++++++++++++++++++-------- Sources/SQLite/Typed/Query.swift | 38 ++++++++++----------------- Tests/SQLiteTests/QueryTests.swift | 10 ++++---- 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index c817e887..5df00791 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -191,14 +191,6 @@ public final class Statement { } -extension Statement { - - func rowCursorNext() throws -> [Binding?]? { - return try step() ? Array(row) : nil - } - -} - extension Statement : Sequence { public func makeIterator() -> Statement { @@ -208,12 +200,38 @@ extension Statement : Sequence { } -extension Statement : IteratorProtocol { +public protocol FailableIterator : IteratorProtocol { + func failableNext() throws -> Self.Element? +} - public func next() -> [Binding?]? { - return try! step() ? Array(row) : nil +extension FailableIterator { + public func next() -> Element? { + return try! failableNext() } + public func map(_ transform: (Element) throws -> T) throws -> [T] { + var elements = [T]() + while let row = try failableNext() { + elements.append(try transform(row)) + } + return elements + } +} + +extension Array { + public init(_ failableIterator: I) throws where I.Element == Element { + self.init() + while let row = try failableIterator.failableNext() { + append(row) + } + } +} + +extension Statement : FailableIterator { + public typealias Element = [Binding?] + public func failableNext() throws -> [Binding?]? { + return try step() ? Array(row) : nil + } } extension Statement : CustomStringConvertible { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index e9fa941c..cc54649e 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -894,35 +894,19 @@ public struct Delete : ExpressionType { } -public struct RowCursor { + +public struct RowCursor: FailableIterator { + public typealias Element = Row let statement: Statement let columnNames: [String: Int] - - public func next() throws -> Row? { - return try statement.rowCursorNext().flatMap { Row(columnNames, $0) } - } - - public func map(_ transform: (Row) throws -> T) throws -> [T] { - var elements = [T]() - while true { - if let row = try next() { - elements.append(try transform(row)) - } else { - break - } - } - - return elements + + public func failableNext() throws -> Row? { + return try statement.failableNext().flatMap { Row(columnNames, $0) } } } + extension Connection { - - public func prepareCursor(_ query: QueryType) throws -> RowCursor { - let expression = query.expression - let statement = try prepare(expression.template, expression.bindings) - return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) - } public func prepare(_ query: QueryType) throws -> AnySequence { let expression = query.expression @@ -935,6 +919,12 @@ extension Connection { } } + public func prepareRowCursor(_ query: QueryType) throws -> RowCursor { + let expression = query.expression + let statement = try prepare(expression.template, expression.bindings) + return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) + } + private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { var (columnNames, idx) = ([String: Int](), 0) column: for each in query.clauses.select.columns { @@ -1002,7 +992,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepareCursor(query.limit(1, query.clauses.limit?.offset)).next() + return try prepareRowCursor(query.limit(1, query.clauses.limit?.offset)).failableNext() } /// Runs an `Insert` query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index feb0596d..2c026b9b 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -343,14 +343,14 @@ class QueryIntegrationTests : SQLiteTestCase { _ = user[users[managerId]] } } - - func test_prepareCursor() { + + func test_prepareRowCursor() { let names = ["a", "b", "c"] try! InsertUsers(names) - + let emailColumn = Expression("email") - let emails = try! db.prepareCursor(users).map { $0[emailColumn] } - + let emails = try! db.prepareRowCursor(users).map { $0[emailColumn] } + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } From 30b78fdcf995cbe75dd3469188de6b326cfb2f08 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 25 Sep 2017 17:53:18 +0200 Subject: [PATCH 41/86] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e59e221c..2963d8d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Add cursor type for more safety ([#647](), [#726][]) * Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) @@ -56,9 +57,11 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#647]: https://github.com/stephencelis/SQLite.swift/pull/647 [#649]: https://github.com/stephencelis/SQLite.swift/pull/649 [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 +[#726]: https://github.com/stephencelis/SQLite.swift/pull/726 From 2ebdf7802bbea4b009c6d8493842d5eeccf3113d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 25 Sep 2017 18:18:17 +0200 Subject: [PATCH 42/86] RowCursor -> RowIterator --- CHANGELOG.md | 2 +- Sources/SQLite/Typed/Query.swift | 9 +++++---- Tests/SQLiteTests/QueryTests.swift | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2963d8d4..3fa68dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== -* Add cursor type for more safety ([#647](), [#726][]) +* Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index cc54649e..a08561cb 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -895,7 +895,7 @@ public struct Delete : ExpressionType { } -public struct RowCursor: FailableIterator { +public struct RowIterator: FailableIterator { public typealias Element = Row let statement: Statement let columnNames: [String: Int] @@ -919,10 +919,11 @@ extension Connection { } } - public func prepareRowCursor(_ query: QueryType) throws -> RowCursor { + + public func prepareRowIterator(_ query: QueryType) throws -> RowIterator { let expression = query.expression let statement = try prepare(expression.template, expression.bindings) - return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) + return RowIterator(statement: statement, columnNames: try columnNamesForQuery(query)) } private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { @@ -992,7 +993,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepareRowCursor(query.limit(1, query.clauses.limit?.offset)).failableNext() + return try prepareRowIterator(query.limit(1, query.clauses.limit?.offset)).failableNext() } /// Runs an `Insert` query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2c026b9b..df0e7bec 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -344,12 +344,12 @@ class QueryIntegrationTests : SQLiteTestCase { } } - func test_prepareRowCursor() { + func test_prepareRowIterator() { let names = ["a", "b", "c"] try! InsertUsers(names) let emailColumn = Expression("email") - let emails = try! db.prepareRowCursor(users).map { $0[emailColumn] } + let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } From c3c8360e89cd47a500622ded3268c77ca77f839f Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 19 Sep 2017 22:33:38 -0600 Subject: [PATCH 43/86] Add Coding support - Insert or update from an Encodable type - Decode a row into a Decodable type Closes stephencelis/SQLite.swift#727 --- Sources/SQLite/Typed/Query.swift | 321 ++++++++++++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 77 +++++++ Tests/SQLiteTests/TestHelpers.swift | 20 ++ 3 files changed, 418 insertions(+) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 8fa2c539..45d15c79 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import Foundation + public protocol QueryType : Expressible { var clauses: QueryClauses { get set } @@ -671,6 +673,26 @@ extension QueryType { ]).expression) } + /// Creates an `INSERT` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.insert(encoder.setters + otherSetters) + } + // MARK: UPDATE public func update(_ values: Setter...) -> Update { @@ -691,6 +713,26 @@ extension QueryType { return Update(" ".join(clauses.flatMap { $0 }).expression) } + /// Creates an `UPDATE` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `UPDATE` statement fort the encodable object + public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.update(encoder.setters + otherSetters) + } + // MARK: DELETE public func delete() -> Delete { @@ -1036,6 +1078,13 @@ public struct Row { self.values = values } + fileprivate func hasValue(for column: String) -> Bool { + guard let idx = columnNames[column.quote()] else { + return false + } + return values[idx] != nil + } + /// Returns a row’s value for the given column. /// /// - Parameter column: An expression representing a column selected in a Query. @@ -1071,6 +1120,22 @@ public struct Row { return valueAtIndex(idx) } + /// Decode an object from this row + /// This method expects any custom nested types to be in the form of JSON data and does not handle + /// any sort of object relationships. If you want to support relationships between objects you will + /// have to provide your own Decodable implementations that decodes the correct columns. + /// + /// - Parameter: userInfo + /// + /// - Returns: a decoded object from this row + public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { + return try V(from: self.decoder(userInfo: userInfo)) + } + + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { + return SQLiteDecoder(row: self, userInfo: userInfo) + } + public subscript(column: Expression) -> T { return try! get(column) } @@ -1134,3 +1199,259 @@ public struct QueryClauses { } } + +/// Generates a list of settings for an Encodable object +fileprivate class SQLiteEncoder: Encoder { + struct EncodingError: Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { + typealias Key = MyKey + + let encoder: SQLiteEncoder + let codingPath: [CodingKey] = [] + + init(encoder: SQLiteEncoder) { + self.encoder = encoder + } + + func superEncoder() -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func superEncoder(forKey key: Key) -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- nil) + } + + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Bool, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Float, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) + } + + func encode(_ value: Double, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: String, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { + if let data = value as? Data { + self.encoder.setters.append(Expression(key.stringValue) <- data) + } + else { + let encoded = try JSONEncoder().encode(value) + self.encoder.setters.append(Expression(key.stringValue) <- encoded) + } + } + + func encode(_ value: Int8, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int8 is not supported") + } + + func encode(_ value: Int16, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int16 is not supported") + } + + func encode(_ value: Int32, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int32 is not supported") + } + + func encode(_ value: Int64, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int64 is not supported") + } + + func encode(_ value: UInt, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt is not supported") + } + + func encode(_ value: UInt8, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt8 is not supported") + } + + func encode(_ value: UInt16, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt16 is not supported") + } + + func encode(_ value: UInt32, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt32 is not supported") + } + + func encode(_ value: UInt64, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt64 is not supported") + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { + fatalError("encoding a nested container is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + fatalError("encoding nested values is not supported") + } + } + + fileprivate var setters: [SQLite.Setter] = [] + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(userInfo: [CodingUserInfoKey: Any]) { + self.userInfo = userInfo + } + + func singleValueContainer() -> SingleValueEncodingContainer { + fatalError("not supported") + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + fatalError("not supported") + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + } +} + +class SQLiteDecoder : Decoder { + struct DecodingError : Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { + typealias Key = MyKey + + let codingPath: [CodingKey] = [] + let row: Row + + init(row: Row) { + self.row = row + } + + var allKeys: [Key] { + return self.row.columnNames.keys.flatMap({Key(stringValue: $0)}) + } + + func contains(_ key: Key) -> Bool { + return self.row.hasValue(for: key.stringValue) + } + + func decodeNil(forKey key: Key) throws -> Bool { + return !self.contains(key) + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + throw DecodingError(description: "decoding an Int8 is not supported") + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + throw DecodingError(description: "decoding an Int16 is not supported") + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + throw DecodingError(description: "decoding an Int32 is not supported") + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + throw DecodingError(description: "decoding an Int64 is not supported") + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + throw DecodingError(description: "decoding an UInt is not supported") + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + throw DecodingError(description: "decoding an UInt8 is not supported") + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + throw DecodingError(description: "decoding an UInt16 is not supported") + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + throw DecodingError(description: "decoding an UInt32 is not supported") + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + throw DecodingError(description: "decoding an UInt64 is not supported") + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + return Float(try self.row.get(Expression(key.stringValue))) + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { + guard let data = try self.row.get(Expression(key.stringValue)) else { + throw DecodingError(description: "an unsupported type was found") + } + if type == Data.self { + return data as! T + } + return try JSONDecoder().decode(type, from: data) + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + throw DecodingError(description: "decoding nested containers is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding unkeyed containers is not supported") + } + + func superDecoder() throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + + func superDecoder(forKey key: Key) throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + } + + let row: Row + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(row: Row, userInfo: [CodingUserInfoKey: Any]) { + self.row = row + self.userInfo = userInfo + } + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") + } +} diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 7dde0c53..be414735 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -238,6 +238,27 @@ class QueryTests : XCTestCase { ) } + func test_insert_encodable() throws { + let emails = Table("emails") + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let insert = try emails.insert(value) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0)", + insert + ) + } + + func test_insert_encodable_with_nested_encodable() throws { + let emails = Table("emails") + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: "optional", sub: value1) + let insert = try emails.insert(value) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d')", + insert + ) + } + func test_update_compilesUpdateExpression() { AssertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", @@ -252,6 +273,27 @@ class QueryTests : XCTestCase { ) } + func test_update_encodable() throws { + let emails = Table("emails") + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let update = try emails.update(value) + AssertSQL( + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0", + update + ) + } + + func test_update_encodable_with_nested_encodable() throws { + let emails = Table("emails") + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: value1) + let update = try emails.update(value) + AssertSQL( + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d'", + update + ) + } + func test_delete_compilesDeleteExpression() { AssertSQL( "DELETE FROM \"users\" WHERE (\"id\" = 1)", @@ -356,6 +398,41 @@ class QueryIntegrationTests : SQLiteTestCase { } } + func test_select_codable() throws { + let table = Table("codable") + try db.run(table.create { builder in + builder.column(Expression("int")) + builder.column(Expression("string")) + builder.column(Expression("bool")) + builder.column(Expression("float")) + builder.column(Expression("double")) + builder.column(Expression("optional")) + builder.column(Expression("sub")) + }) + + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, optional: "optional", sub: value1) + + try db.run(table.insert(value)) + + let rows = try db.prepare(table) + let values: [TestCodable] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0].int, 5) + XCTAssertEqual(values[0].string, "6") + XCTAssertEqual(values[0].bool, true) + XCTAssertEqual(values[0].float, 7) + XCTAssertEqual(values[0].double, 8) + XCTAssertEqual(values[0].optional, "optional") + XCTAssertEqual(values[0].sub?.int, 1) + XCTAssertEqual(values[0].sub?.string, "2") + XCTAssertEqual(values[0].sub?.bool, true) + XCTAssertEqual(values[0].sub?.float, 3) + XCTAssertEqual(values[0].sub?.double, 4) + XCTAssertNil(values[0].sub?.optional) + XCTAssertNil(values[0].sub?.sub) + } + func test_scalar() { XCTAssertEqual(0, try! db.scalar(users.count)) XCTAssertEqual(false, try! db.scalar(users.exists)) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index b66144b3..4572f6fe 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -113,3 +113,23 @@ let table = Table("table") let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") let _view = View("view") // avoid Mac XCTestCase collision + +class TestCodable: Codable { + let int: Int + let string: String + let bool: Bool + let float: Float + let double: Double + let optional: String? + let sub: TestCodable? + + init(int: Int, string: String, bool: Bool, float: Float, double: Double, optional: String?, sub: TestCodable?) { + self.int = int + self.string = string + self.bool = bool + self.float = float + self.double = double + self.optional = optional + self.sub = sub + } +} From 7312423d83bb713591a0d827a89fe664eba14451 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 26 Sep 2017 01:28:01 +0200 Subject: [PATCH 44/86] Avoid ambiguity --- Sources/SQLite/Core/Statement.swift | 8 -------- Sources/SQLite/Typed/Query.swift | 9 ++++++++- Tests/SQLiteTests/QueryTests.swift | 9 +++++++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 5df00791..1e48d3e8 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -208,14 +208,6 @@ extension FailableIterator { public func next() -> Element? { return try! failableNext() } - - public func map(_ transform: (Element) throws -> T) throws -> [T] { - var elements = [T]() - while let row = try failableNext() { - elements.append(try transform(row)) - } - return elements - } } extension Array { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index a08561cb..d5680cd0 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -903,8 +903,15 @@ public struct RowIterator: FailableIterator { public func failableNext() throws -> Row? { return try statement.failableNext().flatMap { Row(columnNames, $0) } } -} + public func map(_ transform: (Element) throws -> T) throws -> [T] { + var elements = [T]() + while let row = try failableNext() { + elements.append(try transform(row)) + } + return elements + } +} extension Connection { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index df0e7bec..0ba469a9 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -354,6 +354,15 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } + func test_ambiguousMap() { + let names = ["a", "b", "c"] + try! InsertUsers(names) + + let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } + func test_select_optional() { let managerId = Expression("manager_id") let managers = users.alias("managers") From 87b16d77db5df72d4c414658cc76d85f41914510 Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Mon, 25 Sep 2017 19:29:35 -0600 Subject: [PATCH 45/86] Store embedded Codable objects as JSON text instead of data --- Sources/SQLite/Typed/Query.swift | 13 +++++++++---- Tests/SQLiteTests/QueryTests.swift | 8 ++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 45d15c79..0b257fbe 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1254,7 +1254,8 @@ fileprivate class SQLiteEncoder: Encoder { } else { let encoded = try JSONEncoder().encode(value) - self.encoder.setters.append(Expression(key.stringValue) <- encoded) + let string = String(data: encoded, encoding: .utf8) + self.encoder.setters.append(Expression(key.stringValue) <- string) } } @@ -1408,12 +1409,16 @@ class SQLiteDecoder : Decoder { } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { - guard let data = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError(description: "an unsupported type was found") - } if type == Data.self { + let data = try self.row.get(Expression(key.stringValue)) return data as! T } + guard let JSONString = try self.row.get(Expression(key.stringValue)) else { + throw DecodingError(description: "an unsupported type was found") + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError(description: "invalid utf8 data found") + } return try JSONDecoder().decode(type, from: data) } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index be414735..6ba55d16 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -253,8 +253,10 @@ class QueryTests : XCTestCase { let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: "optional", sub: value1) let insert = try emails.insert(value) + let encodedJSON = try JSONEncoder().encode(value1) + let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d')", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', '\(encodedJSONString)')", insert ) } @@ -288,8 +290,10 @@ class QueryTests : XCTestCase { let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: value1) let update = try emails.update(value) + let encodedJSON = try JSONEncoder().encode(value1) + let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d'", + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = '\(encodedJSONString)'", update ) } From 96e55b19d31e30e4f3369d2ba077494c2c3d20b3 Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 26 Sep 2017 20:01:00 -0600 Subject: [PATCH 46/86] Extract Coding code to a separate file --- SQLite.xcodeproj/project.pbxproj | 10 + Sources/SQLite/Typed/Coding.swift | 345 ++++++++++++++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 320 +-------------------------- 3 files changed, 357 insertions(+), 318 deletions(-) create mode 100644 Sources/SQLite/Typed/Coding.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index c033cf3a..3093b11d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -97,6 +97,10 @@ 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; + 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.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 */; }; @@ -213,6 +217,7 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -446,6 +451,7 @@ EE247B001C3F06E900AE3E12 /* Query.swift */, EE247B011C3F06E900AE3E12 /* Schema.swift */, EE247B021C3F06E900AE3E12 /* Setter.swift */, + 49EB68C31F7B3CB400D89D40 /* Coding.swift */, ); path = Typed; sourceTree = ""; @@ -779,6 +785,7 @@ buildActionMask = 2147483647; files = ( 03A65E801C6BB2FB0062603F /* CoreFunctions.swift in Sources */, + 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */, 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */, @@ -834,6 +841,7 @@ buildActionMask = 2147483647; files = ( 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */, + 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, @@ -862,6 +870,7 @@ buildActionMask = 2147483647; files = ( EE247B0F1C3F06E900AE3E12 /* CoreFunctions.swift in Sources */, + 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B0A1C3F06E900AE3E12 /* RTree.swift in Sources */, EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */, EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */, @@ -917,6 +926,7 @@ buildActionMask = 2147483647; files = ( EE247B6F1C3F3FEC00AE3E12 /* CoreFunctions.swift in Sources */, + 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m in Sources */, diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift new file mode 100644 index 00000000..68f0a998 --- /dev/null +++ b/Sources/SQLite/Typed/Coding.swift @@ -0,0 +1,345 @@ +// +// 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. +// + +extension QueryType { + /// Creates an `INSERT` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.insert(encoder.setters + otherSetters) + } + + /// Creates an `UPDATE` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `UPDATE` statement fort the encodable object + public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.update(encoder.setters + otherSetters) + } +} + +extension Row { + /// Decode an object from this row + /// This method expects any custom nested types to be in the form of JSON data and does not handle + /// any sort of object relationships. If you want to support relationships between objects you will + /// have to provide your own Decodable implementations that decodes the correct columns. + /// + /// - Parameter: userInfo + /// + /// - Returns: a decoded object from this row + public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { + return try V(from: self.decoder(userInfo: userInfo)) + } + + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { + return SQLiteDecoder(row: self, userInfo: userInfo) + } +} + +/// Generates a list of settings for an Encodable object +fileprivate class SQLiteEncoder: Encoder { + struct EncodingError: Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { + typealias Key = MyKey + + let encoder: SQLiteEncoder + let codingPath: [CodingKey] = [] + + init(encoder: SQLiteEncoder) { + self.encoder = encoder + } + + func superEncoder() -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func superEncoder(forKey key: Key) -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- nil) + } + + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Bool, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Float, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) + } + + func encode(_ value: Double, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: String, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { + if let data = value as? Data { + self.encoder.setters.append(Expression(key.stringValue) <- data) + } + else { + let encoded = try JSONEncoder().encode(value) + let string = String(data: encoded, encoding: .utf8) + self.encoder.setters.append(Expression(key.stringValue) <- string) + } + } + + func encode(_ value: Int8, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int8 is not supported") + } + + func encode(_ value: Int16, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int16 is not supported") + } + + func encode(_ value: Int32, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int32 is not supported") + } + + func encode(_ value: Int64, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int64 is not supported") + } + + func encode(_ value: UInt, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt is not supported") + } + + func encode(_ value: UInt8, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt8 is not supported") + } + + func encode(_ value: UInt16, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt16 is not supported") + } + + func encode(_ value: UInt32, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt32 is not supported") + } + + func encode(_ value: UInt64, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt64 is not supported") + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { + fatalError("encoding a nested container is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + fatalError("encoding nested values is not supported") + } + } + + fileprivate var setters: [SQLite.Setter] = [] + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(userInfo: [CodingUserInfoKey: Any]) { + self.userInfo = userInfo + } + + func singleValueContainer() -> SingleValueEncodingContainer { + fatalError("not supported") + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + fatalError("not supported") + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + } +} + +fileprivate class SQLiteDecoder : Decoder { + struct DecodingError : Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { + typealias Key = MyKey + + let codingPath: [CodingKey] = [] + let row: Row + + init(row: Row) { + self.row = row + } + + var allKeys: [Key] { + return self.row.columnNames.keys.flatMap({Key(stringValue: $0)}) + } + + func contains(_ key: Key) -> Bool { + return self.row.hasValue(for: key.stringValue) + } + + func decodeNil(forKey key: Key) throws -> Bool { + return !self.contains(key) + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + throw DecodingError(description: "decoding an Int8 is not supported") + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + throw DecodingError(description: "decoding an Int16 is not supported") + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + throw DecodingError(description: "decoding an Int32 is not supported") + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + throw DecodingError(description: "decoding an Int64 is not supported") + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + throw DecodingError(description: "decoding an UInt is not supported") + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + throw DecodingError(description: "decoding an UInt8 is not supported") + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + throw DecodingError(description: "decoding an UInt16 is not supported") + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + throw DecodingError(description: "decoding an UInt32 is not supported") + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + throw DecodingError(description: "decoding an UInt64 is not supported") + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + return Float(try self.row.get(Expression(key.stringValue))) + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { + if type == Data.self { + let data = try self.row.get(Expression(key.stringValue)) + return data as! T + } + guard let JSONString = try self.row.get(Expression(key.stringValue)) else { + throw DecodingError(description: "an unsupported type was found") + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError(description: "invalid utf8 data found") + } + return try JSONDecoder().decode(type, from: data) + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + throw DecodingError(description: "decoding nested containers is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding unkeyed containers is not supported") + } + + func superDecoder() throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + + func superDecoder(forKey key: Key) throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + } + + let row: Row + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(row: Row, userInfo: [CodingUserInfoKey: Any]) { + self.row = row + self.userInfo = userInfo + } + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") + } +} + diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 0b257fbe..5b778431 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -673,26 +673,6 @@ extension QueryType { ]).expression) } - /// Creates an `INSERT` statement by encoding the given object - /// This method converts any custom nested types to JSON data and does not handle any sort - /// of object relationships. If you want to support relationships between objects you will - /// have to provide your own Encodable implementations that encode the correct ids. - /// - /// - Parameters: - /// - /// - encodable: An encodable object to insert - /// - /// - userInfo: User info to be passed to encoder - /// - /// - otherSetters: Any other setters to include in the insert - /// - /// - Returns: An `INSERT` statement fort the encodable object - public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { - let encoder = SQLiteEncoder(userInfo: userInfo) - try encodable.encode(to: encoder) - return self.insert(encoder.setters + otherSetters) - } - // MARK: UPDATE public func update(_ values: Setter...) -> Update { @@ -713,26 +693,6 @@ extension QueryType { return Update(" ".join(clauses.flatMap { $0 }).expression) } - /// Creates an `UPDATE` statement by encoding the given object - /// This method converts any custom nested types to JSON data and does not handle any sort - /// of object relationships. If you want to support relationships between objects you will - /// have to provide your own Encodable implementations that encode the correct ids. - /// - /// - Parameters: - /// - /// - encodable: An encodable object to insert - /// - /// - userInfo: User info to be passed to encoder - /// - /// - otherSetters: Any other setters to include in the insert - /// - /// - Returns: An `UPDATE` statement fort the encodable object - public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { - let encoder = SQLiteEncoder(userInfo: userInfo) - try encodable.encode(to: encoder) - return self.update(encoder.setters + otherSetters) - } - // MARK: DELETE public func delete() -> Delete { @@ -1069,7 +1029,7 @@ extension Connection { public struct Row { - fileprivate let columnNames: [String: Int] + let columnNames: [String: Int] fileprivate let values: [Binding?] @@ -1078,7 +1038,7 @@ public struct Row { self.values = values } - fileprivate func hasValue(for column: String) -> Bool { + func hasValue(for column: String) -> Bool { guard let idx = columnNames[column.quote()] else { return false } @@ -1120,22 +1080,6 @@ public struct Row { return valueAtIndex(idx) } - /// Decode an object from this row - /// This method expects any custom nested types to be in the form of JSON data and does not handle - /// any sort of object relationships. If you want to support relationships between objects you will - /// have to provide your own Decodable implementations that decodes the correct columns. - /// - /// - Parameter: userInfo - /// - /// - Returns: a decoded object from this row - public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { - return try V(from: self.decoder(userInfo: userInfo)) - } - - public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { - return SQLiteDecoder(row: self, userInfo: userInfo) - } - public subscript(column: Expression) -> T { return try! get(column) } @@ -1200,263 +1144,3 @@ public struct QueryClauses { } -/// Generates a list of settings for an Encodable object -fileprivate class SQLiteEncoder: Encoder { - struct EncodingError: Error, CustomStringConvertible { - let description: String - } - - class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { - typealias Key = MyKey - - let encoder: SQLiteEncoder - let codingPath: [CodingKey] = [] - - init(encoder: SQLiteEncoder) { - self.encoder = encoder - } - - func superEncoder() -> Swift.Encoder { - fatalError("SQLiteEncoding does not support super encoders") - } - - func superEncoder(forKey key: Key) -> Swift.Encoder { - fatalError("SQLiteEncoding does not support super encoders") - } - - func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- nil) - } - - func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: Bool, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: Float, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) - } - - func encode(_ value: Double, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: String, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { - if let data = value as? Data { - self.encoder.setters.append(Expression(key.stringValue) <- data) - } - else { - let encoded = try JSONEncoder().encode(value) - let string = String(data: encoded, encoding: .utf8) - self.encoder.setters.append(Expression(key.stringValue) <- string) - } - } - - func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int8 is not supported") - } - - func encode(_ value: Int16, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int16 is not supported") - } - - func encode(_ value: Int32, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int32 is not supported") - } - - func encode(_ value: Int64, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int64 is not supported") - } - - func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt is not supported") - } - - func encode(_ value: UInt8, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt8 is not supported") - } - - func encode(_ value: UInt16, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt16 is not supported") - } - - func encode(_ value: UInt32, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt32 is not supported") - } - - func encode(_ value: UInt64, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt64 is not supported") - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - fatalError("encoding a nested container is not supported") - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - fatalError("encoding nested values is not supported") - } - } - - fileprivate var setters: [SQLite.Setter] = [] - let codingPath: [CodingKey] = [] - let userInfo: [CodingUserInfoKey: Any] - - init(userInfo: [CodingUserInfoKey: Any]) { - self.userInfo = userInfo - } - - func singleValueContainer() -> SingleValueEncodingContainer { - fatalError("not supported") - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - fatalError("not supported") - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) - } -} - -class SQLiteDecoder : Decoder { - struct DecodingError : Error, CustomStringConvertible { - let description: String - } - - class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { - typealias Key = MyKey - - let codingPath: [CodingKey] = [] - let row: Row - - init(row: Row) { - self.row = row - } - - var allKeys: [Key] { - return self.row.columnNames.keys.flatMap({Key(stringValue: $0)}) - } - - func contains(_ key: Key) -> Bool { - return self.row.hasValue(for: key.stringValue) - } - - func decodeNil(forKey key: Key) throws -> Bool { - return !self.contains(key) - } - - func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError(description: "decoding an Int8 is not supported") - } - - func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw DecodingError(description: "decoding an Int16 is not supported") - } - - func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw DecodingError(description: "decoding an Int32 is not supported") - } - - func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - throw DecodingError(description: "decoding an Int64 is not supported") - } - - func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError(description: "decoding an UInt is not supported") - } - - func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw DecodingError(description: "decoding an UInt8 is not supported") - } - - func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw DecodingError(description: "decoding an UInt16 is not supported") - } - - func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw DecodingError(description: "decoding an UInt32 is not supported") - } - - func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw DecodingError(description: "decoding an UInt64 is not supported") - } - - func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - return Float(try self.row.get(Expression(key.stringValue))) - } - - func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: String.Type, forKey key: Key) throws -> String { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { - if type == Data.self { - let data = try self.row.get(Expression(key.stringValue)) - return data as! T - } - guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError(description: "an unsupported type was found") - } - guard let data = JSONString.data(using: .utf8) else { - throw DecodingError(description: "invalid utf8 data found") - } - return try JSONDecoder().decode(type, from: data) - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw DecodingError(description: "decoding nested containers is not supported") - } - - func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding unkeyed containers is not supported") - } - - func superDecoder() throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") - } - - func superDecoder(forKey key: Key) throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") - } - } - - let row: Row - let codingPath: [CodingKey] = [] - let userInfo: [CodingUserInfoKey: Any] - - init(row: Row, userInfo: [CodingUserInfoKey: Any]) { - self.row = row - self.userInfo = userInfo - } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") - } -} From 6751ed293efb3b92e5f0e9369c440ffe55e40a34 Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 26 Sep 2017 20:34:13 -0600 Subject: [PATCH 47/86] Add documentation for coding functionality --- Documentation/Index.md | 90 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 7d13b5a7..cf673d05 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -50,6 +50,7 @@ - [Date-Time Values](#date-time-values) - [Binary Data](#binary-data) - [Custom Type Caveats](#custom-type-caveats) + - [Codable Types](#codable-types) - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) @@ -72,7 +73,7 @@ [Carthage][] is a simple, decentralized dependency manager for Cocoa. To install SQLite.swift with Carthage: - +## Custom Types 1. Make sure Carthage is [installed][Carthage Installation]. 2. Update your Cartfile to include the following: @@ -1296,6 +1297,93 @@ extension Row { } ``` +## Codable Types + +Codable types were introduced as a part of Swift 4 to allow serializing and deserializing types. SQLite.swift +supports the insertion, updating, and retrieval of basic Codable types. + +### Inserting Codable Types + +Queries have a method to allow inserting an Encodable type. + +``` swift +try db.run(users.insert(user)) + +``` + +There are two other parameters also available to this method: + +- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. +- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. + +### Updating Codable Types + +Queries have a method to allow updating an Encodable type. + +``` swift +try db.run(users.update(user)) + +``` + +There are two other parameters also available to this method: + +- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. +- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. + +### Retrieving Codable Types + +Rows have a method to decode a Decodable type. + +``` swift +let loadedUsers: [User] = try db.prepare(users).map { row in + return try row.decode() +} +``` + +You can also create a decoder to use manually yourself. This can be useful for example if you are using +the [Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide subclasses behind a super class. +For example, you may want to encode an Image type that can be multiple different formats such as PNGImage, JPGImage, +or HEIFIamge. You will need to determine the correct subclass before you know which type to decode. + +``` swift +enum ImageCodingKeys: String, CodingKey { + case kind +} + +enum ImageKind: Int, Codable { + case png, jpg, heif +} + +let loadedImages: [Image] = try db.prepare(images).map { row in + let decoder = row.decoder() + let container = try decoder.container(keyedBy: ImageCodingKeys.self) + switch try container.decode(ImageKind.self, forKey: .kind) { + case .png: + return try PNGImage(from: decoder) + case .jpg: + return try JPGImage(from: decoder) + case .heif: + return try HEIFImage(from: decoder) + } +} +``` + +Both of the above methods also have the following optional parameter: + +- `userInfo` is a dictionary that is passed to the decoder and made available to decodable types to allow customizing their behavior. + +### Restrictions + +There are a few restrictions on using Codable types: + +- The encodable and decodable objects can only use the following types: + - Int, Bool, Float, Double, String + - Nested Codable types that will be encoded as JSON to a single column +- These methods will not handle object relationships for you. You must write your own Codable and Decodable +implementations if you wish to support this. +- The Codable types may not try to access nested containers or nested unkeyed containers +- The Codable types may not access single value containers or unkeyed containers +- The Codable types may not access super decoders or encoders ## Other Operators From c614d80b5eca8c29b64f8e64405397361b17f08a Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 26 Sep 2017 22:36:58 -0600 Subject: [PATCH 48/86] Fix CI --- Sources/SQLite/Typed/Coding.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 68f0a998..1e049666 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import Foundation + extension QueryType { /// Creates an `INSERT` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort From 7cb407d43d7446740ef2cad8716c74d8525f1594 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Sep 2017 12:45:11 +0200 Subject: [PATCH 49/86] use PackageDescription API Version 4 --- .travis.yml | 2 +- Documentation/Index.md | 6 +++--- Package.swift | 18 +++++++----------- README.md | 4 ++-- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62f36185..7ff85f9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: - env: CARTHAGE_PLATFORM="Mac" - env: CARTHAGE_PLATFORM="watchOS" - env: CARTHAGE_PLATFORM="tvOS" - - env: PACKAGE_MANAGER_COMMAND="test -Xlinker -lsqlite3" + - env: PACKAGE_MANAGER_COMMAND="test" before_install: - gem update bundler - gem install xcpretty --no-document diff --git a/Documentation/Index.md b/Documentation/Index.md index 7d13b5a7..42c247b5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -172,16 +172,16 @@ It is the recommended approach for using SQLite.swift in OSX CLI applications. 1. Add the following to your `Package.swift` file: - ``` swift + ```swift dependencies: [ - .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") ] ``` 2. Build your project: ``` sh - $ swift build -Xlinker -lsqlite3 + $ swift build ``` [Swift Package Manager]: https://swift.org/package-manager diff --git a/Package.swift b/Package.swift index fc7c5a0e..bab00031 100644 --- a/Package.swift +++ b/Package.swift @@ -1,17 +1,13 @@ +// swift-tools-version:4.0 import PackageDescription let package = Package( - name: "SQLite", + name: "SQLite.swift", + products: [.library(name: "SQLite", targets: ["SQLite"])], targets: [ - Target( - name: "SQLite", - dependencies: [ - .Target(name: "SQLiteObjc") - ]), - Target(name: "SQLiteObjc") + .target(name: "SQLite", dependencies: ["SQLiteObjc"]), + .target(name: "SQLiteObjc"), + .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - dependencies: [ - .Package(url: "https://github.com/stephencelis/CSQLite.git", majorVersion: 0) - ], - exclude: ["Tests/CocoaPods", "Tests/Carthage"] + swiftLanguageVersions: [4] ) diff --git a/README.md b/README.md index fe06c6f7..a10f2c09 100644 --- a/README.md +++ b/README.md @@ -168,14 +168,14 @@ The [Swift Package Manager][] is a tool for managing the distribution of Swift c ```swift dependencies: [ - .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") ] ``` 2. Build your project: ``` sh - $ swift build -Xlinker -lsqlite3 + $ swift build ``` [Swift Package Manager]: https://swift.org/package-manager From 7d3714deb0dbaed58ca718d7d1d8dc137f725045 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Sep 2017 14:47:52 +0200 Subject: [PATCH 50/86] Workaround no longer needed --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- Sources/SQLiteObjc/SQLite-Bridging.m | 4 ++-- Sources/SQLiteObjc/include/SQLite-Bridging.h | 8 ++------ Tests/SQLiteTests/ConnectionTests.swift | 2 +- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 0189d300..13e62fa1 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -28,7 +28,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 1e48d3e8..0ad5cd45 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 52b158b1..4748ee53 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif diff --git a/Sources/SQLiteObjc/SQLite-Bridging.m b/Sources/SQLiteObjc/SQLite-Bridging.m index d8fe6b68..e00a7315 100644 --- a/Sources/SQLiteObjc/SQLite-Bridging.m +++ b/Sources/SQLiteObjc/SQLite-Bridging.m @@ -112,14 +112,14 @@ static int __SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char __SQLiteTokenizerNext }; -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * moduleName, const char * submoduleName, _SQLiteTokenizerNextCallback callback) { +int _SQLiteRegisterTokenizer(sqlite3 *db, const char * moduleName, const char * submoduleName, _SQLiteTokenizerNextCallback callback) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ __SQLiteTokenizerMap = [NSMutableDictionary new]; }); sqlite3_stmt * stmt; - int status = sqlite3_prepare_v2((sqlite3 *)db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); + int status = sqlite3_prepare_v2(db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); if (status != SQLITE_OK ){ return status; } diff --git a/Sources/SQLiteObjc/include/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLite-Bridging.h index d15e8d56..5b356593 100644 --- a/Sources/SQLiteObjc/include/SQLite-Bridging.h +++ b/Sources/SQLiteObjc/include/SQLite-Bridging.h @@ -24,14 +24,10 @@ @import Foundation; -#ifndef COCOAPODS #import "sqlite3.h" -#endif - -typedef struct SQLiteHandle SQLiteHandle; // CocoaPods workaround NS_ASSUME_NONNULL_BEGIN -typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); +typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); +int _SQLiteRegisterTokenizer(sqlite3 *db, const char *module, const char *tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); NS_ASSUME_NONNULL_END diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 373cba0a..84620f4e 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -5,7 +5,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif From bc005192d93eb92dc5ff6a7fd6cccc7ff025c2a6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Sep 2017 15:36:50 +0200 Subject: [PATCH 51/86] Linux support --- CHANGELOG.md | 3 +++ Package.swift | 11 +++++++++++ Sources/SQLite/Core/Connection.swift | 12 ++++++++---- Sources/SQLite/Core/Statement.swift | 4 +++- Sources/SQLite/Helpers.swift | 4 +++- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- Tests/LinuxMain.swift | 6 ++++++ Tests/SQLiteTests/ConnectionTests.swift | 8 ++++++-- Tests/SQLiteTests/TestHelpers.swift | 2 +- 9 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 Tests/LinuxMain.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fa68dfe..bbb5d671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) @@ -46,6 +47,7 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 +[#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 [#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 @@ -62,6 +64,7 @@ [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 +[#681]: https://github.com/stephencelis/SQLite.swift/issues/681 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 diff --git a/Package.swift b/Package.swift index bab00031..c008e482 100644 --- a/Package.swift +++ b/Package.swift @@ -11,3 +11,14 @@ let package = Package( ], swiftLanguageVersions: [4] ) + +#if os(Linux) + package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] + package.targets = [ + .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), + .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ + "FTS4Tests.swift", + "FTS5Tests.swift" + ]) + ] +#endif diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 13e62fa1..a3b2f362 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -22,13 +22,15 @@ // THE SOFTWARE. // -import Foundation.NSUUID +import Foundation import Dispatch #if SQLITE_SWIFT_STANDALONE import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif @@ -413,7 +415,7 @@ public final class Connection { /// /// db.trace { SQL in print(SQL) } public func trace(_ callback: ((String) -> Void)?) { - #if SQLITE_SWIFT_SQLCIPHER + #if SQLITE_SWIFT_SQLCIPHER || os(Linux) trace_v1(callback) #else if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { @@ -583,9 +585,11 @@ public final class Connection { } } var flags = SQLITE_UTF8 + #if !os(Linux) if deterministic { flags |= SQLITE_DETERMINISTIC } + #endif sqlite3_create_function_v2(handle, function, Int32(argc), flags, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self) function(context, argc, value) @@ -702,7 +706,7 @@ extension Result : CustomStringConvertible { } } -#if !SQLITE_SWIFT_SQLCIPHER +#if !SQLITE_SWIFT_SQLCIPHER && !os(Linux) @available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) extension Connection { fileprivate func trace_v2(_ callback: ((String) -> Void)?) { diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 0ad5cd45..dc91d3d8 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,7 +26,9 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 4748ee53..64e5eca8 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,9 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 9d17a326..be260ec8 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -import Foundation.NSData +import Foundation extension ExpressionType where UnderlyingType : Number { diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 00000000..59796fde --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,6 @@ +import XCTest +@testable import SQLiteTests + +XCTMain([ +testCase([ +])]) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 84620f4e..157fbe9f 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -1,11 +1,15 @@ import XCTest +import Foundation +import Dispatch @testable import SQLite #if SQLITE_SWIFT_STANDALONE import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif @@ -336,7 +340,7 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) try! stmt.run() - let deadline = DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC) + let deadline = DispatchTime.now() + 0.01 _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) AssertThrows(try stmt.run()) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index b66144b3..2ebbc56e 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -69,7 +69,7 @@ class SQLiteTestCase : XCTestCase { func async(expect description: String = "async", timeout: Double = 5, block: (@escaping () -> Void) -> Void) { let expectation = self.expectation(description: description) - block(expectation.fulfill) + block({ expectation.fulfill() }) waitForExpectations(timeout: timeout, handler: nil) } From 112bc6fbc28e4db26afcd63b1c36e979d8f9e198 Mon Sep 17 00:00:00 2001 From: Andrew Wagner Date: Wed, 27 Sep 2017 14:05:47 -0600 Subject: [PATCH 52/86] Use native decoding and encoding errors --- Documentation/Index.md | 1 - Sources/SQLite/Typed/Coding.swift | 61 ++++++++++++++----------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index cf673d05..a1370cd5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -73,7 +73,6 @@ [Carthage][] is a simple, decentralized dependency manager for Cocoa. To install SQLite.swift with Carthage: -## Custom Types 1. Make sure Carthage is [installed][Carthage Installation]. 2. Update your Cartfile to include the following: diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 1e049666..dd6a4ec2 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -86,10 +86,6 @@ extension Row { /// Generates a list of settings for an Encodable object fileprivate class SQLiteEncoder: Encoder { - struct EncodingError: Error, CustomStringConvertible { - let description: String - } - class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { typealias Key = MyKey @@ -144,39 +140,39 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int8 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int8 is not supported")) } func encode(_ value: Int16, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int16 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int16 is not supported")) } func encode(_ value: Int32, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int32 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int32 is not supported")) } func encode(_ value: Int64, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int64 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int64 is not supported")) } func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt is not supported")) } func encode(_ value: UInt8, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt8 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt8 is not supported")) } func encode(_ value: UInt16, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt16 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt16 is not supported")) } func encode(_ value: UInt32, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt32 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt32 is not supported")) } func encode(_ value: UInt64, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt64 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt64 is not supported")) } func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { @@ -210,10 +206,6 @@ fileprivate class SQLiteEncoder: Encoder { } fileprivate class SQLiteDecoder : Decoder { - struct DecodingError : Error, CustomStringConvertible { - let description: String - } - class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { typealias Key = MyKey @@ -245,39 +237,40 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError(description: "decoding an Int8 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int8 is not supported")) } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw DecodingError(description: "decoding an Int16 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int16 is not supported")) } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw DecodingError(description: "decoding an Int32 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int32 is not supported")) } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - throw DecodingError(description: "decoding an Int64 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError(description: "decoding an UInt is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt is not supported")) + } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw DecodingError(description: "decoding an UInt8 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt8 is not supported")) } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw DecodingError(description: "decoding an UInt16 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt16 is not supported")) } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw DecodingError(description: "decoding an UInt32 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt32 is not supported")) } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw DecodingError(description: "decoding an UInt64 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { @@ -298,28 +291,28 @@ fileprivate class SQLiteDecoder : Decoder { return data as! T } guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError(description: "an unsupported type was found") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "an unsupported type was found")) } guard let data = JSONString.data(using: .utf8) else { - throw DecodingError(description: "invalid utf8 data found") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "invalid utf8 data found")) } return try JSONDecoder().decode(type, from: data) } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw DecodingError(description: "decoding nested containers is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding nested containers is not supported")) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding unkeyed containers is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding unkeyed containers is not supported")) } func superDecoder() throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super encoders containers is not supported")) } func superDecoder(forKey key: Key) throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super decoders is not supported")) } } @@ -337,11 +330,11 @@ fileprivate class SQLiteDecoder : Decoder { } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an unkeyed container is not supported")) } func singleValueContainer() throws -> SingleValueDecodingContainer { - throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding a single value container is not supported")) } } From 364a240a6217d4d1b6fd7c237a3cf9039cbad6ec Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 02:09:47 +0200 Subject: [PATCH 53/86] Add date and time functions Closes #142 --- CHANGELOG.md | 2 + Documentation/Index.md | 13 +++ SQLite.xcodeproj/project.pbxproj | 18 +++ .../SQLite/Typed/DateAndTimeFunctions.swift | 106 ++++++++++++++++++ Sources/SQLite/Typed/Operators.swift | 17 ++- .../DateAndTimeFunctionTests.swift | 66 +++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 31 +++++ 7 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 Sources/SQLite/Typed/DateAndTimeFunctions.swift create mode 100644 Tests/SQLiteTests/DateAndTimeFunctionTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index bbb5d671..e36e40d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Added Date and Time functions ([#142][]) * Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) @@ -47,6 +48,7 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 +[#142]: https://github.com/stephencelis/SQLit1e.swift/issues/142 [#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 diff --git a/Documentation/Index.md b/Documentation/Index.md index eb5cbc53..9a6d07ad 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -54,6 +54,7 @@ - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) + - [Date and Time Functions](#date-and-time-functions) - [Custom SQL Functions](#custom-sql-functions) - [Custom Collations](#custom-collations) - [Full-text Search](#full-text-search) @@ -1430,6 +1431,18 @@ Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) h Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift. +## Date and Time functions + +SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) are available: + +```swift +DateFunctions.date("now") +// date('now') +Date().date +// date('2007-01-09T09:41:00.000') +Expression("date").date +// date("date") +``` ## Custom SQL Functions diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3093b11d..cb776608 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,17 +46,22 @@ 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; + 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -66,8 +71,10 @@ 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; @@ -209,12 +216,14 @@ 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; + 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; @@ -408,6 +417,7 @@ 19A17399EA9E61235D5D77BF /* CipherTests.swift */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, + 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -452,6 +462,7 @@ EE247B011C3F06E900AE3E12 /* Schema.swift */, EE247B021C3F06E900AE3E12 /* Setter.swift */, 49EB68C31F7B3CB400D89D40 /* Coding.swift */, + 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */, ); path = Typed; sourceTree = ""; @@ -806,6 +817,7 @@ 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, + 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -833,6 +845,7 @@ 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, + 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -862,6 +875,7 @@ 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */, 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, + 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -891,6 +905,7 @@ 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, + 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -918,6 +933,7 @@ 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, + 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -947,6 +963,7 @@ 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, + 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -974,6 +991,7 @@ 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, + 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Typed/DateAndTimeFunctions.swift b/Sources/SQLite/Typed/DateAndTimeFunctions.swift new file mode 100644 index 00000000..0b9a497f --- /dev/null +++ b/Sources/SQLite/Typed/DateAndTimeFunctions.swift @@ -0,0 +1,106 @@ +// +// 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 + +/// All five date and time functions take a time string as an argument. +/// The time string is followed by zero or more modifiers. +/// The strftime() function also takes a format string as its first argument. +/// +/// https://www.sqlite.org/lang_datefunc.html +public class DateFunctions { + /// The date() function returns the date in this format: YYYY-MM-DD. + public static func date(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("date", timestring: timestring, modifiers: modifiers) + } + + /// The time() function returns the time as HH:MM:SS. + public static func time(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("time", timestring: timestring, modifiers: modifiers) + } + + /// The datetime() function returns "YYYY-MM-DD HH:MM:SS". + public static func datetime(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("datetime", timestring: timestring, modifiers: modifiers) + } + + /// The julianday() function returns the Julian day - + /// the number of days since noon in Greenwich on November 24, 4714 B.C. + public static func julianday(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("julianday", timestring: timestring, modifiers: modifiers) + } + + /// The strftime() routine returns the date formatted according to the format string specified as the first argument. + public static func strftime(_ format: String, _ timestring: String, _ modifiers: String...) -> Expression { + if !modifiers.isEmpty { + let templates = [String](repeating: "?", count: modifiers.count).joined(separator: ", ") + return Expression("strftime(?, ?, \(templates))", [format, timestring] + modifiers) + } + return Expression("strftime(?, ?)", [format, timestring]) + } + + private static func timefunction(_ name: String, timestring: String, modifiers: [String]) -> Expression { + if !modifiers.isEmpty { + let templates = [String](repeating: "?", count: modifiers.count).joined(separator: ", ") + return Expression("\(name)(?, \(templates))", [timestring] + modifiers) + } + return Expression("\(name)(?)", [timestring]) + } +} + +extension Date { + public var date: Expression { + return DateFunctions.date(dateFormatter.string(from: self)) + } + + public var time: Expression { + return DateFunctions.time(dateFormatter.string(from: self)) + } + + public var datetime: Expression { + return DateFunctions.datetime(dateFormatter.string(from: self)) + } + + public var julianday: Expression { + return DateFunctions.julianday(dateFormatter.string(from: self)) + } +} + +extension Expression where UnderlyingType == Date { + public var date: Expression { + return Expression("date(\(template))", bindings) + } + + public var time: Expression { + return Expression("time(\(template))", bindings) + } + + public var datetime: Expression { + return Expression("datetime(\(template))", bindings) + } + + public var julianday: Expression { + return Expression("julianday(\(template))", bindings) + } +} diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 01381bb8..5f95993f 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -474,11 +474,20 @@ public func <=(lhs: V, rhs: Expression) -> Expression wher return infix(lhs, rhs) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) + +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } // MARK: - diff --git a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift new file mode 100644 index 00000000..628b5910 --- /dev/null +++ b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift @@ -0,0 +1,66 @@ +import XCTest +@testable import SQLite + +class DateAndTimeFunctionsTests : XCTestCase { + + func test_date() { + AssertSQL("date('now')", DateFunctions.date("now")) + AssertSQL("date('now', 'localtime')", DateFunctions.date("now", "localtime")) + } + + func test_time() { + AssertSQL("time('now')", DateFunctions.time("now")) + AssertSQL("time('now', 'localtime')", DateFunctions.time("now", "localtime")) + } + + func test_datetime() { + AssertSQL("datetime('now')", DateFunctions.datetime("now")) + AssertSQL("datetime('now', 'localtime')", DateFunctions.datetime("now", "localtime")) + } + + func test_julianday() { + AssertSQL("julianday('now')", DateFunctions.julianday("now")) + AssertSQL("julianday('now', 'localtime')", DateFunctions.julianday("now", "localtime")) + } + + func test_strftime() { + AssertSQL("strftime('%Y-%m-%d', 'now')", DateFunctions.strftime("%Y-%m-%d", "now")) + AssertSQL("strftime('%Y-%m-%d', 'now', 'localtime')", DateFunctions.strftime("%Y-%m-%d", "now", "localtime")) + } +} + +class DateExtensionTests : XCTestCase { + func test_time() { + AssertSQL("time('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).time) + } + + func test_date() { + AssertSQL("date('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).date) + } + + func test_datetime() { + AssertSQL("datetime('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).datetime) + } + + func test_julianday() { + AssertSQL("julianday('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).julianday) + } +} + +class DateExpressionTests : XCTestCase { + func test_date() { + AssertSQL("date(\"date\")", date.date) + } + + func test_time() { + AssertSQL("time(\"date\")", date.time) + } + + func test_datetime() { + AssertSQL("datetime(\"date\")", date.datetime) + } + + func test_julianday() { + AssertSQL("julianday(\"date\")", date.julianday) + } +} diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index f0e585cd..08e679c1 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -255,6 +255,11 @@ class OperatorsTests : XCTestCase { AssertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) } + func test_patternMatchingOperator_withComparableRange_buildsBooleanExpression() { + AssertSQL("\"double\" >= 1.2 AND \"double\" < 4.5", 1.2..<4.5 ~= double) + AssertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) + } + func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() { AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) @@ -293,4 +298,30 @@ class OperatorsTests : XCTestCase { AssertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } + func test_dateExpressionLessGreater() { + let begin = Date(timeIntervalSince1970: 0) + AssertSQL("(\"date\" < '1970-01-01T00:00:00.000')", date < begin) + AssertSQL("(\"date\" > '1970-01-01T00:00:00.000')", date > begin) + AssertSQL("(\"date\" >= '1970-01-01T00:00:00.000')", date >= begin) + AssertSQL("(\"date\" <= '1970-01-01T00:00:00.000')", date <= begin) + } + + func test_dateExpressionRange() { + let begin = Date(timeIntervalSince1970: 0) + let end = Date(timeIntervalSince1970: 5000) + AssertSQL( + "\"date\" >= '1970-01-01T00:00:00.000' AND \"date\" < '1970-01-01T01:23:20.000'", + (begin.. Date: Thu, 28 Sep 2017 08:36:31 +0200 Subject: [PATCH 54/86] Reflow documentation --- Documentation/Index.md | 849 +++++++++++++++++++++++++++-------------- README.md | 47 ++- 2 files changed, 590 insertions(+), 306 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9a6d07ad..9657a94a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,7 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 3 (and [Xcode 8](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 4 (and +> [Xcode 9](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage @@ -78,7 +79,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: - ``` + ```ruby github "stephencelis/SQLite.swift" ~> 0.11.4 ``` @@ -94,9 +95,10 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). + 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift + requires version 1.0.0 or greater). - ``` sh + ```sh # Using the default Ruby install will require you to use sudo when # installing and updating gems. [sudo] gem install cocoapods @@ -104,7 +106,7 @@ install SQLite.swift with Carthage: 2. Update your Podfile to include the following: - ``` ruby + ```ruby use_frameworks! target 'YourAppTargetName' do @@ -117,17 +119,20 @@ install SQLite.swift with Carthage: #### Requiring a specific version of SQLite - If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: +If you want to use a more recent version of SQLite than what is provided +with the OS you can require the `standalone` subspec: -``` ruby +```ruby target 'YourAppTargetName' do pod 'SQLite.swift/standalone', '~> 0.11.4' end ``` -By default this will use the most recent version of SQLite without any extras. If you want you can further customize this by adding another dependency to sqlite3 or one of its subspecs: +By default this will use the most recent version of SQLite without any +extras. If you want you can further customize this by adding another +dependency to sqlite3 or one of its subspecs: -``` ruby +```ruby target 'YourAppTargetName' do pod 'SQLite.swift/standalone', '~> 0.11.4' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled @@ -138,19 +143,19 @@ See the [sqlite3 podspec][sqlite3pod] for more details. #### Using SQLite.swift with SQLCipher -If you want to use [SQLCipher][] with SQLite.swift you can require the `SQLCipher` -subspec in your Podfile: +If you want to use [SQLCipher][] with SQLite.swift you can require the +`SQLCipher` subspec in your Podfile: -``` ruby +```ruby target 'YourAppTargetName' do pod 'SQLite.swift/SQLCipher', '~> 0.11.4' end ``` -This will automatically add a dependency to the SQLCipher pod as well as extend -`Connection` with methods to change the database key: +This will automatically add a dependency to the SQLCipher pod as well as +extend `Connection` with methods to change the database key: -``` swift +```swift import SQLite let db = try Connection("path/to/db.sqlite3") @@ -165,11 +170,12 @@ try db.rekey("another secret") ### Swift Package Manager -The [Swift Package Manager][] is a tool for managing the distribution of Swift code. -It’s integrated with the Swift build system to automate the process of -downloading, compiling, and linking dependencies. +The [Swift Package Manager][] is a tool for managing the distribution of +Swift code. It’s integrated with the Swift build system to automate the +process of downloading, compiling, and linking dependencies. -It is the recommended approach for using SQLite.swift in OSX CLI applications. +It is the recommended approach for using SQLite.swift in OSX CLI +applications. 1. Add the following to your `Package.swift` file: @@ -181,7 +187,7 @@ It is the recommended approach for using SQLite.swift in OSX CLI applications. 2. Build your project: - ``` sh + ```sh $ swift build ``` @@ -191,21 +197,28 @@ It is the recommended approach for using SQLite.swift in OSX CLI applications. To install SQLite.swift as an Xcode sub-project: - 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) the project first.) + 1. Drag the **SQLite.xcodeproj** file into your own project. + ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or + [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) + the project first.) ![Installation Screen Shot](Resources/installation@2x.png) - 2. In your target’s **General** tab, click the **+** button under **Linked Frameworks and Libraries**. + 2. In your target’s **General** tab, click the **+** button under **Linked + Frameworks and Libraries**. 3. Select the appropriate **SQLite.framework** for your platform. 4. **Add**. -You should now be able to `import SQLite` from any of your target’s source files and begin using SQLite.swift. +You should now be able to `import SQLite` from any of your target’s source +files and begin using SQLite.swift. -Some additional steps are required to install the application on an actual device: +Some additional steps are required to install the application on an actual +device: - 5. In the **General** tab, click the **+** button under **Embedded Binaries**. + 5. In the **General** tab, click the **+** button under **Embedded + Binaries**. 6. Select the appropriate **SQLite.framework** for your platform. @@ -213,27 +226,31 @@ Some additional steps are required to install the application on an actual devic ## Getting Started -To use SQLite.swift classes or structures in your target’s source file, first import the `SQLite` module. +To use SQLite.swift classes or structures in your target’s source file, first +import the `SQLite` module. -``` swift +```swift import SQLite ``` ### Connecting to a Database -Database connections are established using the `Connection` class. A connection is initialized with a path to a database. SQLite will attempt to create the database file if it does not already exist. +Database connections are established using the `Connection` class. A +connection is initialized with a path to a database. SQLite will attempt to +create the database file if it does not already exist. -``` swift +```swift let db = try Connection("path/to/db.sqlite3") ``` #### Read-Write Databases -On iOS, you can create a writable database in your app’s **Documents** directory. +On iOS, you can create a writable database in your app’s **Documents** +directory. -``` swift +```swift let path = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true ).first! @@ -243,7 +260,7 @@ let db = try Connection("\(path)/db.sqlite3") On OS X, you can use your app’s **Application Support** directory: -``` swift +```swift var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true ).first! + "/" + Bundle.main.bundleIdentifier! @@ -259,38 +276,53 @@ let db = try Connection("\(path)/db.sqlite3") #### Read-Only Databases -If you bundle a database with your app (_i.e._, you’ve copied a database file into your Xcode project and added it to your application target), you can establish a _read-only_ connection to it. +If you bundle a database with your app (_i.e._, you’ve copied a database file +into your Xcode project and added it to your application target), you can +establish a _read-only_ connection to it. -``` swift +```swift let path = Bundle.main.pathForResource("db", ofType: "sqlite3")! let db = try Connection(path, readonly: true) ``` -> _Note:_ Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). +> _Note:_ Signed applications cannot modify their bundle resources. If you +> bundle a database file with your app for the purpose of bootstrapping, copy +> it to a writable location _before_ establishing a connection (see [Read- +> Write Databases](#read-write-databases), above, for typical, writable +> locations). > -> See these two Stack Overflow questions for more information about iOS apps with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). We welcome sample code to show how to successfully copy and use a bundled "seed" database for writing in an app. +> See these two Stack Overflow questions for more information about iOS apps +> with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), +> [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). +> We welcome sample code to show how to successfully copy and use a bundled "seed" +> database for writing in an app. #### In-Memory Databases -If you omit the path, SQLite.swift will provision an [in-memory database](https://www.sqlite.org/inmemorydb.html). +If you omit the path, SQLite.swift will provision an [in-memory +database](https://www.sqlite.org/inmemorydb.html). -``` swift +```swift let db = try Connection() // equivalent to `Connection(.inMemory)` ``` To create a temporary, disk-backed database, pass an empty file name. -``` swift +```swift let db = try Connection(.temporary) ``` -In-memory databases are automatically deleted when the database connection is closed. +In-memory databases are automatically deleted when the database connection is +closed. #### Thread-Safety -Every Connection comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. +Every Connection comes equipped with its own serial queue for statement +execution and can be safely accessed across threads. Threads that open +transactions and savepoints will block other threads from executing +statements while the transaction is open. If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: @@ -305,12 +337,16 @@ db.busyHandler({ tries in }) ``` -> _Note:_ The default timeout is 0, so if you see `database is locked` errors, you may be trying to access the same database simultaneously from multiple connections. +> _Note:_ The default timeout is 0, so if you see `database is locked` +> errors, you may be trying to access the same database simultaneously from +> multiple connections. ## Building Type-Safe SQL -SQLite.swift comes with a typed expression layer that directly maps [Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). +SQLite.swift comes with a typed expression layer that directly maps +[Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) +to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). | Swift Type | SQLite Type | | --------------- | ----------- | @@ -320,22 +356,32 @@ SQLite.swift comes with a typed expression layer that directly maps [Swift types | `nil` | `NULL` | | `SQLite.Blob`† | `BLOB` | -> *While `Int64` is the basic, raw type (to preserve 64-bit integers on 32-bit platforms), `Int` and `Bool` work transparently. +> *While `Int64` is the basic, raw type (to preserve 64-bit integers on +> 32-bit platforms), `Int` and `Bool` work transparently. > -> †SQLite.swift defines its own `Blob` structure, which safely wraps the underlying bytes. +> †SQLite.swift defines its own `Blob` structure, which safely wraps the +> underlying bytes. > -> See [Custom Types](#custom-types) for more information about extending other classes and structures to work with SQLite.swift. +> See [Custom Types](#custom-types) for more information about extending +> other classes and structures to work with SQLite.swift. > -> See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed layer and execute raw SQL, instead. +> See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed +> layer and execute raw SQL, instead. -These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`QueryType`](#queries)), can create and execute SQL statements. +These expressions (in the form of the structure, +[`Expression`](#expressions)) build on one another and, with a query +([`QueryType`](#queries)), can create and execute SQL statements. ### Expressions -Expressions are generic structures associated with a type ([built-in](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and (optionally) values to bind to that SQL. Typically, you will only explicitly create expressions to describe your columns, and typically only once per column. +Expressions are generic structures associated with a type ([built-in +](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and +(optionally) values to bind to that SQL. Typically, you will only explicitly +create expressions to describe your columns, and typically only once per +column. -``` swift +```swift let id = Expression("id") let email = Expression("email") let balance = Expression("balance") @@ -344,34 +390,48 @@ let verified = Expression("verified") Use optional generics for expressions that can evaluate to `NULL`. -``` swift +```swift let name = Expression("name") ``` -> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`. +> _Note:_ The default `Expression` initializer is for [quoted +> identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column +> names). To build a literal SQL expression, use `init(literal:)`. +> ### Compound Expressions -Expressions can be combined with other expressions and types using [filter operators and functions](#filter-operators-and-functions) (as well as other [non-filter operators](#other-operators) and [functions](#core-sqlite-functions)). These building blocks can create complex SQLite statements. +Expressions can be combined with other expressions and types using +[filter operators and functions](#filter-operators-and-functions) +(as well as other [non-filter operators](#other-operators) and +[functions](#core-sqlite-functions)). These building blocks can create complex SQLite statements. ### Queries -Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a query by initializing a `Table`, `View`, or `VirtualTable`. +Queries are structures that reference a database and table name, and can be +used to build a variety of statements using expressions. We can create a +query by initializing a `Table`, `View`, or `VirtualTable`. -``` swift +```swift let users = Table("users") ``` -Assuming [the table exists](#creating-a-table), we can immediately [insert](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and [delete](#deleting-rows) rows. +Assuming [the table exists](#creating-a-table), we can immediately [insert +](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and +[delete](#deleting-rows) rows. ## Creating a Table -We can build [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create` function on a `Table`. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. +We can build [`CREATE TABLE` +statements](https://www.sqlite.org/lang_createtable.html) by calling the +`create` function on a `Table`. The following is a basic example of +SQLite.swift code (using the [expressions](#expressions) and +[query](#queries) above) and the corresponding SQL it generates. -``` swift +```swift try db.run(users.create { t in // CREATE TABLE "users" ( t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL, t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL, @@ -379,34 +439,42 @@ try db.run(users.create { t in // CREATE TABLE "users" ( }) // ) ``` -> _Note:_ `Expression` structures (in this case, the `id` and `email` columns), generate `NOT NULL` constraints automatically, while `Expression` structures (`name`) do not. +> _Note:_ `Expression` structures (in this case, the `id` and `email` +> columns), generate `NOT NULL` constraints automatically, while +> `Expression` structures (`name`) do not. ### Create Table Options The `Table.create` function has several default parameters we can override. - - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to create a temporary table that will automatically drop when the database connection closes). Default: `false`. + - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to + create a temporary table that will automatically drop when the database + connection closes). Default: `false`. - ``` swift + ```swift try db.run(users.create(temporary: true) { t in /* ... */ }) // CREATE TEMPORARY TABLE "users" -- ... ``` - - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. + - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` + statement (which will bail out gracefully if the table already exists). + Default: `false`. - ``` swift + ```swift try db.run(users.create(ifNotExists: true) { t in /* ... */ }) // CREATE TABLE "users" IF NOT EXISTS -- ... ``` ### Column Constraints -The `column` function is used for a single column definition. It takes an [expression](#expressions) describing the column name and type, and accepts several parameters that map to various column constraints and clauses. +The `column` function is used for a single column definition. It takes an +[expression](#expressions) describing the column name and type, and accepts +several parameters that map to various column constraints and clauses. - `primaryKey` adds a `PRIMARY KEY` constraint to a single column. - ``` swift + ```swift t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL @@ -414,38 +482,59 @@ The `column` function is used for a single column definition. It takes an [expre // "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ``` - > _Note:_ The `primaryKey` parameter cannot be used alongside `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > _Note:_ The `primaryKey` parameter cannot be used alongside + > `references`. If you need to create a column that has a default value + > and is also a primary and/or foreign key, use the `primaryKey` and + > `foreignKey` functions mentioned under + > [Table Constraints](#table-constraints). > > Primary keys cannot be optional (_e.g._, `Expression`). > > Only an `INTEGER PRIMARY KEY` can take `.autoincrement`. - - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` function under [Table Constraints](#table-constraints) for uniqueness over multiple columns). + - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` + function under [Table Constraints](#table-constraints) for uniqueness + over multiple columns). - ``` swift + ```swift t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL ``` - - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` function under [Table Constraints](#table-constraints).) + - `check` attaches a `CHECK` constraint to a column definition in the form + of a boolean expression (`Expression`). Boolean expressions can be + easily built using + [filter operators and functions](#filter-operators-and-functions). + (See also the `check` function under + [Table Constraints](#table-constraints).) - ``` swift + ```swift t.column(email, check: email.like("%@%")) // "email" TEXT NOT NULL CHECK ("email" LIKE '%@%') ``` - - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value (or expression) matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). + - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ + accepts a value (or expression) matching the column’s type. This value is + used if none is explicitly provided during [an `INSERT`](#inserting- + rows). - ``` swift + ```swift t.column(name, defaultValue: "Anonymous") // "name" TEXT DEFAULT 'Anonymous' ``` - > _Note:_ The `defaultValue` parameter cannot be used alongside `primaryKey` and `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > _Note:_ The `defaultValue` parameter cannot be used alongside + > `primaryKey` and `references`. If you need to create a column that has + > a default value and is also a primary and/or foreign key, use the + > `primaryKey` and `foreignKey` functions mentioned under + > [Table Constraints](#table-constraints). - - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. + - `collate` adds a `COLLATE` clause to `Expression` (and + `Expression`) column definitions with + [a collating sequence](https://www.sqlite.org/datatype3.html#collation) + defined in the `Collation` enumeration. - ``` swift + ```swift t.column(email, collate: .nocase) // "email" TEXT NOT NULL COLLATE "NOCASE" @@ -453,50 +542,66 @@ The `column` function is used for a single column definition. It takes an [expre // "name" TEXT COLLATE "RTRIM" ``` - - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`SchemaType`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Expression` (and + `Expression`) column definitions and accepts a table + (`SchemaType`) or namespaced column expression. (See the `foreignKey` + function under [Table Constraints](#table-constraints) for non-integer + foreign key support.) - ``` swift + ```swift t.column(user_id, references: users, id) // "user_id" INTEGER REFERENCES "users" ("id") - - - > _Note:_ The `references` parameter cannot be used alongside `primaryKey` and `defaultValue`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > _Note:_ The `references` parameter cannot be used alongside + > `primaryKey` and `defaultValue`. If you need to create a column that + > has a default value and is also a primary and/or foreign key, use the + > `primaryKey` and `foreignKey` functions mentioned under + > [Table Constraints](#table-constraints). ### Table Constraints -Additional constraints may be provided outside the scope of a single column using the following functions. +Additional constraints may be provided outside the scope of a single column +using the following functions. - - `primaryKey` adds a `PRIMARY KEY` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports all SQLite types, [ascending and descending orders](#sorting-rows), and composite (multiple column) keys. + - `primaryKey` adds a `PRIMARY KEY` constraint to the table. Unlike [the + column constraint, above](#column-constraints), it supports all SQLite + types, [ascending and descending orders](#sorting-rows), and composite + (multiple column) keys. - ``` swift + ```swift t.primaryKey(email.asc, name) // PRIMARY KEY("email" ASC, "name") ``` - - `unique` adds a `UNIQUE` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports composite (multiple column) constraints. + - `unique` adds a `UNIQUE` constraint to the table. Unlike + [the column constraint, above](#column-constraints), it + supports composite (multiplecolumn) constraints. - ``` swift + ```swift t.unique(local, domain) // UNIQUE("local", "domain") ``` - - `check` adds a `CHECK` constraint to the table in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` parameter under [Column Constraints](#column-constraints).) + - `check` adds a `CHECK` constraint to the table in the form of a boolean + expression (`Expression`). Boolean expressions can be easily built + using [filter operators and functions](#filter-operators-and-functions). + (See also the `check` parameter under [Column Constraints](#column- + constraints).) - ``` swift + ```swift t.check(balance >= 0) // CHECK ("balance" >= 0.0) ``` - - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and composite (multiple column) keys. + - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the + `references` constraint, above](#column-constraints), it supports all + SQLite types, both [`ON UPDATE` and `ON DELETE` + actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and + composite (multiple column) keys. - ``` swift + ```swift t.foreignKey(user_id, references: users, id, delete: .setNull) // FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL ``` @@ -508,9 +613,12 @@ Additional constraints may be provided outside the scope of a single column usin ## Inserting Rows -We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. +We can insert rows into a table by calling a [query’s](#queries) `insert` +function with a list of [setters](#setters)—typically [typed column +expressions](#expressions) and values (which can also be expressions)—each +joined by the `<-` operator. -``` swift +```swift try db.run(users.insert(email <- "alice@mac.com", name <- "Alice")) // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') @@ -518,9 +626,10 @@ try db.run(users.insert(or: .replace, email <- "alice@mac.com", name <- "Alice B // INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.') ``` -The `insert` function, when run successfully, returns an `Int64` representing the inserted row’s [`ROWID`][ROWID]. +The `insert` function, when run successfully, returns an `Int64` representing +the inserted row’s [`ROWID`][ROWID]. -``` swift +```swift do { let rowid = try db.run(users.insert(email <- "alice@mac.com")) print("inserted id: \(rowid)") @@ -529,11 +638,14 @@ do { } ``` -The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns. +The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions +follow similar patterns. -> _Note:_ If `insert` is called without any arguments, the statement will run with a `DEFAULT VALUES` clause. The table must not have any constraints that aren’t fulfilled by default values. +> _Note:_ If `insert` is called without any arguments, the statement will run +> with a `DEFAULT VALUES` clause. The table must not have any constraints +> that aren’t fulfilled by default values. > -> ``` swift +> ```swift > try db.run(timestamps.insert()) > // INSERT INTO "timestamps" DEFAULT VALUES > ``` @@ -541,25 +653,27 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s ### Setters -SQLite.swift typically uses the `<-` operator to set values during [inserts](#inserting-rows) and [updates](#updating-rows). +SQLite.swift typically uses the `<-` operator to set values during [inserts +](#inserting-rows) and [updates](#updating-rows). -``` swift +```swift try db.run(counter.update(count <- 0)) // UPDATE "counters" SET "count" = 0 WHERE ("id" = 1) ``` -There are also a number of convenience setters that take the existing value into account using native Swift operators. +There are also a number of convenience setters that take the existing value +into account using native Swift operators. For example, to atomically increment a column, we can use `++`: -``` swift +```swift try db.run(counter.update(count++)) // equivalent to `counter.update(count -> count + 1)` // UPDATE "counters" SET "count" = "count" + 1 WHERE ("id" = 1) ``` To take an amount and “move” it via transaction, we can use `-=` and `+=`: -``` swift +```swift let amount = 100.0 try db.transaction { try db.run(alice.update(balance -= amount)) @@ -600,14 +714,18 @@ try db.transaction { ## Selecting Rows -[Query structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. +[Query structures](#queries) are `SELECT` statements waiting to happen. They +execute via [iteration](#iterating-and-accessing-values) and [other means +](#plucking-values) of sequence access. ### Iterating and Accessing Values -Prepared [queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned. +Prepared [queries](#queries) execute lazily upon iteration. Each row is +returned as a `Row` object, which can be subscripted with a [column +expression](#expressions) matching one of the columns returned. -``` swift +```swift for user in try db.prepare(users) { print("id: \(user[id]), email: \(user[email]), name: \(user[name])") // id: 1, email: alice@mac.com, name: Optional("Alice") @@ -615,21 +733,25 @@ for user in try db.prepare(users) { // SELECT * FROM "users" ``` -`Expression` column values are _automatically unwrapped_ (we’ve made a promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. +`Expression` column values are _automatically unwrapped_ (we’ve made a +promise to the compiler that they’ll never be `NULL`), while `Expression` +values remain wrapped. ### Plucking Rows -We can pluck the first row by passing a query to the `pluck` function on a database connection. +We can pluck the first row by passing a query to the `pluck` function on a +database connection. -``` swift +```swift if let user = try db.pluck(users) { /* ... */ } // Row // SELECT * FROM "users" LIMIT 1 ``` -To collect all rows into an array, we can simply wrap the sequence (though this is not always the most memory-efficient idea). +To collect all rows into an array, we can simply wrap the sequence (though +this is not always the most memory-efficient idea). -``` swift +```swift let all = Array(try db.prepare(users)) // SELECT * FROM "users" ``` @@ -637,9 +759,12 @@ let all = Array(try db.prepare(users)) ### Building Complex Queries -[Queries](#queries) have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. +[Queries](#queries) have a number of chainable functions that can be used +(with [expressions](#expressions)) to add and modify [a number of +clauses](https://www.sqlite.org/lang_select.html) to the underlying +statement. -``` swift +```swift let query = users.select(email) // SELECT "email" FROM "users" .filter(name != nil) // WHERE "name" IS NOT NULL .order(email.desc, name) // ORDER BY "email" DESC, "name" @@ -649,9 +774,11 @@ let query = users.select(email) // SELECT "email" FROM "users" #### Selecting Columns -By default, [queries](#queries) select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. +By default, [queries](#queries) select every column of the result set (using +`SELECT *`). We can use the `select` function with a list of +[expressions](#expressions) to return specific columns instead. -``` swift +```swift for user in try db.prepare(users.select(id, email)) { print("id: \(user[id]), email: \(user[email])") // id: 1, email: alice@mac.com @@ -659,9 +786,10 @@ for user in try db.prepare(users.select(id, email)) { // SELECT "id", "email" FROM "users" ``` -We can access the results of more complex expressions by holding onto a reference of the expression itself. +We can access the results of more complex expressions by holding onto a +reference of the expression itself. -``` swift +```swift let sentence = name + " is " + cast(age) as Expression + " years old!" for user in users.select(sentence) { print(user[sentence]) @@ -675,35 +803,42 @@ for user in users.select(sentence) { We can join tables using a [query’s](#queries) `join` function. -``` swift +```swift users.join(posts, on: user_id == users[id]) // SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id") ``` -The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require [namespacing](#column-namespacing), and sometimes require [aliasing](#table-aliasing). +The `join` function takes a [query](#queries) object (for the table being +joined on), a join condition (`on`), and is prefixed with an optional join +type (default: `.inner`). Join conditions can be built using [filter +operators and functions](#filter-operators-and-functions), generally require +[namespacing](#column-namespacing), and sometimes require [aliasing](#table- +aliasing). ##### Column Namespacing -When joining tables, column names can become ambiguous. _E.g._, both tables may have an `id` column. +When joining tables, column names can become ambiguous. _E.g._, both tables +may have an `id` column. -``` swift +```swift let query = users.join(posts, on: user_id == id) // assertion failure: ambiguous column 'id' ``` We can disambiguate by namespacing `id`. -``` swift +```swift let query = users.join(posts, on: user_id == users[id]) // SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id") ``` -Namespacing is achieved by subscripting a [query](#queries) with a [column expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`). +Namespacing is achieved by subscripting a [query](#queries) with a [column +expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`). > _Note:_ We can namespace all of a table’s columns using `*`. > -> ``` swift +> ```swift > let query = users.select(users[*]) > // SELECT "users".* FROM "users" > ``` @@ -711,9 +846,11 @@ Namespacing is achieved by subscripting a [query](#queries) with a [column expre ##### Table Aliasing -Occasionally, we need to join a table to itself, in which case we must alias the table with another name. We can achieve this using the [query’s](#queries) `alias` function. +Occasionally, we need to join a table to itself, in which case we must alias +the table with another name. We can achieve this using the +[query’s](#queries) `alias` function. -``` swift +```swift let managers = users.alias("managers") let query = users.join(managers, on: managers[id] == users[managerId]) @@ -721,9 +858,11 @@ let query = users.join(managers, on: managers[id] == users[managerId]) // INNER JOIN ("users") AS "managers" ON ("managers"."id" = "users"."manager_id") ``` -If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set. +If query results can have ambiguous column names, row values should be +accessed with namespaced [column expressions](#expressions). In the above +case, `SELECT *` immediately namespaces all columns of the result set. -``` swift +```swift let user = try db.pluck(query) user[id] // fatal error: ambiguous column 'id' // (please disambiguate: ["users"."id", "managers"."id"]) @@ -735,9 +874,10 @@ user[managers[id]] // returns "managers"."id" #### Filtering Rows -SQLite.swift filters rows using a [query’s](#queries) `filter` function with a boolean [expression](#expressions) (`Expression`). +SQLite.swift filters rows using a [query’s](#queries) `filter` function with +a boolean [expression](#expressions) (`Expression`). -``` swift +```swift users.filter(id == 1) // SELECT * FROM "users" WHERE ("id" = 1) @@ -754,18 +894,21 @@ users.filter(verified || balance >= 10_000) // SELECT * FROM "users" WHERE ("verified" OR ("balance" >= 10000.0)) ``` -We can build our own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). +We can build our own boolean expressions by using one of the many [filter +operators and functions](#filter-operators-and-functions). Instead of `filter` we can also use the `where` function which is an alias: -``` swift +```swift users.where(id == 1) // SELECT * FROM "users" WHERE ("id" = 1) ``` ##### Filter Operators and Functions -SQLite.swift defines a number of operators for building filtering predicates. Operators and functions work together in a type-safe manner, so attempting to equate or compare different types will prevent compilation. +SQLite.swift defines a number of operators for building filtering predicates. +Operators and functions work together in a type-safe manner, so attempting to +equate or compare different types will prevent compilation. ###### Infix Filter Operators @@ -782,7 +925,8 @@ SQLite.swift defines a number of operators for building filtering predicates. Op | `&&` | `Bool -> Bool` | `AND` | | `||` | `Bool -> Bool` | `OR` | -> *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` accordingly. +> *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` +> accordingly. ###### Prefix Filter Operators @@ -813,16 +957,18 @@ We can pre-sort returned rows using the [query’s](#queries) `order` function. _E.g._, to return users sorted by `email`, then `name`, in ascending order: -``` swift +```swift users.order(email, name) // SELECT * FROM "users" ORDER BY "email", "name" ``` The `order` function takes a list of [column expressions](#expressions). -`Expression` objects have two computed properties to assist sorting: `asc` and `desc`. These properties append the expression with `ASC` and `DESC` to mark ascending and descending order respectively. +`Expression` objects have two computed properties to assist sorting: `asc` +and `desc`. These properties append the expression with `ASC` and `DESC` to +mark ascending and descending order respectively. -``` swift +```swift users.order(email.desc, name.asc) // SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC ``` @@ -830,9 +976,10 @@ users.order(email.desc, name.asc) #### Limiting and Paging Results -We can limit and skip returned rows using a [query’s](#queries) `limit` function (and its optional `offset` parameter). +We can limit and skip returned rows using a [query’s](#queries) `limit` +function (and its optional `offset` parameter). -``` swift +```swift users.limit(5) // SELECT * FROM "users" LIMIT 5 @@ -843,67 +990,79 @@ users.limit(5, offset: 5) #### Aggregation -[Queries](#queries) come with a number of functions that quickly return aggregate scalar values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query. +[Queries](#queries) come with a number of functions that quickly return +aggregate scalar values from the table. These mirror the [core aggregate +functions](#aggregate-sqlite-functions) and are executed immediately against +the query. -``` swift +```swift let count = try db.scalar(users.count) // SELECT count(*) FROM "users" ``` Filtered queries will appropriately filter aggregate values. -``` swift +```swift let count = try db.scalar(users.filter(name != nil).count) // SELECT count(*) FROM "users" WHERE "name" IS NOT NULL ``` - - `count` as a computed property on a query (see examples above) returns the total number of rows matching the query. + - `count` as a computed property on a query (see examples above) returns + the total number of rows matching the query. - `count` as a computed property on a column expression returns the total number of rows where that column is not `NULL`. + `count` as a computed property on a column expression returns the total + number of rows where that column is not `NULL`. - ``` swift + ```swift let count = try db.scalar(users.select(name.count)) // -> Int // SELECT count("name") FROM "users" ``` - - `max` takes a comparable column expression and returns the largest value if any exists. + - `max` takes a comparable column expression and returns the largest value + if any exists. - ``` swift + ```swift let max = try db.scalar(users.select(id.max)) // -> Int64? // SELECT max("id") FROM "users" ``` - - `min` takes a comparable column expression and returns the smallest value if any exists. + - `min` takes a comparable column expression and returns the smallest value + if any exists. - ``` swift + ```swift let min = try db.scalar(users.select(id.min)) // -> Int64? // SELECT min("id") FROM "users" ``` - - `average` takes a numeric column expression and returns the average row value (as a `Double`) if any exists. + - `average` takes a numeric column expression and returns the average row + value (as a `Double`) if any exists. - ``` swift + ```swift let average = try db.scalar(users.select(balance.average)) // -> Double? // SELECT avg("balance") FROM "users" ``` - - `sum` takes a numeric column expression and returns the sum total of all rows if any exist. + - `sum` takes a numeric column expression and returns the sum total of all + rows if any exist. - ``` swift + ```swift let sum = try db.scalar(users.select(balance.sum)) // -> Double? // SELECT sum("balance") FROM "users" ``` - - `total`, like `sum`, takes a numeric column expression and returns the sum total of all rows, but in this case always returns a `Double`, and returns `0.0` for an empty query. + - `total`, like `sum`, takes a numeric column expression and returns the + sum total of all rows, but in this case always returns a `Double`, and + returns `0.0` for an empty query. - ``` swift + ```swift let total = try db.scalar(users.select(balance.total)) // -> Double // SELECT total("balance") FROM "users" ``` -> _Note:_ Expressions can be prefixed with a `DISTINCT` clause by calling the `distinct` computed property. +> _Note:_ Expressions can be prefixed with a `DISTINCT` clause by calling the +> `distinct` computed property. > -> ``` swift +> ```swift > let count = try db.scalar(users.select(name.distinct.count) // -> Int > // SELECT count(DISTINCT "name") FROM "users" > ``` @@ -911,26 +1070,32 @@ let count = try db.scalar(users.filter(name != nil).count) ## Updating Rows -We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. +We can update a table’s rows by calling a [query’s](#queries) `update` +function with a list of [setters](#setters)—typically [typed column +expressions](#expressions) and values (which can also be expressions)—each +joined by the `<-` operator. -When an unscoped query calls `update`, it will update _every_ row in the table. +When an unscoped query calls `update`, it will update _every_ row in the +table. -``` swift +```swift try db.run(users.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' ``` -Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#filtering-rows). +Be sure to scope `UPDATE` statements beforehand using [the `filter` function +](#filtering-rows). -``` swift +```swift let alice = users.filter(id == 1) try db.run(alice.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1) ``` -The `update` function returns an `Int` representing the number of updated rows. +The `update` function returns an `Int` representing the number of updated +rows. -``` swift +```swift do { if try db.run(alice.update(email <- "alice@me.com")) > 0 { print("updated alice") @@ -945,26 +1110,30 @@ do { ## Deleting Rows -We can delete rows from a table by calling a [query’s](#queries) `delete` function. +We can delete rows from a table by calling a [query’s](#queries) `delete` +function. -When an unscoped query calls `delete`, it will delete _every_ row in the table. +When an unscoped query calls `delete`, it will delete _every_ row in the +table. -``` swift +```swift try db.run(users.delete()) // DELETE FROM "users" ``` -Be sure to scope `DELETE` statements beforehand using [the `filter` function](#filtering-rows). +Be sure to scope `DELETE` statements beforehand using +[the `filter` function](#filtering-rows). -``` swift +```swift let alice = users.filter(id == 1) try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) ``` -The `delete` function returns an `Int` representing the number of deleted rows. +The `delete` function returns an `Int` representing the number of deleted +rows. -``` swift +```swift do { if try db.run(alice.delete()) > 0 { print("deleted alice") @@ -979,9 +1148,11 @@ do { ## Transactions and Savepoints -Using the `transaction` and `savepoint` functions, we can run a series of statements in a transaction. If a single statement fails or the block throws an error, the changes will be rolled back. +Using the `transaction` and `savepoint` functions, we can run a series of +statements in a transaction. If a single statement fails or the block throws +an error, the changes will be rolled back. -``` swift +```swift try db.transaction { let rowid = try db.run(users.insert(email <- "betty@icloud.com")) try db.run(users.insert(email <- "cathy@icloud.com", managerId <- rowid)) @@ -997,14 +1168,16 @@ try db.transaction { ## Altering the Schema -SQLite.swift comes with several functions (in addition to `Table.create`) for altering a database schema in a type-safe manner. +SQLite.swift comes with several functions (in addition to `Table.create`) for +altering a database schema in a type-safe manner. ### Renaming Tables -We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` function on a `Table` or `VirtualTable`. +We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` +function on a `Table` or `VirtualTable`. -``` swift +```swift try db.run(users.rename(Table("users_old")) // ALTER TABLE "users" RENAME TO "users_old" ``` @@ -1012,9 +1185,12 @@ try db.run(users.rename(Table("users_old")) ### Adding Columns -We can add columns to a table by calling `addColumn` function on a `Table`. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports. +We can add columns to a table by calling `addColumn` function on a `Table`. +SQLite.swift enforces +[the same limited subset](https://www.sqlite.org/lang_altertable.html) of +`ALTER TABLE` that SQLite supports. -``` swift +```swift try db.run(users.addColumn(suffix)) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT ``` @@ -1022,27 +1198,38 @@ try db.run(users.addColumn(suffix)) #### Added Column Constraints -The `addColumn` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table). +The `addColumn` function shares several of the same [`column` function +parameters](#column-constraints) used when [creating +tables](#creating-a-table). - - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). (See also the `check` function under [Table Constraints](#table-constraints).) + - `check` attaches a `CHECK` constraint to a column definition in the form + of a boolean expression (`Expression`). (See also the `check` + function under [Table Constraints](#table-constraints).) - ``` swift + ```swift try db.run(users.addColumn(suffix, check: ["JR", "SR"].contains(suffix))) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR')) ``` - - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). + - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ + accepts a value matching the column’s type. This value is used if none is + explicitly provided during [an `INSERT`](#inserting-rows). - ``` swift + ```swift try db.run(users.addColumn(suffix, defaultValue: "SR")) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT DEFAULT 'SR' ``` - > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), default values may not be expression structures (including `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`). + > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), + > default values may not be expression structures (including + > `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`). - - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. + - `collate` adds a `COLLATE` clause to `Expression` (and + `Expression`) column definitions with [a collating + sequence](https://www.sqlite.org/datatype3.html#collation) defined in the + `Collation` enumeration. - ``` swift + ```swift try db.run(users.addColumn(email, collate: .nocase)) // ALTER TABLE "users" ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" @@ -1050,9 +1237,12 @@ The `addColumn` function shares several of the same [`column` function parameter // ALTER TABLE "users" ADD COLUMN "name" TEXT COLLATE "RTRIM" ``` - - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column + definitions and accepts a table or namespaced column expression. (See the + `foreignKey` function under [Table Constraints](#table-constraints) for + non-integer foreign key support.) - ``` swift + ```swift try db.run(posts.addColumn(userId, references: users, id) // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` @@ -1063,27 +1253,32 @@ The `addColumn` function shares several of the same [`column` function parameter #### Creating Indexes -We can build [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `createIndex` function on a `SchemaType`. +We can build +[`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) +by calling the `createIndex` function on a `SchemaType`. -``` swift +```swift try db.run(users.createIndex(email)) // CREATE INDEX "index_users_on_email" ON "users" ("email") ``` -The index name is generated automatically based on the table and column names. +The index name is generated automatically based on the table and column +names. The `createIndex` function has a couple default parameters we can override. - `unique` adds a `UNIQUE` constraint to the index. Default: `false`. - ``` swift + ```swift try db.run(users.createIndex(email, unique: true)) // CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email") ``` - - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. + - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` + statement (which will bail out gracefully if the table already exists). + Default: `false`. - ``` swift + ```swift try db.run(users.createIndex(email, ifNotExists: true)) // CREATE INDEX IF NOT EXISTS "index_users_on_email" ON "users" ("email") ``` @@ -1091,16 +1286,19 @@ The `createIndex` function has a couple default parameters we can override. #### Dropping Indexes -We can build [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `dropIndex` function on a `SchemaType`. +We can build +[`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by +calling the `dropIndex` function on a `SchemaType`. -``` swift +```swift try db.run(users.dropIndex(email)) // DROP INDEX "index_users_on_email" ``` -The `dropIndex` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `dropIndex` function has one additional parameter, `ifExists`, which +(when `true`) adds an `IF EXISTS` clause to the statement. -``` swift +```swift try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` @@ -1108,16 +1306,19 @@ try db.run(users.dropIndex(email, ifExists: true)) ### Dropping Tables -We can build [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `dropTable` function on a `SchemaType`. +We can build +[`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) +by calling the `dropTable` function on a `SchemaType`. -``` swift +```swift try db.run(users.drop()) // DROP TABLE "users" ``` -The `drop` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `drop` function has one additional parameter, `ifExists`, which (when +`true`) adds an `IF EXISTS` clause to the statement. -``` swift +```swift try db.run(users.drop(ifExists: true)) // DROP TABLE IF EXISTS "users" ``` @@ -1125,11 +1326,12 @@ try db.run(users.drop(ifExists: true)) ### Migrations and Schema Versioning -You can add a convenience property on `Connection` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_user_version). +You can add a convenience property on `Connection` to query and set the +[`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_user_version). This is a great way to manage your schema’s version over migrations. -``` swift +```swift extension Connection { public var userVersion: Int32 { get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} @@ -1151,14 +1353,15 @@ if db.userVersion == 1 { } ``` -For more complex migration requirements check out the schema management system -[SQLiteMigrationManager.swift][]. +For more complex migration requirements check out the schema management +system [SQLiteMigrationManager.swift][]. ## Custom Types -SQLite.swift supports serializing and deserializing any custom type as long as it conforms to the `Value` protocol. +SQLite.swift supports serializing and deserializing any custom type as long +as it conforms to the `Value` protocol. -> ``` swift +> ```swift > protocol Value { > typealias Datatype: Binding > class var declaredDatatype: String { get } @@ -1167,20 +1370,27 @@ SQLite.swift supports serializing and deserializing any custom type as long as i > } > ``` -The `Datatype` must be one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL](#building-type-safe-sql) for a list of types). +The `Datatype` must be one of the basic Swift types that values are bridged +through before serialization and deserialization (see [Building Type-Safe SQL +](#building-type-safe-sql) for a list of types). -> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to directly map SQLite types to Swift types. **Do _not_** conform custom types to the `Binding` protocol. +> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to +> directly map SQLite types to Swift types. **Do _not_** conform custom types +> to the `Binding` protocol. -Once extended, the type can be used [_almost_](#custom-type-caveats) wherever typed expressions can be. +Once extended, the type can be used [_almost_](#custom-type-caveats) wherever +typed expressions can be. ### Date-Time Values -In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can transparently bridge `Date` objects through Swift’s `String` or `Int` types. +In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can +transparently bridge `Date` objects through Swift’s `String` or `Int` types. -To serialize `Date` objects as `TEXT` values (in ISO 8601), we’ll use `String`. +To serialize `Date` objects as `TEXT` values (in ISO 8601), we’ll use +`String`. -``` swift +```swift extension Date: Value { class var declaredDatatype: String { return String.declaredDatatype @@ -1204,7 +1414,7 @@ let SQLDateFormatter: DateFormatter = { We can also treat them as `INTEGER` values using `Int`. -``` swift +```swift extension Date: Value { class var declaredDatatype: String { return Int.declaredDatatype @@ -1218,11 +1428,14 @@ extension Date: Value { } ``` -> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` helpers return `TEXT` values. Because of this (and the fact that Unix time is far less human-readable when we’re faced with the raw data), we recommend using the `TEXT` extension. +> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` +> helpers return `TEXT` values. Because of this (and the fact that Unix time +> is far less human-readable when we’re faced with the raw data), we +> recommend using the `TEXT` extension. Once defined, we can use these types directly in SQLite statements. -``` swift +```swift let published_at = Expression("published_at") let published = posts.filter(published_at <= Date()) @@ -1237,7 +1450,7 @@ let published = posts.filter(published_at <= Date()) We can bridge any type that can be initialized from and encoded to `Data`. -``` swift +```swift extension UIImage: Value { public class var declaredDatatype: String { return Blob.declaredDatatype @@ -1252,16 +1465,20 @@ extension UIImage: Value { } ``` -> _Note:_ See the [Archives and Serializations Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i) for more information on encoding and decoding custom types. +> _Note:_ See the [Archives and Serializations Programming Guide][] for more +> information on encoding and decoding custom types. +[Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i + ### Custom Type Caveats -Swift does _not_ currently support generic subscripting, which means we cannot, by default, subscript Expressions with custom types to: +Swift does _not_ currently support generic subscripting, which means we +cannot, by default, subscript Expressions with custom types to: 1. **Namespace expressions**. Use the `namespace` function, instead: - ``` swift + ```swift let avatar = Expression("avatar") users[avatar] // fails to compile users.namespace(avatar) // "users"."avatar" @@ -1269,7 +1486,7 @@ Swift does _not_ currently support generic subscripting, which means we cannot, 2. **Access column data**. Use the `get` function, instead: - ``` swift + ```swift let user = users.first! user[avatar] // fails to compile user.get(avatar) // UIImage? @@ -1277,7 +1494,7 @@ Swift does _not_ currently support generic subscripting, which means we cannot, We can, of course, write extensions, but they’re rather wordy. -``` swift +```swift extension Query { subscript(column: Expression) -> Expression { return namespace(column) @@ -1299,53 +1516,63 @@ extension Row { ## Codable Types -Codable types were introduced as a part of Swift 4 to allow serializing and deserializing types. SQLite.swift -supports the insertion, updating, and retrieval of basic Codable types. +Codable types were introduced as a part of Swift 4 to allow serializing and +deserializing types. SQLite.swift supports the insertion, updating, and +retrieval of basic Codable types. ### Inserting Codable Types Queries have a method to allow inserting an Encodable type. -``` swift +```swift try db.run(users.insert(user)) ``` There are two other parameters also available to this method: -- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. -- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. +- `userInfo` is a dictionary that is passed to the encoder and made available + to encodable types to allow customizing their behavior. + +- `otherSetters` allows you to specify additional setters on top of those + that are generated from the encodable types themselves. ### Updating Codable Types Queries have a method to allow updating an Encodable type. -``` swift +```swift try db.run(users.update(user)) ``` There are two other parameters also available to this method: -- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. -- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. +- `userInfo` is a dictionary that is passed to the encoder and made available + to encodable types to allow customizing their behavior. + +- `otherSetters` allows you to specify additional setters on top of those + that are generated from the encodable types themselves. ### Retrieving Codable Types Rows have a method to decode a Decodable type. -``` swift +```swift let loadedUsers: [User] = try db.prepare(users).map { row in return try row.decode() } ``` -You can also create a decoder to use manually yourself. This can be useful for example if you are using -the [Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide subclasses behind a super class. -For example, you may want to encode an Image type that can be multiple different formats such as PNGImage, JPGImage, -or HEIFIamge. You will need to determine the correct subclass before you know which type to decode. +You can also create a decoder to use manually yourself. This can be useful +for example if you are using the +[Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide +subclasses behind a super class. For example, you may want to encode an Image +type that can be multiple different formats such as PNGImage, JPGImage, or +HEIFIamge. You will need to determine the correct subclass before you know +which type to decode. -``` swift +```swift enum ImageCodingKeys: String, CodingKey { case kind } @@ -1370,7 +1597,8 @@ let loadedImages: [Image] = try db.prepare(images).map { row in Both of the above methods also have the following optional parameter: -- `userInfo` is a dictionary that is passed to the decoder and made available to decodable types to allow customizing their behavior. +- `userInfo` is a dictionary that is passed to the decoder and made available + to decodable types to allow customizing their behavior. ### Restrictions @@ -1379,15 +1607,19 @@ There are a few restrictions on using Codable types: - The encodable and decodable objects can only use the following types: - Int, Bool, Float, Double, String - Nested Codable types that will be encoded as JSON to a single column -- These methods will not handle object relationships for you. You must write your own Codable and Decodable -implementations if you wish to support this. -- The Codable types may not try to access nested containers or nested unkeyed containers -- The Codable types may not access single value containers or unkeyed containers +- These methods will not handle object relationships for you. You must write + your own Codable and Decodable implementations if you wish to support this. +- The Codable types may not try to access nested containers or nested unkeyed + containers +- The Codable types may not access single value containers or unkeyed + containers - The Codable types may not access super decoders or encoders ## Other Operators -In addition to [filter operators](#filtering-infix-operators), SQLite.swift defines a number of operators that can modify expression values with arithmetic, bitwise operations, and concatenation. +In addition to [filter operators](#filtering-infix-operators), SQLite.swift +defines a number of operators that can modify expression values with +arithmetic, bitwise operations, and concatenation. ###### Other Infix Operators @@ -1405,7 +1637,8 @@ In addition to [filter operators](#filtering-infix-operators), SQLite.swift defi | `|` | `Int -> Int` | `|` | | `+` | `String -> String` | `||` | -> _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. +> _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which +> expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. ###### Other Prefix Operators @@ -1418,22 +1651,26 @@ In addition to [filter operators](#filtering-infix-operators), SQLite.swift defi ## Core SQLite Functions -Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) have been surfaced in and type-audited for SQLite.swift. +Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) +have been surfaced in and type-audited for SQLite.swift. > _Note:_ SQLite.swift aliases the `??` operator to the `ifnull` function. > -> ``` swift +> ```swift > name ?? email // ifnull("name", "email") > ``` ## Aggregate SQLite Functions -Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift. +Most of SQLite’s +[aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been +surfaced in and type-audited for SQLite.swift. ## Date and Time functions -SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) are available: +SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) +functions are available: ```swift DateFunctions.date("now") @@ -1446,11 +1683,14 @@ Expression("date").date ## Custom SQL Functions -We can create custom SQL functions by calling `createFunction` on a database connection. +We can create custom SQL functions by calling `createFunction` on a database +connection. -For example, to give queries access to [`MobileCoreServices.UTTypeConformsTo`](https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo), we can write the following: +For example, to give queries access to +[`MobileCoreServices.UTTypeConformsTo`][UTTypeConformsTo], we can +write the following: -``` swift +```swift import MobileCoreServices let typeConformsTo: (Expression, Expression) -> Expression = ( @@ -1460,23 +1700,27 @@ let typeConformsTo: (Expression, Expression) -> Expression ) ``` -> _Note:_ The optional `deterministic` parameter is an optimization that causes the function to be created with [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/create_function.html). +> _Note:_ The optional `deterministic` parameter is an optimization that +> causes the function to be created with +> [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/create_function.html). Note `typeConformsTo`’s signature: -``` swift +```swift (Expression, Expression) -> Expression ``` -Because of this, `createFunction` expects a block with the following signature: +Because of this, `createFunction` expects a block with the following +signature: -``` swift +```swift (String, String) -> Bool ``` -Once assigned, the closure can be called wherever boolean expressions are accepted. +Once assigned, the closure can be called wherever boolean expressions are +accepted. -``` swift +```swift let attachments = Table("attachments") let UTI = Expression("UTI") @@ -1484,38 +1728,44 @@ let images = attachments.filter(typeConformsTo(UTI, kUTTypeImage)) // SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image') ``` -> _Note:_ The return type of a function must be [a core SQL type](#building-type-safe-sql) or [conform to `Value`](#custom-types). +> _Note:_ The return type of a function must be [a core SQL type](#building- +> type-safe-sql) or [conform to `Value`](#custom-types). -We can create loosely-typed functions by handling an array of raw arguments, instead. +We can create loosely-typed functions by handling an array of raw arguments, +instead. -``` swift +```swift db.createFunction("typeConformsTo", deterministic: true) { args in guard let UTI = args[0] as? String, conformsToUTI = args[1] as? String else { return nil } return UTTypeConformsTo(UTI, conformsToUTI) } ``` -Creating a loosely-typed function cannot return a closure and instead must be wrapped manually or executed [using raw SQL](#executing-arbitrary-sql). +Creating a loosely-typed function cannot return a closure and instead must be +wrapped manually or executed [using raw SQL](#executing-arbitrary-sql). -``` swift +```swift let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` +[UTTypeConformsTo]: https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo ## Custom Collations -We can create custom collating sequences by calling `createCollation` on a database connection. +We can create custom collating sequences by calling `createCollation` on a +database connection. -``` swift +```swift try db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .diacriticInsensitiveSearch) } ``` -We can reference a custom collation using the `Custom` member of the `Collation` enumeration. +We can reference a custom collation using the `Custom` member of the +`Collation` enumeration. -``` swift +```swift restaurants.order(collate(.custom("NODIACRITIC"), name)) // SELECT * FROM "restaurants" ORDER BY "name" COLLATE "NODIACRITIC" ``` @@ -1523,9 +1773,11 @@ restaurants.order(collate(.custom("NODIACRITIC"), name)) ## Full-text Search -We can create a virtual table using the [FTS4 module](http://www.sqlite.org/fts3.html) by calling `create` on a `VirtualTable`. +We can create a virtual table using the [FTS4 +module](http://www.sqlite.org/fts3.html) by calling `create` on a +`VirtualTable`. -``` swift +```swift let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") @@ -1536,14 +1788,14 @@ try db.run(emails.create(.FTS4(subject, body))) We can specify a [tokenizer](http://www.sqlite.org/fts3.html#tokenizer) using the `tokenize` parameter. -``` swift +```swift try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter) ``` We can set the full range of parameters by creating a `FTS4Config` object. -``` swift +```swift let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") @@ -1557,9 +1809,11 @@ try db.run(emails.create(.FTS4(config)) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", notindexed="body", languageid="lid", order="desc") ``` -Once we insert a few rows, we can search using the `match` function, which takes a table or column as its first argument and a query string as its second. +Once we insert a few rows, we can search using the `match` function, which +takes a table or column as its first argument and a query string as its +second. -``` swift +```swift try db.run(emails.insert( subject <- "Just Checking In", body <- "Hey, I was just wondering...did you get my last email?" @@ -1574,8 +1828,9 @@ let replies = emails.filter(subject.match("Re:*")) ### FTS5 -When linking against a version of SQLite with [FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual table -in a similar fashion. +When linking against a version of SQLite with +[FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual +table in a similar fashion. ```swift let emails = VirtualTable("emails") @@ -1598,11 +1853,14 @@ let replies = emails.filter(emails.match("subject:\"Re:\"*)) ## Executing Arbitrary SQL -Though we recommend you stick with SQLite.swift’s [type-safe system](#building-type-safe-sql) whenever possible, it is possible to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. +Though we recommend you stick with SQLite.swift’s [type-safe system +](#building-type-safe-sql) whenever possible, it is possible to simply and +safely prepare and execute raw SQL statements via a `Database` connection +using the following functions. - `execute` runs an arbitrary number of SQL statements as a convenience. - ``` swift + ```swift try db.execute( "BEGIN TRANSACTION;" + "CREATE TABLE users (" + @@ -1621,22 +1879,26 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building ) ``` - - `prepare` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), and returns the statement for deferred execution. + - `prepare` prepares a single `Statement` object from a SQL string, + optionally binds values to it (using the statement’s `bind` function), + and returns the statement for deferred execution. - ``` swift + ```swift let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") ``` - Once prepared, statements may be executed using `run`, binding any unbound parameters. + Once prepared, statements may be executed using `run`, binding any + unbound parameters. - ``` swift + ```swift try stmt.run("alice@mac.com") db.changes // -> {Some 1} ``` - Statements with results may be iterated over, using the columnNames if useful. + Statements with results may be iterated over, using the columnNames if + useful. - ``` swift + ```swift let stmt = try db.prepare("SELECT id, email FROM users") for row in stmt { for (index, name) in stmt.columnNames.enumerate() { @@ -1646,21 +1908,26 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building } ``` - - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement. + - `run` prepares a single `Statement` object from a SQL string, optionally + binds values to it (using the statement’s `bind` function), executes, + and returns the statement. - ``` swift + ```swift try db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com") ``` - - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row. + - `scalar` prepares a single `Statement` object from a SQL string, + optionally binds values to it (using the statement’s `bind` function), + executes, and returns the first value of the first row. - ``` swift + ```swift let count = try db.scalar("SELECT count(*) FROM users") as! Int64 ``` - Statements also have a `scalar` function, which can optionally re-bind values at execution. + Statements also have a `scalar` function, which can optionally re-bind + values at execution. - ``` swift + ```swift let stmt = try db.prepare("SELECT count (*) FROM users") let count = try stmt.scalar() as! Int64 ``` @@ -1670,7 +1937,7 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building We can log SQL using the database’s `trace` function. -``` swift +```swift #if DEBUG db.trace { print($0) } #endif diff --git a/README.md b/README.md index a10f2c09..07802fa8 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,10 @@ syntax _and_ intent. - [Well-documented][See Documentation] - Extensively tested - SQLCipher support via CocoaPods - - Active support at [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) + - Active support at + [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), + and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) + (_experimental_) [Full-text search]: Documentation/Index.md#full-text-search [See Documentation]: Documentation/Index.md#sqliteswift-documentation @@ -34,7 +37,7 @@ syntax _and_ intent. ## Usage -``` swift +```swift import SQLite let db = try Connection("path/to/db.sqlite3") @@ -81,7 +84,7 @@ try db.scalar(users.count) // 0 SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API. -``` swift +```swift let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") for email in ["betty@icloud.com", "cathy@icloud.com"] { try stmt.run(email) @@ -105,7 +108,13 @@ interactively, from the Xcode project’s playground. ![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) -For a more comprehensive example, see [this article](http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html) and the [companion repository](https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master). +For a more comprehensive example, see +[this article][Create a Data Access Layer with SQLite.swift and Swift 2] +and the [companion repository][SQLiteDataAccessLayer2]. + + +[Create a Data Access Layer with SQLite.swift and Swift 2]: http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html +[SQLiteDataAccessLayer2]: https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master ## Installation @@ -120,11 +129,12 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: - ``` + ```ruby github "stephencelis/SQLite.swift" ~> 0.11.4 ``` - 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. + 3. Run `carthage update` and + [add the appropriate framework][Carthage Usage]. [Carthage]: https://github.com/Carthage/Carthage @@ -137,9 +147,10 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift + requires version 1.0.0 or greater.) - ``` sh + ```sh # Using the default Ruby install will require you to use sudo when # installing and updating gems. [sudo] gem install cocoapods @@ -147,7 +158,7 @@ SQLite.swift with CocoaPods: 2. Update your Podfile to include the following: - ``` ruby + ```ruby use_frameworks! target 'YourAppTargetName' do @@ -162,7 +173,8 @@ SQLite.swift with CocoaPods: ### Swift Package Manager -The [Swift Package Manager][] is a tool for managing the distribution of Swift code. +The [Swift Package Manager][] is a tool for managing the distribution of +Swift code. 1. Add the following to your `Package.swift` file: @@ -174,7 +186,7 @@ The [Swift Package Manager][] is a tool for managing the distribution of Swift c 2. Build your project: - ``` sh + ```sh $ swift build ``` @@ -196,9 +208,11 @@ To install SQLite.swift as an Xcode sub-project: 4. **Add**. -Some additional steps are required to install the application on an actual device: +Some additional steps are required to install the application on an actual +device: - 5. In the **General** tab, click the **+** button under **Embedded Binaries**. + 5. In the **General** tab, click the **+** button under **Embedded + Binaries**. 6. Select the appropriate **SQLite.framework** for your platform. @@ -243,7 +257,8 @@ file](./LICENSE.txt) for more information. These projects enhance or use SQLite.swift: - - [SQLiteMigrationManager.swift](https://github.com/garriguv/SQLiteMigrationManager.swift) (inspired by [FMDBMigrationManager](https://github.com/layerhq/FMDBMigrationManager)) + - [SQLiteMigrationManager.swift][] (inspired by + [FMDBMigrationManager][]) ## Alternatives @@ -257,5 +272,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftData](https://github.com/ryanfowler/SwiftData) - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) -[FMDB]: https://github.com/ccgus/fmdb [swift-4]: https://github.com/stephencelis/SQLite.swift/tree/swift-4 +[SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift +[FMDB]: https://github.com/ccgus/fmdb +[FMDBMigrationManager]: https://github.com/layerhq/FMDBMigrationManager From f7dfd88229c5e882dc84fa916b224f30dcab39cd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 08:56:29 +0200 Subject: [PATCH 55/86] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e36e40d7..09c5c33f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================================== * Added Date and Time functions ([#142][]) +* Add Swift4 Coding support ([#733][]) * Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) @@ -69,4 +70,5 @@ [#681]: https://github.com/stephencelis/SQLite.swift/issues/681 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 +[#733]: https://github.com/stephencelis/SQLite.swift/pull/733 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 From 25b2ae6e3eddfce2d0c18b69bb997316bfac4b25 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 09:02:34 +0200 Subject: [PATCH 56/86] Add link --- Documentation/Index.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9657a94a..74837148 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1516,9 +1516,11 @@ extension Row { ## Codable Types -Codable types were introduced as a part of Swift 4 to allow serializing and -deserializing types. SQLite.swift supports the insertion, updating, and -retrieval of basic Codable types. +[Codable types][Encoding and Decoding Custom Types] were introduced as a part +of Swift 4 to allow serializing and deserializing types. SQLite.swift supports +the insertion, updating, and retrieval of basic Codable types. + +[Encoding and Decoding Custom Types]: https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types ### Inserting Codable Types From 93f2f63f38f021583ac7a31c5c5b07a1e9fd7069 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 09:06:46 +0200 Subject: [PATCH 57/86] clarify --- Documentation/Index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 74837148..4b5c0025 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1527,7 +1527,10 @@ the insertion, updating, and retrieval of basic Codable types. Queries have a method to allow inserting an Encodable type. ```swift -try db.run(users.insert(user)) +struct User: Codable { + let name: String +} +try db.run(users.insert(User(name: "test"))) ``` From 865f4ad448b746b7ce82d55e0fee46478f87f38b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 09:27:42 +0200 Subject: [PATCH 58/86] Doc fixes --- Documentation/Index.md | 138 ++++++++++++++--------------------------- README.md | 32 +++++++--- 2 files changed, 70 insertions(+), 100 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4b5c0025..f3569374 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -288,8 +288,8 @@ let db = try Connection(path, readonly: true) > _Note:_ Signed applications cannot modify their bundle resources. If you > bundle a database file with your app for the purpose of bootstrapping, copy -> it to a writable location _before_ establishing a connection (see [Read- -> Write Databases](#read-write-databases), above, for typical, writable +> it to a writable location _before_ establishing a connection (see +> [Read-Write Databases](#read-write-databases), above, for typical, writable > locations). > > See these two Stack Overflow questions for more information about iOS apps @@ -515,8 +515,8 @@ several parameters that map to various column constraints and clauses. - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value (or expression) matching the column’s type. This value is - used if none is explicitly provided during [an `INSERT`](#inserting- - rows). + used if none is explicitly provided during + [an `INSERT`](#inserting-rows). ```swift t.column(name, defaultValue: "Anonymous") @@ -587,8 +587,8 @@ using the following functions. - `check` adds a `CHECK` constraint to the table in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). - (See also the `check` parameter under [Column Constraints](#column- - constraints).) + (See also the `check` parameter under + [Column Constraints](#column-constraints).) ```swift t.check(balance >= 0) @@ -812,8 +812,8 @@ The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require -[namespacing](#column-namespacing), and sometimes require [aliasing](#table- -aliasing). +[namespacing](#column-namespacing), and sometimes require +[aliasing](#table-aliasing). ##### Column Namespacing @@ -1361,14 +1361,14 @@ system [SQLiteMigrationManager.swift][]. SQLite.swift supports serializing and deserializing any custom type as long as it conforms to the `Value` protocol. -> ```swift -> protocol Value { -> typealias Datatype: Binding -> class var declaredDatatype: String { get } -> class func fromDatatypeValue(datatypeValue: Datatype) -> Self -> var datatypeValue: Datatype { get } -> } -> ``` +```swift +protocol Value { + typealias Datatype: Binding + class var declaredDatatype: String { get } + class func fromDatatypeValue(datatypeValue: Datatype) -> Self + var datatypeValue: Datatype { get } +} +``` The `Datatype` must be one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL @@ -1385,64 +1385,19 @@ typed expressions can be. ### Date-Time Values In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can -transparently bridge `Date` objects through Swift’s `String` or `Int` types. - -To serialize `Date` objects as `TEXT` values (in ISO 8601), we’ll use -`String`. - -```swift -extension Date: Value { - class var declaredDatatype: String { - return String.declaredDatatype - } - class func fromDatatypeValue(stringValue: String) -> Date { - return SQLDateFormatter.dateFromString(stringValue)! - } - var datatypeValue: String { - return SQLDateFormatter.stringFromDate(self) - } -} +transparently bridge `Date` objects through Swift’s `String` types. -let SQLDateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" - formatter.locale = Locale(localeIdentifier: "en_US_POSIX") - formatter.timeZone = TimeZone(forSecondsFromGMT: 0) - return formatter -}() -``` - -We can also treat them as `INTEGER` values using `Int`. - -```swift -extension Date: Value { - class var declaredDatatype: String { - return Int.declaredDatatype - } - class func fromDatatypeValue(intValue: Int) -> Self { - return self(timeIntervalSince1970: TimeInterval(intValue)) - } - var datatypeValue: Int { - return Int(timeIntervalSince1970) - } -} -``` - -> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` -> helpers return `TEXT` values. Because of this (and the fact that Unix time -> is far less human-readable when we’re faced with the raw data), we -> recommend using the `TEXT` extension. - -Once defined, we can use these types directly in SQLite statements. +We can use these types directly in SQLite statements. ```swift let published_at = Expression("published_at") let published = posts.filter(published_at <= Date()) -// extension where Datatype == String: -// SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18 12:45:30' -// extension where Datatype == Int: -// SELECT * FROM "posts" WHERE "published_at" <= 1416314730 +// SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18T12:45:30.000' + +let startDate = Date(timeIntervalSince1970: 0) +let published = posts.filter(startDate...Date() ~= published_at) +// SELECT * FROM "posts" WHERE "published_at" BETWEEN '1970-01-01T00:00:00.000' AND '2014-11-18T12:45:30.000' ``` @@ -1469,7 +1424,7 @@ extension UIImage: Value { > information on encoding and decoding custom types. -[Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i +[Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html ### Custom Type Caveats @@ -1707,7 +1662,7 @@ let typeConformsTo: (Expression, Expression) -> Expression > _Note:_ The optional `deterministic` parameter is an optimization that > causes the function to be created with -> [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/create_function.html). +> [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/c_deterministic.html). Note `typeConformsTo`’s signature: @@ -1733,8 +1688,8 @@ let images = attachments.filter(typeConformsTo(UTI, kUTTypeImage)) // SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image') ``` -> _Note:_ The return type of a function must be [a core SQL type](#building- -> type-safe-sql) or [conform to `Value`](#custom-types). +> _Note:_ The return type of a function must be +> [a core SQL type](#building-type-safe-sql) or [conform to `Value`](#custom-types). We can create loosely-typed functions by handling an array of raw arguments, instead. @@ -1754,7 +1709,7 @@ let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ? for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` -[UTTypeConformsTo]: https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo +[UTTypeConformsTo]: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto ## Custom Collations @@ -1858,29 +1813,30 @@ let replies = emails.filter(emails.match("subject:\"Re:\"*)) ## Executing Arbitrary SQL -Though we recommend you stick with SQLite.swift’s [type-safe system -](#building-type-safe-sql) whenever possible, it is possible to simply and -safely prepare and execute raw SQL statements via a `Database` connection +Though we recommend you stick with SQLite.swift’s +[type-safe system](#building-type-safe-sql) whenever possible, it is possible +to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. - `execute` runs an arbitrary number of SQL statements as a convenience. ```swift - try db.execute( - "BEGIN TRANSACTION;" + - "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY NOT NULL," + - "email TEXT UNIQUE NOT NULL," + - "name TEXT" + - ");" + - "CREATE TABLE posts (" + - "id INTEGER PRIMARY KEY NOT NULL," + - "title TEXT NOT NULL," + - "body TEXT NOT NULL," + - "published_at DATETIME" + - ");" + - "PRAGMA user_version = 1;" + - "COMMIT TRANSACTION;" + try db.execute(""" + BEGIN TRANSACTION; + CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL, + email TEXT UNIQUE NOT NULL, + name TEXT + ); + CREATE TABLE posts ( + id INTEGER PRIMARY KEY NOT NULL, + title TEXT NOT NULL, + body TEXT NOT NULL, + published_at DATETIME + ); + PRAGMA user_version = 1; + COMMIT TRANSACTION; + """ ) ``` diff --git a/README.md b/README.md index 07802fa8..3a9ceecb 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,12 @@ # SQLite.swift -[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-4-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. [SQLite.swift][] provides compile-time confidence in SQL statement syntax _and_ intent. -[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat -[Travis]: https://travis-ci.org/stephencelis/SQLite.swift -[Swift]: https://developer.apple.com/swift/ -[SQLite3]: http://www.sqlite.org -[SQLite.swift]: https://github.com/stephencelis/SQLite.swift - - ## Features - A pure-Swift interface @@ -272,7 +265,28 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftData](https://github.com/ryanfowler/SwiftData) - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) -[swift-4]: https://github.com/stephencelis/SQLite.swift/tree/swift-4 +[Swift]: https://swift.org/ +[SQLite3]: http://www.sqlite.org +[SQLite.swift]: https://github.com/stephencelis/SQLite.swift + +[TravisBadge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat +[TravisLink]: https://travis-ci.org/stephencelis/SQLite.swift + +[CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png +[CocoaPodsVersionLink]: http://cocoadocs.org/docsets/SQLite.swift + +[PlatformBadge]: https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png +[PlatformLink]: http://cocoadocs.org/docsets/SQLite.swift + +[CartagheBadge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat +[CarthageLink]: https://github.com/Carthage/Carthage + +[GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg +[GitterLink]: https://gitter.im/stephencelis/SQLite.swift + +[Swift4Badge]: https://img.shields.io/badge/swift-4-orange.svg?style=flat +[Swift4Link]: https://developer.apple.com/swift/ + [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift [FMDB]: https://github.com/ccgus/fmdb [FMDBMigrationManager]: https://github.com/layerhq/FMDBMigrationManager From 5df5620a218d560fe727648b579cd988da13537b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 10:17:12 +0200 Subject: [PATCH 59/86] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c5c33f..3f0e9f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) * Add Swift4 Coding support ([#733][]) * Preliminary Linux support ([#315][], [#681][]) @@ -61,6 +62,7 @@ [#560]: https://github.com/stephencelis/SQLite.swift/pull/560 [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 +[#591]: https://github.com/stephencelis/SQLite.swift/pull/591 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 [#647]: https://github.com/stephencelis/SQLite.swift/pull/647 [#649]: https://github.com/stephencelis/SQLite.swift/pull/649 From bc60dcdec186040c77df5f164ec1909ba803955a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 10:24:14 +0200 Subject: [PATCH 60/86] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f0e9f0d..7f3db59e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Fix transactions not being rolled back when committing fails ([#426][]) * Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) * Add Swift4 Coding support ([#733][]) @@ -52,6 +53,7 @@ [#142]: https://github.com/stephencelis/SQLit1e.swift/issues/142 [#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 +[#426]: https://github.com/stephencelis/SQLit1e.swift/pull/426 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 [#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 From 0fa5e5e00a3e0456ba292b502013ed3708bab756 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 11:09:38 +0200 Subject: [PATCH 61/86] Optional types should not enforce NOT NULL Closes #697 --- CHANGELOG.md | 2 ++ Sources/SQLite/Typed/Schema.swift | 12 ++++---- Tests/SQLiteTests/SchemaTests.swift | 48 ++++++++++++++--------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3db59e..f9a920d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Collate .nocase strictly enforces NOT NULL even when using Optional ([#697][]) * Fix transactions not being rolled back when committing fails ([#426][]) * Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) @@ -72,6 +73,7 @@ [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 [#681]: https://github.com/stephencelis/SQLite.swift/issues/681 +[#697]: https://github.com/stephencelis/SQLite.swift/issues/697 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 690a26a5..46a1f87a 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -300,27 +300,27 @@ public final class TableBuilder { } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 34226b3c..b9a08881 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -435,99 +435,99 @@ class SchemaTests : XCTestCase { ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT COLLATE RTRIM)", table.create { t in t.column(stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) } From 44452ad553e72e76d567ccefa1208317f60c8626 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 12:09:04 +0200 Subject: [PATCH 62/86] Document error handling Closes #700 --- Documentation/Index.md | 14 ++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index f3569374..aa219633 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -650,6 +650,20 @@ follow similar patterns. > // INSERT INTO "timestamps" DEFAULT VALUES > ``` +### Handling specific SQLite errors + +You can pattern match on the error to selectively catch SQLite errors: + +```swift +do { + try db.run(users.insert(email <- "alice@mac.com")) + try db.run(users.insert(email <- "alice@mac.com")) +} catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { + print("constraint failed") +} catch let error { + print("insertion failed: \(error)") +} +``` ### Setters diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index bb99d016..cd609708 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -518,4 +518,16 @@ class QueryIntegrationTests : SQLiteTestCase { } } } + + func test_catchConstraintError() { + try! db.run(users.insert(email <- "alice@example.com")) + do { + try db.run(users.insert(email <- "alice@example.com")) + XCTFail("expected error") + } catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { + // expected + } catch let error { + XCTFail("unexpected error: \(error)") + } + } } From 1602cc044ece758cd1a80b0c403aea9b21452bb7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 12:32:01 +0200 Subject: [PATCH 63/86] Link milestone --- Documentation/Planning.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index d814d26b..8a3d5a11 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -1,7 +1,13 @@ # SQLite.swift Planning -This document captures both near term steps (aka Roadmap) and feature requests. -The goal is to add some visibility and guidance for future additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. +This document captures both near term steps (aka Roadmap) and feature +requests. The goal is to add some visibility and guidance for future +additions and Pull Requests, as well as to keep the Issues list clear of +enhancement requests so that bugs are more visible. + +> ⚠ This document is currently not actively maintained. See +> the [0.12.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.12.0) +> on Github for additional information about planned features for the next release. ## Roadmap @@ -9,16 +15,24 @@ _Lists agreed upon next steps in approximate priority order._ ## Feature Requests -_A gathering point for ideas for new features. In general, the corresponding issue will be closed once it is added here, with the assumption that it will be referred to when it comes time to add the corresponding feature._ +_A gathering point for ideas for new features. In general, the corresponding +issue will be closed once it is added here, with the assumption that it will +be referred to when it comes time to add the corresponding feature._ ### Features - * encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per [#30](https://github.com/stephencelis/SQLite.swift/issues/30) - * provide separate threads for update vs read, so updates don't block reads, per [#236](https://github.com/stephencelis/SQLite.swift/issues/236) - * expose triggers, per [#164](https://github.com/stephencelis/SQLite.swift/issues/164) + * encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per + [#30](https://github.com/stephencelis/SQLite.swift/issues/30) + * provide separate threads for update vs read, so updates don't block reads, + per [#236](https://github.com/stephencelis/SQLite.swift/issues/236) + * expose triggers, per + [#164](https://github.com/stephencelis/SQLite.swift/issues/164) ## Suspended Feature Requests -_Features that are not actively being considered, perhaps because of no clean type-safe way to implement them with the current Swift, or bugs, or just general uncertainty._ +_Features that are not actively being considered, perhaps because of no clean +type-safe way to implement them with the current Swift, or bugs, or just +general uncertainty._ - * provide a mechanism for INSERT INTO multiple values, per [#168](https://github.com/stephencelis/SQLite.swift/issues/168) + * provide a mechanism for INSERT INTO multiple values, per + [#168](https://github.com/stephencelis/SQLite.swift/issues/168) From 1bdd7f3855a5e5f5afc41c5fa07e0126cf8f10dd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 12:54:08 +0200 Subject: [PATCH 64/86] Docs: how to get error message Closes #366 --- Documentation/Index.md | 13 +++++++++---- Sources/SQLite/Core/Connection.swift | 7 +++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index aa219633..8ac564a8 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -650,21 +650,26 @@ follow similar patterns. > // INSERT INTO "timestamps" DEFAULT VALUES > ``` -### Handling specific SQLite errors +### Handling SQLite errors -You can pattern match on the error to selectively catch SQLite errors: +You can pattern match on the error to selectively catch SQLite errors. For example, to +specifically handle constraint errors ([SQLITE_CONSTRAINT](https://sqlite.org/rescode.html#constraint)): ```swift do { try db.run(users.insert(email <- "alice@mac.com")) try db.run(users.insert(email <- "alice@mac.com")) -} catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { - print("constraint failed") +} catch let Result.error(message, code, statement) where code == SQLITE_CONSTRAINT { + print("constraint failed: \(message), in \(statement)") } catch let error { print("insertion failed: \(error)") } ``` +The `Result.error` type contains the English-language text that describes the error (`message`), +the error `code` (see [SQLite result code list](https://sqlite.org/rescode.html#primary_result_code_list) +for details) and a optional reference to the `statement` which produced the error. + ### Setters SQLite.swift typically uses the `<-` operator to set values during [inserts diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index d2f5f523..f7513115 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -681,6 +681,13 @@ public enum Result : Error { fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + /// Represents a SQLite specific [error code](https://sqlite.org/rescode.html) + /// + /// - message: English-language text that describes the error + /// + /// - code: SQLite [error code](https://sqlite.org/rescode.html#primary_result_code_list) + /// + /// - statement: the statement which produced the error case error(message: String, code: Int32, statement: Statement?) init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { From e9d93b5913e3e1326daee7a6bb9acabb874956de Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 14:02:48 +0200 Subject: [PATCH 65/86] Missing import --- Tests/SQLiteTests/QueryTests.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index cd609708..2a9e4ecb 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -1,4 +1,13 @@ import XCTest +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif @testable import SQLite class QueryTests : XCTestCase { From 56d3a65e99cba3af5ee07795822dff1dfb3436d6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 14:53:07 +0200 Subject: [PATCH 66/86] Clear out trace --- Tests/SQLiteTests/ConnectionTests.swift | 2 +- Tests/SQLiteTests/TestHelpers.swift | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ac738961..ad902ed3 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -157,7 +157,7 @@ class ConnectionTests : SQLiteTestCase { func test_transaction_rollsBackTransactionsIfCommitsFail() { // This test case needs to emulate an environment where the individual statements succeed, but committing the - // transactuin fails. Using deferred foreign keys is one option to achieve this. + // transaction fails. Using deferred foreign keys is one option to achieve this. try! db.execute("PRAGMA foreign_keys = ON;") try! db.execute("PRAGMA defer_foreign_keys = ON;") let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index dc32bb83..bec6f751 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -2,15 +2,13 @@ import XCTest @testable import SQLite class SQLiteTestCase : XCTestCase { - - var trace = [String: Int]() - - let db = try! Connection() - + private var trace:[String: Int]! let users = Table("users") + let db = try! Connection() override func setUp() { super.setUp() + trace = [String:Int]() db.trace { SQL in print(SQL) From 7cc98a8d006caa35475f744944ad2b30b97f8e4e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 16:10:36 +0200 Subject: [PATCH 67/86] Recreate connection --- Tests/SQLiteTests/ConnectionTests.swift | 4 ++-- Tests/SQLiteTests/TestHelpers.swift | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ad902ed3..b0be9a72 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -200,7 +200,7 @@ class ConnectionTests : SQLiteTestCase { } func test_savepoint_beginsAndCommitsSavepoints() { - let db = self.db + let db:Connection = self.db try! db.savepoint("1") { try db.savepoint("2") { @@ -218,7 +218,7 @@ class ConnectionTests : SQLiteTestCase { } func test_savepoint_beginsAndRollsSavepointsBack() { - let db = self.db + let db:Connection = self.db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index bec6f751..7f790ef7 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -3,11 +3,12 @@ import XCTest class SQLiteTestCase : XCTestCase { private var trace:[String: Int]! + var db:Connection! let users = Table("users") - let db = try! Connection() override func setUp() { super.setUp() + db = try! Connection() trace = [String:Int]() db.trace { SQL in From 616e5bde44191e40f4722eada2e924216861c5cd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 17:33:36 +0200 Subject: [PATCH 68/86] https://sqlite.org/pragma.html#pragma_defer_foreign_keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “The defer_foreign_keys pragma is automatically switched off at each COMMIT or ROLLBACK. Hence, the defer_foreign_keys pragma must be separately enabled for each transaction. This pragma is only meaningful if foreign key constraints are enabled, of course.” --- Tests/SQLiteTests/ConnectionTests.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index b0be9a72..f1af1fd0 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -159,14 +159,18 @@ class ConnectionTests : SQLiteTestCase { // This test case needs to emulate an environment where the individual statements succeed, but committing the // transaction fails. Using deferred foreign keys is one option to achieve this. try! db.execute("PRAGMA foreign_keys = ON;") - try! db.execute("PRAGMA defer_foreign_keys = ON;") let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) do { try db.transaction { + try db.execute("PRAGMA defer_foreign_keys = ON;") try stmt.run() } - } catch { + XCTFail("expected error") + } catch let Result.error(_, code, _) { + XCTAssertEqual(SQLITE_CONSTRAINT, code) + } catch let error { + XCTFail("unexpected error: \(error)") } AssertSQL("BEGIN DEFERRED TRANSACTION") From 1d6167fe877c327a29bbf2c4b7343f99d36bedae Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 22:31:33 +0200 Subject: [PATCH 69/86] Swift 4 --- Tests/SQLiteTests/TestHelpers.swift | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 7f790ef7..271f4166 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -13,21 +13,22 @@ class SQLiteTestCase : XCTestCase { db.trace { SQL in print(SQL) - self.trace[SQL] = (self.trace[SQL] ?? 0) + 1 + self.trace[SQL, default: 0] += 1 } } func CreateUsersTable() { - try! db.execute( - "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "salary REAL, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" + try! db.execute(""" + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + age INTEGER, + salary REAL, + admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), + manager_id INTEGER, + FOREIGN KEY(manager_id) REFERENCES users(id) + ) + """ ) } From 2e59493201d19cd6be0f53a080e26729b686d5c4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 22:32:41 +0200 Subject: [PATCH 70/86] Skip tests for older versions of SQLite --- Tests/SQLiteTests/ConnectionTests.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index f1af1fd0..d74a50c1 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -156,14 +156,21 @@ class ConnectionTests : SQLiteTestCase { } func test_transaction_rollsBackTransactionsIfCommitsFail() { + let sqliteVersion = String(describing: try! db.scalar("SELECT sqlite_version()")!) + .split(separator: ".").flatMap { Int($0) } + // PRAGMA defer_foreign_keys only supported in SQLite >= 3.8.0 + guard sqliteVersion[0] == 3 && sqliteVersion[1] >= 8 else { + NSLog("skipping test for SQLite version \(sqliteVersion)") + return + } // This test case needs to emulate an environment where the individual statements succeed, but committing the // transaction fails. Using deferred foreign keys is one option to achieve this. try! db.execute("PRAGMA foreign_keys = ON;") + try! db.execute("PRAGMA defer_foreign_keys = ON;") let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) do { try db.transaction { - try db.execute("PRAGMA defer_foreign_keys = ON;") try stmt.run() } XCTFail("expected error") From 42727c2a3d8024bc081db0f0d16d3d46b64afec2 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:21:48 +0200 Subject: [PATCH 71/86] Add issue template --- .github/ISSUE_TEMPLATE.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..61152bad --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ +> Issues are used to track bugs and feature requests. +> Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). + +## Build Information + +- Include the SQLite.swift version, commit or branch experiencing the issue. +- Mention Xcode and OS X versions affected. +- How do do you integrate SQLite.swift in your project? + - manual + - CocoaPods + - Carthage + - Swift Package manager + +## General guidelines + +- Be as descriptive as possible. +- Provide as much information needed to _reliably reproduce_ the issue. +- Attach screenshots if possible. +- Better yet: attach GIFs or link to video. +- Even better: link to a sample project exhibiting the issue. From fd9b42e5d8657680bd2073c0c1cde139cb8120a9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:21:54 +0200 Subject: [PATCH 72/86] Bump ruby version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7ff85f9a..4f17b26f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -rvm: 2.2 +rvm: 2.3 osx_image: xcode9 env: global: From 2e8611fe06f8125668e346dad8a51218d901083d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:21:58 +0200 Subject: [PATCH 73/86] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a920d5..41006b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -0.11.4 (xx-09-2017), [diff][diff-0.11.4] +0.11.4 (30-09-2017), [diff][diff-0.11.4] ======================================== * Collate .nocase strictly enforces NOT NULL even when using Optional ([#697][]) From ddb8049a58822768b3c1208ea58c8ba18a99952a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:28:28 +0200 Subject: [PATCH 74/86] Warn about `update()` (#733) --- Documentation/Index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 8ac564a8..a20087ea 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1521,10 +1521,13 @@ There are two other parameters also available to this method: Queries have a method to allow updating an Encodable type. ```swift -try db.run(users.update(user)) +try db.run(users.filter(id == userId).update(user)) ``` +> ⚠ Unless filtered, using the update method on an instance of a Codable +> type updates all table rows. + There are two other parameters also available to this method: - `userInfo` is a dictionary that is passed to the encoder and made available From ae406afd75e72a0122e76b44c9abb5f6a2432cb1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:28:54 +0200 Subject: [PATCH 75/86] WS --- .github/ISSUE_TEMPLATE.md | 4 ++-- Tests/SQLiteTests/TestHelpers.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 61152bad..9304a672 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,11 +1,11 @@ -> Issues are used to track bugs and feature requests. +> Issues are used to track bugs and feature requests. > Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). ## Build Information - Include the SQLite.swift version, commit or branch experiencing the issue. - Mention Xcode and OS X versions affected. -- How do do you integrate SQLite.swift in your project? +- How do do you integrate SQLite.swift in your project? - manual - CocoaPods - Carthage diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 271f4166..8d60362c 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -20,9 +20,9 @@ class SQLiteTestCase : XCTestCase { func CreateUsersTable() { try! db.execute(""" CREATE TABLE users ( - id INTEGER PRIMARY KEY, - email TEXT NOT NULL UNIQUE, - age INTEGER, + id INTEGER PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + age INTEGER, salary REAL, admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), manager_id INTEGER, From e12bd38d6f1d6de67efa7ce7894c663e15a232c9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 12:27:57 +0200 Subject: [PATCH 76/86] Modernize --- Tests/SQLiteTests/ConnectionTests.swift | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 05d2676b..03f00719 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -382,31 +382,28 @@ class ConnectionTests : SQLiteTestCase { _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) AssertThrows(try stmt.run()) } - + func test_concurrent_access_single_connection() { - let conn = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Connection Tests.sqlite") + let conn = try! Connection("\(NSTemporaryDirectory())/\(UUID().uuidString)") try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") try! conn.run("INSERT INTO test(value) VALUES(?)", 0) - - let q = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT); - - var reads = [0, 0, 0, 0, 0] + let queue = DispatchQueue(label: "Readers", attributes: [.concurrent]) + let nReaders = 5 + var reads = Array(repeating: 0, count: nReaders) var finished = false - for index in 0..<5 { - dispatch_async(q) { + for index in 0.. 500) } } - } - + } } From 73f4472097a1acdbba42d5ab8805d50a181589e3 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 16:49:22 +0200 Subject: [PATCH 77/86] Changelog --- CHANGELOG.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41006b82..514de7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,25 @@ 0.11.4 (30-09-2017), [diff][diff-0.11.4] ======================================== -* Collate .nocase strictly enforces NOT NULL even when using Optional ([#697][]) +* Collate `.nocase` strictly enforces `NOT NULL` even when using Optional ([#697][]) * Fix transactions not being rolled back when committing fails ([#426][]) * Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) * Add Swift4 Coding support ([#733][]) * Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) -* Make Row.get throw instead of crash ([#649][]) +* Make `Row.get` throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) -* Set deployment target to 8.0 (#624, #671, #717) +* Revert deployment target to 8.0 ([#625][], [#671][], [#717][]) * Added support for the union query clause ([#723][]) -* Add support for ORDER and LIMIT on UPDATE and DELETE ([#657][], [#722][]) +* Add support for `ORDER` and `LIMIT` on `UPDATE` and `DELETE` ([#657][], [#722][]) * Swift 4 support ([#668][]) 0.11.3 (30-03-2017), [diff][diff-0.11.3] ======================================== * Fix compilation problems when using Carthage ([#615][]) -* Add "WITHOUT ROWID" table option ([#541][]) +* Add `WITHOUT ROWID` table option ([#541][]) * Argument count fixed for binary custom functions ([#481][]) * Documentation updates * Tested with Xcode 8.3 / iOS 10.3 @@ -37,7 +37,7 @@ * Integrate SQLCipher via CocoaPods ([#546][], [#553][]) * Made lastInsertRowid consistent with other SQLite wrappers ([#532][]) -* Fix for ~= operator used with Double ranges +* Fix for `~=` operator used with Double ranges * Various documentation updates 0.11.0 (19-10-2016) @@ -67,13 +67,16 @@ [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#591]: https://github.com/stephencelis/SQLite.swift/pull/591 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#625]: https://github.com/stephencelis/SQLite.swift/issues/625 [#647]: https://github.com/stephencelis/SQLite.swift/pull/647 [#649]: https://github.com/stephencelis/SQLite.swift/pull/649 [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 +[#671]: https://github.com/stephencelis/SQLite.swift/issues/671 [#681]: https://github.com/stephencelis/SQLite.swift/issues/681 [#697]: https://github.com/stephencelis/SQLite.swift/issues/697 +[#717]: https://github.com/stephencelis/SQLite.swift/issues/717 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 From 1c929b9e8071a6e144cc928c8077f765a6c7424c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 16:51:41 +0200 Subject: [PATCH 78/86] Link --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a9ceecb..3900c0a3 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,13 @@ syntax _and_ intent. - [Full-text search][] support - [Well-documented][See Documentation] - Extensively tested - - SQLCipher support via CocoaPods + - [SQLCipher][] support via CocoaPods - Active support at [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) +[SQLCipher]: https://www.zetetic.net/sqlcipher/ [Full-text search]: Documentation/Index.md#full-text-search [See Documentation]: Documentation/Index.md#sqliteswift-documentation From 172b3e55284de4f05c13b8d0e45702b3a2c270ca Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 16:53:53 +0200 Subject: [PATCH 79/86] TOC --- Documentation/Index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index a20087ea..3ccc6e21 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -20,6 +20,7 @@ - [Column Constraints](#column-constraints) - [Table Constraints](#table-constraints) - [Inserting Rows](#inserting-rows) + - [Handling SQLite errors](#handling-sqlite-errors) - [Setters](#setters) - [Selecting Rows](#selecting-rows) - [Iterating and Accessing Values](#iterating-and-accessing-values) From d68e0a761e6fafd40030887c24f54ee08f3f03a8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:05:51 +0200 Subject: [PATCH 80/86] Document throwing `Row.get()` --- Documentation/Index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3ccc6e21..be3d32a9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -757,6 +757,18 @@ for user in try db.prepare(users) { promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. +⚠ Column subscripts on `Row` will force try and abort execution in error cases +If you want to handle this yourself, use `Row.get(_ column: Expression)`: + +```swift +for user in try db.prepare(users) { + do { + print("name: \(try user.get(name))") + } catch { + // handle + } +} +``` ### Plucking Rows From e0eb663c114c579226422ccb41324c32d2841381 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:16:56 +0200 Subject: [PATCH 81/86] Doc --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index be3d32a9..88ed24a4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1406,7 +1406,7 @@ The `Datatype` must be one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL ](#building-type-safe-sql) for a list of types). -> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to +> ⚠ _Note:_ `Binding` is a protocol that SQLite.swift uses internally to > directly map SQLite types to Swift types. **Do _not_** conform custom types > to the `Binding` protocol. From d80a5ef128338a1bfea2ed809c4ed5623a01bb44 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:17:27 +0200 Subject: [PATCH 82/86] Support partial ranges for completeness --- Sources/SQLite/Typed/Operators.swift | 24 ++++++++++++++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 15 +++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 5f95993f..d97e52b9 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -490,6 +490,30 @@ public func ~=(lhs: Range, rhs: Expression) -> Expression= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) +} + // MARK: - public func &&(lhs: Expression, rhs: Expression) -> Expression { diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 08e679c1..4dcb6717 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -260,6 +260,21 @@ class OperatorsTests : XCTestCase { AssertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) } + func test_patternMatchingOperator_withComparablePartialRangeThrough_buildsBooleanExpression() { + AssertSQL("\"double\" <= 4.5", ...4.5 ~= double) + AssertSQL("\"doubleOptional\" <= 4.5", ...4.5 ~= doubleOptional) + } + + func test_patternMatchingOperator_withComparablePartialRangeUpTo_buildsBooleanExpression() { + AssertSQL("\"double\" < 4.5", ..<4.5 ~= double) + AssertSQL("\"doubleOptional\" < 4.5", ..<4.5 ~= doubleOptional) + } + + func test_patternMatchingOperator_withComparablePartialRangeFrom_buildsBooleanExpression() { + AssertSQL("\"double\" >= 4.5", 4.5... ~= double) + AssertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) + } + func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() { AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) From 07df3355f45c443704fc973899fabc6a37f9e506 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:21:33 +0200 Subject: [PATCH 83/86] No longer applies --- Documentation/Index.md | 47 ------------------------------------------ 1 file changed, 47 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 88ed24a4..476bc816 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -50,7 +50,6 @@ - [Custom Types](#custom-types) - [Date-Time Values](#date-time-values) - [Binary Data](#binary-data) - - [Custom Type Caveats](#custom-type-caveats) - [Codable Types](#codable-types) - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) @@ -1410,9 +1409,6 @@ through before serialization and deserialization (see [Building Type-Safe SQL > directly map SQLite types to Swift types. **Do _not_** conform custom types > to the `Binding` protocol. -Once extended, the type can be used [_almost_](#custom-type-caveats) wherever -typed expressions can be. - ### Date-Time Values @@ -1458,49 +1454,6 @@ extension UIImage: Value { [Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html -### Custom Type Caveats - -Swift does _not_ currently support generic subscripting, which means we -cannot, by default, subscript Expressions with custom types to: - - 1. **Namespace expressions**. Use the `namespace` function, instead: - - ```swift - let avatar = Expression("avatar") - users[avatar] // fails to compile - users.namespace(avatar) // "users"."avatar" - ``` - - 2. **Access column data**. Use the `get` function, instead: - - ```swift - let user = users.first! - user[avatar] // fails to compile - user.get(avatar) // UIImage? - ``` - -We can, of course, write extensions, but they’re rather wordy. - -```swift -extension Query { - subscript(column: Expression) -> Expression { - return namespace(column) - } - subscript(column: Expression) -> Expression { - return namespace(column) - } -} - -extension Row { - subscript(column: Expression) -> UIImage { - return get(column) - } - subscript(column: Expression) -> UIImage? { - return get(column) - } -} -``` - ## Codable Types [Codable types][Encoding and Decoding Custom Types] were introduced as a part From fa041f8f0b5949ca110e049949d0c470f92b811a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:25:28 +0200 Subject: [PATCH 84/86] Docs --- Documentation/Index.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 476bc816..c97fe8ca 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -756,7 +756,7 @@ for user in try db.prepare(users) { promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. -⚠ Column subscripts on `Row` will force try and abort execution in error cases +⚠ Column subscripts on `Row` will force try and abort execution in error cases. If you want to handle this yourself, use `Row.get(_ column: Expression)`: ```swift @@ -1464,10 +1464,10 @@ the insertion, updating, and retrieval of basic Codable types. ### Inserting Codable Types -Queries have a method to allow inserting an Encodable type. +Queries have a method to allow inserting an [Encodable][] type. ```swift -struct User: Codable { +struct User: Encodable { let name: String } try db.run(users.insert(User(name: "test"))) @@ -1482,6 +1482,8 @@ There are two other parameters also available to this method: - `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. +[Encodable]: https://developer.apple.com/documentation/swift/encodable + ### Updating Codable Types Queries have a method to allow updating an Encodable type. @@ -1504,7 +1506,7 @@ There are two other parameters also available to this method: ### Retrieving Codable Types -Rows have a method to decode a Decodable type. +Rows have a method to decode a [Decodable][] type. ```swift let loadedUsers: [User] = try db.prepare(users).map { row in @@ -1548,6 +1550,8 @@ Both of the above methods also have the following optional parameter: - `userInfo` is a dictionary that is passed to the decoder and made available to decodable types to allow customizing their behavior. +[Decodable]: https://developer.apple.com/documentation/swift/decodable + ### Restrictions There are a few restrictions on using Codable types: From 4ea2f62c453a7a82a1d9ddbd85d28a321d767ec4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:32:21 +0200 Subject: [PATCH 85/86] Typos --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 514de7be..578b59c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,12 +52,12 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 -[#142]: https://github.com/stephencelis/SQLit1e.swift/issues/142 -[#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 -[#426]: https://github.com/stephencelis/SQLit1e.swift/pull/426 -[#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 -[#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 -[#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 +[#142]: https://github.com/stephencelis/SQLite.swift/issues/142 +[#315]: https://github.com/stephencelis/SQLite.swift/issues/315 +[#426]: https://github.com/stephencelis/SQLite.swift/pull/426 +[#481]: https://github.com/stephencelis/SQLite.swift/pull/481 +[#532]: https://github.com/stephencelis/SQLite.swift/issues/532 +[#541]: https://github.com/stephencelis/SQLite.swift/issues/541 [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 [#548]: https://github.com/stephencelis/SQLite.swift/pull/548 [#553]: https://github.com/stephencelis/SQLite.swift/pull/553 From 9d8a8e5a7fff77486726f47f0dfc2c6994a57a31 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:43:31 +0200 Subject: [PATCH 86/86] Typo --- Tests/SQLiteTests/OperatorsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 4dcb6717..948eb0a4 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -275,7 +275,7 @@ class OperatorsTests : XCTestCase { AssertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) } - func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() { + func test_patternMatchingOperator_withComparableClosedRangeString_buildsBetweenBooleanExpression() { AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) }