From 52e3ea84895004f81d647ab8582564d1e7ab3c73 Mon Sep 17 00:00:00 2001 From: Amy While <26681721+elihwyma@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:59:33 +0000 Subject: [PATCH 001/216] Add a Value conformance for Foundation NSURL --- Sources/SQLite/Foundation.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 2acbc00e..c1a4c501 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -84,3 +84,20 @@ extension UUID: Value { } } + +extension URL: Value { + + public static var declaredDatatype: String { + String.declaredDatatype + } + + public static func fromDatatypeValue(_ stringValue: String) -> URL { + URL(string: stringValue)! + } + + public var datatypeValue: String { + absoluteString + } + +} + From 3eb1fdceda903ea3b7ba0f49a522258578a168d5 Mon Sep 17 00:00:00 2001 From: Amy While <26681721+elihwyma@users.noreply.github.com> Date: Sat, 12 Feb 2022 13:13:42 +0000 Subject: [PATCH 002/216] Remove Extra Trailing New Line --- Sources/SQLite/Foundation.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index c1a4c501..44a31736 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -100,4 +100,3 @@ extension URL: Value { } } - From da8d7b3100471349ac924e15b1351fcd17b83ade Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Fri, 18 Feb 2022 13:45:40 -0600 Subject: [PATCH 003/216] UUID Fix --- 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 3dc1e6cf..a8608579 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -207,6 +207,8 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- data) } else if let date = value as? Date { encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + }else if let uuid = value as? UUID { + encoder.setters.append(Expression(key.stringValue) <- uuid.uuidString) } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) From b71549364be768397d09a91707867d2175f6261e Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Sat, 19 Feb 2022 10:52:13 -0600 Subject: [PATCH 004/216] Lint Fix and added test --- Sources/SQLite/Typed/Coding.swift | 2 +- Tests/SQLiteTests/FoundationTests.swift | 26 +++++++++++++++++++++++++ Tests/SQLiteTests/TestHelpers.swift | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index a8608579..fa9ec02d 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -207,7 +207,7 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- data) } else if let date = value as? Date { encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - }else if let uuid = value as? UUID { + } else if let uuid = value as? UUID { encoder.setters.append(Expression(key.stringValue) <- uuid.uuidString) } else { let encoded = try JSONEncoder().encode(value) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index cef485fc..23ae1fe2 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -25,4 +25,30 @@ class FoundationTests: XCTestCase { let uuid = UUID.fromDatatypeValue(string) XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } + + func testUUIDInsert() { + struct Test:Codable{ + var uuid:UUID + var string:String + } + let testUUID = UUID() + let testValue = Test(uuid: testUUID, string: "value") + let db = try! Connection(.temporary) + try! db.run(table.create { t in + t.column(uuid) + t.column(string) + } + ) + + let iQuery = try! table.insert(testValue) + try! db.run(iQuery) + + let fQuery = table.filter(uuid == testUUID) + if let result = try! db.pluck(fQuery){ + let testValueReturned = Test(uuid: result[uuid], string: result[string]) + XCTAssertEqual(testUUID, testValueReturned.uuid) + }else{ + XCTFail() + } + } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 4d8f78fa..a6efa2e7 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -95,6 +95,9 @@ let int64Optional = Expression("int64Optional") let string = Expression("string") let stringOptional = Expression("stringOptional") +let uuid = Expression("uuid") +let uuidOptional = Expression("uuidOptional") + func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) From 83f0f5de5bd978e5c41e45331154572a41204fe9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Feb 2022 23:46:24 +0100 Subject: [PATCH 005/216] Bump iOS version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index baa8c2d8..d1659d9d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: "15.0" + IOS_VERSION: "15.2" jobs: build: runs-on: macos-11 From c4483141f19a4ba796fd0475e279d4db1353000e Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Mon, 21 Feb 2022 19:33:15 -0600 Subject: [PATCH 006/216] Fixed linting issues. --- Tests/SQLiteTests/FoundationTests.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 23ae1fe2..02ab7f13 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -25,11 +25,11 @@ class FoundationTests: XCTestCase { let uuid = UUID.fromDatatypeValue(string) XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } - + func testUUIDInsert() { - struct Test:Codable{ - var uuid:UUID - var string:String + struct Test: Codable { + var uuid: UUID + var string: String } let testUUID = UUID() let testValue = Test(uuid: testUUID, string: "value") @@ -39,16 +39,16 @@ class FoundationTests: XCTestCase { t.column(string) } ) - + let iQuery = try! table.insert(testValue) try! db.run(iQuery) - + let fQuery = table.filter(uuid == testUUID) - if let result = try! db.pluck(fQuery){ + if let result = try! db.pluck(fQuery) { let testValueReturned = Test(uuid: result[uuid], string: result[string]) XCTAssertEqual(testUUID, testValueReturned.uuid) - }else{ - XCTFail() + } else { + XCTFail("Search for uuid failed") } } } From 09e8424805b9ea02b96c6973fe2720c3caa4490f Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Tue, 22 Feb 2022 06:40:20 -0600 Subject: [PATCH 007/216] used UUID.datatypeValue and moved tests --- Sources/SQLite/Typed/Coding.swift | 2 +- Tests/SQLiteTests/FoundationTests.swift | 25 ------------------------ Tests/SQLiteTests/QueryTests.swift | 26 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index fa9ec02d..cea2565a 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -208,7 +208,7 @@ private class SQLiteEncoder: Encoder { } else if let date = value as? Date { encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) } else if let uuid = value as? UUID { - encoder.setters.append(Expression(key.stringValue) <- uuid.uuidString) + encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 02ab7f13..075e755b 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -26,29 +26,4 @@ class FoundationTests: XCTestCase { XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } - func testUUIDInsert() { - struct Test: Codable { - var uuid: UUID - var string: String - } - let testUUID = UUID() - let testValue = Test(uuid: testUUID, string: "value") - let db = try! Connection(.temporary) - try! db.run(table.create { t in - t.column(uuid) - t.column(string) - } - ) - - let iQuery = try! table.insert(testValue) - try! db.run(iQuery) - - let fQuery = table.filter(uuid == testUUID) - if let result = try! db.pluck(fQuery) { - let testValueReturned = Test(uuid: result[uuid], string: result[string]) - XCTAssertEqual(testUUID, testValueReturned.uuid) - } else { - XCTFail("Search for uuid failed") - } - } } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index f4942b88..efcbab94 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -310,6 +310,32 @@ class QueryTests: XCTestCase { } #endif + func test_insert_and_search_for_UUID() { + struct Test: Codable { + var uuid: UUID + var string: String + } + let testUUID = UUID() + let testValue = Test(uuid: testUUID, string: "value") + let db = try! Connection(.temporary) + try! db.run(table.create { t in + t.column(uuid) + t.column(string) + } + ) + + let iQuery = try! table.insert(testValue) + try! db.run(iQuery) + + let fQuery = table.filter(uuid == testUUID) + if let result = try! db.pluck(fQuery) { + let testValueReturned = Test(uuid: result[uuid], string: result[string]) + XCTAssertEqual(testUUID, testValueReturned.uuid) + } else { + XCTFail("Search for uuid failed") + } + } + func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { assertSQL( """ From f1ab605ad2056944eee80cb7cf55f5b94f17e74e Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 24 Feb 2022 09:49:20 -0700 Subject: [PATCH 008/216] improve performance of prefixed column resolution - Previously this allocated arrays every time it was accessed. It will now only allocate in one of the error cases. - It also keeps track of the columnName index for both key and value so it doesn't need to look it up twice. --- Sources/SQLite/Typed/Query.swift | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 2d88db63..ecb501af 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1169,16 +1169,25 @@ public struct Row { } guard let idx = columnNames[column.template] else { - let similar = Array(columnNames.keys).filter { $0.hasSuffix(".\(column.template)") } - - switch similar.count { - case 0: + func match(_ s: String) -> Bool { + return s.hasSuffix(".\(column.template)") + } + + guard let firstIndex = columnNames.firstIndex(where: { match($0.key) }) else { throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) - case 1: - return valueAtIndex(columnNames[similar[0]]!) - default: - throw QueryError.ambiguousColumn(name: column.template, similar: similar) } + + let secondIndex = columnNames + .suffix(from: columnNames.index(after: firstIndex)) + .firstIndex(where: { match($0.key) }) + + guard secondIndex == nil else { + throw QueryError.ambiguousColumn( + name: column.template, + similar: columnNames.keys.filter(match).sorted() + ) + } + return valueAtIndex(columnNames[firstIndex].value) } return valueAtIndex(idx) From 55bf2c10e10b84855ac3a26c10dc601271c6d171 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 24 Feb 2022 09:52:56 -0700 Subject: [PATCH 009/216] rename match -> similar to follow convention --- Sources/SQLite/Typed/Query.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index ecb501af..6dd07ac4 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1169,22 +1169,22 @@ public struct Row { } guard let idx = columnNames[column.template] else { - func match(_ s: String) -> Bool { + func similar(_ s: String) -> Bool { return s.hasSuffix(".\(column.template)") } - guard let firstIndex = columnNames.firstIndex(where: { match($0.key) }) else { + guard let firstIndex = columnNames.firstIndex(where: { similar($0.key) }) else { throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) } let secondIndex = columnNames .suffix(from: columnNames.index(after: firstIndex)) - .firstIndex(where: { match($0.key) }) + .firstIndex(where: { similar($0.key) }) guard secondIndex == nil else { throw QueryError.ambiguousColumn( name: column.template, - similar: columnNames.keys.filter(match).sorted() + similar: columnNames.keys.filter(similar).sorted() ) } return valueAtIndex(columnNames[firstIndex].value) From eecb3cb4615534020bf96e32ad2b3a95c91504d6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 24 Feb 2022 20:14:01 +0100 Subject: [PATCH 010/216] Adding PR template --- .github/PULL_REQUEST_TEMPLATE/template.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/template.md diff --git a/.github/PULL_REQUEST_TEMPLATE/template.md b/.github/PULL_REQUEST_TEMPLATE/template.md new file mode 100644 index 00000000..b74ffd8d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/template.md @@ -0,0 +1,9 @@ +Thanks for taking the time to submit a pull request. + +Before submitting, please do the following: + +- Run `make lint` to check if there are any format errors (install [swiftlint](https://github.com/realm/SwiftLint#installation) first) +- Run `swift test` to see if the tests pass. +- Write new tests for new functionality. +- Update documentation comments where applicable. + From 3614f4e317c5969c5fe102cee1f3b3f4e1294185 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 24 Feb 2022 20:24:24 +0100 Subject: [PATCH 011/216] Rename --- .../template.md => pull_request_template.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE/template.md => pull_request_template.md} (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/template.md rename to .github/pull_request_template.md From c897ce99454216dc33caae5d57a2cd021b672e03 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 24 Feb 2022 20:26:45 +0100 Subject: [PATCH 012/216] rename --- .github/{ISSUE_TEMPLATE/template.md => issue_template.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/{ISSUE_TEMPLATE/template.md => issue_template.md} (87%) diff --git a/.github/ISSUE_TEMPLATE/template.md b/.github/issue_template.md similarity index 87% rename from .github/ISSUE_TEMPLATE/template.md rename to .github/issue_template.md index 9304a672..f9622b9b 100644 --- a/.github/ISSUE_TEMPLATE/template.md +++ b/.github/issue_template.md @@ -1,5 +1,5 @@ > Issues are used to track bugs and feature requests. -> Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). +> Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). ## Build Information From c659dc9256f1b80505d0bd5bbca026005cd43af2 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 24 Feb 2022 13:46:25 -0700 Subject: [PATCH 013/216] Fix lint issues --- Sources/SQLite/Typed/Query.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 6dd07ac4..93dc2a73 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1169,18 +1169,18 @@ public struct Row { } guard let idx = columnNames[column.template] else { - func similar(_ s: String) -> Bool { - return s.hasSuffix(".\(column.template)") + func similar(_ name: String) -> Bool { + return name.hasSuffix(".\(column.template)") } - + guard let firstIndex = columnNames.firstIndex(where: { similar($0.key) }) else { throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) } - + let secondIndex = columnNames .suffix(from: columnNames.index(after: firstIndex)) .firstIndex(where: { similar($0.key) }) - + guard secondIndex == nil else { throw QueryError.ambiguousColumn( name: column.template, From ef2257a885a875868177b2a004a839eb4f21a461 Mon Sep 17 00:00:00 2001 From: JIm Boyd Date: Thu, 17 Mar 2022 11:09:33 -0600 Subject: [PATCH 014/216] Add prepareRowIterator method to an extension of Statement. --- Sources/SQLite/Core/Statement.swift | 15 +++++++++++++++ Tests/SQLiteTests/StatementTests.swift | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 1e2489b5..8e9ed350 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -228,6 +228,21 @@ extension Statement: FailableIterator { } } +extension Statement { + public func prepareRowIterator() -> RowIterator { + return RowIterator(statement: self, columnNames: self.columnNameMap) + } + + var columnNameMap: [String: Int] { + var result = [String: Int]() + for (index, name) in self.columnNames.enumerated() { + result[name.quote()] = index + } + + return result + } +} + extension Statement: CustomStringConvertible { public var description: String { diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index b4ad7c28..7729a4a9 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -23,4 +23,15 @@ class StatementTests: SQLiteTestCase { let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) XCTAssertEqual([], blobValue.bytes) } + + func test_prepareRowIterator() { + let names = ["a", "b", "c"] + try! insertUsers(names) + + let emailColumn = Expression("email") + let statement = try! db.prepare("SELECT email FROM users") + let emails = try! statement.prepareRowIterator().map { $0[emailColumn] } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } } From 1a50a03fe0a216c75bb27cab7a4f2965669e5e30 Mon Sep 17 00:00:00 2001 From: JIm Boyd Date: Thu, 17 Mar 2022 11:22:55 -0600 Subject: [PATCH 015/216] Updates to documentation --- Documentation/Index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 52affae4..b58bf995 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1963,6 +1963,14 @@ using the following functions. } } ``` + Statements with results may be iterated over, using a `RowIterator` if + useful. + + ```swift + let emailColumn = Expression("email") + let stmt = try db.prepare("SELECT id, email FROM users") + let emails = try! stmt.prepareRowIterator().map { $0[emailColumn] } + ``` - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, From 4459338f778d49445bdfd182e40e9cd90220f8c7 Mon Sep 17 00:00:00 2001 From: JIm Boyd Date: Thu, 17 Mar 2022 13:43:06 -0600 Subject: [PATCH 016/216] Fix linting issues --- Sources/SQLite/Core/Statement.swift | 2 +- Tests/SQLiteTests/StatementTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 8e9ed350..5ec430cf 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -232,7 +232,7 @@ extension Statement { public func prepareRowIterator() -> RowIterator { return RowIterator(statement: self, columnNames: self.columnNameMap) } - + var columnNameMap: [String: Int] { var result = [String: Int]() for (index, name) in self.columnNames.enumerated() { diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 7729a4a9..aa05de34 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -23,7 +23,7 @@ class StatementTests: SQLiteTestCase { let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) XCTAssertEqual([], blobValue.bytes) } - + func test_prepareRowIterator() { let names = ["a", "b", "c"] try! insertUsers(names) From 5af5c858fff85bf7a880bd01bd06b010c91c7436 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 27 Mar 2022 12:37:45 +0200 Subject: [PATCH 017/216] Adding primary key support to column with references --- CHANGELOG.md | 9 +++++++++ Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- Sources/SQLite/Typed/Schema.swift | 20 ++++++++++++++++++++ Tests/SQLiteTests/SchemaTests.swift | 27 +++++++++++++++++++++++++++ 8 files changed, 69 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d0218e..9bf5682b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.13.3 (25-01-2022), [diff][diff-0.13.3] +======================================== + +* UUID Fix ([#1112][]) +* Adding primary key support to column with references ([#1121][]) + 0.13.2 (25-01-2022), [diff][diff-0.13.2] ======================================== @@ -102,6 +108,7 @@ [diff-0.13.0]: https://github.com/stephencelis/SQLite.swift/compare/0.12.2...0.13.0 [diff-0.13.1]: https://github.com/stephencelis/SQLite.swift/compare/0.13.0...0.13.1 [diff-0.13.2]: https://github.com/stephencelis/SQLite.swift/compare/0.13.1...0.13.2 +[diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -142,3 +149,5 @@ [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 +[#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 +[#1121]: https://github.com/stephencelis/SQLite.swift/pull/1121 diff --git a/Documentation/Index.md b/Documentation/Index.md index 52affae4..987b0814 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -81,7 +81,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") ] ``` @@ -102,7 +102,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.2 + github "stephencelis/SQLite.swift" ~> 0.13.3 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -132,7 +132,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.2' + pod 'SQLite.swift', '~> 0.13.3' end ``` @@ -146,7 +146,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.2' + pod 'SQLite.swift/standalone', '~> 0.13.3' end ``` @@ -156,7 +156,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.2' + pod 'SQLite.swift/standalone', '~> 0.13.3' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -172,7 +172,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.13.2' + pod 'SQLite.swift/SQLCipher', '~> 0.13.3' end ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 5b50219a..0dea6d71 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ 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.13.3 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.3) +> the [0.13.4 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.4) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index a2b5e537..1b04c7c8 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.2 + github "stephencelis/SQLite.swift" ~> 0.13.3 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.2' + pod 'SQLite.swift', '~> 0.13.3' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 23146eee..4a1787bb 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.13.2" + s.version = "0.13.3" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 26c5e975..7387d0e9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1333,7 +1333,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.2; + MARKETING_VERSION = 0.13.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1356,7 +1356,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.2; + MARKETING_VERSION = 0.13.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 726f3e27..55e525bc 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -308,6 +308,26 @@ public final class TableBuilder { references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, nil, (table, other), nil) + } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, nil, (table, other), nil) + } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, true, false, check, nil, (table, other), nil) + } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, true, false, check, nil, (table, other), nil) + } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 495a5e51..a7de8bcf 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -317,6 +317,10 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, unique: true, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, check: int64 > 0, references: table, int64) } @@ -329,6 +333,10 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, unique: true, check: int64 > 0, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0, references: table, int64) } + ) XCTAssertEqual( """ CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES @@ -336,6 +344,13 @@ class SchemaTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), table.create { t in t.column(int64, unique: true, check: int64Optional > 0, references: table, int64) } ) + XCTAssertEqual( + """ + CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES + \"table\" (\"int64\")) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\"))", @@ -345,6 +360,10 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, unique: true, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER PRIMARY KEY REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, primaryKey: true, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, check: int64 > 0, references: table, int64) } @@ -357,10 +376,18 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER PRIMARY KEY CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, primaryKey: true, check: int64 > 0, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER PRIMARY KEY CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, primaryKey: true, check: int64Optional > 0, references: table, int64) } + ) } func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() { From 05fce4eeeb7b77aeaf75792276e502d9b88ecff1 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 27 Mar 2022 12:42:30 +0200 Subject: [PATCH 018/216] Fixing Trailing Whitespace Violation --- Sources/SQLite/Typed/Schema.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 55e525bc..e162cf34 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -308,7 +308,7 @@ public final class TableBuilder { references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } - + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, nil, (table, other), nil) From b53434e2d2bf6c4457b25c212104fca94dcbba2d Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 27 Mar 2022 12:47:24 +0200 Subject: [PATCH 019/216] Updated Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf5682b..786084f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================================== * UUID Fix ([#1112][]) +* Add prepareRowIterator method to an extension of Statement. ([#1119][]) * Adding primary key support to column with references ([#1121][]) 0.13.2 (25-01-2022), [diff][diff-0.13.2] @@ -150,4 +151,5 @@ [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 +[#1119]: https://github.com/stephencelis/SQLite.swift/pull/1119 [#1121]: https://github.com/stephencelis/SQLite.swift/pull/1121 From c42167feb9e33dc97e9da05232fa96cf354efbbb Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Apr 2022 09:37:46 +0200 Subject: [PATCH 020/216] removeDiacritics => remove_diacritics https://www.sqlite.org/fts3.html#tokenizer Closes #1128 --- Sources/SQLite/Extensions/FTS4.swift | 2 +- Tests/SQLiteTests/FTS4Tests.swift | 6 +++--- Tests/SQLiteTests/FTS5Tests.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 0e48943a..52fec955 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -103,7 +103,7 @@ public struct Tokenizer { var arguments = [String]() if let removeDiacritics = removeDiacritics { - arguments.append("removeDiacritics=\(removeDiacritics ? 1 : 0)".quote()) + arguments.append("remove_diacritics=\(removeDiacritics ? 1 : 0)".quote()) } if !tokenchars.isEmpty { diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index 33be422c..d6385b0c 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -21,12 +21,12 @@ class FTS4Tests: XCTestCase { virtualTable.create(.FTS4([string], tokenize: .Porter)) ) XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=0\")", + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"remove_diacritics=0\")", virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: false))) ) XCTAssertEqual( """ - CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"remove_diacritics=1\" \"tokenchars=.\" \"separators=X\") """.replacingOccurrences(of: "\n", with: ""), virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, @@ -116,7 +116,7 @@ class FTS4ConfigTests: XCTestCase { func test_tokenizer_unicode61_with_options() { XCTAssertEqual( """ - CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"remove_diacritics=1\" \"tokenchars=.\" \"separators=X\") """.replacingOccurrences(of: "\n", with: ""), sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index c271be9d..199ec415 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -77,7 +77,7 @@ class FTS5Tests: XCTestCase { func test_tokenizer_unicode61_with_options() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61 \"remove_diacritics=1\" \"tokenchars=.\" \"separators=X\")", sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) } From 626cc79344db350887b28016673b71645de5668b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=9Awi=C4=87?= Date: Fri, 29 Apr 2022 17:17:44 +0300 Subject: [PATCH 021/216] Fixed build order --- SQLite.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 7387d0e9..61c2773a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -592,9 +592,9 @@ isa = PBXNativeTarget; buildConfigurationList = 03A65E6F1C6BB0F60062603F /* Build configuration list for PBXNativeTarget "SQLite tvOS" */; buildPhases = ( + 03A65E571C6BB0F50062603F /* Headers */, 03A65E551C6BB0F50062603F /* Sources */, 03A65E561C6BB0F50062603F /* Frameworks */, - 03A65E571C6BB0F50062603F /* Headers */, 03A65E581C6BB0F50062603F /* Resources */, ); buildRules = ( @@ -628,9 +628,9 @@ isa = PBXNativeTarget; buildConfigurationList = A121AC4C1CA35C79005A31D1 /* Build configuration list for PBXNativeTarget "SQLite watchOS" */; buildPhases = ( + A121AC421CA35C79005A31D1 /* Headers */, A121AC401CA35C79005A31D1 /* Sources */, A121AC411CA35C79005A31D1 /* Frameworks */, - A121AC421CA35C79005A31D1 /* Headers */, A121AC431CA35C79005A31D1 /* Resources */, ); buildRules = ( @@ -646,9 +646,9 @@ isa = PBXNativeTarget; buildConfigurationList = EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */; buildPhases = ( + EE247AD01C3F04ED00AE3E12 /* Headers */, EE247ACE1C3F04ED00AE3E12 /* Sources */, EE247ACF1C3F04ED00AE3E12 /* Frameworks */, - EE247AD01C3F04ED00AE3E12 /* Headers */, EE247AD11C3F04ED00AE3E12 /* Resources */, ); buildRules = ( @@ -682,9 +682,9 @@ isa = PBXNativeTarget; buildConfigurationList = EE247B511C3F3ED000AE3E12 /* Build configuration list for PBXNativeTarget "SQLite Mac" */; buildPhases = ( + EE247B391C3F3ED000AE3E12 /* Headers */, EE247B371C3F3ED000AE3E12 /* Sources */, EE247B381C3F3ED000AE3E12 /* Frameworks */, - EE247B391C3F3ED000AE3E12 /* Headers */, EE247B3A1C3F3ED000AE3E12 /* Resources */, ); buildRules = ( From b45781a6659e186f85509a83b3678d4701221cc6 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Mon, 9 May 2022 15:06:50 -0600 Subject: [PATCH 022/216] Improve string quote performance --- Sources/SQLite/Helpers.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index d4c6828e..e3d37e11 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -54,12 +54,17 @@ extension Optional: _OptionalType { let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) extension String { - func quote(_ mark: Character = "\"") -> String { - let escaped = reduce("") { string, character in - string + (character == mark ? "\(mark)\(mark)" : "\(character)") + var quoted = "" + quoted.append(mark) + for character in self { + quoted.append(character) + if character == mark { + quoted.append(character) + } } - return "\(mark)\(escaped)\(mark)" + quoted.append(mark) + return quoted } func join(_ expressions: [Expressible]) -> Expressible { From 75dfb4e4af53b572ed433588fece3950d2c52c35 Mon Sep 17 00:00:00 2001 From: Atulya Date: Thu, 9 Jun 2022 17:20:20 -0700 Subject: [PATCH 023/216] add decoding for UUID --- Sources/SQLite/Typed/Coding.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index cea2565a..ca9ca055 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -387,7 +387,11 @@ private class SQLiteDecoder: Decoder { } else if type == Date.self { let date = try row.get(Expression(key.stringValue)) return date as! T + } else if type == UUID.self { + let uuid = try row.get(Expression(key.stringValue)) + return uuid as! T } + // swiftlint:enable force_cast guard let JSONString = try row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, From 84783d370c94b15ac23e4624027d56f0735d7b5b Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 13 Jun 2022 18:20:22 -0700 Subject: [PATCH 024/216] remove trailing whitespace for swiftlint --- Sources/SQLite/Typed/Coding.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index ca9ca055..4033365f 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -391,7 +391,7 @@ private class SQLiteDecoder: Decoder { let uuid = try row.get(Expression(key.stringValue)) return uuid as! T } - + // swiftlint:enable force_cast guard let JSONString = try row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, From 7715f7d626261e152c5f718e0770a28d728ba632 Mon Sep 17 00:00:00 2001 From: Atulya Date: Tue, 14 Jun 2022 18:22:44 -0700 Subject: [PATCH 025/216] add tests for codable UUID --- Tests/SQLiteTests/QueryIntegrationTests.swift | 7 +-- Tests/SQLiteTests/QueryTests.swift | 44 +++++++++---------- Tests/SQLiteTests/TestHelpers.swift | 5 ++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 4b9c4bbf..82ee50a3 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -75,15 +75,15 @@ class QueryIntegrationTests: SQLiteTestCase { builder.column(Expression("float")) builder.column(Expression("double")) builder.column(Expression("date")) + builder.column(Expression("uuid")) builder.column(Expression("optional")) builder.column(Expression("sub")) }) let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, - date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) - + date: Date(timeIntervalSince1970: 5000), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: "optional", sub: value1) try db.run(table.insert(value)) let rows = try db.prepare(table) @@ -95,6 +95,7 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(values[0].float, 7) XCTAssertEqual(values[0].double, 8) XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) + XCTAssertEqual(values[0].uuid, UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!) XCTAssertEqual(values[0].optional, "optional") XCTAssertEqual(values[0].sub?.int, 1) XCTAssertEqual(values[0].sub?.string, "2") diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index efcbab94..efaf7d15 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -279,12 +279,12 @@ 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, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let insert = try emails.insert(value) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -294,16 +294,16 @@ class QueryTests: XCTestCase { 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, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: "optional", sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, 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\", \"date\", \"optional\", - \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'optional', '\(encodedJSONString)') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", + \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', '\(encodedJSONString)') """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -350,14 +350,14 @@ class QueryTests: XCTestCase { let emails = Table("emails") let string = Expression("string") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\") + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", - \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\" + \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\", \"uuid\" = \"excluded\".\"uuid\" """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -366,17 +366,17 @@ class QueryTests: XCTestCase { func test_insert_many_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000'), - (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), + (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -399,12 +399,12 @@ 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, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, 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, - \"date\" = '1970-01-01T00:00:00.000' + \"date\" = '1970-01-01T00:00:00.000', \"uuid\" = 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F' """.replacingOccurrences(of: "\n", with: ""), update ) @@ -413,9 +413,9 @@ class QueryTests: XCTestCase { 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, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: value1) let update = try emails.update(value) // NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix, @@ -424,7 +424,7 @@ class QueryTests: XCTestCase { let expectedPrefix = """ UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, - \"date\" = '1970-01-01T00:00:00.000', \"sub\" = ' + \"date\" = '1970-01-01T00:00:00.000', \"uuid\" = 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', \"sub\" = ' """.replacingOccurrences(of: "\n", with: "") let expectedSuffix = "'" diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index a6efa2e7..bc538c5b 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -115,16 +115,18 @@ class TestCodable: Codable, Equatable { let float: Float let double: Double let date: Date + let uuid: UUID let optional: String? let sub: TestCodable? - init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, optional: String?, sub: TestCodable?) { + init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, uuid: UUID, optional: String?, sub: TestCodable?) { self.int = int self.string = string self.bool = bool self.float = float self.double = double self.date = date + self.uuid = uuid self.optional = optional self.sub = sub } @@ -136,6 +138,7 @@ class TestCodable: Codable, Equatable { lhs.float == rhs.float && lhs.double == rhs.double && lhs.date == rhs.date && + lhs.uuid == lhs.uuid && lhs.optional == rhs.optional && lhs.sub == rhs.sub } From 8fece72b9be693a41d15ff20d7432bd30964610e Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 27 Jun 2022 12:38:57 -0700 Subject: [PATCH 026/216] create a constant for the UUID used in tests --- Tests/SQLiteTests/QueryIntegrationTests.swift | 6 +++--- Tests/SQLiteTests/QueryTests.swift | 20 +++++++++---------- Tests/SQLiteTests/TestHelpers.swift | 2 ++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 82ee50a3..b3d9cf96 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -81,9 +81,9 @@ class QueryIntegrationTests: SQLiteTestCase { }) let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, - date: Date(timeIntervalSince1970: 5000), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: "optional", sub: value1) + date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue, optional: "optional", sub: value1) try db.run(table.insert(value)) let rows = try db.prepare(table) @@ -95,7 +95,7 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(values[0].float, 7) XCTAssertEqual(values[0].double, 8) XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) - XCTAssertEqual(values[0].uuid, UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!) + XCTAssertEqual(values[0].uuid, testUUIDValue) XCTAssertEqual(values[0].optional, "optional") XCTAssertEqual(values[0].sub?.int, 1) XCTAssertEqual(values[0].sub?.string, "2") diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index efaf7d15..e4353e52 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -279,7 +279,7 @@ 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, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insert(value) assertSQL( """ @@ -294,9 +294,9 @@ class QueryTests: XCTestCase { 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, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: "optional", sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: "optional", sub: value1) let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! @@ -350,7 +350,7 @@ class QueryTests: XCTestCase { let emails = Table("emails") let string = Expression("string") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) assertSQL( """ @@ -366,11 +366,11 @@ class QueryTests: XCTestCase { func test_insert_many_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) assertSQL( """ @@ -399,7 +399,7 @@ 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, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let update = try emails.update(value) assertSQL( """ @@ -413,9 +413,9 @@ class QueryTests: XCTestCase { 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, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: value1) let update = try emails.update(value) // NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix, diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index bc538c5b..4a71fd40 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -98,6 +98,8 @@ let stringOptional = Expression("stringOptional") let uuid = Expression("uuid") let uuidOptional = Expression("uuidOptional") +let testUUIDValue = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! + func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) From 92760e03cc71643dbb51402eb21c2dbbb59405b3 Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 27 Jun 2022 12:51:15 -0700 Subject: [PATCH 027/216] use a switch to accommodate more types --- Sources/SQLite/Typed/Coding.swift | 166 +++++++++++++++--------------- 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 4033365f..e7f5b7bb 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -44,7 +44,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } - + /// 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 @@ -69,7 +69,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(or: onConflict, encoder.setters + otherSetters) } - + /// Creates a batch `INSERT` statement by encoding the array of given objects /// 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 @@ -93,7 +93,7 @@ extension QueryType { } return self.insertMany(combinedSetters) } - + /// Creates an `INSERT ON CONFLICT DO UPDATE` statement, aka upsert, 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 @@ -116,7 +116,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.upsert(encoder.setters + otherSetters, onConflictOf: conflicting) } - + /// 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 @@ -151,7 +151,7 @@ extension Row { public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { try V(from: decoder(userInfo: userInfo)) } - + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { SQLiteDecoder(row: self, userInfo: userInfo) } @@ -162,130 +162,131 @@ private class SQLiteEncoder: Encoder { class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { // swiftlint:disable nesting 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 { encoder.setters.append(Expression(key.stringValue) <- nil) } - + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Bool, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Float, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- Double(value)) } - + func encode(_ value: Double, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: String, forKey key: Key) throws { 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 { + switch value { + case let data as Data: encoder.setters.append(Expression(key.stringValue) <- data) - } else if let date = value as? Date { + case let date as Date: encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - } else if let uuid = value as? UUID { + case let uuid as UUID: encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) - } else { + default: let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) encoder.setters.append(Expression(key.stringValue) <- string) } } - + func encode(_ value: Int8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int8 is not supported")) } - + func encode(_ value: Int16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int16 is not supported")) } - + func encode(_ value: Int32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int32 is not supported")) } - + func encode(_ value: Int64, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: UInt, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt is not supported")) } - + func encode(_ value: UInt8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt8 is not supported")) } - + func encode(_ value: UInt16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt16 is not supported")) } - + func encode(_ value: UInt32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt32 is not supported")) } - + func encode(_ value: UInt64, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt64 is not supported")) } - + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) - -> KeyedEncodingContainer where NestedKey: CodingKey { + -> 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: [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 { KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } @@ -294,156 +295,157 @@ private class SQLiteEncoder: Encoder { private class SQLiteDecoder: Decoder { class SQLiteKeyedDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = MyKey - + let codingPath: [CodingKey] = [] let row: Row - + init(row: Row) { self.row = row } - + var allKeys: [Key] { row.columnNames.keys.compactMap({ Key(stringValue: $0) }) } - + func contains(_ key: Key) -> Bool { row.hasValue(for: key.stringValue) } - + func decodeNil(forKey key: Key) throws -> Bool { !contains(key) } - + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int8 is not supported")) } - + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int16 is not supported")) } - + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int32 is not supported")) } - + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { try row.get(Expression(key.stringValue)) } - + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt is not supported")) - + } - + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt8 is not supported")) } - + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt16 is not supported")) } - + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt32 is not supported")) } - + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt64 is not supported")) } - + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { Float(try row.get(Expression(key.stringValue))) } - + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { try row.get(Expression(key.stringValue)) } - + func decode(_ type: String.Type, forKey key: Key) throws -> String { try row.get(Expression(key.stringValue)) } - + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { // swiftlint:disable force_cast - if type == Data.self { + switch type { + case is Data.Type: let data = try row.get(Expression(key.stringValue)) return data as! T - } else if type == Date.self { + case is Date.Type: let date = try row.get(Expression(key.stringValue)) return date as! T - } else if type == UUID.self { + case is UUID.Type: let uuid = try row.get(Expression(key.stringValue)) return uuid as! T + default: + // swiftlint:enable force_cast + guard let JSONString = try row.get(Expression(key.stringValue)) else { + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, + debugDescription: "an unsupported type was found")) + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, + debugDescription: "invalid utf8 data found")) + } + return try JSONDecoder().decode(type, from: data) } - - // swiftlint:enable force_cast - guard let JSONString = try row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, - debugDescription: "an unsupported type was found")) - } - guard let data = JSONString.data(using: .utf8) else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: 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 { + -> KeyedDecodingContainer where NestedKey: CodingKey { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding nested containers is not supported")) } - + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding unkeyed containers is not supported")) } - + func superDecoder() throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super encoders containers is not supported")) } - + func superDecoder(forKey key: Key) throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super decoders 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 { KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } - + func unkeyedContainer() throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an unkeyed container is not supported")) } - + func singleValueContainer() throws -> SingleValueDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding a single value container is not supported")) From bc63d3ab1a154d941ac67b8c75216b625cd0efdf Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 27 Jun 2022 12:52:36 -0700 Subject: [PATCH 028/216] swiftlint fix --- Sources/SQLite/Typed/Coding.swift | 124 +++++++++++++++--------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index e7f5b7bb..ec2e0d6c 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -44,7 +44,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } - + /// 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 @@ -69,7 +69,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(or: onConflict, encoder.setters + otherSetters) } - + /// Creates a batch `INSERT` statement by encoding the array of given objects /// 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 @@ -93,7 +93,7 @@ extension QueryType { } return self.insertMany(combinedSetters) } - + /// Creates an `INSERT ON CONFLICT DO UPDATE` statement, aka upsert, 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 @@ -116,7 +116,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.upsert(encoder.setters + otherSetters, onConflictOf: conflicting) } - + /// 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 @@ -151,7 +151,7 @@ extension Row { public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { try V(from: decoder(userInfo: userInfo)) } - + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { SQLiteDecoder(row: self, userInfo: userInfo) } @@ -162,46 +162,46 @@ private class SQLiteEncoder: Encoder { class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { // swiftlint:disable nesting 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 { encoder.setters.append(Expression(key.stringValue) <- nil) } - + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Bool, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Float, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- Double(value)) } - + func encode(_ value: Double, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: String, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { switch value { case let data as Data: @@ -216,77 +216,77 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- string) } } - + func encode(_ value: Int8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int8 is not supported")) } - + func encode(_ value: Int16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int16 is not supported")) } - + func encode(_ value: Int32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int32 is not supported")) } - + func encode(_ value: Int64, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: UInt, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt is not supported")) } - + func encode(_ value: UInt8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt8 is not supported")) } - + func encode(_ value: UInt16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt16 is not supported")) } - + func encode(_ value: UInt32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt32 is not supported")) } - + func encode(_ value: UInt64, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "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: [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 { KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } @@ -295,91 +295,91 @@ private class SQLiteEncoder: Encoder { private class SQLiteDecoder: Decoder { class SQLiteKeyedDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = MyKey - + let codingPath: [CodingKey] = [] let row: Row - + init(row: Row) { self.row = row } - + var allKeys: [Key] { row.columnNames.keys.compactMap({ Key(stringValue: $0) }) } - + func contains(_ key: Key) -> Bool { row.hasValue(for: key.stringValue) } - + func decodeNil(forKey key: Key) throws -> Bool { !contains(key) } - + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int8 is not supported")) } - + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int16 is not supported")) } - + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int32 is not supported")) } - + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { try row.get(Expression(key.stringValue)) } - + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt is not supported")) - + } - + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt8 is not supported")) } - + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt16 is not supported")) } - + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt32 is not supported")) } - + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt64 is not supported")) } - + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { Float(try row.get(Expression(key.stringValue))) } - + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { try row.get(Expression(key.stringValue)) } - + func decode(_ type: String.Type, forKey key: Key) throws -> String { try row.get(Expression(key.stringValue)) } - + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { // swiftlint:disable force_cast switch type { @@ -405,47 +405,47 @@ private class SQLiteDecoder: Decoder { return try JSONDecoder().decode(type, from: data) } } - + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding nested containers is not supported")) } - + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding unkeyed containers is not supported")) } - + func superDecoder() throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super encoders containers is not supported")) } - + func superDecoder(forKey key: Key) throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super decoders 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 { KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } - + func unkeyedContainer() throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an unkeyed container is not supported")) } - + func singleValueContainer() throws -> SingleValueDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding a single value container is not supported")) From 3fac726365dc5813625aed56069c3b9feb446762 Mon Sep 17 00:00:00 2001 From: Atulya Date: Tue, 28 Jun 2022 12:27:52 -0700 Subject: [PATCH 029/216] silence swiftlint line_length for some sql strings --- Tests/SQLiteTests/QueryTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index e4353e52..d9cce086 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -300,6 +300,7 @@ class QueryTests: XCTestCase { let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! + // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", @@ -307,6 +308,7 @@ class QueryTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), insert ) + // swiftlint:enable line_length } #endif @@ -352,6 +354,7 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) + // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") @@ -361,6 +364,7 @@ class QueryTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), insert ) + // swiftlint:enable line_length } func test_insert_many_encodable() throws { @@ -372,6 +376,7 @@ class QueryTests: XCTestCase { let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) + // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") @@ -380,6 +385,7 @@ class QueryTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), insert ) + // swiftlint:enable line_length } func test_update_compilesUpdateExpression() { From be3a5ced7e790619f225024db1ac93795b01cf81 Mon Sep 17 00:00:00 2001 From: Atulya Date: Wed, 29 Jun 2022 09:34:32 -0700 Subject: [PATCH 030/216] remove swiftlint line_length disables --- Tests/SQLiteTests/QueryTests.swift | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index d9cce086..2201caef 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -300,15 +300,14 @@ class QueryTests: XCTestCase { let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", - \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', '\(encodedJSONString)') + \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', + 'optional', '\(encodedJSONString)') """.replacingOccurrences(of: "\n", with: ""), insert ) - // swiftlint:enable line_length } #endif @@ -354,17 +353,16 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) - // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", - \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\", \"uuid\" = \"excluded\".\"uuid\" + \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\", + \"uuid\" = \"excluded\".\"uuid\" """.replacingOccurrences(of: "\n", with: ""), insert ) - // swiftlint:enable line_length } func test_insert_many_encodable() throws { @@ -376,16 +374,15 @@ class QueryTests: XCTestCase { let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) - // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), + (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') """.replacingOccurrences(of: "\n", with: ""), insert ) - // swiftlint:enable line_length } func test_update_compilesUpdateExpression() { From ba5165ac7aaddc3f240f04e54d3f978ae2208a5d Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 1 Jul 2022 12:36:17 -0700 Subject: [PATCH 031/216] fix insertMany() failing with encodables which have assymmetric optional values --- Sources/SQLite/Typed/Coding.swift | 88 +++++++++++++++++-- Tests/SQLiteTests/QueryIntegrationTests.swift | 21 +++++ Tests/SQLiteTests/QueryTests.swift | 12 +-- Tests/SQLiteTests/TestHelpers.swift | 20 +++++ 4 files changed, 129 insertions(+), 12 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index ec2e0d6c..14a469ab 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -86,12 +86,22 @@ extension QueryType { /// - Returns: An `INSERT` statement for the encodable objects public func insertMany(_ encodables: [Encodable], userInfo: [CodingUserInfoKey: Any] = [:], otherSetters: [Setter] = []) throws -> Insert { - let combinedSetters = try encodables.map { encodable -> [Setter] in - let encoder = SQLiteEncoder(userInfo: userInfo) + let combinedSettersWithoutNils = try encodables.map { encodable -> [Setter] in + let encoder = SQLiteEncoder(userInfo: userInfo, forcingNilValueSetters: false) try encodable.encode(to: encoder) return encoder.setters + otherSetters } - return self.insertMany(combinedSetters) + // requires the same number of setters per encodable + guard Set(combinedSettersWithoutNils.map(\.count)).count == 1 else { + // asymmetric sets of value insertions (some nil, some not), requires NULL value to satisfy INSERT query + let combinedSymmetricSetters = try encodables.map { encodable -> [Setter] in + let encoder = SQLiteEncoder(userInfo: userInfo, forcingNilValueSetters: true) + try encodable.encode(to: encoder) + return encoder.setters + otherSetters + } + return self.insertMany(combinedSymmetricSetters) + } + return self.insertMany(combinedSettersWithoutNils) } /// Creates an `INSERT ON CONFLICT DO UPDATE` statement, aka upsert, by encoding the given object @@ -165,9 +175,11 @@ private class SQLiteEncoder: Encoder { let encoder: SQLiteEncoder let codingPath: [CodingKey] = [] + let forcingNilValueSetters: Bool - init(encoder: SQLiteEncoder) { + init(encoder: SQLiteEncoder, forcingNilValueSetters: Bool = false) { self.encoder = encoder + self.forcingNilValueSetters = forcingNilValueSetters } func superEncoder() -> Swift.Encoder { @@ -202,6 +214,46 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- value) } + func encodeIfPresent(_ value: Int?, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: Float?, forKey key: Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- Double(value)) + } else if forcingNilValueSetters{ + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: Double?, forKey key: Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: String?, forKey key: MyKey) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { switch value { case let data as Data: @@ -217,6 +269,28 @@ private class SQLiteEncoder: Encoder { } } + func encodeIfPresent(_ value: T?, forKey key: Key) throws where T: Swift.Encodable { + guard let value = value else { + guard forcingNilValueSetters else { + return + } + encoder.setters.append(Expression(key.stringValue) <- nil) + return + } + switch value { + case let data as Data: + encoder.setters.append(Expression(key.stringValue) <- data) + case let date as Date: + encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + case let uuid as UUID: + encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) + default: + let encoded = try JSONEncoder().encode(value) + let string = String(data: encoded, encoding: .utf8) + encoder.setters.append(Expression(key.stringValue) <- string) + } + } + func encode(_ value: Int8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int8 is not supported")) @@ -274,9 +348,11 @@ private class SQLiteEncoder: Encoder { fileprivate var setters: [Setter] = [] let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] + let forcingNilValueSetters: Bool - init(userInfo: [CodingUserInfoKey: Any]) { + init(userInfo: [CodingUserInfoKey: Any], forcingNilValueSetters: Bool = false) { self.userInfo = userInfo + self.forcingNilValueSetters = forcingNilValueSetters } func singleValueContainer() -> SingleValueEncodingContainer { @@ -288,7 +364,7 @@ private class SQLiteEncoder: Encoder { } func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { - KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self, forcingNilValueSetters: forcingNilValueSetters)) } } diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index b3d9cf96..3ea06745 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -130,6 +130,27 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(2, id) } + func test_insert_many_encodables() 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("date")) + builder.column(Expression("uuid")) + }) + + let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue) + let valueWithNils = TestOptionalCodable(int: nil, string: nil, bool: nil, float: nil, double: nil, date: nil, uuid: nil) + try db.run(table.insertMany([value1, valueWithNils])) + + let rows = try db.prepare(table) + let values: [TestOptionalCodable] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 2) + } + func test_upsert() throws { try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 24)) let fetchAge = { () throws -> Int? in diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2201caef..b2f679e0 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -365,21 +365,21 @@ class QueryTests: XCTestCase { ) } - func test_insert_many_encodable() throws { + func test_insert_many_encodables() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, - date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: "optional", sub: nil) let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), - (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), - (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", \"sub\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', NULL, NULL), + (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', NULL), + (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', NULL, NULL) """.replacingOccurrences(of: "\n", with: ""), insert ) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 4a71fd40..f43310c9 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -145,3 +145,23 @@ class TestCodable: Codable, Equatable { lhs.sub == rhs.sub } } + +struct TestOptionalCodable: Codable, Equatable { + let int: Int? + let string: String? + let bool: Bool? + let float: Float? + let double: Double? + let date: Date? + let uuid: UUID? + + init(int: Int?, string: String?, bool: Bool?, float: Float?, double: Double?, date: Date?, uuid: UUID?) { + self.int = int + self.string = string + self.bool = bool + self.float = float + self.double = double + self.date = date + self.uuid = uuid + } +} From c4bdad8f3c47a41d1902d32e988f148e4734075e Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 1 Jul 2022 12:39:10 -0700 Subject: [PATCH 032/216] lint --- Sources/SQLite/Typed/Coding.swift | 2 +- Tests/SQLiteTests/QueryIntegrationTests.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 14a469ab..08b7131a 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -233,7 +233,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Float?, forKey key: Key) throws { if let value = value { encoder.setters.append(Expression(key.stringValue) <- Double(value)) - } else if forcingNilValueSetters{ + } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } } diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 3ea06745..f3b4bcd3 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -142,7 +142,8 @@ class QueryIntegrationTests: SQLiteTestCase { builder.column(Expression("uuid")) }) - let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue) + let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, + date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue) let valueWithNils = TestOptionalCodable(int: nil, string: nil, bool: nil, float: nil, double: nil, date: nil, uuid: nil) try db.run(table.insertMany([value1, valueWithNils])) From abff512bb57f46cb155a8b9ccf744c72f72a886f Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Sat, 2 Jul 2022 11:04:17 -0700 Subject: [PATCH 033/216] feedback --- Sources/SQLite/Typed/Coding.swift | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 08b7131a..b96bc64e 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -216,7 +216,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Int?, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -224,7 +224,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -232,7 +232,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Float?, forKey key: Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- Double(value)) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -240,7 +240,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Double?, forKey key: Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -248,7 +248,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: String?, forKey key: MyKey) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -277,18 +277,7 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- nil) return } - switch value { - case let data as Data: - encoder.setters.append(Expression(key.stringValue) <- data) - case let date as Date: - encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - case let uuid as UUID: - encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) - default: - let encoded = try JSONEncoder().encode(value) - let string = String(data: encoded, encoding: .utf8) - encoder.setters.append(Expression(key.stringValue) <- string) - } + try encode(value, forKey: key) } func encode(_ value: Int8, forKey key: Key) throws { From 12e2166c90b24f900869b916d9d8b8a6ac24e839 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 15:38:55 -0700 Subject: [PATCH 034/216] Add support for the WITH clause The `WITH` clause provides the ability to do hierarchical or recursive queries of tree and graph-like data. See https://www.sqlite.org/lang_with.html. **Details** - Add `all` parameter to `QueryType.union` to allow `UNION ALL` to be used in a query. I opted to add the parameter to the start of the list so that it does not dangle at the end when the union's query is long: ```swift users.union(all: true, posts.join(users, on: users[id] == posts[userId])) // It's a little easier to read than: users.union(posts.join(users, on: users[id] == posts[userId]), all: true) ``` - Add `with` function to `QueryType`. This function adds a `WITH` clause to a query. The function may be called multiple times to add multiple clauses to a query. If multiple clauses are added to the query with conflicting `recursive` parameters, the whole `WITH` clause will be considered recursive. Like the `union` function, I put the `subquery` parameter at the end so that the `recursive` and `materializationHint` options don't dangle at the end of a long query. ```swift let users = Table("users") let users = Table("posts") let first = Table("first") let second = Table("second") first.with(first, recursive: true as: users).with(second, recursive: false, as: posts) // WITH RECURSIVE "first" AS (SELECT * from users), "second" AS (SELECT * from posts) SELECT * from "first" ``` --- Sources/SQLite/Typed/Query.swift | 105 +++++++++++++++++- Tests/SQLiteTests/QueryIntegrationTests.swift | 41 +++++++ Tests/SQLiteTests/QueryTests.swift | 60 ++++++++++ 3 files changed, 201 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 93dc2a73..c328aef6 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -193,12 +193,14 @@ extension QueryType { /// /// - Parameters: /// + /// - all: If false, duplicate rows are removed from the result. + /// /// - table: A query representing the other table. /// /// - Returns: A query with the given `UNION` clause applied. - public func union(_ table: QueryType) -> Self { + public func union(all: Bool = false, _ table: QueryType) -> Self { var query = self - query.clauses.union.append(table) + query.clauses.union.append((all, table)) return query } @@ -496,6 +498,37 @@ extension QueryType { query.clauses.limit = length.map { ($0, offset) } return query } + + // MARK: WITH + + /// Sets a `WITH` clause on the query. + /// + /// let users = Table("users") + /// let id = Expression("email") + /// let name = Expression("name") + /// + /// let userNames = Table("user_names") + /// userCategories.with(userNames, as: users.select(name)) + /// // WITH "user_names" as (SELECT "name" FROM "users") SELECT * FROM "user_names" + /// + /// - Parameters: + /// + /// - alias: A name to assign to the table expression. + /// + /// - recursive: Whether to evaluate the expression recursively. + /// + /// - materializationHint: Provides a hint to the query planner for how the expression should be implemented. + /// + /// - subquery: A query that generates the rows for the table expression. + /// + /// - Returns: A query with the given `ORDER BY` clause applied. + public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, materializationHint: MaterializationHint? = nil, as subquery: QueryType) -> Self { + var query = self + let clause = WithClauses.Clause(alias: alias, columns: columns, materializationHint: materializationHint, query: subquery) + query.clauses.with.recursive = query.clauses.with.recursive || recursive + query.clauses.with.clauses.append(clause) + return query + } // MARK: - Clauses // @@ -596,13 +629,50 @@ extension QueryType { return nil } - return " ".join(clauses.union.map { query in + return " ".join(clauses.union.map { (all, query) in " ".join([ - Expression(literal: "UNION"), + Expression(literal: all ? "UNION ALL" : "UNION"), query ]) }) } + + fileprivate var withClause: Expressible? { + guard !clauses.with.clauses.isEmpty else { + return nil + } + + let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in + let hintExpr: Expression? + if let hint = clause.materializationHint { + hintExpr = Expression(literal: hint.rawValue) + } else { + hintExpr = nil + } + + let columnExpr: Expression? + if let columns = clause.columns { + columnExpr = "".wrap(", ".join(columns)) + } else { + columnExpr = nil + } + + let expressions: [Expressible?] = [ + clause.alias.tableName(), + columnExpr, + Expression(literal: "AS"), + hintExpr, + "".wrap(clause.query) as Expression + ] + + return " ".join(expressions.compactMap { $0 }) + }) + + return " ".join([ + Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), + innerClauses + ]) + } // MARK: - @@ -856,6 +926,7 @@ extension QueryType { public var expression: Expression { let clauses: [Expressible?] = [ + withClause, selectClause, joinClause, whereClause, @@ -1233,8 +1304,30 @@ public enum OnConflict: String { } +/// Materialization hints for `WITH` clause +public enum MaterializationHint: String { + + case materialized = "MATERIALIZED" + + case notMaterialized = "NOT MATERIALIZED" +} + // MARK: - Private +struct WithClauses { + struct Clause { + var alias: Table + var columns: [Expressible]? + var materializationHint: MaterializationHint? + var query: QueryType + } + /// The `RECURSIVE` flag is applied to the entire `WITH` clause + var recursive: Bool = false + + /// Each `WITH` clause may have multiple subclauses + var clauses: [Clause] = [] +} + public struct QueryClauses { var select = (distinct: false, columns: [Expression(literal: "*") as Expressible]) @@ -1251,7 +1344,9 @@ public struct QueryClauses { var limit: (length: Int, offset: Int?)? - var union = [QueryType]() + var union = [(all: Bool, table: QueryType)]() + + var with = WithClauses() fileprivate init(_ name: String, alias: String?, database: String?) { from = (name, alias, database) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index f3b4bcd3..c1cd97a5 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -230,6 +230,47 @@ class QueryIntegrationTests: SQLiteTestCase { let result = Array(try db.prepare(users.select(email).order(Expression.random()).limit(1))) XCTAssertEqual(1, result.count) } + + func test_with_recursive() { + let nodes = Table("nodes") + let id = Expression("id") + let parent = Expression("parent") + let value = Expression("value") + + try! db.run(nodes.create { builder in + builder.column(id) + builder.column(parent) + builder.column(value) + }) + + try! db.run(nodes.insertMany([ + [id <- 0, parent <- nil, value <- 2], + [id <- 1, parent <- 0, value <- 4], + [id <- 2, parent <- 0, value <- 9], + [id <- 3, parent <- 2, value <- 8], + [id <- 4, parent <- 2, value <- 7], + [id <- 5, parent <- 4, value <- 3], + ])) + + // Compute the sum of the values of node 5 and its ancestors + let ancestors = Table("ancestors") + let sum = try! db.scalar( + ancestors + .select(value.sum) + .with(ancestors, + columns: [id, parent, value], + recursive: true, + as: nodes + .where(id == 5) + .union(all: true, + nodes.join(ancestors, on: nodes[id] == ancestors[parent]) + .select(nodes[id], nodes[parent], nodes[value]) + ) + ) + ) + + XCTAssertEqual(21, sum) + } } extension Connection { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index b2f679e0..6baf59b3 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -58,6 +58,11 @@ class QueryTests: XCTestCase { func test_selectDistinct_withStar_compilesSelectClause() { assertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) } + + func test_union_compilesUnionClause() { + assertSQL("SELECT * FROM \"users\" UNION SELECT * FROM \"posts\"", users.union(posts)) + assertSQL("SELECT * FROM \"users\" UNION ALL SELECT * FROM \"posts\"", users.union(all: true, posts)) + } func test_join_compilesJoinClause() { assertSQL( @@ -219,6 +224,61 @@ class QueryTests: XCTestCase { users.join(managers, on: managers[id] == users[managerId]) ) } + + func test_with_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, as: users)) + } + + func test_with_recursive_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, recursive: true, as: users)) + + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, recursive: false, as: users)) + } + + func test_with_materialization_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH \"temp\" AS MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, materializationHint: .materialized, as: users)) + + assertSQL("WITH \"temp\" AS NOT MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, materializationHint: .notMaterialized, as: users)) + } + + func test_with_columns_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH \"temp\" (\"id\", \"email\") AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, columns: [id, email], recursive: false, materializationHint: nil, as: users)) + } + + func test_with_multiple_compilesWithClause() { + let temp = Table("temp") + let second = Table("second") + let third = Table("third") + + let query = temp + .with(temp, recursive: true, as: users) + .with(second, recursive: true, as: posts) + .with(third, materializationHint: .materialized, as:categories) + + assertSQL( + """ + WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\"), + \"second\" AS (SELECT * FROM \"posts\"), + \"third\" AS MATERIALIZED (SELECT * FROM \"categories\") + SELECT * FROM \"temp\" + """.replacingOccurrences(of: "\n", with: ""), + query + ) + } func test_insert_compilesInsertExpression() { assertSQL( From 03b56886891d1816bb03567584f7c6b00ae96e75 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 17:03:31 -0700 Subject: [PATCH 035/216] documentation --- Documentation/Index.md | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index e28b24bb..7b5af41f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -34,6 +34,7 @@ - [Filter Operators and Functions](#filter-operators-and-functions) - [Sorting Rows](#sorting-rows) - [Limiting and Paging Results](#limiting-and-paging-results) + - [Recursive and Hierarchical Queries](#recursive-and-hierarchical-queries) - [Aggregation](#aggregation) - [Upserting Rows](#upserting-rows) - [Updating Rows](#updating-rows) @@ -1086,6 +1087,62 @@ users.limit(5, offset: 5) ``` +#### Recursive and Hierarchical Queries + +We can perform a recursive or hierarchical query using a [query's](#queries) `with` +function. Column names and a materialization hint can optionally be provided. + +```swift +// Get the management chain for the manager with id == 8 + +let chain = Table("chain") +let id = Expression("id") +let managerId = Expression("manager_id") + +let query = managers + .where(id == 8) + .union(chain.join(managers, on: chain[managerId] == managers[id]) + +chain.with(chain, recursive: true, as: query) +// WITH RECURSIVE +// "chain" AS ( +// SELECT * FROM "managers" WHERE "id" = 8 +// UNION +// SELECT * from "chain" +// JOIN "managers" ON "chain"."manager_id" = "managers"."id" +// ) +// SELECT * FROM "chain" + +// Add a "level" column to the query representing manager's position in the chain + +let level = Expression("level") + +let queryWithLevel = + managers + .select(id, managerId, 0) + .where(id == 8) + .union( + chain + .select(managers[id], managers[manager_id], level + 1) + .join(managers, on: chain[managerId] == managers[id]) + ) + +chain.with(chain, + columns: [id, managerId, level], + recursive: true, + materializationHint: .materialize, + as: queryWithLevel) +// WITH RECURSIVE +// "chain" ("id", "manager_id", "level") AS MATERIALIZED ( +// SELECT ("id", "manager_id", 0) FROM "managers" WHERE "id" = 8 +// UNION +// SELECT ("manager"."id", "manager"."manager_id", "level" + 1) FROM "chain" +// JOIN "managers" ON "chain"."manager_id" = "managers"."id" +// ) +// SELECT * FROM "chain" +``` + + #### Aggregation [Queries](#queries) come with a number of functions that quickly return From 76d34dd8ff668b58d6b5530528460dd6d2786c0e Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 17:36:18 -0700 Subject: [PATCH 036/216] Heed linter warnings Note sure how to get the line length of Query back down below 500 without harming readability though. --- Documentation/Index.md | 2 +- Sources/SQLite/Typed/Query.swift | 29 +++++++++-------- Tests/SQLiteTests/QueryIntegrationTests.swift | 12 +++---- Tests/SQLiteTests/QueryTests.swift | 32 +++++++++---------- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 7b5af41f..44304c1c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1130,7 +1130,7 @@ let queryWithLevel = chain.with(chain, columns: [id, managerId, level], recursive: true, - materializationHint: .materialize, + hint: .materialize, as: queryWithLevel) // WITH RECURSIVE // "chain" ("id", "manager_id", "level") AS MATERIALIZED ( diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c328aef6..b34570a7 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -498,9 +498,9 @@ extension QueryType { query.clauses.limit = length.map { ($0, offset) } return query } - + // MARK: WITH - + /// Sets a `WITH` clause on the query. /// /// let users = Table("users") @@ -517,14 +517,15 @@ extension QueryType { /// /// - recursive: Whether to evaluate the expression recursively. /// - /// - materializationHint: Provides a hint to the query planner for how the expression should be implemented. + /// - hint: Provides a hint to the query planner for how the expression should be implemented. /// /// - subquery: A query that generates the rows for the table expression. /// /// - Returns: A query with the given `ORDER BY` clause applied. - public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, materializationHint: MaterializationHint? = nil, as subquery: QueryType) -> Self { + public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, + hint: MaterializationHint? = nil, as subquery: QueryType) -> Self { var query = self - let clause = WithClauses.Clause(alias: alias, columns: columns, materializationHint: materializationHint, query: subquery) + let clause = WithClauses.Clause(alias: alias, columns: columns, hint: hint, query: subquery) query.clauses.with.recursive = query.clauses.with.recursive || recursive query.clauses.with.clauses.append(clause) return query @@ -636,7 +637,7 @@ extension QueryType { ]) }) } - + fileprivate var withClause: Expressible? { guard !clauses.with.clauses.isEmpty else { return nil @@ -644,19 +645,19 @@ extension QueryType { let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in let hintExpr: Expression? - if let hint = clause.materializationHint { + if let hint = clause.hint { hintExpr = Expression(literal: hint.rawValue) } else { hintExpr = nil } - + let columnExpr: Expression? if let columns = clause.columns { columnExpr = "".wrap(", ".join(columns)) } else { columnExpr = nil } - + let expressions: [Expressible?] = [ clause.alias.tableName(), columnExpr, @@ -664,10 +665,10 @@ extension QueryType { hintExpr, "".wrap(clause.query) as Expression ] - + return " ".join(expressions.compactMap { $0 }) }) - + return " ".join([ Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), innerClauses @@ -1318,12 +1319,12 @@ struct WithClauses { struct Clause { var alias: Table var columns: [Expressible]? - var materializationHint: MaterializationHint? + var hint: MaterializationHint? var query: QueryType } /// The `RECURSIVE` flag is applied to the entire `WITH` clause var recursive: Bool = false - + /// Each `WITH` clause may have multiple subclauses var clauses: [Clause] = [] } @@ -1345,7 +1346,7 @@ public struct QueryClauses { var limit: (length: Int, offset: Int?)? var union = [(all: Bool, table: QueryType)]() - + var with = WithClauses() fileprivate init(_ name: String, alias: String?, database: String?) { diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index c1cd97a5..db4e2e4e 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -230,28 +230,28 @@ class QueryIntegrationTests: SQLiteTestCase { let result = Array(try db.prepare(users.select(email).order(Expression.random()).limit(1))) XCTAssertEqual(1, result.count) } - + func test_with_recursive() { let nodes = Table("nodes") let id = Expression("id") let parent = Expression("parent") let value = Expression("value") - + try! db.run(nodes.create { builder in builder.column(id) builder.column(parent) builder.column(value) }) - + try! db.run(nodes.insertMany([ [id <- 0, parent <- nil, value <- 2], [id <- 1, parent <- 0, value <- 4], [id <- 2, parent <- 0, value <- 9], [id <- 3, parent <- 2, value <- 8], [id <- 4, parent <- 2, value <- 7], - [id <- 5, parent <- 4, value <- 3], + [id <- 5, parent <- 4, value <- 3] ])) - + // Compute the sum of the values of node 5 and its ancestors let ancestors = Table("ancestors") let sum = try! db.scalar( @@ -268,7 +268,7 @@ class QueryIntegrationTests: SQLiteTestCase { ) ) ) - + XCTAssertEqual(21, sum) } } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 6baf59b3..a96ff38c 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -58,7 +58,7 @@ class QueryTests: XCTestCase { func test_selectDistinct_withStar_compilesSelectClause() { assertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) } - + func test_union_compilesUnionClause() { assertSQL("SELECT * FROM \"users\" UNION SELECT * FROM \"posts\"", users.union(posts)) assertSQL("SELECT * FROM \"users\" UNION ALL SELECT * FROM \"posts\"", users.union(all: true, posts)) @@ -224,41 +224,41 @@ class QueryTests: XCTestCase { users.join(managers, on: managers[id] == users[managerId]) ) } - + func test_with_compilesWithClause() { let temp = Table("temp") - + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, as: users)) } - + func test_with_recursive_compilesWithClause() { let temp = Table("temp") - + assertSQL("WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, recursive: true, as: users)) - + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, recursive: false, as: users)) } - + func test_with_materialization_compilesWithClause() { let temp = Table("temp") - + assertSQL("WITH \"temp\" AS MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, materializationHint: .materialized, as: users)) - + temp.with(temp, hint: .materialized, as: users)) + assertSQL("WITH \"temp\" AS NOT MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, materializationHint: .notMaterialized, as: users)) + temp.with(temp, hint: .notMaterialized, as: users)) } - + func test_with_columns_compilesWithClause() { let temp = Table("temp") assertSQL("WITH \"temp\" (\"id\", \"email\") AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, columns: [id, email], recursive: false, materializationHint: nil, as: users)) + temp.with(temp, columns: [id, email], recursive: false, hint: nil, as: users)) } - + func test_with_multiple_compilesWithClause() { let temp = Table("temp") let second = Table("second") @@ -267,8 +267,8 @@ class QueryTests: XCTestCase { let query = temp .with(temp, recursive: true, as: users) .with(second, recursive: true, as: posts) - .with(third, materializationHint: .materialized, as:categories) - + .with(third, hint: .materialized, as: categories) + assertSQL( """ WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\"), From bdacf4df697eb73cb1a0887a6b3db776b55b3b0d Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 17:39:50 -0700 Subject: [PATCH 037/216] Slight documentation tweak --- Documentation/Index.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 44304c1c..7a9a390f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1090,7 +1090,7 @@ users.limit(5, offset: 5) #### Recursive and Hierarchical Queries We can perform a recursive or hierarchical query using a [query's](#queries) `with` -function. Column names and a materialization hint can optionally be provided. +function. ```swift // Get the management chain for the manager with id == 8 @@ -1112,9 +1112,12 @@ chain.with(chain, recursive: true, as: query) // JOIN "managers" ON "chain"."manager_id" = "managers"."id" // ) // SELECT * FROM "chain" +``` -// Add a "level" column to the query representing manager's position in the chain +Column names and a materialization hint can optionally be provided. +```swift +// Add a "level" column to the query representing manager's position in the chain let level = Expression("level") let queryWithLevel = From ced185132ea17b9ba7391322bc52982f930538bd Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Wed, 13 Jul 2022 20:22:25 -0700 Subject: [PATCH 038/216] Move WITH support members to extension in Query+with.swift --- SQLite.xcodeproj/project.pbxproj | 10 +++ Sources/SQLite/Typed/Query+with.swift | 95 +++++++++++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 69 ------------------- 3 files changed, 105 insertions(+), 69 deletions(-) create mode 100644 Sources/SQLite/Typed/Query+with.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 61c2773a..fc38e5e9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -130,6 +130,10 @@ 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 */; }; + 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; @@ -260,6 +264,7 @@ 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; }; 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; + 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D4DB368A20C09C9B00D5A58E /* SelectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -499,6 +504,7 @@ EE247AFE1C3F06E900AE3E12 /* Expression.swift */, EE247AFF1C3F06E900AE3E12 /* Operators.swift */, EE247B001C3F06E900AE3E12 /* Query.swift */, + 997DF2AD287FC06D00F8DF95 /* Query+with.swift */, EE247B011C3F06E900AE3E12 /* Schema.swift */, EE247B021C3F06E900AE3E12 /* Setter.swift */, 49EB68C31F7B3CB400D89D40 /* Coding.swift */, @@ -862,6 +868,7 @@ 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, 19A17073552293CA063BEA66 /* Result.swift in Sources */, + 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -904,6 +911,7 @@ files = ( 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, + 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, @@ -959,6 +967,7 @@ 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, + 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1024,6 +1033,7 @@ 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, + 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/SQLite/Typed/Query+with.swift b/Sources/SQLite/Typed/Query+with.swift new file mode 100644 index 00000000..005f490c --- /dev/null +++ b/Sources/SQLite/Typed/Query+with.swift @@ -0,0 +1,95 @@ +// +// 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 + +extension QueryType { + + /// Sets a `WITH` clause on the query. + /// + /// let users = Table("users") + /// let id = Expression("email") + /// let name = Expression("name") + /// + /// let userNames = Table("user_names") + /// userCategories.with(userNames, as: users.select(name)) + /// // WITH "user_names" as (SELECT "name" FROM "users") SELECT * FROM "user_names" + /// + /// - Parameters: + /// + /// - alias: A name to assign to the table expression. + /// + /// - recursive: Whether to evaluate the expression recursively. + /// + /// - hint: Provides a hint to the query planner for how the expression should be implemented. + /// + /// - subquery: A query that generates the rows for the table expression. + /// + /// - Returns: A query with the given `ORDER BY` clause applied. + public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, + hint: MaterializationHint? = nil, as subquery: QueryType) -> Self { + var query = self + let clause = WithClauses.Clause(alias: alias, columns: columns, hint: hint, query: subquery) + query.clauses.with.recursive = query.clauses.with.recursive || recursive + query.clauses.with.clauses.append(clause) + return query + } + + /// self.clauses.with transformed to an Expressible + var withClause: Expressible? { + guard !clauses.with.clauses.isEmpty else { + return nil + } + + let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in + let hintExpr: Expression? + if let hint = clause.hint { + hintExpr = Expression(literal: hint.rawValue) + } else { + hintExpr = nil + } + + let columnExpr: Expression? + if let columns = clause.columns { + columnExpr = "".wrap(", ".join(columns)) + } else { + columnExpr = nil + } + + let expressions: [Expressible?] = [ + clause.alias.tableName(), + columnExpr, + Expression(literal: "AS"), + hintExpr, + "".wrap(clause.query) as Expression + ] + + return " ".join(expressions.compactMap { $0 }) + }) + + return " ".join([ + Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), + innerClauses + ]) + } +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index b34570a7..f688cfe6 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -499,38 +499,6 @@ extension QueryType { return query } - // MARK: WITH - - /// Sets a `WITH` clause on the query. - /// - /// let users = Table("users") - /// let id = Expression("email") - /// let name = Expression("name") - /// - /// let userNames = Table("user_names") - /// userCategories.with(userNames, as: users.select(name)) - /// // WITH "user_names" as (SELECT "name" FROM "users") SELECT * FROM "user_names" - /// - /// - Parameters: - /// - /// - alias: A name to assign to the table expression. - /// - /// - recursive: Whether to evaluate the expression recursively. - /// - /// - hint: Provides a hint to the query planner for how the expression should be implemented. - /// - /// - subquery: A query that generates the rows for the table expression. - /// - /// - Returns: A query with the given `ORDER BY` clause applied. - public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, - hint: MaterializationHint? = nil, as subquery: QueryType) -> Self { - var query = self - let clause = WithClauses.Clause(alias: alias, columns: columns, hint: hint, query: subquery) - query.clauses.with.recursive = query.clauses.with.recursive || recursive - query.clauses.with.clauses.append(clause) - return query - } - // MARK: - Clauses // // MARK: SELECT @@ -638,43 +606,6 @@ extension QueryType { }) } - fileprivate var withClause: Expressible? { - guard !clauses.with.clauses.isEmpty else { - return nil - } - - let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in - let hintExpr: Expression? - if let hint = clause.hint { - hintExpr = Expression(literal: hint.rawValue) - } else { - hintExpr = nil - } - - let columnExpr: Expression? - if let columns = clause.columns { - columnExpr = "".wrap(", ".join(columns)) - } else { - columnExpr = nil - } - - let expressions: [Expressible?] = [ - clause.alias.tableName(), - columnExpr, - Expression(literal: "AS"), - hintExpr, - "".wrap(clause.query) as Expression - ] - - return " ".join(expressions.compactMap { $0 }) - }) - - return " ".join([ - Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), - innerClauses - ]) - } - // MARK: - public func alias(_ aliasName: String) -> Self { From ed43285b6e02a7244c86c8aa58d6069cf6ed803d Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 14 Jul 2022 20:55:31 -0700 Subject: [PATCH 039/216] Move WithClauses to Query+with.swift --- Sources/SQLite/Typed/Query+with.swift | 14 ++++++++++++++ Sources/SQLite/Typed/Query.swift | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/SQLite/Typed/Query+with.swift b/Sources/SQLite/Typed/Query+with.swift index 005f490c..697e77fd 100644 --- a/Sources/SQLite/Typed/Query+with.swift +++ b/Sources/SQLite/Typed/Query+with.swift @@ -93,3 +93,17 @@ extension QueryType { ]) } } + +struct WithClauses { + struct Clause { + var alias: Table + var columns: [Expressible]? + var hint: MaterializationHint? + var query: QueryType + } + /// The `RECURSIVE` flag is applied to the entire `WITH` clause + var recursive: Bool = false + + /// Each `WITH` clause may have multiple subclauses + var clauses: [Clause] = [] +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f688cfe6..b4f9f46f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1246,20 +1246,6 @@ public enum MaterializationHint: String { // MARK: - Private -struct WithClauses { - struct Clause { - var alias: Table - var columns: [Expressible]? - var hint: MaterializationHint? - var query: QueryType - } - /// The `RECURSIVE` flag is applied to the entire `WITH` clause - var recursive: Bool = false - - /// Each `WITH` clause may have multiple subclauses - var clauses: [Clause] = [] -} - public struct QueryClauses { var select = (distinct: false, columns: [Expression(literal: "*") as Expressible]) From 0dfc7b0452455b5ef0805939ed35cccd66f46429 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 15 Jul 2022 08:01:00 -0700 Subject: [PATCH 040/216] MaterializationHint > Query+with --- Sources/SQLite/Typed/Query+with.swift | 8 ++++++++ Sources/SQLite/Typed/Query.swift | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Typed/Query+with.swift b/Sources/SQLite/Typed/Query+with.swift index 697e77fd..d06c8896 100644 --- a/Sources/SQLite/Typed/Query+with.swift +++ b/Sources/SQLite/Typed/Query+with.swift @@ -94,6 +94,14 @@ extension QueryType { } } +/// Materialization hints for `WITH` clause +public enum MaterializationHint: String { + + case materialized = "MATERIALIZED" + + case notMaterialized = "NOT MATERIALIZED" +} + struct WithClauses { struct Clause { var alias: Table diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index b4f9f46f..cfa7544e 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1236,14 +1236,6 @@ public enum OnConflict: String { } -/// Materialization hints for `WITH` clause -public enum MaterializationHint: String { - - case materialized = "MATERIALIZED" - - case notMaterialized = "NOT MATERIALIZED" -} - // MARK: - Private public struct QueryClauses { From cfa6010717800bd3ed4bc28bb019223871188853 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 15 Jul 2022 19:28:56 -0700 Subject: [PATCH 041/216] Split test cases --- Tests/SQLiteTests/QueryTests.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index a96ff38c..eae1d923 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -61,6 +61,9 @@ class QueryTests: XCTestCase { func test_union_compilesUnionClause() { assertSQL("SELECT * FROM \"users\" UNION SELECT * FROM \"posts\"", users.union(posts)) + } + + func test_union_compilesUnionAllClause() { assertSQL("SELECT * FROM \"users\" UNION ALL SELECT * FROM \"posts\"", users.union(all: true, posts)) } @@ -232,21 +235,22 @@ class QueryTests: XCTestCase { temp.with(temp, as: users)) } - func test_with_recursive_compilesWithClause() { + func test_with_compilesWithRecursiveClause() { let temp = Table("temp") assertSQL("WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, recursive: true, as: users)) - - assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, recursive: false, as: users)) } - func test_with_materialization_compilesWithClause() { + func test_with_compilesWithMaterializedClause() { let temp = Table("temp") assertSQL("WITH \"temp\" AS MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, hint: .materialized, as: users)) + } + + func test_with_compilesWithNotMaterializedClause() { + let temp = Table("temp") assertSQL("WITH \"temp\" AS NOT MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, hint: .notMaterialized, as: users)) From ee905fe357157b6b48ed6641aa5abcf16a6ac354 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Jul 2022 23:27:17 +0200 Subject: [PATCH 042/216] Add tests for NSURL conformance --- Tests/SQLiteTests/FoundationTests.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 075e755b..453febcd 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -26,4 +26,15 @@ class FoundationTests: XCTestCase { XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } + func testURLFromString() { + let string = "http://foo.com" + let url = URL.fromDatatypeValue(string) + XCTAssertEqual(URL(string: string), url) + } + + func testStringFromURL() { + let url = URL(string: "http://foo.com")! + let string = url.datatypeValue + XCTAssertEqual("http://foo.com", string) + } } From 9d0c0b9f58c62099e767bf29a901879c3b8be668 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 15:49:56 +0200 Subject: [PATCH 043/216] Add support for ATTACH/DETACH (#30) --- Package.swift | 3 +- SQLite.xcodeproj/project.pbxproj | 28 ++ Sources/SQLite/Core/Connection+Attach.swift | 23 ++ Sources/SQLite/Core/Connection.swift | 16 +- Sources/SQLite/Core/URIQueryParameter.swift | 52 ++++ Tests/SQLiteTests/ConnectionTests.swift | 280 +++++++++++--------- Tests/SQLiteTests/Fixtures.swift | 12 +- Tests/SQLiteTests/ResultTests.swift | 52 ++++ Tests/SQLiteTests/TestHelpers.swift | 4 +- Tests/SQLiteTests/fixtures/test.sqlite | Bin 0 -> 12288 bytes 10 files changed, 330 insertions(+), 140 deletions(-) create mode 100644 Sources/SQLite/Core/Connection+Attach.swift create mode 100644 Sources/SQLite/Core/URIQueryParameter.swift create mode 100644 Tests/SQLiteTests/ResultTests.swift create mode 100644 Tests/SQLiteTests/fixtures/test.sqlite diff --git a/Package.swift b/Package.swift index 24898bca..1130d571 100644 --- a/Package.swift +++ b/Package.swift @@ -41,7 +41,8 @@ let package = Package( ], resources: [ .copy("fixtures/encrypted-3.x.sqlite"), - .copy("fixtures/encrypted-4.x.sqlite") + .copy("fixtures/encrypted-4.x.sqlite"), + .copy("fixtures/test.sqlite") ] ) ] diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fc38e5e9..4e01067f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -126,6 +126,17 @@ 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; + 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; + 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; + 3DF7B791288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B794288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 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 */; }; @@ -263,6 +274,9 @@ 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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; }; 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; + 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; + 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; + 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -461,6 +475,7 @@ D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, + 3DF7B78C28842C23005DD8CA /* ResultTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -479,6 +494,8 @@ 02A43A9722738CF100FEC494 /* Backup.swift */, 19A17E723300E5ED3771DCB5 /* Result.swift */, 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, + 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, + 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, ); path = Core; sourceTree = ""; @@ -847,12 +864,14 @@ 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */, 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, + 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */, 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */, 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */, 03A65E7F1C6BB2FB0062603F /* Collation.swift in Sources */, 03A65E861C6BB2FB0062603F /* Setter.swift in Sources */, + 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */, 03A65E811C6BB2FB0062603F /* CustomFunctions.swift in Sources */, 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */, 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, @@ -877,6 +896,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */, 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */, 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, @@ -910,6 +930,7 @@ buildActionMask = 2147483647; files = ( 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, + 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, @@ -925,6 +946,7 @@ 3D67B3F11DB246D100A4F4C6 /* CustomFunctions.swift in Sources */, 3D67B3F21DB246D100A4F4C6 /* Expression.swift in Sources */, 3D67B3F31DB246D100A4F4C6 /* Operators.swift in Sources */, + 3DF7B794288449BA005DD8CA /* URIQueryParameter.swift in Sources */, 3D67B3F41DB246D100A4F4C6 /* Query.swift in Sources */, 3D67B3F51DB246D100A4F4C6 /* Schema.swift in Sources */, 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */, @@ -946,12 +968,14 @@ 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B0A1C3F06E900AE3E12 /* RTree.swift in Sources */, EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */, + 3DF7B791288449BA005DD8CA /* URIQueryParameter.swift in Sources */, EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */, EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */, EE247B111C3F06E900AE3E12 /* Expression.swift in Sources */, EE247B0C1C3F06E900AE3E12 /* Helpers.swift in Sources */, EE247B0E1C3F06E900AE3E12 /* Collation.swift in Sources */, EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */, + 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B101C3F06E900AE3E12 /* CustomFunctions.swift in Sources */, EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */, EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, @@ -976,6 +1000,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */, EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, @@ -1012,12 +1037,14 @@ 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, + 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */, EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */, EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */, EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */, EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */, EE247B6E1C3F3FEC00AE3E12 /* Collation.swift in Sources */, EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */, + 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B701C3F3FEC00AE3E12 /* CustomFunctions.swift in Sources */, EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */, EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, @@ -1042,6 +1069,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */, EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift new file mode 100644 index 00000000..b9c08117 --- /dev/null +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -0,0 +1,23 @@ +import Foundation +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +extension Connection { + + /// See https://www3.sqlite.org/lang_attach.html + public func attach(_ location: Location, as schemaName: String) throws { + try run("ATTACH DATABASE ? AS ?", location.description, schemaName) + } + + /// See https://www3.sqlite.org/lang_detach.html + public func detach(_ schemaName: String) throws { + try run("DETACH DATABASE ?", schemaName) + } +} diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 72e8d857..60b9f1bd 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -55,7 +55,8 @@ public final class Connection { /// See: /// /// - Parameter filename: A URI filename - case uri(String) + /// - Parameter parameters: optional query parameters + case uri(String, parameters: [URIQueryParameter] = []) } /// An SQL operation passed to update callbacks. @@ -727,8 +728,17 @@ extension Connection.Location: CustomStringConvertible { return ":memory:" case .temporary: return "" - case .uri(let URI): - return URI + case let .uri(URI, parameters): + guard parameters.count > 0, + var components = URLComponents(string: URI) else { + return URI + } + components.queryItems = + (components.queryItems ?? []) + parameters.map(\.queryItem) + if components.scheme == nil { + components.scheme = "file" + } + return components.description } } diff --git a/Sources/SQLite/Core/URIQueryParameter.swift b/Sources/SQLite/Core/URIQueryParameter.swift new file mode 100644 index 00000000..c82a7cf7 --- /dev/null +++ b/Sources/SQLite/Core/URIQueryParameter.swift @@ -0,0 +1,52 @@ +import Foundation + +public enum URIQueryParameter: CustomStringConvertible { + public enum FileMode: String { + case readOnly = "ro", readWrite = "rw", readWriteCreate = "rwc", memory + } + + public enum CacheMode: String { + case shared, `private` + } + + /// The cache query parameter determines if the new database is opened using shared cache mode or with a private cache. + case cache(CacheMode) + + /// The immutable query parameter is a boolean that signals to SQLite that the underlying database file is held on read-only media + /// and cannot be modified, even by another process with elevated privileges. + case immutable(Bool) + + /// When creating a new database file during `sqlite3_open_v2()` on unix systems, SQLite will try to set the permissions of the new database + /// file to match the existing file "filename". + case modeOf(String) + + /// The mode query parameter determines if the new database is opened read-only, read-write, read-write and created if it does not exist, + /// or that the database is a pure in-memory database that never interacts with disk, respectively. + case mode(FileMode) + + /// The nolock query parameter is a boolean that disables all calls to the `xLock`, ` xUnlock`, and `xCheckReservedLock` methods + /// of the VFS when true. + case nolock(Bool) + + /// The psow query parameter overrides the `powersafe_overwrite` property of the database file being opened. + case psow(Bool) + + /// The vfs query parameter causes the database connection to be opened using the VFS called NAME. + case vfs(String) + + public var description: String { + queryItem.description + } + + var queryItem: URLQueryItem { + switch self { + case .cache(let mode): return .init(name: "cache", value: mode.rawValue) + case .immutable(let bool): return .init(name: "immutable", value: NSNumber(value: bool).description) + case .modeOf(let filename): return .init(name: "modeOf", value: filename) + case .mode(let fileMode): return .init(name: "mode", value: fileMode.rawValue) + case .nolock(let bool): return .init(name: "nolock", value: NSNumber(value: bool).description) + case .psow(let bool): return .init(name: "psow", value: NSNumber(value: bool).description) + case .vfs(let name): return .init(name: "vfs", value: name) + } + } +} diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 136271a4..cf51c104 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -20,39 +20,49 @@ class ConnectionTests: SQLiteTestCase { try createUsersTable() } - func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! Connection(.inMemory) + func test_init_withInMemory_returnsInMemoryConnection() throws { + let db = try Connection(.inMemory) XCTAssertEqual("", db.description) } - func test_init_returnsInMemoryByDefault() { - let db = try! Connection() + func test_init_returnsInMemoryByDefault() throws { + let db = try Connection() XCTAssertEqual("", db.description) } - func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! Connection(.temporary) + func test_init_withTemporary_returnsTemporaryConnection() throws { + let db = try Connection(.temporary) XCTAssertEqual("", db.description) } - func test_init_withURI_returnsURIConnection() { - let db = try! Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + func test_init_withURI_returnsURIConnection() throws { + let db = try Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) let url = URL(fileURLWithPath: db.description) XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } - func test_init_withString_returnsURIConnection() { - let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + func test_init_withString_returnsURIConnection() throws { + let db = try Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") let url = URL(fileURLWithPath: db.description) XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } + func testLocationWithoutUriParameters() { + let location: Connection.Location = .uri("foo") + XCTAssertEqual(location.description, "foo") + } + + func testLocationWithUriParameters() { + let location: Connection.Location = .uri("foo", parameters: [.mode(.readOnly), .cache(.private)]) + XCTAssertEqual(location.description, "file:foo?mode=ro&cache=private") + } + func test_readonly_returnsFalseOnReadWriteConnections() { XCTAssertFalse(db.readonly) } - func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = try! Connection(readonly: true) + func test_readonly_returnsTrueOnReadOnlyConnections() throws { + let db = try Connection(readonly: true) XCTAssertTrue(db.readonly) } @@ -60,14 +70,14 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(0, db.changes) } - func test_lastInsertRowid_returnsLastIdAfterInserts() { - try! insertUser("alice") + func test_lastInsertRowid_returnsLastIdAfterInserts() throws { + try insertUser("alice") XCTAssertEqual(1, db.lastInsertRowid) } - func test_lastInsertRowid_doesNotResetAfterError() { + func test_lastInsertRowid_doesNotResetAfterError() throws { XCTAssert(db.lastInsertRowid == 0) - try! insertUser("alice") + try insertUser("alice") XCTAssertEqual(1, db.lastInsertRowid) XCTAssertThrowsError( try db.run("INSERT INTO \"users\" (email, age, admin) values ('invalid@example.com', 12, 'invalid')") @@ -81,18 +91,18 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, db.lastInsertRowid) } - func test_changes_returnsNumberOfChanges() { - try! insertUser("alice") + func test_changes_returnsNumberOfChanges() throws { + try insertUser("alice") XCTAssertEqual(1, db.changes) - try! insertUser("betsy") + try insertUser("betsy") XCTAssertEqual(1, db.changes) } - func test_totalChanges_returnsTotalNumberOfChanges() { + func test_totalChanges_returnsTotalNumberOfChanges() throws { XCTAssertEqual(0, db.totalChanges) - try! insertUser("alice") + try insertUser("alice") XCTAssertEqual(1, db.totalChanges) - try! insertUser("betsy") + try insertUser("betsy") XCTAssertEqual(2, db.totalChanges) } @@ -101,53 +111,53 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.userVersion!) } - func test_prepare_preparesAndReturnsStatements() { - _ = try! db.prepare("SELECT * FROM users WHERE admin = 0") - _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", 0) - _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", [0]) - _ = try! db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + func test_prepare_preparesAndReturnsStatements() throws { + _ = try db.prepare("SELECT * FROM users WHERE admin = 0") + _ = try db.prepare("SELECT * FROM users WHERE admin = ?", 0) + _ = try db.prepare("SELECT * FROM users WHERE admin = ?", [0]) + _ = try db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) } - func test_run_preparesRunsAndReturnsStatements() { - try! db.run("SELECT * FROM users WHERE admin = 0") - try! db.run("SELECT * FROM users WHERE admin = ?", 0) - try! db.run("SELECT * FROM users WHERE admin = ?", [0]) - try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + func test_run_preparesRunsAndReturnsStatements() throws { + try db.run("SELECT * FROM users WHERE admin = 0") + try db.run("SELECT * FROM users WHERE admin = ?", 0) + try db.run("SELECT * FROM users WHERE admin = ?", [0]) + try db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) assertSQL("SELECT * FROM users WHERE admin = 0", 4) } - func test_vacuum() { - try! db.vacuum() + func test_vacuum() throws { + try db.vacuum() } - func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + func test_scalar_preparesRunsAndReturnsScalarValues() throws { + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) assertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } - func test_execute_comment() { - try! db.run("-- this is a comment\nSELECT 1") + func test_execute_comment() throws { + try db.run("-- this is a comment\nSELECT 1") assertSQL("-- this is a comment", 0) assertSQL("SELECT 1", 0) } - func test_transaction_executesBeginDeferred() { - try! db.transaction(.deferred) {} + func test_transaction_executesBeginDeferred() throws { + try db.transaction(.deferred) {} assertSQL("BEGIN DEFERRED TRANSACTION") } - func test_transaction_executesBeginImmediate() { - try! db.transaction(.immediate) {} + func test_transaction_executesBeginImmediate() throws { + try db.transaction(.immediate) {} assertSQL("BEGIN IMMEDIATE TRANSACTION") } - func test_transaction_executesBeginExclusive() { - try! db.transaction(.exclusive) {} + func test_transaction_executesBeginExclusive() throws { + try db.transaction(.exclusive) {} assertSQL("BEGIN EXCLUSIVE TRANSACTION") } @@ -164,10 +174,10 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) } - func test_transaction_beginsAndCommitsTransactions() { - let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + func test_transaction_beginsAndCommitsTransactions() throws { + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") - try! db.transaction { + try db.transaction { try stmt.run() } @@ -177,8 +187,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("ROLLBACK TRANSACTION", 0) } - func test_transaction_rollsBackTransactionsIfCommitsFail() { - let sqliteVersion = String(describing: try! db.scalar("SELECT sqlite_version()")!) + func test_transaction_rollsBackTransactionsIfCommitsFail() throws { + let sqliteVersion = String(describing: try db.scalar("SELECT sqlite_version()")!) .split(separator: ".").compactMap { Int($0) } // PRAGMA defer_foreign_keys only supported in SQLite >= 3.8.0 guard sqliteVersion[0] == 3 && sqliteVersion[1] >= 8 else { @@ -187,9 +197,9 @@ 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) + 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 { @@ -209,14 +219,14 @@ class ConnectionTests: SQLiteTestCase { // 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 { + 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") + func test_transaction_beginsAndRollsTransactionsBack() throws { + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { try db.transaction { @@ -232,8 +242,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("COMMIT TRANSACTION", 0) } - func test_savepoint_beginsAndCommitsSavepoints() { - try! db.savepoint("1") { + func test_savepoint_beginsAndCommitsSavepoints() throws { + try db.savepoint("1") { try db.savepoint("2") { try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") } @@ -248,8 +258,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("ROLLBACK TO SAVEPOINT '1'", 0) } - func test_savepoint_beginsAndRollsSavepointsBack() { - let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + func test_savepoint_beginsAndRollsSavepointsBack() throws { + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { try db.savepoint("1") { @@ -276,8 +286,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("RELEASE SAVEPOINT '1'", 0) } - func test_updateHook_setsUpdateHook_withInsert() { - async { done in + func test_updateHook_setsUpdateHook_withInsert() throws { + try async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.insert, operation) XCTAssertEqual("main", db) @@ -285,13 +295,13 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! insertUser("alice") + try insertUser("alice") } } - func test_updateHook_setsUpdateHook_withUpdate() { - try! insertUser("alice") - async { done in + func test_updateHook_setsUpdateHook_withUpdate() throws { + try insertUser("alice") + try async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.update, operation) XCTAssertEqual("main", db) @@ -299,13 +309,13 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! db.run("UPDATE users SET email = 'alice@example.com'") + try db.run("UPDATE users SET email = 'alice@example.com'") } } - func test_updateHook_setsUpdateHook_withDelete() { - try! insertUser("alice") - async { done in + func test_updateHook_setsUpdateHook_withDelete() throws { + try insertUser("alice") + try async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.delete, operation) XCTAssertEqual("main", db) @@ -313,24 +323,24 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! db.run("DELETE FROM users WHERE id = 1") + try db.run("DELETE FROM users WHERE id = 1") } } - func test_commitHook_setsCommitHook() { - async { done in + func test_commitHook_setsCommitHook() throws { + try async { done in db.commitHook { done() } - try! db.transaction { + try db.transaction { try insertUser("alice") } - XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(1, try db.scalar("SELECT count(*) FROM users") as? Int64) } } - func test_rollbackHook_setsRollbackHook() { - async { done in + func test_rollbackHook_setsRollbackHook() throws { + try async { done in db.rollbackHook(done) do { try db.transaction { @@ -339,12 +349,12 @@ class ConnectionTests: SQLiteTestCase { } } catch { } - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users") as? Int64) } } - func test_commitHook_withRollback_rollsBack() { - async { done in + func test_commitHook_withRollback_rollsBack() throws { + try async { done in db.commitHook { throw NSError(domain: "com.stephencelis.SQLiteTests", code: 1, userInfo: nil) } @@ -355,41 +365,41 @@ class ConnectionTests: SQLiteTestCase { } } catch { } - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users") as? Int64) } } // https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) - func test_createFunction_withArrayArguments() { + func test_createFunction_withArrayArguments() throws { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", try! db.scalar("SELECT hello('world')") as? String) - XCTAssert(try! db.scalar("SELECT hello(NULL)") == nil) + XCTAssertEqual("Hello, world!", try db.scalar("SELECT hello('world')") as? String) + XCTAssert(try db.scalar("SELECT hello(NULL)") == nil) } - func test_createFunction_createsQuotableFunction() { + func test_createFunction_createsQuotableFunction() throws { db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", try! db.scalar("SELECT \"hello world\"('world')") as? String) - XCTAssert(try! db.scalar("SELECT \"hello world\"(NULL)") == nil) + XCTAssertEqual("Hello, world!", try db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(try db.scalar("SELECT \"hello world\"(NULL)") == nil) } - func test_createCollation_createsCollation() { - try! db.createCollation("NODIACRITIC") { lhs, rhs in + func test_createCollation_createsCollation() throws { + try db.createCollation("NODIACRITIC") { lhs, rhs in lhs.compare(rhs, options: .diacriticInsensitive) } - XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + XCTAssertEqual(1, try db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } - func test_createCollation_createsQuotableCollation() { - try! db.createCollation("NO DIACRITIC") { lhs, rhs in + func test_createCollation_createsQuotableCollation() throws { + try db.createCollation("NO DIACRITIC") { lhs, rhs in lhs.compare(rhs, options: .diacriticInsensitive) } - XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + XCTAssertEqual(1, try db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } - func test_interrupt_interruptsLongRunningQuery() { + func test_interrupt_interruptsLongRunningQuery() throws { let semaphore = DispatchSemaphore(value: 0) db.createFunction("sleep") { _ in DispatchQueue.global(qos: .background).async { @@ -399,7 +409,7 @@ class ConnectionTests: SQLiteTestCase { semaphore.wait() return nil } - let stmt = try! db.prepare("SELECT sleep()") + let stmt = try db.prepare("SELECT sleep()") XCTAssertThrowsError(try stmt.run()) { error in if case Result.error(_, let code, _) = error { XCTAssertEqual(code, SQLITE_INTERRUPT) @@ -410,13 +420,13 @@ class ConnectionTests: SQLiteTestCase { } #endif - func test_concurrent_access_single_connection() { + func test_concurrent_access_single_connection() throws { // test can fail on iOS/tvOS 9.x: SQLite compile-time differences? guard #available(iOS 10.0, OSX 10.10, tvOS 10.0, watchOS 2.2, *) else { return } - 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 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 queue = DispatchQueue(label: "Readers", attributes: [.concurrent]) let nReaders = 5 @@ -430,43 +440,51 @@ class ConnectionTests: SQLiteTestCase { } semaphores.forEach { $0.wait() } } -} -class ResultTests: XCTestCase { - let connection = try! Connection(.inMemory) + func test_attach_detach_memory_database() throws { + let schemaName = "test" - func test_init_with_ok_code_returns_nil() { - XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?) - } + try db.attach(.inMemory, as: schemaName) - func test_init_with_row_code_returns_nil() { - XCTAssertNil(Result(errorCode: SQLITE_ROW, connection: connection, statement: nil) as Result?) - } + let table = Table("attached_users", database: schemaName) + let name = Expression("string") - func test_init_with_done_code_returns_nil() { - XCTAssertNil(Result(errorCode: SQLITE_DONE, connection: connection, statement: nil) as Result?) - } + // create a table, insert some data + try db.run(table.create { builder in + builder.column(name) + }) + _ = try db.run(table.insert(name <- "test")) - func test_init_with_other_code_returns_error() { - if case .some(.error(let message, let code, let statement)) = - Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { - XCTAssertEqual("not an error", message) - XCTAssertEqual(SQLITE_MISUSE, code) - XCTAssertNil(statement) - XCTAssert(connection === connection) - } else { - XCTFail("no error") - } + // query data + let rows = try db.prepare(table.select(name)).map { $0[name] } + XCTAssertEqual(["test"], rows) + + try db.detach(schemaName) } - func test_description_contains_error_code() { - XCTAssertEqual("not an error (code: 21)", - Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description) + func test_attach_detach_file_database() throws { + let schemaName = "test" + let testDb = fixture("test", withExtension: "sqlite") + + try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) + + let table = Table("tests", database: schemaName) + let email = Expression("email") + + let rows = try db.prepare(table.select(email)).map { $0[email] } + XCTAssertEqual(["foo@bar.com"], rows) + + try db.detach(schemaName) } - func test_description_contains_statement_and_error_code() { - let statement = try! Statement(connection, "SELECT 1") - XCTAssertEqual("not an error (SELECT 1) (code: 21)", - Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) + func test_detach_invalid_schema_name_errors_with_no_such_database() throws { + XCTAssertThrowsError(try db.detach("no-exist")) { error in + if case let Result.error(message, code, _) = error { + XCTAssertEqual(code, SQLITE_ERROR) + XCTAssertEqual("no such database: no-exist", message) + } else { + XCTFail("unexpected error: \(error)") + } + } } } diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index d0683130..f3851cba 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -2,7 +2,13 @@ import Foundation func fixture(_ name: String, withExtension: String?) -> String { let testBundle = Bundle(for: SQLiteTestCase.self) - return testBundle.url( - forResource: name, - withExtension: withExtension)!.path + + for resource in [name, "fixtures/\(name)"] { + if let url = testBundle.url( + forResource: resource, + withExtension: withExtension) { + return url.path + } + } + fatalError("Cannot find \(name).\(withExtension ?? "")") } diff --git a/Tests/SQLiteTests/ResultTests.swift b/Tests/SQLiteTests/ResultTests.swift new file mode 100644 index 00000000..ccedcfe2 --- /dev/null +++ b/Tests/SQLiteTests/ResultTests.swift @@ -0,0 +1,52 @@ +import XCTest +import Foundation +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class ResultTests: XCTestCase { + let connection = try! Connection(.inMemory) + + func test_init_with_ok_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?) + } + + func test_init_with_row_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_ROW, connection: connection, statement: nil) as Result?) + } + + func test_init_with_done_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_DONE, connection: connection, statement: nil) as Result?) + } + + func test_init_with_other_code_returns_error() { + if case .some(.error(let message, let code, let statement)) = + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { + XCTAssertEqual("not an error", message) + XCTAssertEqual(SQLITE_MISUSE, code) + XCTAssertNil(statement) + XCTAssert(connection === connection) + } else { + XCTFail("no error") + } + } + + func test_description_contains_error_code() { + XCTAssertEqual("not an error (code: 21)", + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description) + } + + func test_description_contains_statement_and_error_code() { + let statement = try! Statement(connection, "SELECT 1") + XCTAssertEqual("not an error (SELECT 1) (code: 21)", + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) + } +} diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index f43310c9..6e8e9ebb 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -66,9 +66,9 @@ class SQLiteTestCase: XCTestCase { // if let count = trace[SQL] { trace[SQL] = count - 1 } // } - func async(expect description: String = "async", timeout: Double = 5, block: (@escaping () -> Void) -> Void) { + func async(expect description: String = "async", timeout: Double = 5, block: (@escaping () -> Void) throws -> Void) throws { let expectation = self.expectation(description: description) - block({ expectation.fulfill() }) + try block({ expectation.fulfill() }) waitForExpectations(timeout: timeout, handler: nil) } diff --git a/Tests/SQLiteTests/fixtures/test.sqlite b/Tests/SQLiteTests/fixtures/test.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..7b39b423d7afdd43d0d793bb0c87de0248b8910d GIT binary patch literal 12288 zcmeI$O-sWt7zgktUGWWOcgyvGT^x*o>@2KG&|f4x7qKs;6d&VC&-1iO*VVeItcy^& zk>bcb_Z2roC)t9%AX1kqM`Qr)@1Da90%SsRzud$X}f7K zu=eDc6qLO#()aaSZf^F8-c)7toit}8r%hAe Date: Sun, 17 Jul 2022 16:07:32 +0200 Subject: [PATCH 044/216] Remove force tries in tests --- Tests/SQLiteTests/CipherTests.swift | 52 +++++------ .../SQLiteTests/CustomAggregationTests.swift | 22 ++--- Tests/SQLiteTests/CustomFunctionsTests.swift | 90 +++++++++---------- Tests/SQLiteTests/FTS4Tests.swift | 10 +-- Tests/SQLiteTests/QueryIntegrationTests.swift | 76 ++++++++-------- Tests/SQLiteTests/QueryTests.swift | 12 +-- Tests/SQLiteTests/ResultTests.swift | 10 ++- Tests/SQLiteTests/RowTests.swift | 16 ++-- Tests/SQLiteTests/SelectTests.swift | 8 +- Tests/SQLiteTests/StatementTests.swift | 24 ++--- Tests/SQLiteTests/TestHelpers.swift | 4 +- 11 files changed, 164 insertions(+), 160 deletions(-) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index 0995fca0..f25dbe5b 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -5,8 +5,8 @@ import SQLCipher class CipherTests: XCTestCase { - let db1 = try! Connection() - let db2 = try! Connection() + let db1 = try Connection() + let db2 = try Connection() override func setUpWithError() throws { // db @@ -26,41 +26,41 @@ class CipherTests: XCTestCase { try super.setUpWithError() } - func test_key() { - XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) + func test_key() throws { + XCTAssertEqual(1, try db1.scalar("SELECT count(*) FROM foo") as? Int64) } func test_key_blob_literal() { - let db = try! Connection() - try! db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'") + let db = try Connection() + try db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'") } - func test_rekey() { - try! db1.rekey("goodbye") - XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) + func test_rekey() throws { + try db1.rekey("goodbye") + XCTAssertEqual(1, try db1.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_data_key() { - XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) + func test_data_key() throws { + XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_data_rekey() { + func test_data_rekey() throws { let newKey = keyData() - try! db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) - XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) + try db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) + XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_keyFailure() { + func test_keyFailure() throws { let path = "\(NSTemporaryDirectory())/db.sqlite3" _ = try? FileManager.default.removeItem(atPath: path) - let connA = try! Connection(path) - defer { try! FileManager.default.removeItem(atPath: path) } + let connA = try Connection(path) + defer { try FileManager.default.removeItem(atPath: path) } - try! connA.key("hello") - try! connA.run("CREATE TABLE foo (bar TEXT)") + try connA.key("hello") + try connA.run("CREATE TABLE foo (bar TEXT)") - let connB = try! Connection(path, readonly: true) + let connB = try Connection(path, readonly: true) do { try connB.key("world") @@ -72,7 +72,7 @@ class CipherTests: XCTestCase { } } - func test_open_db_encrypted_with_sqlcipher() { + func test_open_db_encrypted_with_sqlcipher() throws { // $ sqlcipher Tests/SQLiteTests/fixtures/encrypted-[version].x.sqlite // sqlite> pragma key = 'sqlcipher-test'; // sqlite> CREATE TABLE foo (bar TEXT); @@ -85,17 +85,17 @@ class CipherTests: XCTestCase { fixture("encrypted-3.x", withExtension: "sqlite") : fixture("encrypted-4.x", withExtension: "sqlite") - try! FileManager.default.setAttributes([FileAttributeKey.immutable: 1], ofItemAtPath: encryptedFile) + try FileManager.default.setAttributes([FileAttributeKey.immutable: 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) defer { // ensure file can be cleaned up afterwards - try! FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) + try FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) } - let conn = try! Connection(encryptedFile) - try! conn.key("sqlcipher-test") - XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64) + let conn = try Connection(encryptedFile) + try conn.key("sqlcipher-test") + XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) } private func keyData(length: Int = 64) -> NSMutableData { diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 5cbfbbef..71cbba9c 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -25,7 +25,7 @@ class CustomAggregationTests: SQLiteTestCase { try insertUser("Eve", age: 28, admin: false) } - func testUnsafeCustomSum() { + func testUnsafeCustomSum() throws { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v @@ -43,7 +43,7 @@ class CustomAggregationTests: SQLiteTestCase { v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM1(age) AS s FROM users") + let result = try db.prepare("SELECT mySUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 @@ -51,7 +51,7 @@ class CustomAggregationTests: SQLiteTestCase { } } - func testUnsafeCustomSumGrouping() { + func testUnsafeCustomSumGrouping() throws { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v @@ -68,19 +68,19 @@ class CustomAggregationTests: SQLiteTestCase { v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM2(age) AS s FROM users GROUP BY admin ORDER BY s") + let result = try db.prepare("SELECT mySUM2(age) AS s FROM users GROUP BY admin ORDER BY s") let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([28, 55])) } - func testCustomSum() { + func testCustomSum() throws { let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) - let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") + let result = try db.prepare("SELECT myReduceSUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 @@ -88,26 +88,26 @@ class CustomAggregationTests: SQLiteTestCase { } } - func testCustomSumGrouping() { + func testCustomSumGrouping() throws { let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) - let result = try! db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") + let result = try db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([3028, 3055])) } - func testCustomStringAgg() { + func testCustomStringAgg() throws { let initial = String(repeating: " ", count: 64) let reduce: (String, [Binding?]) -> String = { (last, bindings) in let v = (bindings[0] as? String) ?? "" return last + v } db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) - let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") + let result = try db.prepare("SELECT myReduceSUM3(email) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -116,7 +116,7 @@ class CustomAggregationTests: SQLiteTestCase { } } - func testCustomObjectSum() { + func testCustomObjectSum() throws { { let initial = TestObject(value: 1000) let reduce: (TestObject, [Binding?]) -> TestObject = { (last, bindings) in diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 3d897f8e..c4eb51e6 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -8,19 +8,19 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> Expression typealias FunctionResultOptional = () -> Expression - func testFunctionNoOptional() { - let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { + func testFunctionNoOptional() throws { + let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { "a" } - let result = try! db.prepare("SELECT test()").scalar() as! String + let result = try db.prepare("SELECT test()").scalar() as! String XCTAssertEqual("a", result) } - func testFunctionResultOptional() { - let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { + func testFunctionResultOptional() throws { + let _: FunctionResultOptional = try db.createFunction("test", deterministic: true) { "a" } - let result = try! db.prepare("SELECT test()").scalar() as! String? + let result = try db.prepare("SELECT test()").scalar() as! String? XCTAssertEqual("a", result) } } @@ -31,35 +31,35 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { typealias FunctionResultOptional = (Expression) -> Expression typealias FunctionLeftResultOptional = (Expression) -> Expression - func testFunctionNoOptional() { - let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a in + func testFunctionNoOptional() throws { + let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a in "b" + a } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } - func testFunctionLeftOptional() { - let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a in + func testFunctionLeftOptional() throws { + let _: FunctionLeftOptional = try db.createFunction("test", deterministic: true) { a in "b" + a! } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } - func testFunctionResultOptional() { - let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a in + func testFunctionResultOptional() throws { + let _: FunctionResultOptional = try db.createFunction("test", deterministic: true) { a in "b" + a } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } - func testFunctionLeftResultOptional() { - let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a: String?) -> String? in + func testFunctionLeftResultOptional() throws { + let _: FunctionLeftResultOptional = try db.createFunction("test", deterministic: true) { (a: String?) -> String? in "b" + a! } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } } @@ -74,76 +74,76 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { typealias FunctionRightResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression - func testNoOptional() { - let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testNoOptional() throws { + let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a, b in a + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testLeftOptional() { - let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testLeftOptional() throws { + let _: FunctionLeftOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testRightOptional() { - let _: FunctionRightOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testRightOptional() throws { + let _: FunctionRightOptional = try db.createFunction("test", deterministic: true) { a, b in a + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testResultOptional() { - let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testResultOptional() throws { + let _: FunctionResultOptional = try db.createFunction("test", deterministic: true) { a, b in a + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } - func testFunctionLeftRightOptional() { - let _: FunctionLeftRightOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionLeftRightOptional() throws { + let _: FunctionLeftRightOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testFunctionLeftResultOptional() { - let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionLeftResultOptional() throws { + let _: FunctionLeftResultOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } - func testFunctionRightResultOptional() { - let _: FunctionRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionRightResultOptional() throws { + let _: FunctionRightResultOptional = try db.createFunction("test", deterministic: true) { a, b in a + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } - func testFunctionLeftRightResultOptional() { - let _: FunctionLeftRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionLeftRightResultOptional() throws { + let _: FunctionLeftRightResultOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } } class CustomFunctionTruncation: SQLiteTestCase { // https://github.com/stephencelis/SQLite.swift/issues/468 - func testStringTruncation() { - _ = try! db.createFunction("customLower") { (value: String) in value.lowercased() } - let result = try! db.prepare("SELECT customLower(?)").scalar("TÖL-AA 12") as? String + func testStringTruncation() throws { + _ = try db.createFunction("customLower") { (value: String) in value.lowercased() } + let result = try db.prepare("SELECT customLower(?)").scalar("TÖL-AA 12") as? String XCTAssertEqual("töl-aa 12", result) } } diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index d6385b0c..aa6ac41f 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -191,7 +191,7 @@ class FTS4ConfigTests: XCTestCase { class FTS4IntegrationTests: SQLiteTestCase { #if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER - func test_registerTokenizer_registersTokenizer() { + func test_registerTokenizer_registersTokenizer() throws { let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") @@ -200,7 +200,7 @@ class FTS4IntegrationTests: SQLiteTestCase { let tokenizerName = "tokenizer" let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) - try! db.registerTokenizer(tokenizerName) { string in + try db.registerTokenizer(tokenizerName) { string in CFStringTokenizerSetString(tokenizer, string as CFString, CFRangeMake(0, CFStringGetLength(string as CFString))) if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { @@ -214,14 +214,14 @@ class FTS4IntegrationTests: SQLiteTestCase { return (token as String, string.range(of: input as String)!) } - try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) + try db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) assertSQL(""" CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\") """.replacingOccurrences(of: "\n", with: "")) - try! _ = db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) + try _ = db.run(emails.insert(subject <- "Aún más cáfe!")) + XCTAssertEqual(1, try db.scalar(emails.filter(emails.match("aun")).count)) } #endif } diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index db4e2e4e..27b78c0a 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -23,45 +23,45 @@ class QueryIntegrationTests: SQLiteTestCase { // MARK: - - func test_select() { + func test_select() throws { 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)) + 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])) { + for user in try db.prepare(users.join(managers, on: managers[id] == users[managerId])) { _ = user[users[managerId]] } } - func test_prepareRowIterator() { + func test_prepareRowIterator() throws { let names = ["a", "b", "c"] - try! insertUsers(names) + try insertUsers(names) let emailColumn = Expression("email") - let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } + let emails = try db.prepareRowIterator(users).map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } - func test_ambiguousMap() { + func test_ambiguousMap() throws { let names = ["a", "b", "c"] - try! insertUsers(names) + try insertUsers(names) - let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } + 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() { + func test_select_optional() throws { 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)) + 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])) { + for user in try db.prepare(users.join(managers, on: managers[id] == users[managerId])) { _ = user[users[managerId]] } } @@ -107,26 +107,26 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertNil(values[0].sub?.sub) } - func test_scalar() { - XCTAssertEqual(0, try! db.scalar(users.count)) - XCTAssertEqual(false, try! db.scalar(users.exists)) + func test_scalar() throws { + XCTAssertEqual(0, try db.scalar(users.count)) + XCTAssertEqual(false, try db.scalar(users.exists)) - try! insertUsers("alice") - XCTAssertEqual(1, try! db.scalar(users.select(id.average))) + try insertUsers("alice") + XCTAssertEqual(1, try db.scalar(users.select(id.average))) } - func test_pluck() { - let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, try! db.pluck(users)![id]) + func test_pluck() throws { + let rowid = try db.run(users.insert(email <- "alice@example.com")) + XCTAssertEqual(rowid, try db.pluck(users)![id]) } - func test_insert() { - let id = try! db.run(users.insert(email <- "alice@example.com")) + func test_insert() throws { + let id = try db.run(users.insert(email <- "alice@example.com")) XCTAssertEqual(1, id) } - func test_insert_many() { - let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) + func test_insert_many() throws { + let id = try db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) XCTAssertEqual(2, id) } @@ -167,13 +167,13 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(42, try fetchAge()) } - func test_update() { - let changes = try! db.run(users.update(email <- "alice@example.com")) + func test_update() throws { + let changes = try db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) } - func test_delete() { - let changes = try! db.run(users.delete()) + func test_delete() throws { + let changes = try db.run(users.delete()) XCTAssertEqual(0, changes) } @@ -200,8 +200,8 @@ class QueryIntegrationTests: SQLiteTestCase { func test_no_such_column() throws { let doesNotExist = Expression("doesNotExist") - try! insertUser("alice") - let row = try! db.pluck(users.filter(email == "alice@example.com"))! + 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 { @@ -212,8 +212,8 @@ class QueryIntegrationTests: SQLiteTestCase { } } - func test_catchConstraintError() { - try! db.run(users.insert(email <- "alice@example.com")) + func test_catchConstraintError() throws { + try db.run(users.insert(email <- "alice@example.com")) do { try db.run(users.insert(email <- "alice@example.com")) XCTFail("expected error") @@ -231,19 +231,19 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(1, result.count) } - func test_with_recursive() { + func test_with_recursive() throws { let nodes = Table("nodes") let id = Expression("id") let parent = Expression("parent") let value = Expression("value") - try! db.run(nodes.create { builder in + try db.run(nodes.create { builder in builder.column(id) builder.column(parent) builder.column(value) }) - try! db.run(nodes.insertMany([ + try db.run(nodes.insertMany([ [id <- 0, parent <- nil, value <- 2], [id <- 1, parent <- 0, value <- 4], [id <- 2, parent <- 0, value <- 9], @@ -254,7 +254,7 @@ class QueryIntegrationTests: SQLiteTestCase { // Compute the sum of the values of node 5 and its ancestors let ancestors = Table("ancestors") - let sum = try! db.scalar( + let sum = try db.scalar( ancestors .select(value.sum) .with(ancestors, diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index eae1d923..82a33808 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -375,25 +375,25 @@ class QueryTests: XCTestCase { } #endif - func test_insert_and_search_for_UUID() { + func test_insert_and_search_for_UUID() throws { struct Test: Codable { var uuid: UUID var string: String } let testUUID = UUID() let testValue = Test(uuid: testUUID, string: "value") - let db = try! Connection(.temporary) - try! db.run(table.create { t in + let db = try Connection(.temporary) + try db.run(table.create { t in t.column(uuid) t.column(string) } ) - let iQuery = try! table.insert(testValue) - try! db.run(iQuery) + let iQuery = try table.insert(testValue) + try db.run(iQuery) let fQuery = table.filter(uuid == testUUID) - if let result = try! db.pluck(fQuery) { + if let result = try db.pluck(fQuery) { let testValueReturned = Test(uuid: result[uuid], string: result[string]) XCTAssertEqual(testUUID, testValueReturned.uuid) } else { diff --git a/Tests/SQLiteTests/ResultTests.swift b/Tests/SQLiteTests/ResultTests.swift index ccedcfe2..fab4a0bb 100644 --- a/Tests/SQLiteTests/ResultTests.swift +++ b/Tests/SQLiteTests/ResultTests.swift @@ -13,7 +13,11 @@ import SQLite3 #endif class ResultTests: XCTestCase { - let connection = try! Connection(.inMemory) + var connection: Connection! + + override func setUpWithError() throws { + connection = try Connection(.inMemory) + } func test_init_with_ok_code_returns_nil() { XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?) @@ -44,8 +48,8 @@ class ResultTests: XCTestCase { Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description) } - func test_description_contains_statement_and_error_code() { - let statement = try! Statement(connection, "SELECT 1") + func test_description_contains_statement_and_error_code() throws { + let statement = try Statement(connection, "SELECT 1") XCTAssertEqual("not an error (SELECT 1) (code: 21)", Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) } diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift index 36721f80..506f6b10 100644 --- a/Tests/SQLiteTests/RowTests.swift +++ b/Tests/SQLiteTests/RowTests.swift @@ -3,9 +3,9 @@ import XCTest class RowTests: XCTestCase { - public func test_get_value() { + public func test_get_value() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertEqual("value", result) } @@ -17,9 +17,9 @@ class RowTests: XCTestCase { XCTAssertEqual("value", result) } - public func test_get_value_optional() { + public func test_get_value_optional() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertEqual("value", result) } @@ -31,9 +31,9 @@ class RowTests: XCTestCase { XCTAssertEqual("value", result) } - public func test_get_value_optional_nil() { + public func test_get_value_optional_nil() throws { let row = Row(["\"foo\"": 0], [nil]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertNil(result) } @@ -56,9 +56,9 @@ class RowTests: XCTestCase { } } - public func test_get_type_mismatch_optional_returns_nil() { + public func test_get_type_mismatch_optional_returns_nil() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertNil(result) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift index d1126b66..52d5bb6b 100644 --- a/Tests/SQLiteTests/SelectTests.swift +++ b/Tests/SQLiteTests/SelectTests.swift @@ -20,7 +20,7 @@ class SelectTests: SQLiteTestCase { ) } - func test_select_columns_from_multiple_tables() { + func test_select_columns_from_multiple_tables() throws { let usersData = Table("users_name") let users = Table("users") @@ -29,14 +29,14 @@ class SelectTests: SQLiteTestCase { let userID = Expression("user_id") let email = Expression("email") - try! insertUser("Joey") - try! db.run(usersData.insert( + try insertUser("Joey") + try db.run(usersData.insert( id <- 1, userID <- 1, name <- "Joey" )) - try! db.prepare(users.select(name, email).join(usersData, on: userID == users[id])).forEach { + try db.prepare(users.select(name, email).join(usersData, on: userID == users[id])).forEach { XCTAssertEqual($0[name], "Joey") XCTAssertEqual($0[email], "Joey@example.com") } diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index aa05de34..3286b792 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -7,30 +7,30 @@ class StatementTests: SQLiteTestCase { try createUsersTable() } - func test_cursor_to_blob() { - try! insertUsers("alice") - let statement = try! db.prepare("SELECT email FROM users") - XCTAssert(try! statement.step()) + func test_cursor_to_blob() throws { + try insertUsers("alice") + let statement = try db.prepare("SELECT email FROM users") + XCTAssert(try statement.step()) let blob = statement.row[0] as Blob XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) } - func test_zero_sized_blob_returns_null() { + func test_zero_sized_blob_returns_null() throws { let blobs = Table("blobs") let blobColumn = Expression("blob_column") - try! db.run(blobs.create { $0.column(blobColumn) }) - try! db.run(blobs.insert(blobColumn <- Blob(bytes: []))) - let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) + try db.run(blobs.create { $0.column(blobColumn) }) + try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) + let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) XCTAssertEqual([], blobValue.bytes) } - func test_prepareRowIterator() { + func test_prepareRowIterator() throws { let names = ["a", "b", "c"] - try! insertUsers(names) + try insertUsers(names) let emailColumn = Expression("email") - let statement = try! db.prepare("SELECT email FROM users") - let emails = try! statement.prepareRowIterator().map { $0[emailColumn] } + let statement = try db.prepare("SELECT email FROM users") + let emails = try statement.prepareRowIterator().map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 6e8e9ebb..2a8c7e39 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -54,8 +54,8 @@ class SQLiteTestCase: XCTestCase { ) } - func assertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { - try! statement.run() + func assertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) throws { + try statement.run() assertSQL(SQL, 1, message, file: file, line: line) if let count = trace[SQL] { trace[SQL] = count - 1 } } From 44383dfe5f4d9c4b07a5f921f153e051e82d32a4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 16:21:17 +0200 Subject: [PATCH 045/216] Fix bundle lookup --- Tests/SQLiteTests/Fixtures.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index f3851cba..1306aa5a 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -1,7 +1,11 @@ import Foundation func fixture(_ name: String, withExtension: String?) -> String { + #if SWIFT_PACKAGE + let testBundle = Bundle.module + #else let testBundle = Bundle(for: SQLiteTestCase.self) + #endif for resource in [name, "fixtures/\(name)"] { if let url = testBundle.url( From 96b014df6095a7434d38950fb819f945d1222191 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 18:14:08 +0200 Subject: [PATCH 046/216] Always enable URI filenames https://sqlite.org/uri.html --- Sources/SQLite/Core/Connection.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 60b9f1bd..35edfce9 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -105,7 +105,10 @@ public final class Connection { /// - Returns: A new database connection. public init(_ location: Location = .inMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + try check(sqlite3_open_v2(location.description, + &_handle, + flags | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI, + nil)) queue.setSpecific(key: Connection.queueKey, value: queueContext) } From 41ce3784a0a521aecb309bc7fbc24fa5017a3daf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 19:06:11 +0200 Subject: [PATCH 047/216] Move fixtures to more default `Resources` --- Package.swift | 19 ++++++++++-------- SQLite.swift.podspec | 6 +++--- SQLite.xcodeproj/project.pbxproj | 16 +++++++-------- Sources/SQLite/Core/Connection.swift | 2 +- Tests/SQLiteTests/CipherTests.swift | 15 +++++++------- Tests/SQLiteTests/ConnectionTests.swift | 9 +++++++-- Tests/SQLiteTests/Fixtures.swift | 2 +- .../encrypted-3.x.sqlite | Bin .../encrypted-4.x.sqlite | Bin .../{fixtures => Resources}/test.sqlite | Bin 10 files changed, 39 insertions(+), 30 deletions(-) rename Tests/SQLiteTests/{fixtures => Resources}/encrypted-3.x.sqlite (100%) rename Tests/SQLiteTests/{fixtures => Resources}/encrypted-4.x.sqlite (100%) rename Tests/SQLiteTests/{fixtures => Resources}/test.sqlite (100%) diff --git a/Package.swift b/Package.swift index 1130d571..b32fd881 100644 --- a/Package.swift +++ b/Package.swift @@ -40,9 +40,7 @@ let package = Package( "Info.plist" ], resources: [ - .copy("fixtures/encrypted-3.x.sqlite"), - .copy("fixtures/encrypted-4.x.sqlite"), - .copy("fixtures/test.sqlite") + .copy("Resources") ] ) ] @@ -56,10 +54,15 @@ package.targets = [ dependencies: [.product(name: "CSQLite", package: "CSQLite")], exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] ), - .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ - "FTSIntegrationTests.swift", - "FTS4Tests.swift", - "FTS5Tests.swift" - ]) + .testTarget( + name: "SQLiteTests", + dependencies: ["SQLite"], + path: "Tests/SQLiteTests", exclude: [ + "FTSIntegrationTests.swift", + "FTS4Tests.swift", + "FTS5Tests.swift" + ], + resources: [ .copy("Resources") ] + ) ] #endif diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4a1787bb..451b4886 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -35,7 +35,7 @@ Pod::Spec.new do |s| ss.library = 'sqlite3' ss.test_spec 'tests' do |test_spec| - test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' test_spec.ios.deployment_target = ios_deployment_target test_spec.tvos.deployment_target = tvos_deployment_target @@ -55,7 +55,7 @@ Pod::Spec.new do |s| ss.dependency 'sqlite3' ss.test_spec 'tests' do |test_spec| - test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' test_spec.ios.deployment_target = ios_deployment_target test_spec.tvos.deployment_target = tvos_deployment_target @@ -73,7 +73,7 @@ Pod::Spec.new do |s| ss.dependency 'SQLCipher', '>= 4.0.0' ss.test_spec 'tests' do |test_spec| - test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' test_spec.ios.deployment_target = ios_deployment_target test_spec.tvos.deployment_target = tvos_deployment_target diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 4e01067f..45e6ea63 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ 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 */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; @@ -92,10 +91,8 @@ 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.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 */; }; 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; @@ -137,6 +134,9 @@ 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B794288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B79628846FCC005DD8CA /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; + 3DF7B79828846FED005DD8CA /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; + 3DF7B79928847055005DD8CA /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; 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 */; }; @@ -267,7 +267,6 @@ 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 = ""; }; 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; - 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; @@ -277,6 +276,7 @@ 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; + 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -448,7 +448,7 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( - 19A17E2695737FAB5D6086E3 /* fixtures */, + 3DF7B79528846FCC005DD8CA /* Resources */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -812,7 +812,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */, + 3DF7B79828846FED005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -834,7 +834,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 19A17FDA323BAFDEC627E76F /* fixtures in Resources */, + 3DF7B79628846FCC005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -849,7 +849,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 19A175DFF47B84757E547C62 /* fixtures in Resources */, + 3DF7B79928847055005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 35edfce9..68b83821 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -104,7 +104,7 @@ public final class Connection { /// /// - Returns: A new database connection. public init(_ location: Location = .inMemory, readonly: Bool = false) throws { - let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE + let flags = readonly ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE) try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI, diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index f25dbe5b..e2d09901 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -4,12 +4,13 @@ import SQLite import SQLCipher class CipherTests: XCTestCase { - - let db1 = try Connection() - let db2 = try Connection() + var db1: Connection! + var db2: Connection! override func setUpWithError() throws { - // db + db1 = try Connection() + db2 = try Connection() + // db1 try db1.key("hello") @@ -30,7 +31,7 @@ class CipherTests: XCTestCase { XCTAssertEqual(1, try db1.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_key_blob_literal() { + func test_key_blob_literal() throws { let db = try Connection() try db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'") } @@ -55,7 +56,7 @@ class CipherTests: XCTestCase { _ = try? FileManager.default.removeItem(atPath: path) let connA = try Connection(path) - defer { try FileManager.default.removeItem(atPath: path) } + defer { try? FileManager.default.removeItem(atPath: path) } try connA.key("hello") try connA.run("CREATE TABLE foo (bar TEXT)") @@ -90,7 +91,7 @@ class CipherTests: XCTestCase { defer { // ensure file can be cleaned up afterwards - try FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) + try? FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) } let conn = try Connection(encryptedFile) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index cf51c104..347516f5 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -47,12 +47,17 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } - func testLocationWithoutUriParameters() { + func test_init_with_Uri_and_Parameters() throws { + let testDb = fixture("test", withExtension: "sqlite") + _ = try Connection(.uri(testDb, parameters: [.cache(.shared)])) + } + + func test_location_without_Uri_parameters() { let location: Connection.Location = .uri("foo") XCTAssertEqual(location.description, "foo") } - func testLocationWithUriParameters() { + func test_location_with_Uri_parameters() { let location: Connection.Location = .uri("foo", parameters: [.mode(.readOnly), .cache(.private)]) XCTAssertEqual(location.description, "file:foo?mode=ro&cache=private") } diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index 1306aa5a..95b2b6dc 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -7,7 +7,7 @@ func fixture(_ name: String, withExtension: String?) -> String { let testBundle = Bundle(for: SQLiteTestCase.self) #endif - for resource in [name, "fixtures/\(name)"] { + for resource in [name, "Resources/\(name)"] { if let url = testBundle.url( forResource: resource, withExtension: withExtension) { diff --git a/Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite b/Tests/SQLiteTests/Resources/encrypted-3.x.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite rename to Tests/SQLiteTests/Resources/encrypted-3.x.sqlite diff --git a/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite b/Tests/SQLiteTests/Resources/encrypted-4.x.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite rename to Tests/SQLiteTests/Resources/encrypted-4.x.sqlite diff --git a/Tests/SQLiteTests/fixtures/test.sqlite b/Tests/SQLiteTests/Resources/test.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/test.sqlite rename to Tests/SQLiteTests/Resources/test.sqlite From 9023fbc52ba86e0b002275698f071efa6eaeb899 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 00:44:53 +0200 Subject: [PATCH 048/216] Add documenation, update changelog --- CHANGELOG.md | 24 ++++++++++++++++ Documentation/Index.md | 32 ++++++++++++++++++++- SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Core/URIQueryParameter.swift | 5 ++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 786084f8..210e324d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +0.14.0 (tbd), [diff][diff-0.14.0] +======================================== + +* Support ATTACH/DETACH ([#30][], [##1142][]) +* Support WITH clause ([#1139][]) +* Add value conformance for NSURL ([#1141][]) +* Add decoding for UUID ([#1137][]) +* Fix insertMany([Encodable]) ([#1130][], [#1138]][]) +* Fix incorrect spelling of 'remove_diacritics' ([#1128][]) +* Fix project build order ([#1131][]) +* Performance improvements ([#1109][], [#1115][], [#1132][]) + 0.13.3 (25-01-2022), [diff][diff-0.13.3] ======================================== @@ -110,7 +122,9 @@ [diff-0.13.1]: https://github.com/stephencelis/SQLite.swift/compare/0.13.0...0.13.1 [diff-0.13.2]: https://github.com/stephencelis/SQLite.swift/compare/0.13.1...0.13.2 [diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 +[diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 +[#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#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 @@ -150,6 +164,16 @@ [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 +[#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 [#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 +[#1115]: https://github.com/stephencelis/SQLite.swift/pull/1115 [#1119]: https://github.com/stephencelis/SQLite.swift/pull/1119 [#1121]: https://github.com/stephencelis/SQLite.swift/pull/1121 +[#1128]: https://github.com/stephencelis/SQLite.swift/issues/1128 +[#1130]: https://github.com/stephencelis/SQLite.swift/issues/1130 +[#1131]: https://github.com/stephencelis/SQLite.swift/pull/1131 +[#1132]: https://github.com/stephencelis/SQLite.swift/pull/1132 +[#1137]: https://github.com/stephencelis/SQLite.swift/pull/1137 +[#1138]: https://github.com/stephencelis/SQLite.swift/pull/1138 +[#1139]: https://github.com/stephencelis/SQLite.swift/pull/1139 +[#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 diff --git a/Documentation/Index.md b/Documentation/Index.md index 7a9a390f..aded5739 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -10,6 +10,7 @@ - [Read-Write Databases](#read-write-databases) - [Read-Only Databases](#read-only-databases) - [In-Memory Databases](#in-memory-databases) + - [URI parameters](#uri-parameters) - [Thread-Safety](#thread-safety) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) @@ -61,9 +62,9 @@ - [Custom Collations](#custom-collations) - [Full-text Search](#full-text-search) - [Executing Arbitrary SQL](#executing-arbitrary-sql) + - [Attaching and detaching databases](#attaching-and-detaching-databases) - [Logging](#logging) - [↩]: #sqliteswift-documentation @@ -334,6 +335,16 @@ let db = try Connection(.temporary) In-memory databases are automatically deleted when the database connection is closed. +#### URI parameters + +We can pass `.uri` to the `Connection` initializer to control more aspects of +the database connection with the help of `URIQueryParameter`s: + +```swift +let db = try Connection(.uri("file.sqlite", parameters: [.cache(.private)], .noLock(true)])) +``` + +See [Uniform Resource Identifiers](https://www.sqlite.org/uri.html) for more details. #### Thread-Safety @@ -2070,6 +2081,25 @@ let backup = try db.backup(usingConnection: target) try backup.step() ``` +## Attaching and detaching databases + +We can [ATTACH](https://www3.sqlite.org/lang_attach.html) and [DETACH](https://www3.sqlite.org/lang_detach.html) +databases to an existing connection: + +```swift +let db = try Connection("db.sqlite") + +try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)), as: "external") +// ATTACH DATABASE 'file:external.sqlite?mode=ro' AS 'external' + +let table = Table("table", database: "external") +let count = try db.scalar(table.count) +// SELECT count(*) FROM 'external.table' + +try db.detach("external") +// DETACH DATABASE 'external' +``` + ## Logging We can log SQL using the database’s `trace` function. diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 45e6ea63..064357a2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -277,6 +277,7 @@ 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; + 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -534,6 +535,7 @@ isa = PBXGroup; children = ( EE247B771C3F40D700AE3E12 /* README.md */, + 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, EE247B8D1C3F821200AE3E12 /* Makefile */, diff --git a/Sources/SQLite/Core/URIQueryParameter.swift b/Sources/SQLite/Core/URIQueryParameter.swift index c82a7cf7..abbab2e7 100644 --- a/Sources/SQLite/Core/URIQueryParameter.swift +++ b/Sources/SQLite/Core/URIQueryParameter.swift @@ -1,5 +1,6 @@ import Foundation +/// See https://www.sqlite.org/uri.html public enum URIQueryParameter: CustomStringConvertible { public enum FileMode: String { case readOnly = "ro", readWrite = "rw", readWriteCreate = "rwc", memory @@ -29,7 +30,7 @@ public enum URIQueryParameter: CustomStringConvertible { case nolock(Bool) /// The psow query parameter overrides the `powersafe_overwrite` property of the database file being opened. - case psow(Bool) + case powersafeOverwrite(Bool) /// The vfs query parameter causes the database connection to be opened using the VFS called NAME. case vfs(String) @@ -45,7 +46,7 @@ public enum URIQueryParameter: CustomStringConvertible { case .modeOf(let filename): return .init(name: "modeOf", value: filename) case .mode(let fileMode): return .init(name: "mode", value: fileMode.rawValue) case .nolock(let bool): return .init(name: "nolock", value: NSNumber(value: bool).description) - case .psow(let bool): return .init(name: "psow", value: NSNumber(value: bool).description) + case .powersafeOverwrite(let bool): return .init(name: "psow", value: NSNumber(value: bool).description) case .vfs(let name): return .init(name: "vfs", value: name) } } From 2eed2003e548630a3116a6d0fb850a341e3405a9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 00:55:40 +0200 Subject: [PATCH 049/216] Fix doc errors --- CHANGELOG.md | 16 +++++++++------- Documentation/Index.md | 6 +++--- Documentation/Planning.md | 2 -- SQLite.xcodeproj/project.pbxproj | 2 ++ 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 210e324d..37e96955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,16 @@ 0.14.0 (tbd), [diff][diff-0.14.0] ======================================== -* Support ATTACH/DETACH ([#30][], [##1142][]) -* Support WITH clause ([#1139][]) -* Add value conformance for NSURL ([#1141][]) -* Add decoding for UUID ([#1137][]) -* Fix insertMany([Encodable]) ([#1130][], [#1138]][]) -* Fix incorrect spelling of 'remove_diacritics' ([#1128][]) +* Support `ATTACH`/`DETACH` ([#30][], [#1142][]) +* Support `WITH` clause ([#1139][]) +* Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) +* Add decoding for `UUID` ([#1137][]) +* Fix `insertMany([Encodable])` ([#1130][], [#1138][]) +* Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) * Performance improvements ([#1109][], [#1115][], [#1132][]) -0.13.3 (25-01-2022), [diff][diff-0.13.3] +0.13.3 (27-03-2022), [diff][diff-0.13.3] ======================================== * UUID Fix ([#1112][]) @@ -165,6 +165,7 @@ [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 +[#1110]: https://github.com/stephencelis/SQLite.swift/pull/1110 [#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 [#1115]: https://github.com/stephencelis/SQLite.swift/pull/1115 [#1119]: https://github.com/stephencelis/SQLite.swift/pull/1119 @@ -176,4 +177,5 @@ [#1137]: https://github.com/stephencelis/SQLite.swift/pull/1137 [#1138]: https://github.com/stephencelis/SQLite.swift/pull/1138 [#1139]: https://github.com/stephencelis/SQLite.swift/pull/1139 +[#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141 [#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 diff --git a/Documentation/Index.md b/Documentation/Index.md index aded5739..f036520f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -341,10 +341,10 @@ We can pass `.uri` to the `Connection` initializer to control more aspects of the database connection with the help of `URIQueryParameter`s: ```swift -let db = try Connection(.uri("file.sqlite", parameters: [.cache(.private)], .noLock(true)])) +let db = try Connection(.uri("file.sqlite", parameters: [.cache(.private), .noLock(true)])) ``` -See [Uniform Resource Identifiers](https://www.sqlite.org/uri.html) for more details. +See [Uniform Resource Identifiers](https://www.sqlite.org/uri.html#recognized_query_parameters) for more details. #### Thread-Safety @@ -2089,7 +2089,7 @@ databases to an existing connection: ```swift let db = try Connection("db.sqlite") -try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)), as: "external") +try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)], as: "external") // ATTACH DATABASE 'file:external.sqlite?mode=ro' AS 'external' let table = Table("table", database: "external") diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 0dea6d71..6067b81e 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -21,8 +21,6 @@ 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 diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 064357a2..45ff075f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -278,6 +278,7 @@ 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 3DF7B79B2884C901005DD8CA /* Planning.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Planning.md; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -551,6 +552,7 @@ isa = PBXGroup; children = ( EE247B8F1C3F822500AE3E12 /* Index.md */, + 3DF7B79B2884C901005DD8CA /* Planning.md */, EE247B901C3F822500AE3E12 /* Resources */, 19A17EA3A313F129011B3FA0 /* Release.md */, 19A1794B7972D14330A65BBD /* Linux.md */, From 0af0e4d55e7571bc5a53bb10f12e0669fdf0470e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 01:03:57 +0200 Subject: [PATCH 050/216] More supported types --- Documentation/Index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index f036520f..0930aab0 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -383,6 +383,9 @@ to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). | `String` | `TEXT` | | `nil` | `NULL` | | `SQLite.Blob`† | `BLOB` | +| `URL` | `TEXT` | +| `UUID` | `TEXT` | +| `Date` | `TEXT` | > *While `Int64` is the basic, raw type (to preserve 64-bit integers on > 32-bit platforms), `Int` and `Bool` work transparently. From 7868fdfcf606c2aaf18fb0471393234113b537a5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 01:22:27 +0200 Subject: [PATCH 051/216] Fix doc --- Documentation/Index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 0930aab0..75c1d2cc 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1103,8 +1103,8 @@ users.limit(5, offset: 5) #### Recursive and Hierarchical Queries -We can perform a recursive or hierarchical query using a [query's](#queries) `with` -function. +We can perform a recursive or hierarchical query using a [query's](#queries) +[`WITH`](https://sqlite.org/lang_with.html) function. ```swift // Get the management chain for the manager with id == 8 @@ -2092,7 +2092,7 @@ databases to an existing connection: ```swift let db = try Connection("db.sqlite") -try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)], as: "external") +try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)]), as: "external") // ATTACH DATABASE 'file:external.sqlite?mode=ro' AS 'external' let table = Table("table", database: "external") From be14b42fc71f3c3e8cd977d858ecfe593a168434 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 12:45:04 +0200 Subject: [PATCH 052/216] Add sqlcipher_export --- CHANGELOG.md | 2 ++ Documentation/Index.md | 11 +++++++++-- Sources/SQLite/Core/Connection+Attach.swift | 12 ++++++++++- Sources/SQLite/Extensions/Cipher.swift | 22 +++++++++++++++++++-- Tests/SQLiteTests/CipherTests.swift | 9 +++++++++ Tests/SQLiteTests/Fixtures.swift | 4 ++++ 6 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37e96955..eb7213c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) +* SQLCipher: improve documentation ([#1098][]), add `sqlcipher_export` * Fix `insertMany([Encodable])` ([#1130][], [#1138][]) * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) @@ -162,6 +163,7 @@ [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 +[#1098]: https://github.com/stephencelis/SQLite.swift/issues/1098 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 diff --git a/Documentation/Index.md b/Documentation/Index.md index 75c1d2cc..dd832ee2 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -184,9 +184,16 @@ extend `Connection` with methods to change the database key: ```swift import SQLite -let db = try Connection("path/to/db.sqlite3") +let db = try Connection("path/to/encrypted.sqlite3") try db.key("secret") -try db.rekey("another secret") +try db.rekey("new secret") // changes encryption key on already encrypted db +``` + +To encrypt an existing database: + +```swift +let db = try Connection("path/to/unencrypted.sqlite3") +try db.sqlcipher_export(.uri("encrypted.sqlite3"), key: "secret") ``` [CocoaPods]: https://cocoapods.org diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index b9c08117..47e6af5e 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -10,11 +10,21 @@ import SQLite3 #endif extension Connection { - + #if SQLITE_SWIFT_SQLCIPHER + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#attach + public func attach(_ location: Location, as schemaName: String, key: String? = nil) throws { + if let key = key { + try run("ATTACH DATABASE ? AS ? KEY ?", location.description, schemaName, key) + } else { + try run("ATTACH DATABASE ? AS ?", location.description, schemaName) + } + } + #else /// See https://www3.sqlite.org/lang_attach.html public func attach(_ location: Location, as schemaName: String) throws { try run("ATTACH DATABASE ? AS ?", location.description, schemaName) } + #endif /// See https://www3.sqlite.org/lang_detach.html public func detach(_ schemaName: String) throws { diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index b96c2761..8af04df9 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -6,6 +6,8 @@ import SQLCipher extension Connection { /// - Returns: the SQLCipher version + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_version public var cipherVersion: String? { (try? scalar("PRAGMA cipher_version")) as? String } @@ -23,6 +25,8 @@ extension Connection { /// of key data. /// e.g. x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99' /// @param db name of the database, defaults to 'main' + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlite3_key public func key(_ key: String, db: String = "main") throws { try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count) } @@ -39,6 +43,7 @@ extension Connection { /// As "PRAGMA cipher_migrate;" is time-consuming, it is recommended to use this function /// only after failure of `key(_ key: String, db: String = "main")`, if older versions of /// your app may ise older version of SQLCipher + /// /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_migrate /// and https://discuss.zetetic.net/t/upgrading-to-sqlcipher-4/3283 /// for more details regarding SQLCipher upgrade @@ -51,11 +56,13 @@ extension Connection { try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count, migrate: true) } - /// Change the key on an open database. If the current database is not encrypted, this routine - /// will encrypt it. + /// Change the key on an open database. NB: only works if the database is already encrypted. + /// /// To change the key on an existing encrypted database, it must first be unlocked with the /// current encryption key. Once the database is readable and writeable, rekey can be used /// to re-encrypt every page in the database with a new key. + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlite3_rekey public func rekey(_ key: String, db: String = "main") throws { try _rekey_v2(db: db, keyPointer: key, keySize: key.utf8.count) } @@ -64,6 +71,17 @@ extension Connection { try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } + /// Converts a non-encrypted database to an encrypted one. + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export + public func sqlcipher_export(_ location: Location, key: String) throws { + let schemaName = "cipher_export" + + try attach(location, as: schemaName, key: key) + try run("SELECT sqlcipher_export(?)", schemaName) + try detach(schemaName) + } + // MARK: - private private func _key_v2(db: String, keyPointer: UnsafePointer, diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index e2d09901..cc43272e 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -99,6 +99,15 @@ class CipherTests: XCTestCase { XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) } + func test_export() throws { + let tmp = temporaryFile() + try db1.sqlcipher_export(.uri(tmp), key: "mykey") + + let conn = try Connection(tmp) + try conn.key("mykey") + XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) + } + private func keyData(length: Int = 64) -> NSMutableData { let keyData = NSMutableData(length: length)! let result = SecRandomCopyBytes(kSecRandomDefault, length, diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index 95b2b6dc..bd261d2c 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -16,3 +16,7 @@ func fixture(_ name: String, withExtension: String?) -> String { } fatalError("Cannot find \(name).\(withExtension ?? "")") } + +func temporaryFile() -> String { + URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).path +} From ceb13aa1adc8b05fd5d8ccb29cf1af97c79ebd1d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 01:13:28 +0200 Subject: [PATCH 053/216] Document changes --- CHANGELOG.md | 3 ++- Documentation/Index.md | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb7213c1..68a5133f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) -* SQLCipher: improve documentation ([#1098][]), add `sqlcipher_export` +* SQLCipher: improve documentation ([#1098][]), add `sqlcipher_export` ([#1101][]) * Fix `insertMany([Encodable])` ([#1130][], [#1138][]) * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) @@ -165,6 +165,7 @@ [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 [#1098]: https://github.com/stephencelis/SQLite.swift/issues/1098 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 +[#1101]: https://github.com/stephencelis/SQLite.swift/issues/1101 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 [#1110]: https://github.com/stephencelis/SQLite.swift/pull/1110 diff --git a/Documentation/Index.md b/Documentation/Index.md index dd832ee2..96fc1ee1 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -2110,6 +2110,13 @@ try db.detach("external") // DETACH DATABASE 'external' ``` +When compiled for SQLCipher, you can additionally pass a `key` parameter to `attach`: + +```swift +try db.attach(.uri("encrypted.sqlite"), as: "encrypted", key: "secret") +// ATTACH DATABASE 'encrypted.sqlite' AS 'encrypted' KEY 'secret' +``` + ## Logging We can log SQL using the database’s `trace` function. From 136c8a8c4358255fc921be5b1f3148acc514ee36 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 13:01:26 +0200 Subject: [PATCH 054/216] Remove FTS4 Tokenizer / SQLiteObjc Closes #1104 --- Package.swift | 11 +- SQLite.xcodeproj/project.pbxproj | 38 +----- Sources/SQLite/Extensions/FTS4.swift | 27 ---- Sources/SQLite/SQLite.h | 2 - Sources/SQLiteObjc/SQLiteObjc.m | 138 -------------------- Sources/SQLiteObjc/fts3_tokenizer.h | 161 ------------------------ Sources/SQLiteObjc/include/SQLiteObjc.h | 38 ------ Tests/SQLiteTests/FTS4Tests.swift | 37 ------ 8 files changed, 7 insertions(+), 445 deletions(-) delete mode 100644 Sources/SQLiteObjc/SQLiteObjc.m delete mode 100644 Sources/SQLiteObjc/fts3_tokenizer.h delete mode 100644 Sources/SQLiteObjc/include/SQLiteObjc.h diff --git a/Package.swift b/Package.swift index b32fd881..ab9cbd43 100644 --- a/Package.swift +++ b/Package.swift @@ -18,18 +18,10 @@ let package = Package( targets: [ .target( name: "SQLite", - dependencies: ["SQLiteObjc"], exclude: [ "Info.plist" ] ), - .target( - name: "SQLiteObjc", - dependencies: [], - exclude: [ - "fts3_tokenizer.h" - ] - ), .testTarget( name: "SQLiteTests", dependencies: [ @@ -57,7 +49,8 @@ package.targets = [ .testTarget( name: "SQLiteTests", dependencies: ["SQLite"], - path: "Tests/SQLiteTests", exclude: [ + path: "Tests/SQLiteTests", + exclude: [ "FTSIntegrationTests.swift", "FTS4Tests.swift", "FTS5Tests.swift" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 45ff075f..3e67c03e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -17,8 +17,6 @@ 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; 03A65E761C6BB2E60062603F /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 03A65E771C6BB2E60062603F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; - 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -116,13 +114,7 @@ 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; - 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; 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 */; }; - 3DDC112F26CDBA0200CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; @@ -152,8 +144,6 @@ EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; - EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - EE247B061C3F06E900AE3E12 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B081C3F06E900AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -205,8 +195,6 @@ EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; - EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -272,13 +260,13 @@ 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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; }; - 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 3DF7B79B2884C901005DD8CA /* Planning.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Planning.md; sourceTree = ""; }; + 3DFC0B862886C239001C8FC9 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -290,8 +278,6 @@ EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../../SQLiteObjc/fts3_tokenizer.h; sourceTree = ""; }; - EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SQLiteObjc.m; path = ../../SQLiteObjc/SQLiteObjc.m; sourceTree = ""; }; EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; @@ -406,9 +392,10 @@ EE247AC91C3F04ED00AE3E12 = { isa = PBXGroup; children = ( + 3DFC0B862886C239001C8FC9 /* Package.swift */, + 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, - 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, 3D67B3E41DB2469200A4F4C6 /* Frameworks */, @@ -435,7 +422,6 @@ isa = PBXGroup; children = ( EE247AD61C3F04ED00AE3E12 /* SQLite.h */, - 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, @@ -488,8 +474,6 @@ children = ( EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, - EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, @@ -505,10 +489,10 @@ EE247AF41C3F06E900AE3E12 /* Extensions */ = { isa = PBXGroup; children = ( + 19A178A39ACA9667A62663CC /* Cipher.swift */, EE247AF51C3F06E900AE3E12 /* FTS4.swift */, - EE247AF61C3F06E900AE3E12 /* RTree.swift */, 19A1730E4390C775C25677D1 /* FTS5.swift */, - 19A178A39ACA9667A62663CC /* Cipher.swift */, + EE247AF61C3F06E900AE3E12 /* RTree.swift */, ); path = Extensions; sourceTree = ""; @@ -576,8 +560,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, - 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -587,8 +569,6 @@ buildActionMask = 2147483647; files = ( 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, - 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */, - 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -596,8 +576,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, - 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -606,8 +584,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, - 3DDC112F26CDBA0200CE369F /* SQLiteObjc.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -869,7 +845,6 @@ 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */, - 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */, 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */, @@ -933,7 +908,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, @@ -986,7 +960,6 @@ EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, - EE247B061C3F06E900AE3E12 /* SQLiteObjc.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, @@ -1042,7 +1015,6 @@ EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */, - EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */, EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */, EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */, EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */, diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 52fec955..e9e65746 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -22,10 +22,6 @@ // THE SOFTWARE. // -#if SWIFT_PACKAGE -import SQLiteObjc -#endif - extension Module { public static func FTS4(_ column: Expressible, _ more: Expressible...) -> Module { @@ -149,29 +145,6 @@ extension Tokenizer: CustomStringConvertible { } -extension Connection { - - public func registerTokenizer(_ submoduleName: String, next: @escaping (String) -> (String, Range)?) throws { - try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { - (input: UnsafePointer, offset: UnsafeMutablePointer, length: UnsafeMutablePointer) in - let string = String(cString: input) - - guard let (token, range) = next(string) else { return nil } - - 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..callback = [__SQLiteTokenizerMap objectForKey:key]; - if (!tokenizer->callback) { - return SQLITE_ERROR; - } - - *ppTokenizer = &tokenizer->base; - return SQLITE_OK; -} - -static int __SQLiteTokenizerDestroy(sqlite3_tokenizer * pTokenizer) { - sqlite3_free(pTokenizer); - return SQLITE_OK; -} - -static int __SQLiteTokenizerOpen(sqlite3_tokenizer * pTokenizer, const char * pInput, int nBytes, sqlite3_tokenizer_cursor ** ppCursor) { - __SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)sqlite3_malloc(sizeof(__SQLiteTokenizerCursor)); - if (!cursor) { - return SQLITE_NOMEM; - } - - cursor->input = pInput; - cursor->inputOffset = 0; - cursor->inputLength = 0; - cursor->idx = 0; - - *ppCursor = (sqlite3_tokenizer_cursor *)cursor; - return SQLITE_OK; -} - -static int __SQLiteTokenizerClose(sqlite3_tokenizer_cursor * pCursor) { - sqlite3_free(pCursor); - return SQLITE_OK; -} - -static int __SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char ** ppToken, int * pnBytes, int * piStartOffset, int * piEndOffset, int * piPosition) { - __SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)pCursor; - __SQLiteTokenizer * tokenizer = (__SQLiteTokenizer *)cursor->base; - - cursor->inputOffset += cursor->inputLength; - const char * input = cursor->input + cursor->inputOffset; - const char * token = [tokenizer->callback(input, &cursor->inputOffset, &cursor->inputLength) cStringUsingEncoding:NSUTF8StringEncoding]; - if (!token) { - return SQLITE_DONE; - } - - *ppToken = token; - *pnBytes = (int)strlen(token); - *piStartOffset = cursor->inputOffset; - *piEndOffset = cursor->inputOffset + cursor->inputLength; - *piPosition = cursor->idx++; - return SQLITE_OK; -} - -static const sqlite3_tokenizer_module __SQLiteTokenizerModule = { - 0, - __SQLiteTokenizerCreate, - __SQLiteTokenizerDestroy, - __SQLiteTokenizerOpen, - __SQLiteTokenizerClose, - __SQLiteTokenizerNext -}; - -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(db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); - if (status != SQLITE_OK ){ - return status; - } - const sqlite3_tokenizer_module * pModule = &__SQLiteTokenizerModule; - sqlite3_bind_text(stmt, 1, moduleName, -1, SQLITE_STATIC); - sqlite3_bind_blob(stmt, 2, &pModule, sizeof(pModule), SQLITE_STATIC); - sqlite3_step(stmt); - status = sqlite3_finalize(stmt); - if (status != SQLITE_OK ){ - return status; - } - - [__SQLiteTokenizerMap setObject:[callback copy] forKey:[NSString stringWithUTF8String:submoduleName]]; - - return SQLITE_OK; -} diff --git a/Sources/SQLiteObjc/fts3_tokenizer.h b/Sources/SQLiteObjc/fts3_tokenizer.h deleted file mode 100644 index d8a1e44b..00000000 --- a/Sources/SQLiteObjc/fts3_tokenizer.h +++ /dev/null @@ -1,161 +0,0 @@ -/* -** 2006 July 10 -** -** The author disclaims copyright to this source code. -** -************************************************************************* -** Defines the interface to tokenizers used by fulltext-search. There -** are three basic components: -** -** sqlite3_tokenizer_module is a singleton defining the tokenizer -** interface functions. This is essentially the class structure for -** tokenizers. -** -** sqlite3_tokenizer is used to define a particular tokenizer, perhaps -** including customization information defined at creation time. -** -** sqlite3_tokenizer_cursor is generated by a tokenizer to generate -** tokens from a particular input. -*/ -#ifndef _FTS3_TOKENIZER_H_ -#define _FTS3_TOKENIZER_H_ - -/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. -** If tokenizers are to be allowed to call sqlite3_*() functions, then -** we will need a way to register the API consistently. -*/ -#import "sqlite3.h" - -/* -** Structures used by the tokenizer interface. When a new tokenizer -** implementation is registered, the caller provides a pointer to -** an sqlite3_tokenizer_module containing pointers to the callback -** functions that make up an implementation. -** -** When an fts3 table is created, it passes any arguments passed to -** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the -** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer -** implementation. The xCreate() function in turn returns an -** sqlite3_tokenizer structure representing the specific tokenizer to -** be used for the fts3 table (customized by the tokenizer clause arguments). -** -** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() -** method is called. It returns an sqlite3_tokenizer_cursor object -** that may be used to tokenize a specific input buffer based on -** the tokenization rules supplied by a specific sqlite3_tokenizer -** object. -*/ -typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; -typedef struct sqlite3_tokenizer sqlite3_tokenizer; -typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; - -struct sqlite3_tokenizer_module { - - /* - ** Structure version. Should always be set to 0 or 1. - */ - int iVersion; - - /* - ** Create a new tokenizer. The values in the argv[] array are the - ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL - ** TABLE statement that created the fts3 table. For example, if - ** the following SQL is executed: - ** - ** CREATE .. USING fts3( ... , tokenizer arg1 arg2) - ** - ** then argc is set to 2, and the argv[] array contains pointers - ** to the strings "arg1" and "arg2". - ** - ** This method should return either SQLITE_OK (0), or an SQLite error - ** code. If SQLITE_OK is returned, then *ppTokenizer should be set - ** to point at the newly created tokenizer structure. The generic - ** sqlite3_tokenizer.pModule variable should not be initialized by - ** this callback. The caller will do so. - */ - int (*xCreate)( - int argc, /* Size of argv array */ - const char *const*argv, /* Tokenizer argument strings */ - sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ - ); - - /* - ** Destroy an existing tokenizer. The fts3 module calls this method - ** exactly once for each successful call to xCreate(). - */ - int (*xDestroy)(sqlite3_tokenizer *pTokenizer); - - /* - ** Create a tokenizer cursor to tokenize an input buffer. The caller - ** is responsible for ensuring that the input buffer remains valid - ** until the cursor is closed (using the xClose() method). - */ - int (*xOpen)( - sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ - const char *pInput, int nBytes, /* Input buffer */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ - ); - - /* - ** Destroy an existing tokenizer cursor. The fts3 module calls this - ** method exactly once for each successful call to xOpen(). - */ - int (*xClose)(sqlite3_tokenizer_cursor *pCursor); - - /* - ** Retrieve the next token from the tokenizer cursor pCursor. This - ** method should either return SQLITE_OK and set the values of the - ** "OUT" variables identified below, or SQLITE_DONE to indicate that - ** the end of the buffer has been reached, or an SQLite error code. - ** - ** *ppToken should be set to point at a buffer containing the - ** normalized version of the token (i.e. after any case-folding and/or - ** stemming has been performed). *pnBytes should be set to the length - ** of this buffer in bytes. The input text that generated the token is - ** identified by the byte offsets returned in *piStartOffset and - ** *piEndOffset. *piStartOffset should be set to the index of the first - ** byte of the token in the input buffer. *piEndOffset should be set - ** to the index of the first byte just past the end of the token in - ** the input buffer. - ** - ** The buffer *ppToken is set to point at is managed by the tokenizer - ** implementation. It is only required to be valid until the next call - ** to xNext() or xClose(). - */ - /* TODO(shess) current implementation requires pInput to be - ** nul-terminated. This should either be fixed, or pInput/nBytes - ** should be converted to zInput. - */ - int (*xNext)( - sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ - const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ - int *piStartOffset, /* OUT: Byte offset of token in input buffer */ - int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ - int *piPosition /* OUT: Number of tokens returned before this one */ - ); - - /*********************************************************************** - ** Methods below this point are only available if iVersion>=1. - */ - - /* - ** Configure the language id of a tokenizer cursor. - */ - int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid); -}; - -struct sqlite3_tokenizer { - const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ - /* Tokenizer implementations will typically add additional fields */ -}; - -struct sqlite3_tokenizer_cursor { - sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ - /* Tokenizer implementations will typically add additional fields */ -}; - -int fts3_global_term_cnt(int iTerm, int iCol); -int fts3_term_cnt(int iTerm, int iCol); - - -#endif /* _FTS3_TOKENIZER_H_ */ diff --git a/Sources/SQLiteObjc/include/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h deleted file mode 100644 index 610cdf10..00000000 --- a/Sources/SQLiteObjc/include/SQLiteObjc.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@import Foundation; -#if defined(SQLITE_SWIFT_STANDALONE) -@import sqlite3; -#elif defined(SQLITE_SWIFT_SQLCIPHER) -@import SQLCipher; -#else -@import SQLite3; -#endif - -NS_ASSUME_NONNULL_BEGIN -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/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index aa6ac41f..5e595007 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -188,40 +188,3 @@ class FTS4ConfigTests: XCTestCase { virtualTable.create(.FTS4(config)) } } - -class FTS4IntegrationTests: SQLiteTestCase { -#if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER - func test_registerTokenizer_registersTokenizer() throws { - let emails = VirtualTable("emails") - let subject = Expression("subject") - let body = Expression("body") - - let locale = CFLocaleCopyCurrent() - let tokenizerName = "tokenizer" - let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), - UInt(kCFStringTokenizerUnitWord), locale) - try db.registerTokenizer(tokenizerName) { string in - CFStringTokenizerSetString(tokenizer, string as CFString, - CFRangeMake(0, CFStringGetLength(string as CFString))) - if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { - return nil - } - let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) - let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string as CFString, range)! - let token = CFStringCreateMutableCopy(nil, range.length, input)! - CFStringLowercase(token, locale) - CFStringTransform(token, nil, kCFStringTransformStripDiacritics, false) - return (token as String, string.range(of: input as String)!) - } - - try db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) - assertSQL(""" - CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", - tokenize=\"SQLite.swift\" \"tokenizer\") - """.replacingOccurrences(of: "\n", with: "")) - - try _ = db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, try db.scalar(emails.filter(emails.match("aun")).count)) - } -#endif -} From f63aca44e06e511dd0bdeeeb4b335b2aafbf36b1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 13:40:38 +0200 Subject: [PATCH 055/216] Fix podspec, update docs --- CHANGELOG.md | 3 +++ Documentation/Index.md | 2 -- Package.swift | 23 +++++------------------ SQLite.swift.podspec | 9 +++------ 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a5133f..71ce9858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) * Performance improvements ([#1109][], [#1115][], [#1132][]) +* Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144[]]) 0.13.3 (27-03-2022), [diff][diff-0.13.3] ======================================== @@ -166,6 +167,7 @@ [#1098]: https://github.com/stephencelis/SQLite.swift/issues/1098 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1101]: https://github.com/stephencelis/SQLite.swift/issues/1101 +[#1104]: https://github.com/stephencelis/SQLite.swift/issues/1104 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 [#1110]: https://github.com/stephencelis/SQLite.swift/pull/1110 @@ -182,3 +184,4 @@ [#1139]: https://github.com/stephencelis/SQLite.swift/pull/1139 [#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141 [#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 +[#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144 diff --git a/Documentation/Index.md b/Documentation/Index.md index 96fc1ee1..b71cdc4f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1983,8 +1983,6 @@ try db.run(emails.create(.FTS5(config))) // the last FTS4 query above as: let replies = emails.filter(emails.match("subject:\"Re:\"*")) // SELECT * FROM "emails" WHERE "emails" MATCH 'subject:"Re:"*' - -// https://www.sqlite.org/fts5.html#_changes_to_select_statements_ ``` ## Executing Arbitrary SQL diff --git a/Package.swift b/Package.swift index ab9cbd43..9664e99a 100644 --- a/Package.swift +++ b/Package.swift @@ -39,23 +39,10 @@ let package = Package( ) #if os(Linux) -package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] -package.targets = [ - .target( - name: "SQLite", - dependencies: [.product(name: "CSQLite", package: "CSQLite")], - exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] - ), - .testTarget( - name: "SQLiteTests", - dependencies: ["SQLite"], - path: "Tests/SQLiteTests", - exclude: [ - "FTSIntegrationTests.swift", - "FTS4Tests.swift", - "FTS5Tests.swift" - ], - resources: [ .copy("Resources") ] - ) +package.dependencies = [ + .package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3") +] +package.targets.first?.dependencies += [ + .product(name: "CSQLite", package: "CSQLite") ] #endif diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 451b4886..d36d0330 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -29,9 +29,8 @@ Pod::Spec.new do |s| s.watchos.deployment_target = watchos_deployment_target s.subspec 'standard' do |ss| - ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.library = 'sqlite3' ss.test_spec 'tests' do |test_spec| @@ -44,9 +43,8 @@ Pod::Spec.new do |s| end s.subspec 'standalone' do |ss| - ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', @@ -64,8 +62,7 @@ Pod::Spec.new do |s| end s.subspec 'SQLCipher' do |ss| - ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' - ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' From 8d9f74e919a75da41f19c47869a4577c10638a0c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 13:59:27 +0200 Subject: [PATCH 056/216] Works now --- CHANGELOG.md | 2 +- Documentation/Linux.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ce9858..7b3f78b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) * Performance improvements ([#1109][], [#1115][], [#1132][]) -* Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144[]]) +* Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144][]) 0.13.3 (27-03-2022), [diff][diff-0.13.3] ======================================== diff --git a/Documentation/Linux.md b/Documentation/Linux.md index 640ced6f..0c88ff5c 100644 --- a/Documentation/Linux.md +++ b/Documentation/Linux.md @@ -2,9 +2,8 @@ ## Limitations -* Custom functions are currently not supported and crash, caused by a bug in Swift. +* Custom functions/aggregations are currently not supported and crash, caused by a bug in Swift. See [#1071](https://github.com/stephencelis/SQLite.swift/issues/1071). -* FTS5 might not work, see [#1007](https://github.com/stephencelis/SQLite.swift/issues/1007) ## Debugging From ef4854f0f742f1f52416aab88db75c6b686385ec Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 14:29:59 +0200 Subject: [PATCH 057/216] Update mileston --- Documentation/Planning.md | 2 +- SQLite.xcodeproj/project.pbxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 6067b81e..cdfca9c4 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ 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.13.4 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.4) +> the [0.14.1 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.14.1) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3e67c03e..2656ed6a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -392,7 +392,6 @@ EE247AC91C3F04ED00AE3E12 = { isa = PBXGroup; children = ( - 3DFC0B862886C239001C8FC9 /* Package.swift */, 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, @@ -523,6 +522,7 @@ 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, + 3DFC0B862886C239001C8FC9 /* Package.swift */, EE247B8D1C3F821200AE3E12 /* Makefile */, EE9180931C46EA210038162A /* libsqlite3.tbd */, EE9180911C46E9D30038162A /* libsqlite3.tbd */, From 4442ad0bbd06d2e843a5bb280cc96070aafec2c8 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Thu, 21 Jul 2022 15:01:04 -0500 Subject: [PATCH 058/216] Adding public method to reset a prepared statement. This is needed so that clients can reset any prepared statements they keep in memory, which allows for transactions to completely finish. without closing transactions (regardless of commit), the wal file cannot be checkpointed and will grow unbounded. --- Sources/SQLite/Core/Statement.swift | 6 ++- Tests/SQLiteTests/StatementTests.swift | 57 ++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 5ec430cf..7ba4026c 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -185,7 +185,11 @@ public final class Statement { try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } } - fileprivate func reset(clearBindings shouldClear: Bool = true) { + public func reset() { + reset(clearBindings: true) + } + + fileprivate func reset(clearBindings shouldClear: Bool) { sqlite3_reset(handle) if shouldClear { sqlite3_clear_bindings(handle) } } diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 3286b792..77bc09c1 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -34,4 +34,61 @@ class StatementTests: SQLiteTestCase { XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } + + /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint + func test_reset_statement() throws { + // Remove old test db if any + let path = "\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3" + try? FileManager.default.removeItem(atPath: path) + try? FileManager.default.removeItem(atPath: path + "-shm") + try? FileManager.default.removeItem(atPath: path + "-wal") + + // create new db on disk in wal mode + let db = try Connection(.uri(path)) + let url = URL(fileURLWithPath: db.description) + XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") + try db.run("PRAGMA journal_mode=WAL;") + + // create users table + 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, + created_at DATETIME, + FOREIGN KEY(manager_id) REFERENCES users(id) + ) + """ + ) + + // insert single row + try db.run("INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", + "alice@example.com", 1.datatypeValue, false.datatypeValue) + + // prepare a statement and read a single row. This will incremeent the cursor which + // prevents the implicit transaction from closing. + // https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions + let statement = try db.prepare("SELECT email FROM users") + XCTAssert(try statement.step()) + let blob = statement.row[0] as Blob + XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) + + // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) + do { + try db.run("pragma wal_checkpoint(truncate)") + XCTFail("Database should be locked") + } catch { + // pass + } + + // reset the prepared statement, allowing the implicit transaction to close + statement.reset() + + // truncate succeeds + try db.run("pragma wal_checkpoint(truncate)") + } + } From dd3e813577b8b6ba363b12cff024dd633e9e29ab Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Fri, 22 Jul 2022 12:37:48 +0200 Subject: [PATCH 059/216] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..119ccc65 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [stephencelis, jberkel, NathanFallet] From ac1988c9663e8c5f64d21b255d91c154a30e2f35 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 14:12:11 -0500 Subject: [PATCH 060/216] Test cleanup for statement reset --- Tests/SQLiteTests/StatementTests.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 77bc09c1..c12fe8cb 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -38,15 +38,13 @@ class StatementTests: SQLiteTestCase { /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint func test_reset_statement() throws { // Remove old test db if any - let path = "\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3" + let path = temporaryFile() + ".sqlite3" try? FileManager.default.removeItem(atPath: path) try? FileManager.default.removeItem(atPath: path + "-shm") try? FileManager.default.removeItem(atPath: path + "-wal") // create new db on disk in wal mode let db = try Connection(.uri(path)) - let url = URL(fileURLWithPath: db.description) - XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") try db.run("PRAGMA journal_mode=WAL;") // create users table From 012194b271dc32a0a9d1182edb4bea5201d8d59d Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 15:43:14 -0500 Subject: [PATCH 061/216] Adding documentation for Statement.reset(). --- Documentation/Index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index b71cdc4f..ed79f33e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1872,6 +1872,14 @@ let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ? for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` +> _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either implicit or explicit) might be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. +> +> ```swift +> someObj.statement = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") +> for row in someObj.statement.bind(kUTTypeImage) { /* ... */ } +> someObj.statement.reset() +> ``` + [UTTypeConformsTo]: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto ## Custom Aggregations From 58fd9dc1b6db7544135af10ced07f720e2dc36ac Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 15:43:42 -0500 Subject: [PATCH 062/216] Fix close of markdown code block --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index ed79f33e..2a877a71 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -828,7 +828,7 @@ let query = try db.prepare(users) for user in query { // 💥 can throw an error here } -```` +``` #### Failable iteration From 7b3de3d24b3eaea797a3db1b5a6373196edd4561 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 15:48:15 -0500 Subject: [PATCH 063/216] Add link to relevant sqlite.org documentation page --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 2a877a71..29b18c96 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1872,7 +1872,7 @@ let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ? for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` -> _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either implicit or explicit) might be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. +> _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either [implicit or explicit](https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions)) will be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. > > ```swift > someObj.statement = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") From 5a30f299c41f335a0745b0eab773db4f3ee184af Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 17:45:52 -0500 Subject: [PATCH 064/216] cleanup test for Statement.reset() --- Tests/SQLiteTests/StatementTests.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index c12fe8cb..b3048211 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -37,13 +37,8 @@ class StatementTests: SQLiteTestCase { /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint func test_reset_statement() throws { - // Remove old test db if any - let path = temporaryFile() + ".sqlite3" - try? FileManager.default.removeItem(atPath: path) - try? FileManager.default.removeItem(atPath: path + "-shm") - try? FileManager.default.removeItem(atPath: path + "-wal") - // create new db on disk in wal mode + let path = temporaryFile() + ".sqlite3" let db = try Connection(.uri(path)) try db.run("PRAGMA journal_mode=WAL;") @@ -70,9 +65,7 @@ class StatementTests: SQLiteTestCase { // prevents the implicit transaction from closing. // https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions let statement = try db.prepare("SELECT email FROM users") - XCTAssert(try statement.step()) - let blob = statement.row[0] as Blob - XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) + _ = try statement.step() // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) do { From efa5fcb5c24352d266e9a87075dbfb3e1c5fd7ea Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 17:47:46 -0500 Subject: [PATCH 065/216] Better check for throwing error in test --- Tests/SQLiteTests/StatementTests.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index b3048211..e41fa714 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -68,12 +68,7 @@ class StatementTests: SQLiteTestCase { _ = try statement.step() // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) - do { - try db.run("pragma wal_checkpoint(truncate)") - XCTFail("Database should be locked") - } catch { - // pass - } + XCTAssertThrowsError(try db.run("pragma wal_checkpoint(truncate)")) // reset the prepared statement, allowing the implicit transaction to close statement.reset() From da0cd09219fe02d22c721637bc55d0ce600821ff Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 19:25:52 -0500 Subject: [PATCH 066/216] Remove unneeded wal mode --- Tests/SQLiteTests/StatementTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index e41fa714..3cc68aca 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -40,7 +40,6 @@ class StatementTests: SQLiteTestCase { // create new db on disk in wal mode let path = temporaryFile() + ".sqlite3" let db = try Connection(.uri(path)) - try db.run("PRAGMA journal_mode=WAL;") // create users table try db.execute(""" From 210303f7b21f829bf82e17f41a8f912742ea2be3 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Sat, 23 Jul 2022 15:19:10 -0500 Subject: [PATCH 067/216] Use in memory database from test setup, verify error when statement isn't reset, update comments --- Tests/SQLiteTests/StatementTests.swift | 46 ++++++++++++-------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 3cc68aca..bc100cd2 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -1,6 +1,16 @@ import XCTest import SQLite +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + class StatementTests: SQLiteTestCase { override func setUpWithError() throws { try super.setUpWithError() @@ -37,28 +47,8 @@ class StatementTests: SQLiteTestCase { /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint func test_reset_statement() throws { - // create new db on disk in wal mode - let path = temporaryFile() + ".sqlite3" - let db = try Connection(.uri(path)) - - // create users table - 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, - created_at DATETIME, - FOREIGN KEY(manager_id) REFERENCES users(id) - ) - """ - ) - // insert single row - try db.run("INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - "alice@example.com", 1.datatypeValue, false.datatypeValue) + try insertUsers("bob") // prepare a statement and read a single row. This will incremeent the cursor which // prevents the implicit transaction from closing. @@ -66,14 +56,20 @@ class StatementTests: SQLiteTestCase { let statement = try db.prepare("SELECT email FROM users") _ = try statement.step() - // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) - XCTAssertThrowsError(try db.run("pragma wal_checkpoint(truncate)")) + // verify implicit transaction is not closed, and the users table is still locked + XCTAssertThrowsError(try db.run("DROP TABLE users")) { error in + if case let Result.error(_, code, _) = error { + XCTAssertEqual(code, SQLITE_LOCKED) + } else { + XCTFail("unexpected error") + } + } - // reset the prepared statement, allowing the implicit transaction to close + // reset the prepared statement, unlocking the table and allowing the implicit transaction to close statement.reset() // truncate succeeds - try db.run("pragma wal_checkpoint(truncate)") + try db.run("DROP TABLE users") } } From d970a11bd66747fd3b67d8ce072d944fca03b341 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Sat, 23 Jul 2022 15:21:03 -0500 Subject: [PATCH 068/216] typo --- Tests/SQLiteTests/StatementTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index bc100cd2..eeb513fe 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -50,7 +50,7 @@ class StatementTests: SQLiteTestCase { // insert single row try insertUsers("bob") - // prepare a statement and read a single row. This will incremeent the cursor which + // prepare a statement and read a single row. This will increment the cursor which // prevents the implicit transaction from closing. // https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions let statement = try db.prepare("SELECT email FROM users") From 5c66460cb97375d9cdc68364afe8e6db434f6cb9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 23 Jul 2022 22:21:59 +0200 Subject: [PATCH 069/216] Adds SchemaChanger to perform database schema changes --- SQLite.xcodeproj/project.pbxproj | 70 ++++ Sources/SQLite/Schema/Connection+Schema.swift | 145 ++++++++ Sources/SQLite/Schema/SchemaChanger.swift | 231 ++++++++++++ Sources/SQLite/Schema/SchemaDefinitions.swift | 336 +++++++++++++++++ .../Schema/ConnectionSchemaTests.swift | 139 +++++++ .../Schema/SchemaChangerTests.swift | 87 +++++ .../Schema/SchemaDefinitionsTests.swift | 342 ++++++++++++++++++ 7 files changed, 1350 insertions(+) create mode 100644 Sources/SQLite/Schema/Connection+Schema.swift create mode 100644 Sources/SQLite/Schema/SchemaChanger.swift create mode 100644 Sources/SQLite/Schema/SchemaDefinitions.swift create mode 100644 Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift create mode 100644 Tests/SQLiteTests/Schema/SchemaChangerTests.swift create mode 100644 Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2656ed6a..abd69301 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -51,24 +51,33 @@ 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.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 */; }; + 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.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 */; }; 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.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 */; }; + 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.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 */; }; @@ -78,19 +87,31 @@ 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; + 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; + 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; + 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; @@ -241,22 +262,28 @@ 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; }; + 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Schema.swift"; sourceTree = ""; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 19A171B262DDE8718513CFDA /* SchemaChanger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChanger.swift; sourceTree = ""; }; 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSIntegrationTests.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitions.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 = ""; }; 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Aggregation.swift"; sourceTree = ""; }; 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; + 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitionsTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794B7972D14330A65BBD /* Linux.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Linux.md; 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 = ""; }; 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; + 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionSchemaTests.swift; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; + 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChangerTests.swift; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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; }; @@ -381,6 +408,26 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 19A1792D261C689FC988A90A /* Schema */ = { + isa = PBXGroup; + children = ( + 19A171B262DDE8718513CFDA /* SchemaChanger.swift */, + 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */, + 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */, + ); + path = Schema; + sourceTree = ""; + }; + 19A17B56FBA20E7245BC8AC0 /* Schema */ = { + isa = PBXGroup; + children = ( + 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */, + 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */, + 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */, + ); + path = Schema; + sourceTree = ""; + }; 3D67B3E41DB2469200A4F4C6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -427,6 +474,7 @@ EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, EE247AF91C3F06E900AE3E12 /* Typed */, + 19A1792D261C689FC988A90A /* Schema */, ); name = SQLite; path = Sources/SQLite; @@ -463,6 +511,7 @@ 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, 3DF7B78C28842C23005DD8CA /* ResultTests.swift */, + 19A17B56FBA20E7245BC8AC0 /* Schema */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -868,6 +917,9 @@ 19A17073552293CA063BEA66 /* Result.swift in Sources */, 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, + 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */, + 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, + 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -901,6 +953,9 @@ D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */, + 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */, + 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */, + 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -935,6 +990,9 @@ 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */, + 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */, + 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */, + 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -970,6 +1028,9 @@ 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, + 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */, + 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, + 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1003,6 +1064,9 @@ D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */, + 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */, + 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */, + 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1038,6 +1102,9 @@ 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, + 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */, + 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, + 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1071,6 +1138,9 @@ D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */, + 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */, + 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */, + 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift new file mode 100644 index 00000000..e368bdaf --- /dev/null +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -0,0 +1,145 @@ +import Foundation + +extension Connection { + // Changing the foreign_keys setting affects the execution of all statements prepared using the database + // connection, including those prepared before the setting was changed. + // + // https://sqlite.org/pragma.html#pragma_foreign_keys + var foreignKeys: Bool { + get { getBoolPragma("foreign_keys") } + set { setBoolPragma("foreign_keys", newValue) } + } + + var deferForeignKeys: Bool { + get { getBoolPragma("defer_foreign_keys") } + set { setBoolPragma("defer_foreign_keys", newValue) } + } + + // https://sqlite.org/pragma.html#pragma_foreign_key_check + + // There are four columns in each result row. + // The first column is the name of the table that + // contains the REFERENCES clause. + // The second column is the rowid of the row that contains the + // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. + // The third column is the name of the table that is referred to. + // The fourth column is the index of the specific foreign key constraint that failed. + func foreignKeyCheck() throws -> [ForeignKeyError] { + try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in + guard let table = row[0] as? String, + let rowId = row[1] as? Int64, + let target = row[2] as? String else { return nil } + + return ForeignKeyError(from: table, rowId: rowId, to: target) + } + } + + // https://sqlite.org/pragma.html#pragma_table_info + // + // This pragma returns one row for each column in the named table. Columns in the result set include the + // column name, data type, whether or not the column can be NULL, and the default value for the column. The + // "pk" column in the result set is zero for columns that are not part of the primary key, and is the + // index of the column in the primary key for columns that are part of the primary key. + func columnInfo(table: String) throws -> [ColumnDefinition] { + func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { + try createTableSQL(name: table).flatMap { .init(sql: $0) } + } + + let foreignKeys: [String: [ForeignKeyDefinition]] = + Dictionary(grouping: try foreignKeyInfo(table: table), by: { $0.column }) + + return try run("PRAGMA table_info(\(table.quote()))").compactMap { row -> ColumnDefinition? in + guard let name = row[1] as? String, + let type = row[2] as? String, + let notNull = row[3] as? Int64, + let defaultValue = row[4] as? String?, + let primaryKey = row[5] as? Int64 else { return nil } + return ColumnDefinition(name: name, + primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, + type: ColumnDefinition.Affinity.from(type), + null: notNull == 0, + defaultValue: LiteralValue.from(defaultValue), + references: foreignKeys[name]?.first) + } + } + + func indexInfo(table: String) throws -> [IndexDefinition] { + func indexSQL(name: String) throws -> String? { + try run(""" + SELECT sql FROM sqlite_master WHERE name=? AND type='index' + UNION ALL + SELECT sql FROM sqlite_temp_master WHERE name=? AND type='index' + """, name, name) + .compactMap { row in row[0] as? String } + .first + } + + func columns(name: String) throws -> [String] { + try run("PRAGMA index_info(\(name.quote()))").compactMap { row in + row[2] as? String + } + } + + return try run("PRAGMA index_list(\(table.quote()))").compactMap { row -> IndexDefinition? in + guard let name = row[1] as? String, + let unique = row[2] as? Int64, + // Indexes SQLite creates implicitly for internal use start with "sqlite_". + // See https://www.sqlite.org/fileformat2.html#intschema + !name.starts(with: "sqlite_") else { + return nil + } + return .init(table: table, + name: name, + unique: unique == 1, + columns: try columns(name: name), + indexSQL: try indexSQL(name: name)) + } + } + + func tableInfo() throws -> [String] { + try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in + if let name = row[0] as? String, !name.starts(with: "sqlite_") { + return name + } else { + return nil + } + } + } + + func foreignKeyInfo(table: String) throws -> [ForeignKeyDefinition] { + try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in + if let table = row[2] as? String, // table + let column = row[3] as? String, // from + let primaryKey = row[4] as? String, // to + let onUpdate = row[5] as? String, + let onDelete = row[6] as? String { + return ForeignKeyDefinition(table: table, column: column, primaryKey: primaryKey, + onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, + onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete + ) + } else { + return nil + } + } + } + + private func createTableSQL(name: String) throws -> String? { + try run(""" + SELECT sql FROM sqlite_master WHERE name=? AND type='table' + UNION ALL + SELECT sql FROM sqlite_temp_master WHERE name=? AND type='table' + """, name, name) + .compactMap { row in row[0] as? String } + .first + } + + private func getBoolPragma(_ key: String) -> Bool { + guard let binding = try? scalar("PRAGMA \(key)"), + let intBinding = binding as? Int64 else { return false } + return intBinding == 1 + } + + private func setBoolPragma(_ key: String, _ newValue: Bool) { + _ = try? run("PRAGMA \(key) = \(newValue ? "1" : "0")") + } +} diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift new file mode 100644 index 00000000..ff0befc3 --- /dev/null +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -0,0 +1,231 @@ +import Foundation + +/* + https://www.sqlite.org/lang_altertable.html + + The only schema altering commands directly supported by SQLite are the "rename table" and "add column" + commands shown above. + + (SQLite 3.25.0: RENAME COLUMN) + (SQLite 3.35.0: DROP COLUMN) + + However, applications can make other arbitrary changes to the format of a table using a + simple sequence of operations. The steps to make arbitrary changes to the schema design of some table X are as follows: + + 1. If foreign key constraints are enabled, disable them using PRAGMA foreign_keys=OFF. + 2. Start a transaction. + 3. Remember the format of all indexes and triggers associated with table X + (SELECT sql FROM sqlite_master WHERE tbl_name='X' AND type='index') + 4. Use CREATE TABLE to construct a new table "new_X" that is in the desired revised format of table X. + 5. Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X. + 6. Drop the old table X: DROP TABLE X. + 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. + 8. Use CREATE INDEX and CREATE TRIGGER to reconstruct indexes and triggers associated with table X. + 9. If any views refer to table X in a way that is affected by the schema change, then drop those views using DROP VIEW + 10. If foreign key constraints were originally enabled then run PRAGMA foreign_key_check + 11. Commit the transaction started in step 2. + 12. If foreign keys constraints were originally enabled, reenable them now. +*/ +public class SchemaChanger: CustomStringConvertible { + enum SchemaChangeError: LocalizedError { + case foreignKeyError([ForeignKeyError]) + + var errorDescription: String? { + switch self { + case .foreignKeyError(let errors): + return "Foreign key errors: \(errors)" + } + } + } + + public enum Operation { + case none + case add(ColumnDefinition) + case remove(String) + case renameColumn(String, String) + case renameTable(String) + + /// Returns non-nil if the operation can be executed with a simple SQL statement + func toSQL(_ table: String) -> String? { + switch self { + case .add(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + default: return nil + } + } + } + + public class AlterTableDefinition { + fileprivate var operations: [Operation] = [] + + let name: String + + init(name: String) { + self.name = name + } + + public func add(_ column: ColumnDefinition) { + operations.append(.add(column)) + } + + public func remove(_ column: String) { + operations.append(.remove(column)) + } + + public func rename(_ column: String, to: String) { + operations.append(.renameColumn(column, to)) + } + } + + private let connection: Connection + static let tempPrefix = "tmp_" + typealias Block = () throws -> Void + public typealias AlterTableDefinitionBlock = (AlterTableDefinition) -> Void + + struct Options: OptionSet { + let rawValue: Int + static let `default`: Options = [] + static let temp = Options(rawValue: 1) + } + + public init(connection: Connection) { + self.connection = connection + } + + public func alter(table: String, block: AlterTableDefinitionBlock) throws { + let alterTableDefinition = AlterTableDefinition(name: table) + block(alterTableDefinition) + + for operation in alterTableDefinition.operations { + try run(table: table, operation: operation) + } + } + + public func drop(table: String) throws { + try dropTable(table) + } + + private func run(table: String, operation: Operation) throws { + if let sql = operation.toSQL(table) { + try connection.run(sql) + } else { + try doTheTableDance(table: table, operation: operation) + } + } + + private func doTheTableDance(table: String, operation: Operation) throws { + try connection.transaction { + try disableRefIntegrity { + let tempTable = "\(SchemaChanger.tempPrefix)\(table)" + try moveTable(from: table, to: tempTable, options: [.temp], operation: operation) + try moveTable(from: tempTable, to: table) + let foreignKeyErrors = try connection.foreignKeyCheck() + if foreignKeyErrors.count > 0 { + throw SchemaChangeError.foreignKeyError(foreignKeyErrors) + } + } + } + } + + private func disableRefIntegrity(block: Block) throws { + let oldForeignKeys = connection.foreignKeys + let oldDeferForeignKeys = connection.deferForeignKeys + + connection.deferForeignKeys = true + connection.foreignKeys = false + + defer { + connection.deferForeignKeys = oldDeferForeignKeys + connection.foreignKeys = oldForeignKeys + } + + try block() + } + + private func moveTable(from: String, to: String, options: Options = .default, operation: Operation = .none) throws { + try copyTable(from: from, to: to, options: options, operation: operation) + try dropTable(from) + } + + private func copyTable(from: String, to: String, options: Options = .default, operation: Operation) throws { + let fromDefinition = TableDefinition( + name: from, + columns: try connection.columnInfo(table: from), + indexes: try connection.indexInfo(table: from) + ) + let toDefinition = fromDefinition.apply(.renameTable(to)).apply(operation) + + try createTable(definition: toDefinition, options: options) + try createTableIndexes(definition: toDefinition) + if case .remove = operation { + try copyTableContents(from: fromDefinition.apply(operation), to: toDefinition) + } else { + try copyTableContents(from: fromDefinition, to: toDefinition) + } + } + + private func createTable(definition: TableDefinition, options: Options) throws { + try connection.run(definition.toSQL(temporary: options.contains(.temp))) + } + + private func createTableIndexes(definition: TableDefinition) throws { + for index in definition.indexes { + try index.validate() + try connection.run(index.toSQL()) + } + } + + private func dropTable(_ table: String) throws { + try connection.run("DROP TABLE IF EXISTS \(table.quote())") + } + + private func copyTableContents(from: TableDefinition, to: TableDefinition) throws { + try connection.run(from.copySQL(to: to)) + } + + public var description: String { + "SQLiteSchemaChanger: \(connection.description)" + } +} + +extension IndexDefinition { + func renameTable(to: String) -> IndexDefinition { + func indexName() -> String { + if to.starts(with: SchemaChanger.tempPrefix) { + return "\(SchemaChanger.tempPrefix)\(name)" + } else if table.starts(with: SchemaChanger.tempPrefix) { + return name.replacingOccurrences(of: SchemaChanger.tempPrefix, with: "") + } else { + return name + } + } + return IndexDefinition(table: to, name: indexName(), unique: unique, columns: columns, where: `where`, orders: orders) + } + + func renameColumn(from: String, to: String) -> IndexDefinition { + IndexDefinition(table: table, name: name, unique: unique, columns: columns.map { + $0 == from ? to : $0 + }, where: `where`, orders: orders) + } +} + +extension TableDefinition { + func apply(_ operation: SchemaChanger.Operation) -> TableDefinition { + switch operation { + case .none: return self + case .add: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") + case .remove(let column): + return TableDefinition(name: name, + columns: columns.filter { $0.name != column }, + indexes: indexes.filter { !$0.columns.contains(column) } + ) + case .renameColumn(let from, let to): + return TableDefinition( + name: name, + columns: columns.map { $0.rename(from: from, to: to) }, + indexes: indexes.map { $0.renameColumn(from: from, to: to) } + ) + case .renameTable(let to): + return TableDefinition(name: to, columns: columns, indexes: indexes.map { $0.renameTable(to: to) }) + } + } +} diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift new file mode 100644 index 00000000..563ae331 --- /dev/null +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -0,0 +1,336 @@ +import Foundation + +struct TableDefinition: Equatable { + let name: String + let columns: [ColumnDefinition] + let indexes: [IndexDefinition] + + var quotedColumnList: String { + columns.map { $0.name.quote() }.joined(separator: ", ") + } +} + +// https://sqlite.org/syntax/column-def.html +// column-name -> type-name -> column-constraint* +public struct ColumnDefinition: Equatable { + + // The type affinity of a column is the recommended type for data stored in that column. + // The important idea here is that the type is recommended, not required. Any column can still + // store any type of data. It is just that some columns, given the choice, will prefer to use one + // storage class over another. The preferred storage class for a column is called its "affinity". + enum Affinity: String, CustomStringConvertible, CaseIterable { + case INTEGER + case NUMERIC + case REAL + case TEXT + case BLOB + + var description: String { + rawValue + } + + static func from(_ string: String) -> Affinity { + Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? TEXT + } + } + + enum OnConflict: String, CaseIterable { + case ROLLBACK + case ABORT + case FAIL + case IGNORE + case REPLACE + + static func from(_ string: String) -> OnConflict? { + OnConflict.allCases.first { $0.rawValue == string } + } + } + + struct PrimaryKey: Equatable { + let autoIncrement: Bool + let onConflict: OnConflict? + + // swiftlint:disable:next force_try + static let pattern = try! NSRegularExpression(pattern: "PRIMARY KEY\\s*(?:ASC|DESC)?\\s*(?:ON CONFLICT (\\w+)?)?\\s*(AUTOINCREMENT)?") + + init(autoIncrement: Bool = true, onConflict: OnConflict? = nil) { + self.autoIncrement = autoIncrement + self.onConflict = onConflict + } + + init?(sql: String) { + if let match = PrimaryKey.pattern.firstMatch(in: sql, range: NSRange(location: 0, length: sql.count)) { + let conflict = match.range(at: 1) + var onConflict: ColumnDefinition.OnConflict? + if conflict.location != NSNotFound { + onConflict = .from((sql as NSString).substring(with: conflict)) + } + let autoIncrement = match.range(at: 2).location != NSNotFound + self.init(autoIncrement: autoIncrement, onConflict: onConflict) + } else { + return nil + } + } + } + + let name: String + let primaryKey: PrimaryKey? + let type: Affinity + let null: Bool + let defaultValue: LiteralValue + let references: ForeignKeyDefinition? + + func rename(from: String, to: String) -> ColumnDefinition { + guard from == name else { return self } + return ColumnDefinition(name: to, primaryKey: primaryKey, type: type, null: null, defaultValue: defaultValue, references: references) + } +} + +enum LiteralValue: Equatable, CustomStringConvertible { + // swiftlint:disable force_try + private static let singleQuote = try! NSRegularExpression(pattern: "^'(.*)'$") + private static let doubleQuote = try! NSRegularExpression(pattern: "^\"(.*)\"$") + private static let blob = try! NSRegularExpression(pattern: "^[xX]\'(.*)\'$") + // swiftlint:enable force_try + + case numericLiteral(String) + case stringLiteral(String) + // BLOB literals are string literals containing hexadecimal data and preceded by a single "x" or "X" + // character. Example: X'53514C697465' + case blobLiteral(String) + + // If there is no explicit DEFAULT clause attached to a column definition, then the default value of the + // column is NULL + // swiftlint:disable identifier_name + case NULL + + // Beginning with SQLite 3.23.0 (2018-04-02), SQLite recognizes the identifiers "TRUE" and + // "FALSE" as boolean literals, if and only if those identifiers are not already used for some other + // meaning. + // + // The boolean identifiers TRUE and FALSE are usually just aliases for the integer values 1 and 0, respectively. + case TRUE + case FALSE + case CURRENT_TIME + case CURRENT_DATE + case CURRENT_TIMESTAMP + // swiftlint:enable identifier_name + + static func from(_ string: String?) -> LiteralValue { + func parse(_ value: String) -> LiteralValue { + if let match = singleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { + return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "''", with: "'")) + } else if let match = doubleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { + return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "\"\"", with: "\"")) + } else if let match = blob.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { + return blobLiteral((value as NSString).substring(with: match.range(at: 1))) + } else { + return numericLiteral(value) + } + } + guard let string = string else { return NULL } + + switch string { + case "NULL": return NULL + case "TRUE": return TRUE + case "FALSE": return FALSE + case "CURRENT_TIME": return CURRENT_TIME + case "CURRENT_TIMESTAMP": return CURRENT_TIMESTAMP + case "CURRENT_DATE": return CURRENT_DATE + default: return parse(string) + } + } + + var description: String { + switch self { + case .NULL: return "NULL" + case .TRUE: return "TRUE" + case .FALSE: return "FALSE" + case .CURRENT_TIMESTAMP: return "CURRENT_TIMESTAMP" + case .CURRENT_TIME: return "CURRENT_TIME" + case .CURRENT_DATE: return "CURRENT_DATE" + case .stringLiteral(let value): return value.quote("'") + case .blobLiteral(let value): return "X\(value.quote("'"))" + case .numericLiteral(let value): return value + } + } + + func map(block: (LiteralValue) -> U) -> U? { + if self == .NULL { + return nil + } else { + return block(self) + } + } +} + +// https://sqlite.org/lang_createindex.html +// schema-name.index-name ON table-name ( indexed-column+ ) WHERE expr +struct IndexDefinition: Equatable { + // SQLite supports index names up to 64 characters. + static let maxIndexLength = 64 + + // swiftlint:disable force_try + static let whereRe = try! NSRegularExpression(pattern: "\\sWHERE\\s+(.+)$") + static let orderRe = try! NSRegularExpression(pattern: "\"?(\\w+)\"? DESC") + // swiftlint:enable force_try + + enum Order: String { case ASC, DESC } + + init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { + self.table = table + self.name = name + self.unique = unique + self.columns = columns + self.where = `where` + self.orders = orders + } + + init (table: String, name: String, unique: Bool, columns: [String], indexSQL: String?) { + func wherePart(sql: String) -> String? { + IndexDefinition.whereRe.firstMatch(in: sql, options: [], range: NSRange(location: 0, length: sql.count)).map { + (sql as NSString).substring(with: $0.range(at: 1)) + } + } + + func orders(sql: String) -> [String: IndexDefinition.Order] { + IndexDefinition.orderRe.matches(in: sql, range: NSRange(location: 0, length: sql.count)) + .reduce([String: IndexDefinition.Order]()) { (memo, result) in + var memo2 = memo + let column = (sql as NSString).substring(with: result.range(at: 1)) + memo2[column] = .DESC + return memo2 + } + } + self.init(table: table, + name: name, + unique: unique, + columns: columns, + where: indexSQL.flatMap(wherePart), + orders: indexSQL.flatMap(orders)) + } + + let table: String + let name: String + let unique: Bool + let columns: [String] + let `where`: String? + let orders: [String: Order]? + + enum IndexError: LocalizedError { + case tooLong(String, String) + + var errorDescription: String? { + switch self { + case .tooLong(let name, let table): + return "Index name '\(name)' on table '\(table)' is too long; the limit is " + + "\(IndexDefinition.maxIndexLength) characters" + } + } + } + + func validate() throws { + if name.count > IndexDefinition.maxIndexLength { + throw IndexError.tooLong(name, table) + } + } +} + +struct ForeignKeyDefinition: Equatable { + let table: String + let column: String + let primaryKey: String + let onUpdate: String? + let onDelete: String? +} + +struct ForeignKeyError: CustomStringConvertible { + let from: String + let rowId: Int64 + let to: String + + var description: String { + "\(from) [\(rowId)] => \(to)" + } +} + +extension TableDefinition { + func toSQL(temporary: Bool = false) -> String { + assert(columns.count > 0, "no columns to create") + + return ([ + "CREATE", + temporary ? "TEMPORARY" : nil, + "TABLE", + name, + "(", + columns.map { $0.toSQL() }.joined(separator: ",\n"), + ")" + ] as [String?]).compactMap { $0 } + .joined(separator: " ") + } + + func copySQL(to: TableDefinition) -> String { + assert(columns.count > 0, "no columns to copy") + assert(columns.count == to.columns.count, "column counts don't match") + return "INSERT INTO \(to.name.quote()) (\(to.quotedColumnList)) SELECT \(quotedColumnList) FROM \(name.quote())" + } +} + +extension ColumnDefinition { + func toSQL() -> String { + [ + name.quote(), + type.rawValue, + defaultValue.map { "DEFAULT \($0)" }, + primaryKey.map { $0.toSQL() }, + null ? nil : "NOT NULL", + references.map { $0.toSQL() } + ].compactMap { $0 } + .joined(separator: " ") + } +} + +extension IndexDefinition { + func toSQL(ifNotExists: Bool = false) -> String { + let commaSeparatedColumns = columns.map { (column: String) -> String in + column.quote() + (orders?[column].map { " \($0.rawValue)" } ?? "") + }.joined(separator: ", ") + + return ([ + "CREATE", + unique ? "UNIQUE" : nil, + "INDEX", + ifNotExists ? "IF NOT EXISTS" : nil, + name.quote(), + "ON", + table.quote(), + "(\(commaSeparatedColumns))", + `where`.map { "WHERE \($0)" } + ] as [String?]).compactMap { $0 } + .joined(separator: " ") + } +} + +extension ForeignKeyDefinition { + func toSQL() -> String { + ([ + "REFERENCES", + table.quote(), + "(\(primaryKey.quote()))", + onUpdate.map { "ON UPDATE \($0)" }, + onDelete.map { "ON DELETE \($0)" } + ] as [String?]).compactMap { $0 } + .joined(separator: " ") + } +} + +extension ColumnDefinition.PrimaryKey { + func toSQL() -> String { + [ + "PRIMARY KEY", + autoIncrement ? "AUTOINCREMENT" : nil, + onConflict.map { "ON CONFLICT \($0.rawValue)" } + ].compactMap { $0 }.joined(separator: " ") + } +} diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift new file mode 100644 index 00000000..02321510 --- /dev/null +++ b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift @@ -0,0 +1,139 @@ +import XCTest +@testable import SQLite + +class ConnectionSchemaTests: SQLiteTestCase { + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + } + + func test_column_info() throws { + let columns = try db.columnInfo(table: "users") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: false, onConflict: nil), + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "email", + primaryKey: nil, + type: .TEXT, + null: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "salary", + primaryKey: nil, + type: .REAL, + null: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "admin", + primaryKey: nil, + type: .TEXT, + null: false, + defaultValue: .numericLiteral("0"), + references: nil), + ColumnDefinition(name: "manager_id", + primaryKey: nil, type: .INTEGER, + null: true, + defaultValue: .NULL, + references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), + ColumnDefinition(name: "created_at", + primaryKey: nil, + type: .TEXT, + null: true, + defaultValue: .NULL, + references: nil) + ]) + } + + func test_column_info_parses_conflict_modifier() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY ON CONFLICT IGNORE AUTOINCREMENT)") + + XCTAssertEqual( + try db.columnInfo(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_column_info_detects_missing_autoincrement() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + XCTAssertEqual( + try db.columnInfo(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_index_info_no_index() throws { + let indexes = try db.indexInfo(table: "users") + XCTAssertTrue(indexes.isEmpty) + } + + func test_index_info_with_index() throws { + try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") + let indexes = try db.indexInfo(table: "users") + + XCTAssertEqual(indexes, [ + IndexDefinition( + table: "users", + name: "index_users", + unique: true, + columns: ["age"], + where: "age IS NOT NULL", + orders: ["age": .DESC] + ) + ]) + } + + func test_table_info_returns_list_of_tables() throws { + let tables = try db.tableInfo() + XCTAssertEqual(tables, ["users"]) + } + + func test_foreign_key_info_empty() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + let foreignKeys = try db.foreignKeyInfo(table: "t") + XCTAssertTrue(foreignKeys.isEmpty) + } + + func test_foreign_key_info() throws { + let linkTable = Table("test_links") + + let idColumn = SQLite.Expression("id") + let testIdColumn = SQLite.Expression("test_id") + + try db.run(linkTable.create(block: { definition in + definition.column(idColumn, primaryKey: .autoincrement) + definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) + })) + + let foreignKeys = try db.foreignKeyInfo(table: "test_links") + XCTAssertEqual(foreignKeys, [ + ForeignKeyDefinition(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + ]) + } +} diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift new file mode 100644 index 00000000..21ae6e59 --- /dev/null +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -0,0 +1,87 @@ +import XCTest +@testable import SQLite + +class SchemaChangerTests: SQLiteTestCase { + var schemaChanger: SchemaChanger! + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + + try insertUsers("bob") + + schemaChanger = SchemaChanger(connection: db) + } + + func test_empty_migration_does_not_change_column_definitions() throws { + let previous = try db.columnInfo(table: "users") + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.columnInfo(table: "users") + + XCTAssertEqual(previous, current) + } + + func test_empty_migration_does_not_change_index_definitions() throws { + let previous = try db.indexInfo(table: "users") + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.indexInfo(table: "users") + + XCTAssertEqual(previous, current) + } + + func test_empty_migration_does_not_change_foreign_key_definitions() throws { + let previous = try db.foreignKeyInfo(table: "users") + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.foreignKeyInfo(table: "users") + + XCTAssertEqual(previous, current) + } + + func test_empty_migration_does_not_change_the_row_count() throws { + let previous = try db.scalar(users.count) + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.scalar(users.count) + + XCTAssertEqual(previous, current) + } + + func test_remove_column() throws { + try schemaChanger.alter(table: "users") { table in + table.remove("age") + } + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + } + + func test_rename_column() throws { + try schemaChanger.alter(table: "users") { table in + table.rename("age", to: "age2") + } + + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + XCTAssertTrue(columns.contains("age2")) + } + + func test_add_column() throws { + let newColumn = ColumnDefinition(name: "new_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil) + + try schemaChanger.alter(table: "users") { table in + table.add(newColumn) + } + + let columns = try db.columnInfo(table: "users") + XCTAssertTrue(columns.contains(newColumn)) + } + + func test_drop_table() throws { + try schemaChanger.drop(table: "users") + + let tables = try db.tableInfo() + XCTAssertFalse(tables.contains("users")) + } +} diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift new file mode 100644 index 00000000..07fbf371 --- /dev/null +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -0,0 +1,342 @@ +import XCTest +@testable import SQLite + +class ColumnDefinitionTests: XCTestCase { + var definition: ColumnDefinition! + var expected: String! + + static let definitions: [(ColumnDefinition, String)] = [ + (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), + "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), + + (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, + references: ForeignKeyDefinition(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), + "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), + + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil), + "\"text\" TEXT"), + + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: false, defaultValue: .NULL, references: nil), + "\"text\" TEXT NOT NULL"), + + (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .stringLiteral("fo\"o"), references: nil), + "\"text_column\" TEXT DEFAULT 'fo\"o'"), + + (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, null: true, defaultValue: .numericLiteral("123"), references: nil), + "\"integer_column\" INTEGER DEFAULT 123"), + + (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, null: true, defaultValue: .numericLiteral("123.123"), references: nil), + "\"real_column\" REAL DEFAULT 123.123") + ] + + override class var defaultTestSuite: XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) + + for (column, expected) in ColumnDefinitionTests.definitions { + let test = ColumnDefinitionTests(selector: #selector(verify)) + test.definition = column + test.expected = expected + suite.addTest(test) + } + return suite + } + + @objc func verify() { + XCTAssertEqual(definition.toSQL(), expected) + } +} + +class AffinityTests: XCTestCase { + func test_from() { + XCTAssertEqual(ColumnDefinition.Affinity.from("TEXT"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity.from("text"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity.from("INTEGER"), .INTEGER) + } + + func test_returns_TEXT_for_unknown_type() { + XCTAssertEqual(ColumnDefinition.Affinity.from("baz"), .TEXT) + } +} + +class IndexDefinitionTests: XCTestCase { + var definition: IndexDefinition! + var expected: String! + var ifNotExists: Bool! + + static let definitions: [(IndexDefinition, Bool, String)] = [ + (IndexDefinition(table: "tests", name: "index_tests", + unique: false, + columns: ["test_column"], + where: nil, + orders: nil), + false, + "CREATE INDEX \"index_tests\" ON \"tests\" (\"test_column\")"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: true, + columns: ["test_column"], + where: nil, + orders: nil), + false, + "CREATE UNIQUE INDEX \"index_tests\" ON \"tests\" (\"test_column\")"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: true, + columns: ["test_column", "bar_column"], + where: "test_column IS NOT NULL", + orders: nil), + false, + "CREATE UNIQUE INDEX \"index_tests\" ON \"tests\" (\"test_column\", \"bar_column\") WHERE test_column IS NOT NULL"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: true, + columns: ["test_column", "bar_column"], + where: nil, + orders: ["test_column": .DESC]), + false, + "CREATE UNIQUE INDEX \"index_tests\" ON \"tests\" (\"test_column\" DESC, \"bar_column\")"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: false, + columns: ["test_column"], + where: nil, + orders: nil), + true, + "CREATE INDEX IF NOT EXISTS \"index_tests\" ON \"tests\" (\"test_column\")") + ] + + override class var defaultTestSuite: XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: IndexDefinitionTests.self) + + for (column, ifNotExists, expected) in IndexDefinitionTests.definitions { + let test = IndexDefinitionTests(selector: #selector(verify)) + test.definition = column + test.expected = expected + test.ifNotExists = ifNotExists + suite.addTest(test) + } + return suite + } + + @objc func verify() { + XCTAssertEqual(definition.toSQL(ifNotExists: ifNotExists), expected) + } + + func test_validate() { + + let longIndex = IndexDefinition( + table: "tests", + name: String(repeating: "x", count: 65), + unique: false, + columns: ["test_column"], + where: nil, + orders: nil) + + XCTAssertThrowsError(try longIndex.validate()) { error in + XCTAssertEqual(error.localizedDescription, + "Index name 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' " + + "on table 'tests' is too long; the limit is 64 characters") + } + } + + func test_rename() { + let index = IndexDefinition(table: "tests", name: "index_tests_something", + unique: true, + columns: ["test_column"], + where: "test_column IS NOT NULL", + orders: nil) + + let renamedIndex = index.renameTable(to: "foo") + + XCTAssertEqual(renamedIndex, + IndexDefinition( + table: "foo", + name: "index_tests_something", + unique: true, + columns: ["test_column"], + where: "test_column IS NOT NULL", + orders: nil + ) + ) + } +} + +class ForeignKeyDefinitionTests: XCTestCase { + func test_toSQL() { + XCTAssertEqual( + ForeignKeyDefinition( + table: "foo", + column: "bar", + primaryKey: "bar_id", + onUpdate: nil, + onDelete: "SET NULL" + ).toSQL(), """ + REFERENCES "foo" ("bar_id") ON DELETE SET NULL + """ + ) + } +} + +class TableDefinitionTests: XCTestCase { + func test_quoted_columnList() { + let definition = TableDefinition(name: "foo", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), + ColumnDefinition(name: "baz", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(definition.quotedColumnList, """ + "id", "baz" + """) + } + + func test_toSQL() { + let definition = TableDefinition(name: "foo", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(definition.toSQL(), """ + CREATE TABLE foo ( \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ) + """) + } + + func test_toSQL_temp_table() { + let definition = TableDefinition(name: "foo", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(definition.toSQL(temporary: true), """ + CREATE TEMPORARY TABLE foo ( \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ) + """) + } + + /* + func test_throws_an_error_when_columns_are_empty() { + let empty = TableDefinition(name: "empty", columns: [], indexes: []) + XCTAssertThrowsError(empty.toSQL()) + } + */ + + func test_copySQL() { + let from = TableDefinition(name: "from_table", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + let to = TableDefinition(name: "to_table", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(from.copySQL(to: to), """ + INSERT INTO "to_table" ("id") SELECT "id" FROM "from_table" + """) + } +} + +class PrimaryKeyTests: XCTestCase { + func test_toSQL() { + XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false).toSQL(), + "PRIMARY KEY") + } + + func test_toSQL_autoincrement() { + XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: true).toSQL(), + "PRIMARY KEY AUTOINCREMENT") + } + + func test_toSQL_on_conflict() { + XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK).toSQL(), + "PRIMARY KEY ON CONFLICT ROLLBACK") + } +} + +class LiteralValueTests: XCTestCase { + func test_recognizes_TRUE() { + XCTAssertEqual(LiteralValue.from("TRUE"), .TRUE) + } + + func test_recognizes_FALSE() { + XCTAssertEqual(LiteralValue.from("FALSE"), .FALSE) + } + + func test_recognizes_NULL() { + XCTAssertEqual(LiteralValue.from("NULL"), .NULL) + } + + func test_recognizes_nil() { + XCTAssertEqual(LiteralValue.from(nil), .NULL) + } + + func test_recognizes_CURRENT_TIME() { + XCTAssertEqual(LiteralValue.from("CURRENT_TIME"), .CURRENT_TIME) + } + + func test_recognizes_CURRENT_TIMESTAMP() { + XCTAssertEqual(LiteralValue.from("CURRENT_TIMESTAMP"), .CURRENT_TIMESTAMP) + } + + func test_recognizes_CURRENT_DATE() { + XCTAssertEqual(LiteralValue.from("CURRENT_DATE"), .CURRENT_DATE) + } + + func test_recognizes_double_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("\"foo\""), .stringLiteral("foo")) + } + + func test_recognizes_single_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("\'foo\'"), .stringLiteral("foo")) + } + + func test_unquotes_double_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("\"fo\"\"o\""), .stringLiteral("fo\"o")) + } + + func test_unquotes_single_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("'fo''o'"), .stringLiteral("fo'o")) + } + + func test_recognizes_numeric_literals() { + XCTAssertEqual(LiteralValue.from("1.2"), .numericLiteral("1.2")) + XCTAssertEqual(LiteralValue.from("0xdeadbeef"), .numericLiteral("0xdeadbeef")) + } + + func test_recognizes_blob_literals() { + XCTAssertEqual(LiteralValue.from("X'deadbeef'"), .blobLiteral("deadbeef")) + XCTAssertEqual(LiteralValue.from("x'deadbeef'"), .blobLiteral("deadbeef")) + } + + func test_description_TRUE() { + XCTAssertEqual(LiteralValue.TRUE.description, "TRUE") + } + + func test_description_FALSE() { + XCTAssertEqual(LiteralValue.FALSE.description, "FALSE") + } + + func test_description_NULL() { + XCTAssertEqual(LiteralValue.NULL.description, "NULL") + } + + func test_description_CURRENT_TIME() { + XCTAssertEqual(LiteralValue.CURRENT_TIME.description, "CURRENT_TIME") + } + + func test_description_CURRENT_TIMESTAMP() { + XCTAssertEqual(LiteralValue.CURRENT_TIMESTAMP.description, "CURRENT_TIMESTAMP") + } + + func test_description_CURRENT_DATE() { + XCTAssertEqual(LiteralValue.CURRENT_DATE.description, "CURRENT_DATE") + } + + func test_description_string_literal() { + XCTAssertEqual(LiteralValue.stringLiteral("foo").description, "'foo'") + } + + func test_description_numeric_literal() { + XCTAssertEqual(LiteralValue.numericLiteral("1.2").description, "1.2") + XCTAssertEqual(LiteralValue.numericLiteral("0xdeadbeef").description, "0xdeadbeef") + } + + func test_description_blob_literal() { + XCTAssertEqual(LiteralValue.blobLiteral("deadbeef").description, "X'deadbeef'") + } +} From affcec8494488f155a846dc58c388ca798a239e7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 24 Jul 2022 02:43:39 +0200 Subject: [PATCH 070/216] Use DROP/RENAME COLUMN when available --- Sources/SQLite/Schema/Connection+Schema.swift | 13 +++++++++++ Sources/SQLite/Schema/SchemaChanger.swift | 22 +++++++++++++++---- .../Schema/ConnectionSchemaTests.swift | 7 ++++++ .../Schema/SchemaChangerTests.swift | 22 +++++++++++++++++++ .../Schema/SchemaDefinitionsTests.swift | 4 ++++ 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index e368bdaf..c6cf197f 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,6 +1,19 @@ import Foundation extension Connection { + var sqliteVersion: String? { + (try? scalar("SELECT sqlite_version()")) as? String + } + + var sqliteVersionTriple: (Int, Int, Int) { + guard let version = sqliteVersion, + let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, + let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { + return (0, 0, 0) + } + return (major, minor, point) + } + // Changing the foreign_keys setting affects the execution of all statements prepared using the database // connection, including those prepared before the setting was changed. // diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index ff0befc3..cd6b6f25 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -27,6 +27,8 @@ import Foundation 12. If foreign keys constraints were originally enabled, reenable them now. */ public class SchemaChanger: CustomStringConvertible { + typealias SQLiteVersion = (Int, Int, Int) + enum SchemaChangeError: LocalizedError { case foreignKeyError([ForeignKeyError]) @@ -46,9 +48,14 @@ public class SchemaChanger: CustomStringConvertible { case renameTable(String) /// Returns non-nil if the operation can be executed with a simple SQL statement - func toSQL(_ table: String) -> String? { + func toSQL(_ table: String, version: SQLiteVersion) -> String? { switch self { - case .add(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + case .add(let definition): + return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + case .renameColumn(let from, let to) where version.0 >= 3 && version.1 >= 25: + return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" + case .remove(let column) where version.0 >= 3 && version.1 >= 35: + return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } } @@ -77,6 +84,7 @@ public class SchemaChanger: CustomStringConvertible { } private let connection: Connection + private let version: SQLiteVersion static let tempPrefix = "tmp_" typealias Block = () throws -> Void public typealias AlterTableDefinitionBlock = (AlterTableDefinition) -> Void @@ -87,8 +95,14 @@ public class SchemaChanger: CustomStringConvertible { static let temp = Options(rawValue: 1) } - public init(connection: Connection) { + public convenience init(connection: Connection) { + self.init(connection: connection, + version: connection.sqliteVersionTriple) + } + + init(connection: Connection, version: SQLiteVersion) { self.connection = connection + self.version = version } public func alter(table: String, block: AlterTableDefinitionBlock) throws { @@ -105,7 +119,7 @@ public class SchemaChanger: CustomStringConvertible { } private func run(table: String, operation: Operation) throws { - if let sql = operation.toSQL(table) { + if let sql = operation.toSQL(table, version: version) { try connection.run(sql) } else { try doTheTableDance(table: table, operation: operation) diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift index 02321510..9fbad26c 100644 --- a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift +++ b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift @@ -136,4 +136,11 @@ class ConnectionSchemaTests: SQLiteTestCase { ForeignKeyDefinition(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) ]) } + + func test_sqlite_version_triple() { + let version = db.sqliteVersionTriple + XCTAssertEqual(version.0, 3) + XCTAssertGreaterThan(version.1, 0) + XCTAssertGreaterThanOrEqual(version.2, 0) + } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 21ae6e59..165de4a8 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -57,6 +57,16 @@ class SchemaChangerTests: SQLiteTestCase { XCTAssertFalse(columns.contains("age")) } + func test_remove_column_legacy() throws { + schemaChanger = .init(connection: db, version: (3, 24, 0)) // DROP COLUMN introduced in 3.35.0 + + try schemaChanger.alter(table: "users") { table in + table.remove("age") + } + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + } + func test_rename_column() throws { try schemaChanger.alter(table: "users") { table in table.rename("age", to: "age2") @@ -67,6 +77,18 @@ class SchemaChangerTests: SQLiteTestCase { XCTAssertTrue(columns.contains("age2")) } + func test_rename_column_legacy() throws { + schemaChanger = .init(connection: db, version: (3, 24, 0)) // RENAME COLUMN introduced in 3.25.0 + + try schemaChanger.alter(table: "users") { table in + table.rename("age", to: "age2") + } + + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + XCTAssertTrue(columns.contains("age2")) + } + func test_add_column() throws { let newColumn = ColumnDefinition(name: "new_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil) diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 07fbf371..c19840bc 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -29,6 +29,7 @@ class ColumnDefinitionTests: XCTestCase { "\"real_column\" REAL DEFAULT 123.123") ] + #if !os(Linux) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) @@ -44,6 +45,7 @@ class ColumnDefinitionTests: XCTestCase { @objc func verify() { XCTAssertEqual(definition.toSQL(), expected) } + #endif } class AffinityTests: XCTestCase { @@ -105,6 +107,7 @@ class IndexDefinitionTests: XCTestCase { "CREATE INDEX IF NOT EXISTS \"index_tests\" ON \"tests\" (\"test_column\")") ] + #if !os(Linux) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: IndexDefinitionTests.self) @@ -121,6 +124,7 @@ class IndexDefinitionTests: XCTestCase { @objc func verify() { XCTAssertEqual(definition.toSQL(ifNotExists: ifNotExists), expected) } + #endif func test_validate() { From 1b645291774b5d36af7630c8bdd8530373d1368d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 24 Jul 2022 19:49:41 +0200 Subject: [PATCH 071/216] ColumnDefinition needs to be public --- SQLite.xcodeproj/project.pbxproj | 10 +++ Sources/SQLite/Core/Connection+Pragmas.swift | 52 +++++++++++ Sources/SQLite/Core/Connection.swift | 11 --- Sources/SQLite/Schema/Connection+Schema.swift | 87 ++++++------------- Sources/SQLite/Schema/SchemaChanger.swift | 8 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 60 +++++++------ .../Schema/ConnectionSchemaTests.swift | 9 +- .../Schema/SchemaDefinitionsTests.swift | 4 +- 8 files changed, 130 insertions(+), 111 deletions(-) create mode 100644 Sources/SQLite/Core/Connection+Pragmas.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index abd69301..a97ef184 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; + 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; @@ -71,6 +72,7 @@ 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; @@ -82,7 +84,9 @@ 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 */; }; + 19A178A8B2A34FB6B565DEDA /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -283,6 +287,7 @@ 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionSchemaTests.swift; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; + 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Pragmas.swift"; sourceTree = ""; }; 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChangerTests.swift; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -530,6 +535,7 @@ 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, + 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */, ); path = Core; sourceTree = ""; @@ -920,6 +926,7 @@ 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */, 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, + 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -993,6 +1000,7 @@ 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */, 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */, 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */, + 19A178A8B2A34FB6B565DEDA /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1031,6 +1039,7 @@ 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */, 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, + 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1105,6 +1114,7 @@ 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */, 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, + 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Connection+Pragmas.swift b/Sources/SQLite/Core/Connection+Pragmas.swift new file mode 100644 index 00000000..43ff9610 --- /dev/null +++ b/Sources/SQLite/Core/Connection+Pragmas.swift @@ -0,0 +1,52 @@ +import Foundation + +public typealias UserVersion = Int32 +public typealias SQLiteVersion = (Int, Int, Int) + +public extension Connection { + /// The user version of the database. + /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) + var userVersion: UserVersion? { + get { + (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) + } + set { + _ = try? run("PRAGMA user_version = \(newValue ?? 0)") + } + } + + /// The version of SQLite. + /// See SQLite [sqlite_version()](https://sqlite.org/lang_corefunc.html#sqlite_version) + var sqliteVersion: SQLiteVersion { + guard let version = (try? scalar("SELECT sqlite_version()")) as? String, + let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, + let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { + return (0, 0, 0) + } + return (major, minor, point) + } + + // Changing the foreign_keys setting affects the execution of all statements prepared using the database + // connection, including those prepared before the setting was changed. + // + // https://sqlite.org/pragma.html#pragma_foreign_keys + var foreignKeys: Bool { + get { getBoolPragma("foreign_keys") } + set { setBoolPragma("foreign_keys", newValue) } + } + + var deferForeignKeys: Bool { + get { getBoolPragma("defer_foreign_keys") } + set { setBoolPragma("defer_foreign_keys", newValue) } + } + + private func getBoolPragma(_ key: String) -> Bool { + guard let binding = try? scalar("PRAGMA \(key)"), + let intBinding = binding as? Int64 else { return false } + return intBinding == 1 + } + + private func setBoolPragma(_ key: String, _ newValue: Bool) { + _ = try? run("PRAGMA \(key) = \(newValue ? "1" : "0")") + } +} diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 68b83821..0e51e651 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -156,17 +156,6 @@ public final class Connection { Int(sqlite3_total_changes(handle)) } - /// The user version of the database. - /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - public var userVersion: Int32? { - get { - (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) - } - set { - _ = try? run("PRAGMA user_version = \(newValue ?? 0)") - } - } - // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index c6cf197f..8bd219ed 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,52 +1,6 @@ import Foundation extension Connection { - var sqliteVersion: String? { - (try? scalar("SELECT sqlite_version()")) as? String - } - - var sqliteVersionTriple: (Int, Int, Int) { - guard let version = sqliteVersion, - let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, - let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { - return (0, 0, 0) - } - return (major, minor, point) - } - - // Changing the foreign_keys setting affects the execution of all statements prepared using the database - // connection, including those prepared before the setting was changed. - // - // https://sqlite.org/pragma.html#pragma_foreign_keys - var foreignKeys: Bool { - get { getBoolPragma("foreign_keys") } - set { setBoolPragma("foreign_keys", newValue) } - } - - var deferForeignKeys: Bool { - get { getBoolPragma("defer_foreign_keys") } - set { setBoolPragma("defer_foreign_keys", newValue) } - } - - // https://sqlite.org/pragma.html#pragma_foreign_key_check - - // There are four columns in each result row. - // The first column is the name of the table that - // contains the REFERENCES clause. - // The second column is the rowid of the row that contains the - // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. - // The third column is the name of the table that is referred to. - // The fourth column is the index of the specific foreign key constraint that failed. - func foreignKeyCheck() throws -> [ForeignKeyError] { - try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in - guard let table = row[0] as? String, - let rowId = row[1] as? Int64, - let target = row[2] as? String else { return nil } - - return ForeignKeyError(from: table, rowId: rowId, to: target) - } - } - // https://sqlite.org/pragma.html#pragma_table_info // // This pragma returns one row for each column in the named table. Columns in the result set include the @@ -58,7 +12,7 @@ extension Connection { try createTableSQL(name: table).flatMap { .init(sql: $0) } } - let foreignKeys: [String: [ForeignKeyDefinition]] = + let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = Dictionary(grouping: try foreignKeyInfo(table: table), by: { $0.column }) return try run("PRAGMA table_info(\(table.quote()))").compactMap { row -> ColumnDefinition? in @@ -71,7 +25,7 @@ extension Connection { primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, type: ColumnDefinition.Affinity.from(type), null: notNull == 0, - defaultValue: LiteralValue.from(defaultValue), + defaultValue: .from(defaultValue), references: foreignKeys[name]?.first) } } @@ -119,16 +73,16 @@ extension Connection { } } - func foreignKeyInfo(table: String) throws -> [ForeignKeyDefinition] { + func foreignKeyInfo(table: String) throws -> [ColumnDefinition.ForeignKey] { try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in if let table = row[2] as? String, // table let column = row[3] as? String, // from let primaryKey = row[4] as? String, // to let onUpdate = row[5] as? String, let onDelete = row[6] as? String { - return ForeignKeyDefinition(table: table, column: column, primaryKey: primaryKey, - onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, - onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete + return .init(table: table, column: column, primaryKey: primaryKey, + onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, + onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete ) } else { return nil @@ -136,6 +90,25 @@ extension Connection { } } + // https://sqlite.org/pragma.html#pragma_foreign_key_check + + // There are four columns in each result row. + // The first column is the name of the table that + // contains the REFERENCES clause. + // The second column is the rowid of the row that contains the + // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. + // The third column is the name of the table that is referred to. + // The fourth column is the index of the specific foreign key constraint that failed. + func foreignKeyCheck() throws -> [ForeignKeyError] { + try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in + guard let table = row[0] as? String, + let rowId = row[1] as? Int64, + let target = row[2] as? String else { return nil } + + return ForeignKeyError(from: table, rowId: rowId, to: target) + } + } + private func createTableSQL(name: String) throws -> String? { try run(""" SELECT sql FROM sqlite_master WHERE name=? AND type='table' @@ -145,14 +118,4 @@ extension Connection { .compactMap { row in row[0] as? String } .first } - - private func getBoolPragma(_ key: String) -> Bool { - guard let binding = try? scalar("PRAGMA \(key)"), - let intBinding = binding as? Int64 else { return false } - return intBinding == 1 - } - - private func setBoolPragma(_ key: String, _ newValue: Bool) { - _ = try? run("PRAGMA \(key) = \(newValue ? "1" : "0")") - } } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index cd6b6f25..c6529fbe 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -27,8 +27,6 @@ import Foundation 12. If foreign keys constraints were originally enabled, reenable them now. */ public class SchemaChanger: CustomStringConvertible { - typealias SQLiteVersion = (Int, Int, Int) - enum SchemaChangeError: LocalizedError { case foreignKeyError([ForeignKeyError]) @@ -52,9 +50,9 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .add(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .renameColumn(let from, let to) where version.0 >= 3 && version.1 >= 25: + case .renameColumn(let from, let to) where version >= (3, 25, 0): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .remove(let column) where version.0 >= 3 && version.1 >= 35: + case .remove(let column) where version >= (3, 35, 0): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } @@ -97,7 +95,7 @@ public class SchemaChanger: CustomStringConvertible { public convenience init(connection: Connection) { self.init(connection: connection, - version: connection.sqliteVersionTriple) + version: connection.sqliteVersion) } init(connection: Connection, version: SQLiteVersion) { diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 563ae331..156a06fb 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -18,14 +18,14 @@ public struct ColumnDefinition: Equatable { // The important idea here is that the type is recommended, not required. Any column can still // store any type of data. It is just that some columns, given the choice, will prefer to use one // storage class over another. The preferred storage class for a column is called its "affinity". - enum Affinity: String, CustomStringConvertible, CaseIterable { + public enum Affinity: String, CustomStringConvertible, CaseIterable { case INTEGER case NUMERIC case REAL case TEXT case BLOB - var description: String { + public var description: String { rawValue } @@ -34,7 +34,7 @@ public struct ColumnDefinition: Equatable { } } - enum OnConflict: String, CaseIterable { + public enum OnConflict: String, CaseIterable { case ROLLBACK case ABORT case FAIL @@ -46,7 +46,7 @@ public struct ColumnDefinition: Equatable { } } - struct PrimaryKey: Equatable { + public struct PrimaryKey: Equatable { let autoIncrement: Bool let onConflict: OnConflict? @@ -73,12 +73,30 @@ public struct ColumnDefinition: Equatable { } } - let name: String - let primaryKey: PrimaryKey? - let type: Affinity - let null: Bool - let defaultValue: LiteralValue - let references: ForeignKeyDefinition? + public struct ForeignKey: Equatable { + let table: String + let column: String + let primaryKey: String + let onUpdate: String? + let onDelete: String? + } + + public let name: String + public let primaryKey: PrimaryKey? + public let type: Affinity + public let null: Bool + public let defaultValue: LiteralValue + public let references: ForeignKey? + + public init(name: String, primaryKey: PrimaryKey?, type: Affinity, null: Bool, defaultValue: LiteralValue, + references: ForeignKey?) { + self.name = name + self.primaryKey = primaryKey + self.type = type + self.null = null + self.defaultValue = defaultValue + self.references = references + } func rename(from: String, to: String) -> ColumnDefinition { guard from == name else { return self } @@ -86,7 +104,7 @@ public struct ColumnDefinition: Equatable { } } -enum LiteralValue: Equatable, CustomStringConvertible { +public enum LiteralValue: Equatable, CustomStringConvertible { // swiftlint:disable force_try private static let singleQuote = try! NSRegularExpression(pattern: "^'(.*)'$") private static let doubleQuote = try! NSRegularExpression(pattern: "^\"(.*)\"$") @@ -141,7 +159,7 @@ enum LiteralValue: Equatable, CustomStringConvertible { } } - var description: String { + public var description: String { switch self { case .NULL: return "NULL" case .TRUE: return "TRUE" @@ -166,7 +184,7 @@ enum LiteralValue: Equatable, CustomStringConvertible { // https://sqlite.org/lang_createindex.html // schema-name.index-name ON table-name ( indexed-column+ ) WHERE expr -struct IndexDefinition: Equatable { +public struct IndexDefinition: Equatable { // SQLite supports index names up to 64 characters. static let maxIndexLength = 64 @@ -175,9 +193,9 @@ struct IndexDefinition: Equatable { static let orderRe = try! NSRegularExpression(pattern: "\"?(\\w+)\"? DESC") // swiftlint:enable force_try - enum Order: String { case ASC, DESC } + public enum Order: String { case ASC, DESC } - init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { + public init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { self.table = table self.name = name self.unique = unique @@ -236,14 +254,6 @@ struct IndexDefinition: Equatable { } } -struct ForeignKeyDefinition: Equatable { - let table: String - let column: String - let primaryKey: String - let onUpdate: String? - let onDelete: String? -} - struct ForeignKeyError: CustomStringConvertible { let from: String let rowId: Int64 @@ -292,7 +302,7 @@ extension ColumnDefinition { } extension IndexDefinition { - func toSQL(ifNotExists: Bool = false) -> String { + public func toSQL(ifNotExists: Bool = false) -> String { let commaSeparatedColumns = columns.map { (column: String) -> String in column.quote() + (orders?[column].map { " \($0.rawValue)" } ?? "") }.joined(separator: ", ") @@ -312,7 +322,7 @@ extension IndexDefinition { } } -extension ForeignKeyDefinition { +extension ColumnDefinition.ForeignKey { func toSQL() -> String { ([ "REFERENCES", diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift index 9fbad26c..1c66b761 100644 --- a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift +++ b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift @@ -133,14 +133,11 @@ class ConnectionSchemaTests: SQLiteTestCase { let foreignKeys = try db.foreignKeyInfo(table: "test_links") XCTAssertEqual(foreignKeys, [ - ForeignKeyDefinition(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) ]) } - func test_sqlite_version_triple() { - let version = db.sqliteVersionTriple - XCTAssertEqual(version.0, 3) - XCTAssertGreaterThan(version.1, 0) - XCTAssertGreaterThanOrEqual(version.2, 0) + func test_sqlite_version() { + XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index c19840bc..645bfc34 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -10,7 +10,7 @@ class ColumnDefinitionTests: XCTestCase { "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, - references: ForeignKeyDefinition(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), + references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil), @@ -168,7 +168,7 @@ class IndexDefinitionTests: XCTestCase { class ForeignKeyDefinitionTests: XCTestCase { func test_toSQL() { XCTAssertEqual( - ForeignKeyDefinition( + ColumnDefinition.ForeignKey( table: "foo", column: "bar", primaryKey: "bar_id", From f57c225bad580ddfb22149f8d86e8e12ddca3dbf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 25 Jul 2022 01:04:28 +0200 Subject: [PATCH 072/216] Move tests into separate directories --- SQLite.xcodeproj/project.pbxproj | 424 ++++++++++-------- Sources/SQLite/Schema/Connection+Schema.swift | 10 - Tests/SQLiteTests/{ => Core}/BlobTests.swift | 0 .../Core/Connection+AttachTests.swift | 62 +++ .../Core/Connection+PragmaTests.swift | 42 ++ .../{ => Core}/ConnectionTests.swift | 52 --- .../{ => Core}/CoreFunctionsTests.swift | 0 .../SQLiteTests/{ => Core}/ResultTests.swift | 0 .../{ => Core}/StatementTests.swift | 0 Tests/SQLiteTests/{ => Core}/ValueTests.swift | 0 .../{ => Extensions}/CipherTests.swift | 0 .../{ => Extensions}/FTS4Tests.swift | 0 .../{ => Extensions}/FTS5Tests.swift | 0 .../FTSIntegrationTests.swift | 0 .../{ => Extensions}/RTreeTests.swift | 0 ...sts.swift => Connection+SchemaTests.swift} | 9 - .../Schema/SchemaChangerTests.swift | 10 +- .../{ => Schema}/SchemaTests.swift | 0 .../{ => Typed}/AggregateFunctionsTests.swift | 0 .../{ => Typed}/CustomAggregationTests.swift | 0 .../{ => Typed}/CustomFunctionsTests.swift | 0 .../DateAndTimeFunctionTests.swift | 0 .../{ => Typed}/ExpressionTests.swift | 0 .../{ => Typed}/OperatorsTests.swift | 0 .../{ => Typed}/QueryIntegrationTests.swift | 0 .../SQLiteTests/{ => Typed}/QueryTests.swift | 0 Tests/SQLiteTests/{ => Typed}/RowTests.swift | 0 .../SQLiteTests/{ => Typed}/SelectTests.swift | 0 .../SQLiteTests/{ => Typed}/SetterTests.swift | 0 29 files changed, 343 insertions(+), 266 deletions(-) rename Tests/SQLiteTests/{ => Core}/BlobTests.swift (100%) create mode 100644 Tests/SQLiteTests/Core/Connection+AttachTests.swift create mode 100644 Tests/SQLiteTests/Core/Connection+PragmaTests.swift rename Tests/SQLiteTests/{ => Core}/ConnectionTests.swift (90%) rename Tests/SQLiteTests/{ => Core}/CoreFunctionsTests.swift (100%) rename Tests/SQLiteTests/{ => Core}/ResultTests.swift (100%) rename Tests/SQLiteTests/{ => Core}/StatementTests.swift (100%) rename Tests/SQLiteTests/{ => Core}/ValueTests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/CipherTests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/FTS4Tests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/FTS5Tests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/FTSIntegrationTests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/RTreeTests.swift (100%) rename Tests/SQLiteTests/Schema/{ConnectionSchemaTests.swift => Connection+SchemaTests.swift} (95%) rename Tests/SQLiteTests/{ => Schema}/SchemaTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/AggregateFunctionsTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/CustomAggregationTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/CustomFunctionsTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/DateAndTimeFunctionTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/ExpressionTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/OperatorsTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/QueryIntegrationTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/QueryTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/RowTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/SelectTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/SetterTests.swift (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a97ef184..bc79473e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -30,96 +30,136 @@ 03A65E841C6BB2FB0062603F /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; 03A65E861C6BB2FB0062603F /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - 03A65E871C6BB3030062603F /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; - 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; - 03A65E891C6BB3030062603F /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; - 03A65E8A1C6BB3030062603F /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; - 03A65E8B1C6BB3030062603F /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; - 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; - 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; - 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; - 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; - 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; - 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; - 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; - 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; + 19A17021286A4D8D6C2EF12D /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; + 19A17026DCDCDA405B09A229 /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; 19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A1708D3D58D7BC1168E55F /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; + 19A170AEBAA56DC3355A73B3 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; + 19A170C56745F9D722A73D77 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; + 19A170D938343E30119EDFB3 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; + 19A1714F7CF964D568AB14E0 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A1716BF8E15F91A6B5CB7A /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; - 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; - 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; - 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; - 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A17188B4D96636F9C0C209 /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.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 */; }; - 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; - 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; - 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A171F243A589C5EBC47937 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.swift */; }; + 19A1725658E480B9B378F28B /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */; }; + 19A1726002D24C14F876C8FE /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; + 19A172F71EFD65342072D8D2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; + 19A173088B85A7E18E8582A7 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; + 19A173389E53CB24DFA8CEDD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; + 19A173465F23C64DF3DF469B /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A173F25449876761347072 /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; + 19A173F429D7E46289EB2167 /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A17411403D60640467209E /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + 19A174118D11B93DA5DAAF79 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; + 19A17437659BD7FD787D94A6 /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + 19A17444861E1443143DEB44 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A17457B0461F484AF6BE40 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; + 19A17482E6FC5E563F3E6A47 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.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 */; }; 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A1755C49154C87304C9146 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; - 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A1766135CE9786B1878603 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; - 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A176B3316281F004F92276 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A1772EBE65173EDFB1AFCA /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + 19A1773155AC2BF2CA86A473 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A17746150A815944A6820B /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + 19A1776BD5127DFDF847FF1F /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; + 19A177909023B7B940C5805E /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; + 19A177AA5922527BBDC77CF9 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; - 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A177D5C6542E2D572162E5 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A1781CBA8968ABD3E00877 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + 19A1782444437C7FC6B75CBC /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; - 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; + 19A178767223229E61C5066F /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; + 19A17885B646CB0201BE4BD5 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; 19A178A8B2A34FB6B565DEDA /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; + 19A178C041DDCF80B533AD13 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; + 19A178DA2BB5970778CCAF13 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; + 19A178DF5A96CFEFF1E271F6 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; + 19A178F9008614B8A8425635 /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; + 19A17900387FDCF578B31E3E /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.swift */; }; + 19A17912DB9D3AC8FECF948B /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; + 19A17923494236793893BF72 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A1793972BDDDB027C113BB /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + 19A179786A6826D58A70F8BC /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; - 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A1799AF6643CF5081BFA15 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; + 19A179BB9A6665B2B99DA546 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; + 19A179BCD483DEA21661FD37 /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A17A391BF056E3D729E70A /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; + 19A17A52BF29D27C9AA229E7 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; + 19A17A7B3E3B7E76364A2AEE /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; + 19A17A7DF99B0379FD3396B1 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; + 19A17A9520802ACF45907970 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; + 19A17ABCF0EB4808BDC5B5FF /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; - 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; + 19A17B1D9B5CEBE9CE09280C /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; + 19A17B36ABC6006AB80F693C /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */; }; 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; - 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; - 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; - 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; - 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; - 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A17BACF4C032513DE1F879 /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; + 19A17C74233AFC2EDAFA23DC /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; + 19A17CA4D7B63D845428A9C5 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; + 19A17CA6ADB78A2E545BF836 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; + 19A17CF65C0196E03BC64519 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A17D6EC40BC35A5DC81BA8 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; + 19A17D993398B8215B73E1EA /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; + 19A17DAD5975D9367EAA46E2 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A17DD33C2E43DD6EE05A60 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + 19A17DE1FCDB5695702AD24D /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + 19A17DE34C477232592A8F6B /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17E0ABA6C415F014CD51C /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; + 19A17E1DD976D5CE80018749 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A17E3F47DA087E2B76D087 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A17F2096E83A3181E03317 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A17F7977364EC8CD33C3C3 /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + 19A17F907258E524B3CA2FAE /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; + 19A17FACE8E4D54A50BA934E /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17FBAA26953EB854E790D /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A17FD22EF43DF428DD93BA /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; - 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; - 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; - 3717F90A221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.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 */; }; @@ -144,9 +184,6 @@ 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; - 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; - 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; - 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; 3DF7B791288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; @@ -162,9 +199,6 @@ 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; - D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; - D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; - D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.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 */; }; @@ -185,35 +219,7 @@ EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; EE247B171C3F127200AE3E12 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; - EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; - EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; - EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; - EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; - EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; - EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; - EE247B281C3F137700AE3E12 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; - EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; - EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; - EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - EE247B301C3F141E00AE3E12 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; - EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; - EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; - EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; EE247B461C3F3ED000AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */; }; - EE247B531C3F3FC700AE3E12 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; - EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; - EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; - EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; - EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; - EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; - EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; - EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; - EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - EE247B5C1C3F3FC700AE3E12 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; - EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; - EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; - EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; - EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; @@ -266,34 +272,51 @@ 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; }; + 19A1709D5BDD2691BA160012 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Schema.swift"; sourceTree = ""; }; + 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+AttachTests.swift"; sourceTree = ""; }; + 19A170F141BF21946D159083 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; + 19A17162C9861E5C4900455D /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; + 19A171A2ED4E2640F197F48C /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; + 19A171A7714C6524093255C5 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; 19A171B262DDE8718513CFDA /* SchemaChanger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChanger.swift; sourceTree = ""; }; - 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSIntegrationTests.swift; sourceTree = ""; }; - 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A171ED017645C8B04DF9F2 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitions.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 = ""; }; + 19A1745BE8623D8C6808DB3C /* ResultTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; + 19A17475DCA068453F787613 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; + 19A174FE5B47A97937A27276 /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; + 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Aggregation.swift"; sourceTree = ""; }; - 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; + 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitionsTests.swift; sourceTree = ""; }; + 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; + 19A17855BD524FF888265B3C /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; + 19A1787E16C8562C09C076F5 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; + 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1794B7972D14330A65BBD /* Linux.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Linux.md; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; + 19A17AE284BB1DF31D1B753E /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; + 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; + 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; - 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; - 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionSchemaTests.swift; sourceTree = ""; }; + 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSIntegrationTests.swift; sourceTree = ""; }; + 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+PragmaTests.swift"; sourceTree = ""; }; + 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+SchemaTests.swift"; sourceTree = ""; }; + 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; + 19A17EC0C43015063945D32E /* SelectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Pragmas.swift"; sourceTree = ""; }; + 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChangerTests.swift; sourceTree = ""; }; - 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 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; }; 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; - 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; @@ -302,7 +325,6 @@ 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D4DB368A20C09C9B00D5A58E /* SelectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -326,20 +348,6 @@ EE247B011C3F06E900AE3E12 /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; EE247B021C3F06E900AE3E12 /* Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Setter.swift; sourceTree = ""; }; EE247B161C3F127200AE3E12 /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; - EE247B181C3F134A00AE3E12 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; - EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; - EE247B1B1C3F137700AE3E12 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; - EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; - EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; - EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; - EE247B201C3F137700AE3E12 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; - EE247B211C3F137700AE3E12 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; - EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; - EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; - EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; - EE247B321C3F142E00AE3E12 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; - EE247B331C3F142E00AE3E12 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests Mac.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; EE247B771C3F40D700AE3E12 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -423,16 +431,62 @@ path = Schema; sourceTree = ""; }; + 19A1798E3459573BEE50FA34 /* Core */ = { + isa = PBXGroup; + children = ( + 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */, + 19A171A2ED4E2640F197F48C /* BlobTests.swift */, + 19A17855BD524FF888265B3C /* ConnectionTests.swift */, + 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */, + 19A1745BE8623D8C6808DB3C /* ResultTests.swift */, + 19A17AE284BB1DF31D1B753E /* ValueTests.swift */, + 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */, + 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */, + ); + path = Core; + sourceTree = ""; + }; + 19A17AECBF878B1DAE0AE3DD /* Typed */ = { + isa = PBXGroup; + children = ( + 19A170F141BF21946D159083 /* ExpressionTests.swift */, + 19A171ED017645C8B04DF9F2 /* QueryTests.swift */, + 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */, + 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */, + 19A17475DCA068453F787613 /* OperatorsTests.swift */, + 19A17EC0C43015063945D32E /* SelectTests.swift */, + 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */, + 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */, + 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */, + 19A1709D5BDD2691BA160012 /* SetterTests.swift */, + 19A174FE5B47A97937A27276 /* RowTests.swift */, + ); + path = Typed; + sourceTree = ""; + }; 19A17B56FBA20E7245BC8AC0 /* Schema */ = { isa = PBXGroup; children = ( 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */, 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */, - 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */, + 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */, + 19A171A7714C6524093255C5 /* SchemaTests.swift */, ); path = Schema; sourceTree = ""; }; + 19A17E470E4492D287C0D12F /* Extensions */ = { + isa = PBXGroup; + children = ( + 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */, + 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */, + 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */, + 19A1787E16C8562C09C076F5 /* CipherTests.swift */, + 19A17162C9861E5C4900455D /* RTreeTests.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 3D67B3E41DB2469200A4F4C6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -489,34 +543,14 @@ isa = PBXGroup; children = ( 3DF7B79528846FCC005DD8CA /* Resources */, - EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, - EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, - EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, - EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, - EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, - 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */, - EE247B201C3F137700AE3E12 /* ExpressionTests.swift */, - EE247B211C3F137700AE3E12 /* FTS4Tests.swift */, - EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */, - EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */, - EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */, - EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */, - EE247B181C3F134A00AE3E12 /* SetterTests.swift */, - EE247B321C3F142E00AE3E12 /* StatementTests.swift */, - EE247B331C3F142E00AE3E12 /* ValueTests.swift */, EE247B161C3F127200AE3E12 /* TestHelpers.swift */, EE247AE41C3F04ED00AE3E12 /* Info.plist */, - 19A1721B8984686B9963B45D /* FTS5Tests.swift */, 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, - 19A17399EA9E61235D5D77BF /* CipherTests.swift */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, - 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, - 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, - D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, - 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, - 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, - 3DF7B78C28842C23005DD8CA /* ResultTests.swift */, 19A17B56FBA20E7245BC8AC0 /* Schema */, + 19A17E470E4492D287C0D12F /* Extensions */, + 19A1798E3459573BEE50FA34 /* Core */, + 19A17AECBF878B1DAE0AE3DD /* Typed */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -934,35 +968,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */, - 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, - 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */, - 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, - 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */, - 03A65E8B1C6BB3030062603F /* CustomFunctionsTests.swift in Sources */, - 03A65E871C6BB3030062603F /* AggregateFunctionsTests.swift in Sources */, - 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */, - 03A65E891C6BB3030062603F /* ConnectionTests.swift in Sources */, - 03A65E8A1C6BB3030062603F /* CoreFunctionsTests.swift in Sources */, - 3717F90A221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */, - 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */, - 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, - 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, - 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, - 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, - 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */, - 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, - 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, - 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, - D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, - 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, - 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */, 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */, 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */, - 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */, + 19A17188B4D96636F9C0C209 /* Connection+SchemaTests.swift in Sources */, + 19A17FACE8E4D54A50BA934E /* FTS5Tests.swift in Sources */, + 19A177909023B7B940C5805E /* FTSIntegrationTests.swift in Sources */, + 19A17E1DD976D5CE80018749 /* FTS4Tests.swift in Sources */, + 19A17411403D60640467209E /* ExpressionTests.swift in Sources */, + 19A17CA4D7B63D845428A9C5 /* StatementTests.swift in Sources */, + 19A17885B646CB0201BE4BD5 /* QueryTests.swift in Sources */, + 19A1708D3D58D7BC1168E55F /* CipherTests.swift in Sources */, + 19A178C041DDCF80B533AD13 /* BlobTests.swift in Sources */, + 19A17021286A4D8D6C2EF12D /* ConnectionTests.swift in Sources */, + 19A17DE34C477232592A8F6B /* CoreFunctionsTests.swift in Sources */, + 19A1799AF6643CF5081BFA15 /* DateAndTimeFunctionTests.swift in Sources */, + 19A17457B0461F484AF6BE40 /* CustomFunctionsTests.swift in Sources */, + 19A17900387FDCF578B31E3E /* OperatorsTests.swift in Sources */, + 19A17C74233AFC2EDAFA23DC /* ResultTests.swift in Sources */, + 19A17A9520802ACF45907970 /* RTreeTests.swift in Sources */, + 19A17A391BF056E3D729E70A /* SchemaTests.swift in Sources */, + 19A17746150A815944A6820B /* SelectTests.swift in Sources */, + 19A1766135CE9786B1878603 /* ValueTests.swift in Sources */, + 19A177D5C6542E2D572162E5 /* QueryIntegrationTests.swift in Sources */, + 19A178DF5A96CFEFF1E271F6 /* AggregateFunctionsTests.swift in Sources */, + 19A17437659BD7FD787D94A6 /* CustomAggregationTests.swift in Sources */, + 19A17F907258E524B3CA2FAE /* SetterTests.swift in Sources */, + 19A17ABCF0EB4808BDC5B5FF /* RowTests.swift in Sources */, + 19A17BACF4C032513DE1F879 /* Connection+PragmaTests.swift in Sources */, + 19A173F25449876761347072 /* Connection+AttachTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1047,35 +1083,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */, - EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, - EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, - EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, - EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */, EE247B171C3F127200AE3E12 /* TestHelpers.swift in Sources */, - EE247B281C3F137700AE3E12 /* ExpressionTests.swift in Sources */, - EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */, - EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */, - EE247B301C3F141E00AE3E12 /* RTreeTests.swift in Sources */, - 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */, - EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */, - EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, - EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, - EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, - EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, - EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, - 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */, - 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, - 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, - 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, - D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, - 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, - 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */, 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */, 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */, - 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */, + 19A1725658E480B9B378F28B /* Connection+SchemaTests.swift in Sources */, + 19A178DA2BB5970778CCAF13 /* FTS5Tests.swift in Sources */, + 19A1755C49154C87304C9146 /* FTSIntegrationTests.swift in Sources */, + 19A17444861E1443143DEB44 /* FTS4Tests.swift in Sources */, + 19A17DD33C2E43DD6EE05A60 /* ExpressionTests.swift in Sources */, + 19A17D6EC40BC35A5DC81BA8 /* StatementTests.swift in Sources */, + 19A17E3F47DA087E2B76D087 /* QueryTests.swift in Sources */, + 19A17A7B3E3B7E76364A2AEE /* CipherTests.swift in Sources */, + 19A1782444437C7FC6B75CBC /* BlobTests.swift in Sources */, + 19A17CF65C0196E03BC64519 /* ConnectionTests.swift in Sources */, + 19A179BB9A6665B2B99DA546 /* CoreFunctionsTests.swift in Sources */, + 19A174118D11B93DA5DAAF79 /* DateAndTimeFunctionTests.swift in Sources */, + 19A17A7DF99B0379FD3396B1 /* CustomFunctionsTests.swift in Sources */, + 19A171F243A589C5EBC47937 /* OperatorsTests.swift in Sources */, + 19A173F429D7E46289EB2167 /* ResultTests.swift in Sources */, + 19A17B1D9B5CEBE9CE09280C /* RTreeTests.swift in Sources */, + 19A172F71EFD65342072D8D2 /* SchemaTests.swift in Sources */, + 19A17F7977364EC8CD33C3C3 /* SelectTests.swift in Sources */, + 19A17FD22EF43DF428DD93BA /* ValueTests.swift in Sources */, + 19A177AA5922527BBDC77CF9 /* QueryIntegrationTests.swift in Sources */, + 19A179786A6826D58A70F8BC /* AggregateFunctionsTests.swift in Sources */, + 19A1793972BDDDB027C113BB /* CustomAggregationTests.swift in Sources */, + 19A1773155AC2BF2CA86A473 /* SetterTests.swift in Sources */, + 19A176B3316281F004F92276 /* RowTests.swift in Sources */, + 19A17FBAA26953EB854E790D /* Connection+PragmaTests.swift in Sources */, + 19A17026DCDCDA405B09A229 /* Connection+AttachTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1122,35 +1160,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */, - EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, - EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, - EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, - EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */, - EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */, - EE247B531C3F3FC700AE3E12 /* AggregateFunctionsTests.swift in Sources */, - EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */, - EE247B5C1C3F3FC700AE3E12 /* RTreeTests.swift in Sources */, - EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */, - 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */, - EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */, - EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, - EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, - EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, - EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, - 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */, - 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, - 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, - 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, - D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, - 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, - 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */, 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */, 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */, - 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */, + 19A17B36ABC6006AB80F693C /* Connection+SchemaTests.swift in Sources */, + 19A1776BD5127DFDF847FF1F /* FTS5Tests.swift in Sources */, + 19A173088B85A7E18E8582A7 /* FTSIntegrationTests.swift in Sources */, + 19A178767223229E61C5066F /* FTS4Tests.swift in Sources */, + 19A1781CBA8968ABD3E00877 /* ExpressionTests.swift in Sources */, + 19A17923494236793893BF72 /* StatementTests.swift in Sources */, + 19A17A52BF29D27C9AA229E7 /* QueryTests.swift in Sources */, + 19A17CA6ADB78A2E545BF836 /* CipherTests.swift in Sources */, + 19A1714F7CF964D568AB14E0 /* BlobTests.swift in Sources */, + 19A173465F23C64DF3DF469B /* ConnectionTests.swift in Sources */, + 19A17D993398B8215B73E1EA /* CoreFunctionsTests.swift in Sources */, + 19A170AEBAA56DC3355A73B3 /* DateAndTimeFunctionTests.swift in Sources */, + 19A1716BF8E15F91A6B5CB7A /* CustomFunctionsTests.swift in Sources */, + 19A17482E6FC5E563F3E6A47 /* OperatorsTests.swift in Sources */, + 19A17912DB9D3AC8FECF948B /* ResultTests.swift in Sources */, + 19A17DAD5975D9367EAA46E2 /* RTreeTests.swift in Sources */, + 19A17F2096E83A3181E03317 /* SchemaTests.swift in Sources */, + 19A17DE1FCDB5695702AD24D /* SelectTests.swift in Sources */, + 19A1726002D24C14F876C8FE /* ValueTests.swift in Sources */, + 19A173389E53CB24DFA8CEDD /* QueryIntegrationTests.swift in Sources */, + 19A170C56745F9D722A73D77 /* AggregateFunctionsTests.swift in Sources */, + 19A1772EBE65173EDFB1AFCA /* CustomAggregationTests.swift in Sources */, + 19A17E0ABA6C415F014CD51C /* SetterTests.swift in Sources */, + 19A170D938343E30119EDFB3 /* RowTests.swift in Sources */, + 19A178F9008614B8A8425635 /* Connection+PragmaTests.swift in Sources */, + 19A179BCD483DEA21661FD37 /* Connection+AttachTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 8bd219ed..c3b24f38 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -63,16 +63,6 @@ extension Connection { } } - func tableInfo() throws -> [String] { - try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in - if let name = row[0] as? String, !name.starts(with: "sqlite_") { - return name - } else { - return nil - } - } - } - func foreignKeyInfo(table: String) throws -> [ColumnDefinition.ForeignKey] { try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in if let table = row[2] as? String, // table diff --git a/Tests/SQLiteTests/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift similarity index 100% rename from Tests/SQLiteTests/BlobTests.swift rename to Tests/SQLiteTests/Core/BlobTests.swift diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift new file mode 100644 index 00000000..940a30ca --- /dev/null +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -0,0 +1,62 @@ +import XCTest +import Foundation +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class ConnectionAttachTests: SQLiteTestCase { + func test_attach_detach_memory_database() throws { + let schemaName = "test" + + try db.attach(.inMemory, as: schemaName) + + let table = Table("attached_users", database: schemaName) + let name = Expression("string") + + // create a table, insert some data + try db.run(table.create { builder in + builder.column(name) + }) + _ = try db.run(table.insert(name <- "test")) + + // query data + let rows = try db.prepare(table.select(name)).map { $0[name] } + XCTAssertEqual(["test"], rows) + + try db.detach(schemaName) + } + + func test_attach_detach_file_database() throws { + let schemaName = "test" + let testDb = fixture("test", withExtension: "sqlite") + + try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) + + let table = Table("tests", database: schemaName) + let email = Expression("email") + + let rows = try db.prepare(table.select(email)).map { $0[email] } + XCTAssertEqual(["foo@bar.com"], rows) + + try db.detach(schemaName) + } + + func test_detach_invalid_schema_name_errors_with_no_such_database() throws { + XCTAssertThrowsError(try db.detach("no-exist")) { error in + if case let Result.error(message, code, _) = error { + XCTAssertEqual(code, SQLITE_ERROR) + XCTAssertEqual("no such database: no-exist", message) + } else { + XCTFail("unexpected error: \(error)") + } + } + } +} diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift new file mode 100644 index 00000000..2d0742c9 --- /dev/null +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -0,0 +1,42 @@ +import XCTest +import Foundation +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class ConnectionPragmaTests: SQLiteTestCase { + func test_userVersion() { + db.userVersion = 2 + XCTAssertEqual(2, db.userVersion!) + } + + func test_sqlite_version() { + XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) + } + + func test_foreignKeys_defaults_to_false() { + XCTAssertFalse(db.foreignKeys) + } + + func test_foreignKeys_sets_value() { + db.foreignKeys = true + XCTAssertTrue(db.foreignKeys) + } + + func test_defer_foreignKeys_defaults_to_false() { + XCTAssertFalse(db.deferForeignKeys) + } + + func test_defer_foreignKeys_sets_value() { + db.deferForeignKeys = true + XCTAssertTrue(db.deferForeignKeys) + } +} diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift similarity index 90% rename from Tests/SQLiteTests/ConnectionTests.swift rename to Tests/SQLiteTests/Core/ConnectionTests.swift index 347516f5..e9ceff08 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -111,11 +111,6 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.totalChanges) } - func test_userVersion() { - db.userVersion = 2 - XCTAssertEqual(2, db.userVersion!) - } - func test_prepare_preparesAndReturnsStatements() throws { _ = try db.prepare("SELECT * FROM users WHERE admin = 0") _ = try db.prepare("SELECT * FROM users WHERE admin = ?", 0) @@ -445,51 +440,4 @@ class ConnectionTests: SQLiteTestCase { } semaphores.forEach { $0.wait() } } - - func test_attach_detach_memory_database() throws { - let schemaName = "test" - - try db.attach(.inMemory, as: schemaName) - - let table = Table("attached_users", database: schemaName) - let name = Expression("string") - - // create a table, insert some data - try db.run(table.create { builder in - builder.column(name) - }) - _ = try db.run(table.insert(name <- "test")) - - // query data - let rows = try db.prepare(table.select(name)).map { $0[name] } - XCTAssertEqual(["test"], rows) - - try db.detach(schemaName) - } - - func test_attach_detach_file_database() throws { - let schemaName = "test" - let testDb = fixture("test", withExtension: "sqlite") - - try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) - - let table = Table("tests", database: schemaName) - let email = Expression("email") - - let rows = try db.prepare(table.select(email)).map { $0[email] } - XCTAssertEqual(["foo@bar.com"], rows) - - try db.detach(schemaName) - } - - func test_detach_invalid_schema_name_errors_with_no_such_database() throws { - XCTAssertThrowsError(try db.detach("no-exist")) { error in - if case let Result.error(message, code, _) = error { - XCTAssertEqual(code, SQLITE_ERROR) - XCTAssertEqual("no such database: no-exist", message) - } else { - XCTFail("unexpected error: \(error)") - } - } - } } diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/Core/CoreFunctionsTests.swift similarity index 100% rename from Tests/SQLiteTests/CoreFunctionsTests.swift rename to Tests/SQLiteTests/Core/CoreFunctionsTests.swift diff --git a/Tests/SQLiteTests/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift similarity index 100% rename from Tests/SQLiteTests/ResultTests.swift rename to Tests/SQLiteTests/Core/ResultTests.swift diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift similarity index 100% rename from Tests/SQLiteTests/StatementTests.swift rename to Tests/SQLiteTests/Core/StatementTests.swift diff --git a/Tests/SQLiteTests/ValueTests.swift b/Tests/SQLiteTests/Core/ValueTests.swift similarity index 100% rename from Tests/SQLiteTests/ValueTests.swift rename to Tests/SQLiteTests/Core/ValueTests.swift diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/Extensions/CipherTests.swift similarity index 100% rename from Tests/SQLiteTests/CipherTests.swift rename to Tests/SQLiteTests/Extensions/CipherTests.swift diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/Extensions/FTS4Tests.swift similarity index 100% rename from Tests/SQLiteTests/FTS4Tests.swift rename to Tests/SQLiteTests/Extensions/FTS4Tests.swift diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/Extensions/FTS5Tests.swift similarity index 100% rename from Tests/SQLiteTests/FTS5Tests.swift rename to Tests/SQLiteTests/Extensions/FTS5Tests.swift diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift similarity index 100% rename from Tests/SQLiteTests/FTSIntegrationTests.swift rename to Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift diff --git a/Tests/SQLiteTests/RTreeTests.swift b/Tests/SQLiteTests/Extensions/RTreeTests.swift similarity index 100% rename from Tests/SQLiteTests/RTreeTests.swift rename to Tests/SQLiteTests/Extensions/RTreeTests.swift diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift similarity index 95% rename from Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift rename to Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 1c66b761..56e79734 100644 --- a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -108,11 +108,6 @@ class ConnectionSchemaTests: SQLiteTestCase { ]) } - func test_table_info_returns_list_of_tables() throws { - let tables = try db.tableInfo() - XCTAssertEqual(tables, ["users"]) - } - func test_foreign_key_info_empty() throws { try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") @@ -136,8 +131,4 @@ class ConnectionSchemaTests: SQLiteTestCase { .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) ]) } - - func test_sqlite_version() { - XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) - } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 165de4a8..4d4f8d50 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -102,8 +102,12 @@ class SchemaChangerTests: SQLiteTestCase { func test_drop_table() throws { try schemaChanger.drop(table: "users") - - let tables = try db.tableInfo() - XCTAssertFalse(tables.contains("users")) + XCTAssertThrowsError(try db.scalar(users.count)) { error in + if case Result.error(let message, _, _) = error { + XCTAssertEqual(message, "no such table: users") + } else { + XCTFail("unexpected error \(error)") + } + } } } diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/Schema/SchemaTests.swift similarity index 100% rename from Tests/SQLiteTests/SchemaTests.swift rename to Tests/SQLiteTests/Schema/SchemaTests.swift diff --git a/Tests/SQLiteTests/AggregateFunctionsTests.swift b/Tests/SQLiteTests/Typed/AggregateFunctionsTests.swift similarity index 100% rename from Tests/SQLiteTests/AggregateFunctionsTests.swift rename to Tests/SQLiteTests/Typed/AggregateFunctionsTests.swift diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift similarity index 100% rename from Tests/SQLiteTests/CustomAggregationTests.swift rename to Tests/SQLiteTests/Typed/CustomAggregationTests.swift diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift similarity index 100% rename from Tests/SQLiteTests/CustomFunctionsTests.swift rename to Tests/SQLiteTests/Typed/CustomFunctionsTests.swift diff --git a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift b/Tests/SQLiteTests/Typed/DateAndTimeFunctionTests.swift similarity index 100% rename from Tests/SQLiteTests/DateAndTimeFunctionTests.swift rename to Tests/SQLiteTests/Typed/DateAndTimeFunctionTests.swift diff --git a/Tests/SQLiteTests/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift similarity index 100% rename from Tests/SQLiteTests/ExpressionTests.swift rename to Tests/SQLiteTests/Typed/ExpressionTests.swift diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/Typed/OperatorsTests.swift similarity index 100% rename from Tests/SQLiteTests/OperatorsTests.swift rename to Tests/SQLiteTests/Typed/OperatorsTests.swift diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift similarity index 100% rename from Tests/SQLiteTests/QueryIntegrationTests.swift rename to Tests/SQLiteTests/Typed/QueryIntegrationTests.swift diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift similarity index 100% rename from Tests/SQLiteTests/QueryTests.swift rename to Tests/SQLiteTests/Typed/QueryTests.swift diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/Typed/RowTests.swift similarity index 100% rename from Tests/SQLiteTests/RowTests.swift rename to Tests/SQLiteTests/Typed/RowTests.swift diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/Typed/SelectTests.swift similarity index 100% rename from Tests/SQLiteTests/SelectTests.swift rename to Tests/SQLiteTests/Typed/SelectTests.swift diff --git a/Tests/SQLiteTests/SetterTests.swift b/Tests/SQLiteTests/Typed/SetterTests.swift similarity index 100% rename from Tests/SQLiteTests/SetterTests.swift rename to Tests/SQLiteTests/Typed/SetterTests.swift From a6e6e687b47944b4c338e23bb054fd925998222e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 26 Jul 2022 00:44:55 +0200 Subject: [PATCH 073/216] WIP --- Sources/SQLite/Schema/Connection+Schema.swift | 2 +- Sources/SQLite/Schema/SchemaChanger.swift | 73 ++++++++++++++----- Sources/SQLite/Schema/SchemaDefinitions.swift | 20 +++-- .../Schema/Connection+SchemaTests.swift | 18 ++--- .../Schema/SchemaChangerTests.swift | 30 +++++++- .../Schema/SchemaDefinitionsTests.swift | 26 +++---- 6 files changed, 119 insertions(+), 50 deletions(-) diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index c3b24f38..378fad68 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -24,7 +24,7 @@ extension Connection { return ColumnDefinition(name: name, primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, type: ColumnDefinition.Affinity.from(type), - null: notNull == 0, + nullable: notNull == 0, defaultValue: .from(defaultValue), references: foreignKeys[name]?.first) } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index c6529fbe..daba2f60 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -27,36 +27,63 @@ import Foundation 12. If foreign keys constraints were originally enabled, reenable them now. */ public class SchemaChanger: CustomStringConvertible { - enum SchemaChangeError: LocalizedError { + public enum Error: LocalizedError { + case invalidColumnDefinition(String) case foreignKeyError([ForeignKeyError]) - var errorDescription: String? { + public var errorDescription: String? { switch self { case .foreignKeyError(let errors): return "Foreign key errors: \(errors)" + case .invalidColumnDefinition(let message): + return "Invalid column definition: \(message)" } } } public enum Operation { - case none - case add(ColumnDefinition) - case remove(String) + case addColumn(ColumnDefinition) + case dropColumn(String) case renameColumn(String, String) case renameTable(String) /// Returns non-nil if the operation can be executed with a simple SQL statement func toSQL(_ table: String, version: SQLiteVersion) -> String? { switch self { - case .add(let definition): + case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" case .renameColumn(let from, let to) where version >= (3, 25, 0): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .remove(let column) where version >= (3, 35, 0): + case .dropColumn(let column) where version >= (3, 35, 0): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } } + + func validate() throws { + switch self { + case .addColumn(let definition): + // The new column may take any of the forms permissible in a CREATE TABLE statement, with the following restrictions: + // - The column may not have a PRIMARY KEY or UNIQUE constraint. + // - The column may not have a default value of CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP, or an expression in parentheses + // - If a NOT NULL constraint is specified, then the column must have a default value other than NULL. + guard definition.primaryKey == nil else { + throw Error.invalidColumnDefinition("can not add primary key column") + } + let invalidValues: [LiteralValue] = [.CURRENT_TIME, .CURRENT_DATE, .CURRENT_TIMESTAMP] + if invalidValues.contains(definition.defaultValue) { + throw Error.invalidColumnDefinition("Invalid default value") + } + if !definition.nullable && definition.defaultValue == .NULL { + throw Error.invalidColumnDefinition("NOT NULL columns must have a default value other than NULL") + } + case .dropColumn: + // The DROP COLUMN command only works if the column is not referenced by any other parts of the schema + // and is not a PRIMARY KEY and does not have a UNIQUE constraint + break + default: break + } + } } public class AlterTableDefinition { @@ -69,11 +96,11 @@ public class SchemaChanger: CustomStringConvertible { } public func add(_ column: ColumnDefinition) { - operations.append(.add(column)) + operations.append(.addColumn(column)) } public func remove(_ column: String) { - operations.append(.remove(column)) + operations.append(.dropColumn(column)) } public func rename(_ column: String, to: String) { @@ -116,7 +143,15 @@ public class SchemaChanger: CustomStringConvertible { try dropTable(table) } + // Beginning with release 3.25.0 (2018-09-15), references to the table within trigger bodies and + // view definitions are also renamed. + public func rename(table: String, to: String) throws { + try connection.run("ALTER TABLE \(table.quote()) RENAME TO \(to.quote())") + } + private func run(table: String, operation: Operation) throws { + try operation.validate() + if let sql = operation.toSQL(table, version: version) { try connection.run(sql) } else { @@ -129,10 +164,10 @@ public class SchemaChanger: CustomStringConvertible { try disableRefIntegrity { let tempTable = "\(SchemaChanger.tempPrefix)\(table)" try moveTable(from: table, to: tempTable, options: [.temp], operation: operation) - try moveTable(from: tempTable, to: table) + try rename(table: tempTable, to: table) let foreignKeyErrors = try connection.foreignKeyCheck() if foreignKeyErrors.count > 0 { - throw SchemaChangeError.foreignKeyError(foreignKeyErrors) + throw Error.foreignKeyError(foreignKeyErrors) } } } @@ -153,22 +188,24 @@ public class SchemaChanger: CustomStringConvertible { try block() } - private func moveTable(from: String, to: String, options: Options = .default, operation: Operation = .none) throws { + private func moveTable(from: String, to: String, options: Options = .default, operation: Operation? = nil) throws { try copyTable(from: from, to: to, options: options, operation: operation) try dropTable(from) } - private func copyTable(from: String, to: String, options: Options = .default, operation: Operation) throws { + private func copyTable(from: String, to: String, options: Options = .default, operation: Operation?) throws { let fromDefinition = TableDefinition( name: from, columns: try connection.columnInfo(table: from), indexes: try connection.indexInfo(table: from) ) - let toDefinition = fromDefinition.apply(.renameTable(to)).apply(operation) + let toDefinition = fromDefinition + .apply(.renameTable(to)) + .apply(operation) try createTable(definition: toDefinition, options: options) try createTableIndexes(definition: toDefinition) - if case .remove = operation { + if case .dropColumn = operation { try copyTableContents(from: fromDefinition.apply(operation), to: toDefinition) } else { try copyTableContents(from: fromDefinition, to: toDefinition) @@ -221,11 +258,11 @@ extension IndexDefinition { } extension TableDefinition { - func apply(_ operation: SchemaChanger.Operation) -> TableDefinition { + func apply(_ operation: SchemaChanger.Operation?) -> TableDefinition { switch operation { case .none: return self - case .add: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") - case .remove(let column): + case .addColumn: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") + case .dropColumn(let column): return TableDefinition(name: name, columns: columns.filter { $0.name != column }, indexes: indexes.filter { !$0.columns.contains(column) } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 156a06fb..a06487b2 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -84,23 +84,27 @@ public struct ColumnDefinition: Equatable { public let name: String public let primaryKey: PrimaryKey? public let type: Affinity - public let null: Bool + public let nullable: Bool public let defaultValue: LiteralValue public let references: ForeignKey? - public init(name: String, primaryKey: PrimaryKey?, type: Affinity, null: Bool, defaultValue: LiteralValue, - references: ForeignKey?) { + public init(name: String, + primaryKey: PrimaryKey? = nil, + type: Affinity, + nullable: Bool = false, + defaultValue: LiteralValue = .NULL, + references: ForeignKey? = nil) { self.name = name self.primaryKey = primaryKey self.type = type - self.null = null + self.nullable = nullable self.defaultValue = defaultValue self.references = references } func rename(from: String, to: String) -> ColumnDefinition { guard from == name else { return self } - return ColumnDefinition(name: to, primaryKey: primaryKey, type: type, null: null, defaultValue: defaultValue, references: references) + return ColumnDefinition(name: to, primaryKey: primaryKey, type: type, nullable: nullable, defaultValue: defaultValue, references: references) } } @@ -254,12 +258,12 @@ public struct IndexDefinition: Equatable { } } -struct ForeignKeyError: CustomStringConvertible { +public struct ForeignKeyError: CustomStringConvertible { let from: String let rowId: Int64 let to: String - var description: String { + public var description: String { "\(from) [\(rowId)] => \(to)" } } @@ -294,7 +298,7 @@ extension ColumnDefinition { type.rawValue, defaultValue.map { "DEFAULT \($0)" }, primaryKey.map { $0.toSQL() }, - null ? nil : "NOT NULL", + nullable ? nil : "NOT NULL", references.map { $0.toSQL() } ].compactMap { $0 } .joined(separator: " ") diff --git a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 56e79734..6eded6b2 100644 --- a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -14,42 +14,42 @@ class ConnectionSchemaTests: SQLiteTestCase { ColumnDefinition(name: "id", primaryKey: .init(autoIncrement: false, onConflict: nil), type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "email", primaryKey: nil, type: .TEXT, - null: false, + nullable: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "age", primaryKey: nil, type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "salary", primaryKey: nil, type: .REAL, - null: true, + nullable: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "admin", primaryKey: nil, type: .TEXT, - null: false, + nullable: false, defaultValue: .numericLiteral("0"), references: nil), ColumnDefinition(name: "manager_id", primaryKey: nil, type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, type: .TEXT, - null: true, + nullable: true, defaultValue: .NULL, references: nil) ]) @@ -64,7 +64,7 @@ class ConnectionSchemaTests: SQLiteTestCase { name: "id", primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil) ] @@ -80,7 +80,7 @@ class ConnectionSchemaTests: SQLiteTestCase { name: "id", primaryKey: .init(autoIncrement: false), type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil) ] diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 4d4f8d50..038a8844 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -90,7 +90,11 @@ class SchemaChangerTests: SQLiteTestCase { } func test_add_column() throws { - let newColumn = ColumnDefinition(name: "new_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil) + let column = Expression("new_column") + let newColumn = ColumnDefinition(name: "new_column", + type: .TEXT, + nullable: true, + defaultValue: .stringLiteral("foo")) try schemaChanger.alter(table: "users") { table in table.add(newColumn) @@ -98,6 +102,24 @@ class SchemaChangerTests: SQLiteTestCase { let columns = try db.columnInfo(table: "users") XCTAssertTrue(columns.contains(newColumn)) + + XCTAssertEqual(try db.pluck(users.select(column))?[column], "foo") + } + + func test_add_column_primary_key_fails() throws { + let newColumn = ColumnDefinition(name: "new_column", + primaryKey: .init(autoIncrement: false, onConflict: nil), + type: .TEXT) + + XCTAssertThrowsError(try schemaChanger.alter(table: "users") { table in + table.add(newColumn) + }) { error in + if case SchemaChanger.Error.invalidColumnDefinition(_) = error { + XCTAssertEqual("Invalid column definition: can not add primary key column", error.localizedDescription) + } else { + XCTFail("invalid error: \(error)") + } + } } func test_drop_table() throws { @@ -110,4 +132,10 @@ class SchemaChangerTests: SQLiteTestCase { } } } + + func test_rename_table() throws { + try schemaChanger.rename(table: "users", to: "users_new") + let users_new = Table("users_new") + XCTAssertEqual((try db.scalar(users_new.count)) as Int, 1) + } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 645bfc34..9c8c3041 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -6,26 +6,26 @@ class ColumnDefinitionTests: XCTestCase { var expected: String! static let definitions: [(ColumnDefinition, String)] = [ - (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), + (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil), "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), - (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, + (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil), + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil), "\"text\" TEXT"), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: false, defaultValue: .NULL, references: nil), + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: false, defaultValue: .NULL, references: nil), "\"text\" TEXT NOT NULL"), - (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .stringLiteral("fo\"o"), references: nil), + (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .stringLiteral("fo\"o"), references: nil), "\"text_column\" TEXT DEFAULT 'fo\"o'"), - (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, null: true, defaultValue: .numericLiteral("123"), references: nil), + (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, defaultValue: .numericLiteral("123"), references: nil), "\"integer_column\" INTEGER DEFAULT 123"), - (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, null: true, defaultValue: .numericLiteral("123.123"), references: nil), + (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, defaultValue: .numericLiteral("123.123"), references: nil), "\"real_column\" REAL DEFAULT 123.123") ] @@ -184,8 +184,8 @@ class ForeignKeyDefinitionTests: XCTestCase { class TableDefinitionTests: XCTestCase { func test_quoted_columnList() { let definition = TableDefinition(name: "foo", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), - ColumnDefinition(name: "baz", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil), + ColumnDefinition(name: "baz", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(definition.quotedColumnList, """ @@ -195,7 +195,7 @@ class TableDefinitionTests: XCTestCase { func test_toSQL() { let definition = TableDefinition(name: "foo", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(definition.toSQL(), """ @@ -205,7 +205,7 @@ class TableDefinitionTests: XCTestCase { func test_toSQL_temp_table() { let definition = TableDefinition(name: "foo", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(definition.toSQL(temporary: true), """ @@ -222,11 +222,11 @@ class TableDefinitionTests: XCTestCase { func test_copySQL() { let from = TableDefinition(name: "from_table", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) let to = TableDefinition(name: "to_table", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(from.copySQL(to: to), """ From 17a2cb8985b7188a61ddee5af1f9d549b23ab54d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 26 Jul 2022 00:47:57 +0200 Subject: [PATCH 074/216] Lint --- Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 9c8c3041..75321440 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -22,10 +22,12 @@ class ColumnDefinitionTests: XCTestCase { (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .stringLiteral("fo\"o"), references: nil), "\"text_column\" TEXT DEFAULT 'fo\"o'"), - (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, defaultValue: .numericLiteral("123"), references: nil), + (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, + defaultValue: .numericLiteral("123"), references: nil), "\"integer_column\" INTEGER DEFAULT 123"), - (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, defaultValue: .numericLiteral("123.123"), references: nil), + (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, + defaultValue: .numericLiteral("123.123"), references: nil), "\"real_column\" REAL DEFAULT 123.123") ] From b5a83033d5727fb7a201e3700baa678e2e861bca Mon Sep 17 00:00:00 2001 From: Andrew Vanderbilt <105957236+a-vanderbilt@users.noreply.github.com> Date: Fri, 29 Jul 2022 20:12:52 -0400 Subject: [PATCH 075/216] Fixed typo and added/clarified code comment. Small typo fix (iff -> if). Added code comment, then modified existing code comment to make the intent of the code clearer. --- Documentation/Index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 29b18c96..80f8d9f9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -288,11 +288,13 @@ On macOS, you can use your app’s **Application Support** directory: ```swift + +// set the path corresponding to application support. var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true ).first! + "/" + Bundle.main.bundleIdentifier! -// create parent directory iff it doesn’t exist +// create parent directory inside application support if it doesn’t exist try FileManager.default.createDirectory( atPath: path, withIntermediateDirectories: true, attributes: nil ) From f2609b9e520c4dcf23b5f57e5b1e9dd1a12db147 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 30 Jul 2022 09:09:59 +0200 Subject: [PATCH 076/216] Quick update --- Documentation/Index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 80f8d9f9..f90b5960 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -288,8 +288,7 @@ On macOS, you can use your app’s **Application Support** directory: ```swift - -// set the path corresponding to application support. +// set the path corresponding to application support var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true ).first! + "/" + Bundle.main.bundleIdentifier! From 607a37aca433adffc83cc1c10ddd07a09774b322 Mon Sep 17 00:00:00 2001 From: Goban Date: Tue, 30 Aug 2022 12:04:13 +0900 Subject: [PATCH 077/216] Update Index.md 'pathForResource(_:ofType:)' has been renamed to 'path(forResource:ofType:)' --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index f90b5960..d6374e6e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -308,7 +308,7 @@ into your Xcode project and added it to your application target), you can establish a _read-only_ connection to it. ```swift -let path = Bundle.main.pathForResource("db", ofType: "sqlite3")! +let path = Bundle.main.path(forResource: "db", ofType: "sqlite3")! let db = try Connection(path, readonly: true) ``` From 6a02c202c34c4dc81272a3e792025ee6257bdee9 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 30 Aug 2022 09:06:19 +0200 Subject: [PATCH 078/216] Fix linting --- Sources/SQLite/Typed/Query.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index cfa7544e..7cb2aef3 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1035,11 +1035,9 @@ extension Connection { 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 - } + for q in queries where q.tableName().expression.template == namespace { + try expandGlob(true)(q) + continue column } throw QueryError.noSuchTable(name: namespace) } From f25798f3d047a923976d55f4ba9b8ab9e25e0428 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 18 Sep 2022 23:51:49 +0200 Subject: [PATCH 079/216] SQLiteVersion type --- SQLite.xcodeproj/project.pbxproj | 10 +++++++++ Sources/SQLite/Core/Connection+Pragmas.swift | 5 ++--- Sources/SQLite/Core/SQLiteVersion.swift | 22 +++++++++++++++++++ Sources/SQLite/Schema/Connection+Schema.swift | 16 ++++++++++++++ Sources/SQLite/Schema/SchemaChanger.swift | 4 ++-- .../Core/Connection+PragmaTests.swift | 2 +- .../Schema/SchemaChangerTests.swift | 4 ++-- 7 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 Sources/SQLite/Core/SQLiteVersion.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index bc79473e..d2005575 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -199,6 +199,10 @@ 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.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 */; }; @@ -325,6 +329,7 @@ 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -569,6 +574,7 @@ 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, + DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */, 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */, ); path = Core; @@ -954,6 +960,7 @@ 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, + DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 19A17073552293CA063BEA66 /* Result.swift in Sources */, 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, @@ -1012,6 +1019,7 @@ 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, + DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */, 3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */, 3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */, @@ -1069,6 +1077,7 @@ 02A43A9822738CF100FEC494 /* Backup.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, + DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, @@ -1146,6 +1155,7 @@ 02A43A9922738CF100FEC494 /* Backup.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, + DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, diff --git a/Sources/SQLite/Core/Connection+Pragmas.swift b/Sources/SQLite/Core/Connection+Pragmas.swift index 43ff9610..8f6d854b 100644 --- a/Sources/SQLite/Core/Connection+Pragmas.swift +++ b/Sources/SQLite/Core/Connection+Pragmas.swift @@ -1,7 +1,6 @@ import Foundation public typealias UserVersion = Int32 -public typealias SQLiteVersion = (Int, Int, Int) public extension Connection { /// The user version of the database. @@ -21,9 +20,9 @@ public extension Connection { guard let version = (try? scalar("SELECT sqlite_version()")) as? String, let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { - return (0, 0, 0) + return .zero } - return (major, minor, point) + return .init(major: major, minor: minor, point: point) } // Changing the foreign_keys setting affects the execution of all statements prepared using the database diff --git a/Sources/SQLite/Core/SQLiteVersion.swift b/Sources/SQLite/Core/SQLiteVersion.swift new file mode 100644 index 00000000..fa7358da --- /dev/null +++ b/Sources/SQLite/Core/SQLiteVersion.swift @@ -0,0 +1,22 @@ +import Foundation + +public struct SQLiteVersion: Comparable, CustomStringConvertible { + public let major: Int + public let minor: Int + public var point: Int = 0 + + public var description: String { + "SQLite \(major).\(minor).\(point)" + } + + public static func <(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool { + lhs.tuple < rhs.tuple + } + + public static func ==(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool { + lhs.tuple == rhs.tuple + } + + static var zero: SQLiteVersion = .init(major: 0, minor: 0) + private var tuple: (Int, Int, Int) { (major, minor, point) } +} diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 378fad68..d92236ee 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -80,6 +80,16 @@ extension Connection { } } + func tableInfo() throws -> [String] { + try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in + if let name = row[0] as? String, !name.starts(with: "sqlite_") { + return name + } else { + return nil + } + } + } + // https://sqlite.org/pragma.html#pragma_foreign_key_check // There are four columns in each result row. @@ -99,6 +109,12 @@ extension Connection { } } + // https://sqlite.org/pragma.html#pragma_integrity_check + func integrityCheck() throws -> [String] { + try run("PRAGMA integrity_check").compactMap { $0[0] as? String }.filter { $0 != "ok" } + } + + private func createTableSQL(name: String) throws -> String? { try run(""" SELECT sql FROM sqlite_master WHERE name=? AND type='table' diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index daba2f60..991e3338 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -52,9 +52,9 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .renameColumn(let from, let to) where version >= (3, 25, 0): + case .renameColumn(let from, let to) where version >= .init(major: 3, minor: 25): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .dropColumn(let column) where version >= (3, 35, 0): + case .dropColumn(let column) where version >= .init(major: 3, minor: 35): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift index 2d0742c9..2bcdb6af 100644 --- a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -19,7 +19,7 @@ class ConnectionPragmaTests: SQLiteTestCase { } func test_sqlite_version() { - XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) + XCTAssertTrue(db.sqliteVersion >= .init(major: 3, minor: 0)) } func test_foreignKeys_defaults_to_false() { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 038a8844..738588fd 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -58,7 +58,7 @@ class SchemaChangerTests: SQLiteTestCase { } func test_remove_column_legacy() throws { - schemaChanger = .init(connection: db, version: (3, 24, 0)) // DROP COLUMN introduced in 3.35.0 + schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // DROP COLUMN introduced in 3.35.0 try schemaChanger.alter(table: "users") { table in table.remove("age") @@ -78,7 +78,7 @@ class SchemaChangerTests: SQLiteTestCase { } func test_rename_column_legacy() throws { - schemaChanger = .init(connection: db, version: (3, 24, 0)) // RENAME COLUMN introduced in 3.25.0 + schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // RENAME COLUMN introduced in 3.25.0 try schemaChanger.alter(table: "users") { table in table.rename("age", to: "age2") From ec35c7ea060f08a3f36382564f2a69206da7aa2b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 18 Sep 2022 23:57:43 +0200 Subject: [PATCH 080/216] Lint --- Sources/SQLite/Schema/Connection+Schema.swift | 1 - Sources/SQLite/Typed/Query.swift | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index d92236ee..9c945e18 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -114,7 +114,6 @@ extension Connection { try run("PRAGMA integrity_check").compactMap { $0[0] as? String }.filter { $0 != "ok" } } - private func createTableSQL(name: String) throws -> String? { try run(""" SELECT sql FROM sqlite_master WHERE name=? AND type='table' diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index cfa7544e..7cb2aef3 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1035,11 +1035,9 @@ extension Connection { 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 - } + for q in queries where q.tableName().expression.template == namespace { + try expandGlob(true)(q) + continue column } throw QueryError.noSuchTable(name: namespace) } From 00a86686c4cf88caf09cde6ad0f50d4064a8b653 Mon Sep 17 00:00:00 2001 From: Michael Henry Pantaleon Date: Wed, 12 Oct 2022 23:20:23 +1100 Subject: [PATCH 081/216] Fix playground example as the prepare func can throw an error --- SQLite.playground/Contents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index c089076d..945adb50 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -99,5 +99,5 @@ db.createAggregation("customConcat", initialValue: "users:", reduce: reduce, result: { $0 }) -let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +let result = try db.prepare("SELECT customConcat(email) FROM users").scalar() as! String print(result) From d7c26353330ea851691f3f0359aa13e7dbedced5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 16 Oct 2022 02:24:31 +0200 Subject: [PATCH 082/216] Split into classes, improve queries --- Makefile | 2 + SQLite.xcodeproj/project.pbxproj | 10 + Sources/SQLite/Schema/Connection+Schema.swift | 129 ++----------- Sources/SQLite/Schema/SchemaChanger.swift | 6 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 107 ++++++---- Sources/SQLite/Schema/SchemaReader.swift | 182 ++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 17 ++ Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- .../Schema/Connection+SchemaTests.swift | 140 +++----------- .../Schema/SchemaChangerTests.swift | 24 +-- .../Schema/SchemaDefinitionsTests.swift | 132 ++++++++----- .../Schema/SchemaReaderTests.swift | 180 +++++++++++++++++ 12 files changed, 603 insertions(+), 328 deletions(-) create mode 100644 Sources/SQLite/Schema/SchemaReader.swift create mode 100644 Tests/SQLiteTests/Schema/SchemaReaderTests.swift diff --git a/Makefile b/Makefile index c62da000..74bf5d18 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,8 @@ build: lint: swiftlint --strict +lint-fix: + swiftlint lint fix test: ifdef XCPRETTY diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d2005575..a58e0887 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -199,6 +199,10 @@ 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; @@ -329,6 +333,7 @@ 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -429,6 +434,7 @@ 19A1792D261C689FC988A90A /* Schema */ = { isa = PBXGroup; children = ( + DB58B21028FB864300F8EEA4 /* SchemaReader.swift */, 19A171B262DDE8718513CFDA /* SchemaChanger.swift */, 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */, 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */, @@ -968,6 +974,7 @@ 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */, + DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1018,6 +1025,7 @@ 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, + DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */, @@ -1085,6 +1093,7 @@ 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */, + DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1163,6 +1172,7 @@ 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */, + DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 9c945e18..868b40d5 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,96 +1,7 @@ import Foundation -extension Connection { - // https://sqlite.org/pragma.html#pragma_table_info - // - // This pragma returns one row for each column in the named table. Columns in the result set include the - // column name, data type, whether or not the column can be NULL, and the default value for the column. The - // "pk" column in the result set is zero for columns that are not part of the primary key, and is the - // index of the column in the primary key for columns that are part of the primary key. - func columnInfo(table: String) throws -> [ColumnDefinition] { - func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { - try createTableSQL(name: table).flatMap { .init(sql: $0) } - } - - let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = - Dictionary(grouping: try foreignKeyInfo(table: table), by: { $0.column }) - - return try run("PRAGMA table_info(\(table.quote()))").compactMap { row -> ColumnDefinition? in - guard let name = row[1] as? String, - let type = row[2] as? String, - let notNull = row[3] as? Int64, - let defaultValue = row[4] as? String?, - let primaryKey = row[5] as? Int64 else { return nil } - return ColumnDefinition(name: name, - primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, - type: ColumnDefinition.Affinity.from(type), - nullable: notNull == 0, - defaultValue: .from(defaultValue), - references: foreignKeys[name]?.first) - } - } - - func indexInfo(table: String) throws -> [IndexDefinition] { - func indexSQL(name: String) throws -> String? { - try run(""" - SELECT sql FROM sqlite_master WHERE name=? AND type='index' - UNION ALL - SELECT sql FROM sqlite_temp_master WHERE name=? AND type='index' - """, name, name) - .compactMap { row in row[0] as? String } - .first - } - - func columns(name: String) throws -> [String] { - try run("PRAGMA index_info(\(name.quote()))").compactMap { row in - row[2] as? String - } - } - - return try run("PRAGMA index_list(\(table.quote()))").compactMap { row -> IndexDefinition? in - guard let name = row[1] as? String, - let unique = row[2] as? Int64, - // Indexes SQLite creates implicitly for internal use start with "sqlite_". - // See https://www.sqlite.org/fileformat2.html#intschema - !name.starts(with: "sqlite_") else { - return nil - } - return .init(table: table, - name: name, - unique: unique == 1, - columns: try columns(name: name), - indexSQL: try indexSQL(name: name)) - } - } - - func foreignKeyInfo(table: String) throws -> [ColumnDefinition.ForeignKey] { - try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in - if let table = row[2] as? String, // table - let column = row[3] as? String, // from - let primaryKey = row[4] as? String, // to - let onUpdate = row[5] as? String, - let onDelete = row[6] as? String { - return .init(table: table, column: column, primaryKey: primaryKey, - onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, - onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete - ) - } else { - return nil - } - } - } - - func tableInfo() throws -> [String] { - try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in - if let name = row[0] as? String, !name.starts(with: "sqlite_") { - return name - } else { - return nil - } - } - } - - // https://sqlite.org/pragma.html#pragma_foreign_key_check +public extension Connection { + var schemaReader: SchemaReader { SchemaReader(connection: self) } // There are four columns in each result row. // The first column is the name of the table that @@ -99,28 +10,24 @@ extension Connection { // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. // The third column is the name of the table that is referred to. // The fourth column is the index of the specific foreign key constraint that failed. - func foreignKeyCheck() throws -> [ForeignKeyError] { - try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in - guard let table = row[0] as? String, - let rowId = row[1] as? Int64, - let target = row[2] as? String else { return nil } - - return ForeignKeyError(from: table, rowId: rowId, to: target) - } + // + // https://sqlite.org/pragma.html#pragma_foreign_key_check + func foreignKeyCheck(table: String? = nil) throws -> [ForeignKeyError] { + try run("PRAGMA foreign_key_check" + (table.map { "(\($0.quote()))" } ?? "")) + .compactMap { row -> ForeignKeyError? in + guard let table = row[0] as? String, + let rowId = row[1] as? Int64, + let target = row[2] as? String else { return nil } + + return ForeignKeyError(from: table, rowId: rowId, to: target) + } } + // This pragma does a low-level formatting and consistency check of the database. // https://sqlite.org/pragma.html#pragma_integrity_check - func integrityCheck() throws -> [String] { - try run("PRAGMA integrity_check").compactMap { $0[0] as? String }.filter { $0 != "ok" } - } - - private func createTableSQL(name: String) throws -> String? { - try run(""" - SELECT sql FROM sqlite_master WHERE name=? AND type='table' - UNION ALL - SELECT sql FROM sqlite_temp_master WHERE name=? AND type='table' - """, name, name) - .compactMap { row in row[0] as? String } - .first + func integrityCheck(table: String? = nil, maxErrors: Int? = nil) throws -> [String] { + try run("PRAGMA integrity_check" + (table.map { "(\($0.quote()))" } ?? "")) + .compactMap { $0[0] as? String } + .filter { $0 != "ok" } } } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 991e3338..b557da0a 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -109,6 +109,7 @@ public class SchemaChanger: CustomStringConvertible { } private let connection: Connection + private let schemaReader: SchemaReader private let version: SQLiteVersion static let tempPrefix = "tmp_" typealias Block = () throws -> Void @@ -127,6 +128,7 @@ public class SchemaChanger: CustomStringConvertible { init(connection: Connection, version: SQLiteVersion) { self.connection = connection + schemaReader = connection.schemaReader self.version = version } @@ -196,8 +198,8 @@ public class SchemaChanger: CustomStringConvertible { private func copyTable(from: String, to: String, options: Options = .default, operation: Operation?) throws { let fromDefinition = TableDefinition( name: from, - columns: try connection.columnInfo(table: from), - indexes: try connection.indexInfo(table: from) + columns: try schemaReader.columnDefinitions(table: from), + indexes: try schemaReader.indexDefinitions(table: from) ) let toDefinition = fromDefinition .apply(.renameTable(to)) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index a06487b2..fe2e3931 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -10,6 +10,29 @@ struct TableDefinition: Equatable { } } +// https://sqlite.org/schematab.html#interpretation_of_the_schema_table +public struct ObjectDefinition: Equatable { + public enum ObjectType: String { + case table, index, view, trigger + } + public let type: ObjectType + + // The name of the object + public let name: String + + // The name of a table or view that the object is associated with. + // * For a table or view, a copy of the name column. + // * For an index, the name of the table that is indexed + // * For a trigger, the column stores the name of the table or view that causes the trigger to fire. + public let tableName: String + + // The page number of the root b-tree page for tables and indexes, otherwise 0 or NULL + public let rootpage: Int64 + + // SQL text that describes the object (NULL for the internal indexes) + public let sql: String? +} + // https://sqlite.org/syntax/column-def.html // column-name -> type-name -> column-constraint* public struct ColumnDefinition: Equatable { @@ -29,8 +52,8 @@ public struct ColumnDefinition: Equatable { rawValue } - static func from(_ string: String) -> Affinity { - Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? TEXT + init(_ string: String) { + self = Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? .TEXT } } @@ -41,8 +64,9 @@ public struct ColumnDefinition: Equatable { case IGNORE case REPLACE - static func from(_ string: String) -> OnConflict? { - OnConflict.allCases.first { $0.rawValue == string } + init?(_ string: String) { + guard let value = (OnConflict.allCases.first { $0.rawValue == string }) else { return nil } + self = value } } @@ -59,17 +83,20 @@ public struct ColumnDefinition: Equatable { } init?(sql: String) { - if let match = PrimaryKey.pattern.firstMatch(in: sql, range: NSRange(location: 0, length: sql.count)) { - let conflict = match.range(at: 1) - var onConflict: ColumnDefinition.OnConflict? - if conflict.location != NSNotFound { - onConflict = .from((sql as NSString).substring(with: conflict)) - } - let autoIncrement = match.range(at: 2).location != NSNotFound - self.init(autoIncrement: autoIncrement, onConflict: onConflict) - } else { + guard let match = PrimaryKey.pattern.firstMatch( + in: sql, + range: NSRange(location: 0, length: sql.count)) else { return nil } + let conflict = match.range(at: 1) + let onConflict: ColumnDefinition.OnConflict? + if conflict.location != NSNotFound { + onConflict = OnConflict((sql as NSString).substring(with: conflict)) + } else { + onConflict = nil + } + let autoIncrement = match.range(at: 2).location != NSNotFound + self.init(autoIncrement: autoIncrement, onConflict: onConflict) } } @@ -138,28 +165,19 @@ public enum LiteralValue: Equatable, CustomStringConvertible { case CURRENT_TIMESTAMP // swiftlint:enable identifier_name - static func from(_ string: String?) -> LiteralValue { - func parse(_ value: String) -> LiteralValue { - if let match = singleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { - return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "''", with: "'")) - } else if let match = doubleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { - return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "\"\"", with: "\"")) - } else if let match = blob.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { - return blobLiteral((value as NSString).substring(with: match.range(at: 1))) - } else { - return numericLiteral(value) - } + init(_ string: String?) { + guard let string = string else { + self = .NULL + return } - guard let string = string else { return NULL } - switch string { - case "NULL": return NULL - case "TRUE": return TRUE - case "FALSE": return FALSE - case "CURRENT_TIME": return CURRENT_TIME - case "CURRENT_TIMESTAMP": return CURRENT_TIMESTAMP - case "CURRENT_DATE": return CURRENT_DATE - default: return parse(string) + case "NULL": self = .NULL + case "TRUE": self = .TRUE + case "FALSE": self = .FALSE + case "CURRENT_TIME": self = .CURRENT_TIME + case "CURRENT_TIMESTAMP": self = .CURRENT_TIMESTAMP + case "CURRENT_DATE": self = .CURRENT_DATE + default: self = LiteralValue.parse(string) } } @@ -184,6 +202,17 @@ public enum LiteralValue: Equatable, CustomStringConvertible { return block(self) } } + private static func parse(_ string: String) -> LiteralValue { + if let match = LiteralValue.singleQuote.firstMatch(in: string, range: NSRange(location: 0, length: string.count)) { + return .stringLiteral((string as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "''", with: "'")) + } else if let match = LiteralValue.doubleQuote.firstMatch(in: string, range: NSRange(location: 0, length: string.count)) { + return .stringLiteral((string as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "\"\"", with: "\"")) + } else if let match = LiteralValue.blob.firstMatch(in: string, range: NSRange(location: 0, length: string.count)) { + return .blobLiteral((string as NSString).substring(with: match.range(at: 1))) + } else { + return .numericLiteral(string) + } + } } // https://sqlite.org/lang_createindex.html @@ -259,9 +288,9 @@ public struct IndexDefinition: Equatable { } public struct ForeignKeyError: CustomStringConvertible { - let from: String - let rowId: Int64 - let to: String + public let from: String + public let rowId: Int64 + public let to: String public var description: String { "\(from) [\(rowId)] => \(to)" @@ -270,7 +299,7 @@ public struct ForeignKeyError: CustomStringConvertible { extension TableDefinition { func toSQL(temporary: Bool = false) -> String { - assert(columns.count > 0, "no columns to create") + precondition(columns.count > 0, "no columns to create") return ([ "CREATE", @@ -285,8 +314,8 @@ extension TableDefinition { } func copySQL(to: TableDefinition) -> String { - assert(columns.count > 0, "no columns to copy") - assert(columns.count == to.columns.count, "column counts don't match") + precondition(columns.count > 0) + precondition(columns.count == to.columns.count, "column counts don't match") return "INSERT INTO \(to.name.quote()) (\(to.quotedColumnList)) SELECT \(quotedColumnList) FROM \(name.quote())" } } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift new file mode 100644 index 00000000..3f02eeae --- /dev/null +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -0,0 +1,182 @@ +import Foundation + +public class SchemaReader { + private let connection: Connection + + init(connection: Connection) { + self.connection = connection + } + + // https://sqlite.org/pragma.html#pragma_table_info + // + // This pragma returns one row for each column in the named table. Columns in the result set include the + // column name, data type, whether or not the column can be NULL, and the default value for the column. The + // "pk" column in the result set is zero for columns that are not part of the primary key, and is the + // index of the column in the primary key for columns that are part of the primary key. + public func columnDefinitions(table: String) throws -> [ColumnDefinition] { + func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { + try createTableSQL(name: table).flatMap { .init(sql: $0) } + } + + let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = + Dictionary(grouping: try foreignKeys(table: table), by: { $0.column }) + + return try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") + .map { (row: Row) -> ColumnDefinition in + ColumnDefinition( + name: row[TableInfoTable.nameColumn], + primaryKey: row[TableInfoTable.primaryKeyColumn] == 1 ? + try parsePrimaryKey(column: row[TableInfoTable.nameColumn]) : nil, + type: ColumnDefinition.Affinity(row[TableInfoTable.typeColumn]), + nullable: row[TableInfoTable.notNullColumn] == 0, + defaultValue: LiteralValue(row[TableInfoTable.defaultValueColumn]), + references: foreignKeys[row[TableInfoTable.nameColumn]]?.first + ) + } + } + + public func objectDefinitions(name: String? = nil, + type: ObjectDefinition.ObjectType? = nil, + temp: Bool = false) throws -> [ObjectDefinition] { + var query: QueryType = temp ? SchemaTable.tempName : SchemaTable.name + if let name = name { + query = query.where(SchemaTable.nameColumn == name) + } + if let type = type { + query = query.where(SchemaTable.typeColumn == type.rawValue) + } + return try connection.prepare(query).map { row -> ObjectDefinition in + guard let type = ObjectDefinition.ObjectType(rawValue: row[SchemaTable.typeColumn]) else { + fatalError("unexpected type") + } + return ObjectDefinition( + type: type, + name: row[SchemaTable.nameColumn], + tableName: row[SchemaTable.nameColumn], + rootpage: row[SchemaTable.rootPageColumn] ?? 0, + sql: row[SchemaTable.sqlColumn] + ) + } + } + + public func indexDefinitions(table: String) throws -> [IndexDefinition] { + func indexSQL(name: String) throws -> String? { + try objectDefinitions(name: name, type: .index) + .compactMap(\.sql) + .first + } + + func columns(name: String) throws -> [String] { + try connection.prepareRowIterator("PRAGMA index_info(\(name.quote()))") + .compactMap { row in + row[IndexInfoTable.nameColumn] + } + } + + return try connection.prepareRowIterator("PRAGMA index_list(\(table.quote()))") + .compactMap { row -> IndexDefinition? in + let name = row[IndexListTable.nameColumn] + guard !name.starts(with: "sqlite_") else { + // Indexes SQLite creates implicitly for internal use start with "sqlite_". + // See https://www.sqlite.org/fileformat2.html#intschema + return nil + } + return IndexDefinition( + table: table, + name: name, + unique: row[IndexListTable.uniqueColumn] == 1, + columns: try columns(name: name), + indexSQL: try indexSQL(name: name) + ) + } + } + + func foreignKeys(table: String) throws -> [ColumnDefinition.ForeignKey] { + try connection.prepareRowIterator("PRAGMA foreign_key_list(\(table.quote()))") + .map { row in + ColumnDefinition.ForeignKey( + table: row[ForeignKeyListTable.tableColumn], + column: row[ForeignKeyListTable.fromColumn], + primaryKey: row[ForeignKeyListTable.toColumn], + onUpdate: row[ForeignKeyListTable.onUpdateColumn] == TableBuilder.Dependency.noAction.rawValue + ? nil : row[ForeignKeyListTable.onUpdateColumn], + onDelete: row[ForeignKeyListTable.onDeleteColumn] == TableBuilder.Dependency.noAction.rawValue + ? nil : row[ForeignKeyListTable.onDeleteColumn] + ) + } + } + + func tableDefinitions() throws -> [TableDefinition] { + try objectDefinitions(type: .table) + .map { table in + TableDefinition( + name: table.name, + columns: try columnDefinitions(table: table.name), + indexes: try indexDefinitions(table: table.name) + ) + } + } + + private func createTableSQL(name: String) throws -> String? { + try ( + objectDefinitions(name: name, type: .table) + + objectDefinitions(name: name, type: .table, temp: true) + ).compactMap(\.sql).first + } +} + +private class SchemaTable { + internal static let name = Table("sqlite_schema", database: "main") + internal static let tempName = Table("sqlite_schema", database: "temp") + + static let typeColumn = Expression("type") + static let nameColumn = Expression("name") + static let tableNameColumn = Expression("tbl_name") + static let rootPageColumn = Expression("rootpage") + static let sqlColumn = Expression("sql") +} + +private class TableInfoTable { + static let idColumn = Expression("cid") + static let nameColumn = Expression("name") + static let typeColumn = Expression("type") + static let notNullColumn = Expression("notnull") + static let defaultValueColumn = Expression("dflt_value") + static let primaryKeyColumn = Expression("pk") +} + +private class IndexInfoTable { + // The rank of the column within the index. (0 means left-most.) + static let seqnoColumn = Expression("seqno") + // The rank of the column within the table being indexed. + // A value of -1 means rowid and a value of -2 means that an expression is being used. + static let cidColumn = Expression("cid") + // The name of the column being indexed. This columns is NULL if the column is the rowid or an expression. + static let nameColumn = Expression("name") +} + +private class IndexListTable { + // A sequence number assigned to each index for internal tracking purposes. + static let seqColumn = Expression("seq") + // The name of the index + static let nameColumn = Expression("name") + // "1" if the index is UNIQUE and "0" if not. + static let uniqueColumn = Expression("unique") + // "c" if the index was created by a CREATE INDEX statement, + // "u" if the index was created by a UNIQUE constraint, or + // "pk" if the index was created by a PRIMARY KEY constraint. + static let originColumn = Expression("origin") + // "1" if the index is a partial index and "0" if not. + static let partialColumn = Expression("partial") +} + +private class ForeignKeyListTable { + static let idColumn = Expression("id") + static let seqColumn = Expression("seq") + static let tableColumn = Expression("table") + static let fromColumn = Expression("from") + static let toColumn = Expression("to") + static let onUpdateColumn = Expression("on_update") + static let onDeleteColumn = Expression("on_delete") + static let matchColumn = Expression("match") +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 7cb2aef3..04665f39 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -991,6 +991,15 @@ public struct RowIterator: FailableIterator { } return elements } + + public func compactMap(_ transform: (Element) throws -> T?) throws -> [T] { + var elements = [T]() + while let row = try failableNext() { + guard let element = try transform(row) else { continue } + elements.append(element) + } + return elements + } } extension Connection { @@ -1012,6 +1021,14 @@ extension Connection { return RowIterator(statement: statement, columnNames: try columnNamesForQuery(query)) } + public func prepareRowIterator(_ statement: String, bindings: Binding?...) throws -> RowIterator { + try prepare(statement, bindings).prepareRowIterator() + } + + public func prepareRowIterator(_ statement: String, bindings: [Binding?]) throws -> RowIterator { + try prepare(statement, bindings).prepareRowIterator() + } + private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { var (columnNames, idx) = ([String: Int](), 0) column: for each in query.clauses.select.columns { diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index e9ceff08..9d623f3f 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -399,7 +399,7 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, try db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } - func test_interrupt_interruptsLongRunningQuery() throws { + func XXX_test_interrupt_interruptsLongRunningQuery() throws { let semaphore = DispatchSemaphore(value: 0) db.createFunction("sleep") { _ in DispatchQueue.global(qos: .background).async { diff --git a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 6eded6b2..96300529 100644 --- a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -8,127 +8,43 @@ class ConnectionSchemaTests: SQLiteTestCase { try createUsersTable() } - func test_column_info() throws { - let columns = try db.columnInfo(table: "users") - XCTAssertEqual(columns, [ - ColumnDefinition(name: "id", - primaryKey: .init(autoIncrement: false, onConflict: nil), - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "email", - primaryKey: nil, - type: .TEXT, - nullable: false, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "age", - primaryKey: nil, - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "salary", - primaryKey: nil, - type: .REAL, - nullable: true, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "admin", - primaryKey: nil, - type: .TEXT, - nullable: false, - defaultValue: .numericLiteral("0"), - references: nil), - ColumnDefinition(name: "manager_id", - primaryKey: nil, type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), - ColumnDefinition(name: "created_at", - primaryKey: nil, - type: .TEXT, - nullable: true, - defaultValue: .NULL, - references: nil) - ]) + func test_foreignKeyCheck() throws { + let errors = try db.foreignKeyCheck() + XCTAssert(errors.isEmpty) } - func test_column_info_parses_conflict_modifier() throws { - try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY ON CONFLICT IGNORE AUTOINCREMENT)") - - XCTAssertEqual( - try db.columnInfo(table: "t"), [ - ColumnDefinition( - name: "id", - primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil) - ] - ) + func test_foreignKeyCheck_with_table() throws { + let errors = try db.foreignKeyCheck(table: "users") + XCTAssert(errors.isEmpty) } - func test_column_info_detects_missing_autoincrement() throws { - try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") - - XCTAssertEqual( - try db.columnInfo(table: "t"), [ - ColumnDefinition( - name: "id", - primaryKey: .init(autoIncrement: false), - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil) - ] - ) + func test_foreignKeyCheck_table_not_found() throws { + XCTAssertThrowsError(try db.foreignKeyCheck(table: "xxx")) { error in + guard case Result.error(let message, _, _) = error else { + assertionFailure("invalid error type") + return + } + XCTAssertEqual(message, "no such table: xxx") + } } - func test_index_info_no_index() throws { - let indexes = try db.indexInfo(table: "users") - XCTAssertTrue(indexes.isEmpty) + func test_integrityCheck_global() throws { + let results = try db.integrityCheck() + XCTAssert(results.isEmpty) } - func test_index_info_with_index() throws { - try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") - let indexes = try db.indexInfo(table: "users") - - XCTAssertEqual(indexes, [ - IndexDefinition( - table: "users", - name: "index_users", - unique: true, - columns: ["age"], - where: "age IS NOT NULL", - orders: ["age": .DESC] - ) - ]) + func test_integrityCheck_table() throws { + let results = try db.integrityCheck(table: "users") + XCTAssert(results.isEmpty) } - func test_foreign_key_info_empty() throws { - try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") - - let foreignKeys = try db.foreignKeyInfo(table: "t") - XCTAssertTrue(foreignKeys.isEmpty) - } - - func test_foreign_key_info() throws { - let linkTable = Table("test_links") - - let idColumn = SQLite.Expression("id") - let testIdColumn = SQLite.Expression("test_id") - - try db.run(linkTable.create(block: { definition in - definition.column(idColumn, primaryKey: .autoincrement) - definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) - })) - - let foreignKeys = try db.foreignKeyInfo(table: "test_links") - XCTAssertEqual(foreignKeys, [ - .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) - ]) + func test_integrityCheck_table_not_found() throws { + XCTAssertThrowsError(try db.integrityCheck(table: "xxx")) { error in + guard case Result.error(let message, _, _) = error else { + assertionFailure("invalid error type") + return + } + XCTAssertEqual(message, "no such table: xxx") + } } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 738588fd..af8b6565 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -3,6 +3,7 @@ import XCTest class SchemaChangerTests: SQLiteTestCase { var schemaChanger: SchemaChanger! + var schemaReader: SchemaReader! override func setUpWithError() throws { try super.setUpWithError() @@ -10,32 +11,33 @@ class SchemaChangerTests: SQLiteTestCase { try insertUsers("bob") + schemaReader = SchemaReader(connection: db) schemaChanger = SchemaChanger(connection: db) } func test_empty_migration_does_not_change_column_definitions() throws { - let previous = try db.columnInfo(table: "users") + let previous = try schemaReader.columnDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try db.columnInfo(table: "users") + let current = try schemaReader.columnDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_index_definitions() throws { - let previous = try db.indexInfo(table: "users") + let previous = try schemaReader.indexDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try db.indexInfo(table: "users") + let current = try schemaReader.indexDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_foreign_key_definitions() throws { - let previous = try db.foreignKeyInfo(table: "users") + let previous = try schemaReader.foreignKeys(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try db.foreignKeyInfo(table: "users") + let current = try schemaReader.foreignKeys(table: "users") XCTAssertEqual(previous, current) } @@ -53,7 +55,7 @@ class SchemaChangerTests: SQLiteTestCase { try schemaChanger.alter(table: "users") { table in table.remove("age") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } @@ -63,7 +65,7 @@ class SchemaChangerTests: SQLiteTestCase { try schemaChanger.alter(table: "users") { table in table.remove("age") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } @@ -72,7 +74,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -84,7 +86,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -100,7 +102,7 @@ class SchemaChangerTests: SQLiteTestCase { table.add(newColumn) } - let columns = try db.columnInfo(table: "users") + let columns = try schemaReader.columnDefinitions(table: "users") XCTAssertTrue(columns.contains(newColumn)) XCTAssertEqual(try db.pluck(users.select(column))?[column], "foo") diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 75321440..384aab3f 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -5,37 +5,38 @@ class ColumnDefinitionTests: XCTestCase { var definition: ColumnDefinition! var expected: String! - static let definitions: [(ColumnDefinition, String)] = [ - (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil), - "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), + static let definitions: [(String, ColumnDefinition)] = [ + ("\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL", + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil)), - (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, - references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), - "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), + ("\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")", + ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, + references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil))), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil), - "\"text\" TEXT"), + ("\"text\" TEXT", + ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil)), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: false, defaultValue: .NULL, references: nil), - "\"text\" TEXT NOT NULL"), + ("\"text\" TEXT NOT NULL", + ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: false, defaultValue: .NULL, references: nil)), - (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .stringLiteral("fo\"o"), references: nil), - "\"text_column\" TEXT DEFAULT 'fo\"o'"), + ("\"text_column\" TEXT DEFAULT 'fo\"o'", + ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, + defaultValue: .stringLiteral("fo\"o"), references: nil)), - (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, - defaultValue: .numericLiteral("123"), references: nil), - "\"integer_column\" INTEGER DEFAULT 123"), + ("\"integer_column\" INTEGER DEFAULT 123", + ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, + defaultValue: .numericLiteral("123"), references: nil)), - (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, - defaultValue: .numericLiteral("123.123"), references: nil), - "\"real_column\" REAL DEFAULT 123.123") + ("\"real_column\" REAL DEFAULT 123.123", + ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, + defaultValue: .numericLiteral("123.123"), references: nil)) ] #if !os(Linux) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) - for (column, expected) in ColumnDefinitionTests.definitions { + for (expected, column) in ColumnDefinitionTests.definitions { let test = ColumnDefinitionTests(selector: #selector(verify)) test.definition = column test.expected = expected @@ -51,14 +52,17 @@ class ColumnDefinitionTests: XCTestCase { } class AffinityTests: XCTestCase { - func test_from() { - XCTAssertEqual(ColumnDefinition.Affinity.from("TEXT"), .TEXT) - XCTAssertEqual(ColumnDefinition.Affinity.from("text"), .TEXT) - XCTAssertEqual(ColumnDefinition.Affinity.from("INTEGER"), .INTEGER) + func test_init() { + XCTAssertEqual(ColumnDefinition.Affinity("TEXT"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity("text"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity("INTEGER"), .INTEGER) + XCTAssertEqual(ColumnDefinition.Affinity("BLOB"), .BLOB) + XCTAssertEqual(ColumnDefinition.Affinity("REAL"), .REAL) + XCTAssertEqual(ColumnDefinition.Affinity("NUMERIC"), .NUMERIC) } func test_returns_TEXT_for_unknown_type() { - XCTAssertEqual(ColumnDefinition.Affinity.from("baz"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity("baz"), .TEXT) } } @@ -215,13 +219,6 @@ class TableDefinitionTests: XCTestCase { """) } - /* - func test_throws_an_error_when_columns_are_empty() { - let empty = TableDefinition(name: "empty", columns: [], indexes: []) - XCTAssertThrowsError(empty.toSQL()) - } - */ - func test_copySQL() { let from = TableDefinition(name: "from_table", columns: [ ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) @@ -239,74 +236,105 @@ class TableDefinitionTests: XCTestCase { class PrimaryKeyTests: XCTestCase { func test_toSQL() { - XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false).toSQL(), - "PRIMARY KEY") + XCTAssertEqual( + ColumnDefinition.PrimaryKey(autoIncrement: false).toSQL(), + "PRIMARY KEY" + ) } func test_toSQL_autoincrement() { - XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: true).toSQL(), - "PRIMARY KEY AUTOINCREMENT") + XCTAssertEqual( + ColumnDefinition.PrimaryKey(autoIncrement: true).toSQL(), + "PRIMARY KEY AUTOINCREMENT" + ) } func test_toSQL_on_conflict() { - XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK).toSQL(), - "PRIMARY KEY ON CONFLICT ROLLBACK") + XCTAssertEqual( + ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK).toSQL(), + "PRIMARY KEY ON CONFLICT ROLLBACK" + ) + } + + func test_fromSQL() { + XCTAssertEqual( + ColumnDefinition.PrimaryKey(sql: "PRIMARY KEY"), + ColumnDefinition.PrimaryKey(autoIncrement: false) + ) + } + + func test_fromSQL_invalid_sql_is_nil() { + XCTAssertNil(ColumnDefinition.PrimaryKey(sql: "FOO")) + } + + func test_fromSQL_autoincrement() { + XCTAssertEqual( + ColumnDefinition.PrimaryKey(sql: "PRIMARY KEY AUTOINCREMENT"), + ColumnDefinition.PrimaryKey(autoIncrement: true) + ) + } + + func test_fromSQL_on_conflict() { + XCTAssertEqual( + ColumnDefinition.PrimaryKey(sql: "PRIMARY KEY ON CONFLICT ROLLBACK"), + ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK) + ) } } class LiteralValueTests: XCTestCase { func test_recognizes_TRUE() { - XCTAssertEqual(LiteralValue.from("TRUE"), .TRUE) + XCTAssertEqual(LiteralValue("TRUE"), .TRUE) } func test_recognizes_FALSE() { - XCTAssertEqual(LiteralValue.from("FALSE"), .FALSE) + XCTAssertEqual(LiteralValue("FALSE"), .FALSE) } func test_recognizes_NULL() { - XCTAssertEqual(LiteralValue.from("NULL"), .NULL) + XCTAssertEqual(LiteralValue("NULL"), .NULL) } func test_recognizes_nil() { - XCTAssertEqual(LiteralValue.from(nil), .NULL) + XCTAssertEqual(LiteralValue(nil), .NULL) } func test_recognizes_CURRENT_TIME() { - XCTAssertEqual(LiteralValue.from("CURRENT_TIME"), .CURRENT_TIME) + XCTAssertEqual(LiteralValue("CURRENT_TIME"), .CURRENT_TIME) } func test_recognizes_CURRENT_TIMESTAMP() { - XCTAssertEqual(LiteralValue.from("CURRENT_TIMESTAMP"), .CURRENT_TIMESTAMP) + XCTAssertEqual(LiteralValue("CURRENT_TIMESTAMP"), .CURRENT_TIMESTAMP) } func test_recognizes_CURRENT_DATE() { - XCTAssertEqual(LiteralValue.from("CURRENT_DATE"), .CURRENT_DATE) + XCTAssertEqual(LiteralValue("CURRENT_DATE"), .CURRENT_DATE) } func test_recognizes_double_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("\"foo\""), .stringLiteral("foo")) + XCTAssertEqual(LiteralValue("\"foo\""), .stringLiteral("foo")) } func test_recognizes_single_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("\'foo\'"), .stringLiteral("foo")) + XCTAssertEqual(LiteralValue("\'foo\'"), .stringLiteral("foo")) } func test_unquotes_double_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("\"fo\"\"o\""), .stringLiteral("fo\"o")) + XCTAssertEqual(LiteralValue("\"fo\"\"o\""), .stringLiteral("fo\"o")) } func test_unquotes_single_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("'fo''o'"), .stringLiteral("fo'o")) + XCTAssertEqual(LiteralValue("'fo''o'"), .stringLiteral("fo'o")) } func test_recognizes_numeric_literals() { - XCTAssertEqual(LiteralValue.from("1.2"), .numericLiteral("1.2")) - XCTAssertEqual(LiteralValue.from("0xdeadbeef"), .numericLiteral("0xdeadbeef")) + XCTAssertEqual(LiteralValue("1.2"), .numericLiteral("1.2")) + XCTAssertEqual(LiteralValue("0xdeadbeef"), .numericLiteral("0xdeadbeef")) } func test_recognizes_blob_literals() { - XCTAssertEqual(LiteralValue.from("X'deadbeef'"), .blobLiteral("deadbeef")) - XCTAssertEqual(LiteralValue.from("x'deadbeef'"), .blobLiteral("deadbeef")) + XCTAssertEqual(LiteralValue("X'deadbeef'"), .blobLiteral("deadbeef")) + XCTAssertEqual(LiteralValue("x'deadbeef'"), .blobLiteral("deadbeef")) } func test_description_TRUE() { diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift new file mode 100644 index 00000000..165dbc28 --- /dev/null +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -0,0 +1,180 @@ +import XCTest +@testable import SQLite + +class SchemaReaderTests: SQLiteTestCase { + private var schemaReader: SchemaReader! + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + + schemaReader = db.schemaReader + } + + func test_columnDefinitions() throws { + let columns = try schemaReader.columnDefinitions(table: "users") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: false, onConflict: nil), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "email", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "salary", + primaryKey: nil, + type: .REAL, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "admin", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .numericLiteral("0"), + references: nil), + ColumnDefinition(name: "manager_id", + primaryKey: nil, type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), + ColumnDefinition(name: "created_at", + primaryKey: nil, + type: .TEXT, + nullable: true, + defaultValue: .NULL, + references: nil) + ]) + } + + func test_columnDefinitions_parses_conflict_modifier() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY ON CONFLICT IGNORE AUTOINCREMENT)") + + XCTAssertEqual( + try schemaReader.columnDefinitions(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_columnDefinitions_detects_missing_autoincrement() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + XCTAssertEqual( + try schemaReader.columnDefinitions(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_indexDefinitions_no_index() throws { + let indexes = try schemaReader.indexDefinitions(table: "users") + XCTAssertTrue(indexes.isEmpty) + } + + func test_indexDefinitions_with_index() throws { + try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") + let indexes = try schemaReader.indexDefinitions(table: "users") + + XCTAssertEqual(indexes, [ + IndexDefinition( + table: "users", + name: "index_users", + unique: true, + columns: ["age"], + where: "age IS NOT NULL", + orders: ["age": .DESC] + ) + ]) + } + + func test_foreignKeys_info_empty() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + let foreignKeys = try schemaReader.foreignKeys(table: "t") + XCTAssertTrue(foreignKeys.isEmpty) + } + + func test_foreignKeys() throws { + let linkTable = Table("test_links") + + let idColumn = SQLite.Expression("id") + let testIdColumn = SQLite.Expression("test_id") + + try db.run(linkTable.create(block: { definition in + definition.column(idColumn, primaryKey: .autoincrement) + definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) + })) + + let foreignKeys = try schemaReader.foreignKeys(table: "test_links") + XCTAssertEqual(foreignKeys, [ + .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + ]) + } + + func test_tableDefinitions() throws { + let tables = try schemaReader.tableDefinitions() + XCTAssertEqual(tables.count, 1) + XCTAssertEqual(tables.first?.name, "users") + } + + func test_objectDefinitions() throws { + let tables = try schemaReader.objectDefinitions() + + XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["users", "users", "table"], + ["sqlite_autoindex_users_1", "sqlite_autoindex_users_1", "index"] + ]) + } + + func test_objectDefinitions_temporary() throws { + let tables = try schemaReader.objectDefinitions(temp: true) + XCTAssert(tables.isEmpty) + + try db.run("CREATE TEMPORARY TABLE foo (bar TEXT)") + + let tables2 = try schemaReader.objectDefinitions(temp: true) + XCTAssertEqual(tables2.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["foo", "foo", "table"] + ]) + } + + func test_objectDefinitionsFilterByType() throws { + let tables = try schemaReader.objectDefinitions(type: .table) + + XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["users", "users", "table"] + ]) + } + + func test_objectDefinitionsFilterByName() throws { + let tables = try schemaReader.objectDefinitions(name: "users") + + XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["users", "users", "table"] + ]) + } +} From 2fc0decf41a26787c4d3b2b23ef07786b44f801b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 16 Oct 2022 19:54:06 +0200 Subject: [PATCH 083/216] Fix tests on older version of SQLite --- .swiftlint.yml | 1 + SQLite.xcodeproj/project.pbxproj | 10 +++++++ Sources/SQLite/Core/SQLiteFeature.swift | 19 ++++++++++++++ Sources/SQLite/Schema/Connection+Schema.swift | 6 +++-- Sources/SQLite/Schema/SchemaReader.swift | 26 ++++++++++++++----- .../Schema/Connection+SchemaTests.swift | 4 ++- 6 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 Sources/SQLite/Core/SQLiteFeature.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 34bb253b..d40be28e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -3,6 +3,7 @@ disabled_rules: # rule identifiers to exclude from running - operator_whitespace - large_tuple - closure_parameter_position + - inclusive_language # sqlite_master etc. included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`. - Sources - Tests diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a58e0887..6fe89283 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -203,6 +203,10 @@ DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; @@ -334,6 +338,7 @@ 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; + DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -580,6 +585,7 @@ 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, + DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */, DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */, 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */, ); @@ -970,6 +976,7 @@ 19A17073552293CA063BEA66 /* Result.swift in Sources */, 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, + DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */, 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, @@ -1030,6 +1037,7 @@ DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */, 3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */, + DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */, 3D67B3ED1DB246D100A4F4C6 /* FTS5.swift in Sources */, 3D67B3EE1DB246D100A4F4C6 /* AggregateFunctions.swift in Sources */, @@ -1089,6 +1097,7 @@ 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, + DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */, 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, @@ -1168,6 +1177,7 @@ 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, + DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */, 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, diff --git a/Sources/SQLite/Core/SQLiteFeature.swift b/Sources/SQLite/Core/SQLiteFeature.swift new file mode 100644 index 00000000..36b5b31b --- /dev/null +++ b/Sources/SQLite/Core/SQLiteFeature.swift @@ -0,0 +1,19 @@ +import Foundation + +enum SQLiteFeature { + case partialIntegrityCheck // PRAGMA integrity_check(table) + case sqliteSchemaTable // sqlite_master => sqlite_schema + + func isSupported(by version: SQLiteVersion) -> Bool { + switch self { + case .partialIntegrityCheck, .sqliteSchemaTable: + return version > SQLiteVersion(major: 3, minor: 33) + } + } +} + +extension Connection { + func supports(_ feature: SQLiteFeature) -> Bool { + feature.isSupported(by: sqliteVersion) + } +} diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 868b40d5..d36812eb 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -25,8 +25,10 @@ public extension Connection { // This pragma does a low-level formatting and consistency check of the database. // https://sqlite.org/pragma.html#pragma_integrity_check - func integrityCheck(table: String? = nil, maxErrors: Int? = nil) throws -> [String] { - try run("PRAGMA integrity_check" + (table.map { "(\($0.quote()))" } ?? "")) + func integrityCheck(table: String? = nil) throws -> [String] { + precondition(table == nil || supports(.partialIntegrityCheck), "partial integrity check not supported") + + return try run("PRAGMA integrity_check" + (table.map { "(\($0.quote()))" } ?? "")) .compactMap { $0[0] as? String } .filter { $0 != "ok" } } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 3f02eeae..bfa2e887 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -38,7 +38,7 @@ public class SchemaReader { public func objectDefinitions(name: String? = nil, type: ObjectDefinition.ObjectType? = nil, temp: Bool = false) throws -> [ObjectDefinition] { - var query: QueryType = temp ? SchemaTable.tempName : SchemaTable.name + var query: QueryType = connection.schemaTable(temp: temp) if let name = name { query = query.where(SchemaTable.nameColumn == name) } @@ -99,9 +99,9 @@ public class SchemaReader { column: row[ForeignKeyListTable.fromColumn], primaryKey: row[ForeignKeyListTable.toColumn], onUpdate: row[ForeignKeyListTable.onUpdateColumn] == TableBuilder.Dependency.noAction.rawValue - ? nil : row[ForeignKeyListTable.onUpdateColumn], + ? nil : row[ForeignKeyListTable.onUpdateColumn], onDelete: row[ForeignKeyListTable.onDeleteColumn] == TableBuilder.Dependency.noAction.rawValue - ? nil : row[ForeignKeyListTable.onDeleteColumn] + ? nil : row[ForeignKeyListTable.onDeleteColumn] ) } } @@ -110,9 +110,9 @@ public class SchemaReader { try objectDefinitions(type: .table) .map { table in TableDefinition( - name: table.name, - columns: try columnDefinitions(table: table.name), - indexes: try indexDefinitions(table: table.name) + name: table.name, + columns: try columnDefinitions(table: table.name), + indexes: try indexDefinitions(table: table.name) ) } } @@ -129,6 +129,10 @@ private class SchemaTable { internal static let name = Table("sqlite_schema", database: "main") internal static let tempName = Table("sqlite_schema", database: "temp") + // legacy table names + internal static let masterName = Table("sqlite_master") + internal static let tempMasterName = Table("sqlite_temp_master") + static let typeColumn = Expression("type") static let nameColumn = Expression("name") static let tableNameColumn = Expression("tbl_name") @@ -180,3 +184,13 @@ private class ForeignKeyListTable { static let onDeleteColumn = Expression("on_delete") static let matchColumn = Expression("match") } + +private extension Connection { + func schemaTable(temp: Bool = false) -> Table { + if supports(.sqliteSchemaTable) { + return temp ? SchemaTable.tempName : SchemaTable.name + } else { + return temp ? SchemaTable.tempMasterName : SchemaTable.masterName + } + } +} diff --git a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 96300529..57e2726b 100644 --- a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -33,12 +33,14 @@ class ConnectionSchemaTests: SQLiteTestCase { XCTAssert(results.isEmpty) } - func test_integrityCheck_table() throws { + func test_partial_integrityCheck_table() throws { + guard db.supports(.partialIntegrityCheck) else { return } let results = try db.integrityCheck(table: "users") XCTAssert(results.isEmpty) } func test_integrityCheck_table_not_found() throws { + guard db.supports(.partialIntegrityCheck) else { return } XCTAssertThrowsError(try db.integrityCheck(table: "xxx")) { error in guard case Result.error(let message, _, _) = error else { assertionFailure("invalid error type") From a6a151b3395c83f845d6d88d7d7565df824ef239 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 16 Oct 2022 20:57:25 +0200 Subject: [PATCH 084/216] Reuse SQLiteFeature in SchemaChanger --- Sources/SQLite/Core/SQLiteFeature.swift | 8 +++- Sources/SQLite/Schema/Connection+Schema.swift | 4 +- Sources/SQLite/Schema/SchemaChanger.swift | 6 +-- Sources/SQLite/Schema/SchemaDefinitions.swift | 5 ++- Sources/SQLite/Schema/SchemaReader.swift | 40 +++++++++---------- .../Schema/SchemaReaderTests.swift | 4 +- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Sources/SQLite/Core/SQLiteFeature.swift b/Sources/SQLite/Core/SQLiteFeature.swift index 36b5b31b..50d910a3 100644 --- a/Sources/SQLite/Core/SQLiteFeature.swift +++ b/Sources/SQLite/Core/SQLiteFeature.swift @@ -3,11 +3,17 @@ import Foundation enum SQLiteFeature { case partialIntegrityCheck // PRAGMA integrity_check(table) case sqliteSchemaTable // sqlite_master => sqlite_schema + case renameColumn // ALTER TABLE ... RENAME COLUMN + case dropColumn // ALTER TABLE ... DROP COLUMN func isSupported(by version: SQLiteVersion) -> Bool { switch self { case .partialIntegrityCheck, .sqliteSchemaTable: - return version > SQLiteVersion(major: 3, minor: 33) + return version >= .init(major: 3, minor: 33) + case .renameColumn: + return version >= .init(major: 3, minor: 25) + case .dropColumn: + return version >= .init(major: 3, minor: 35) } } } diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index d36812eb..2977af17 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,7 +1,7 @@ import Foundation public extension Connection { - var schemaReader: SchemaReader { SchemaReader(connection: self) } + var schema: SchemaReader { SchemaReader(connection: self) } // There are four columns in each result row. // The first column is the name of the table that @@ -14,7 +14,7 @@ public extension Connection { // https://sqlite.org/pragma.html#pragma_foreign_key_check func foreignKeyCheck(table: String? = nil) throws -> [ForeignKeyError] { try run("PRAGMA foreign_key_check" + (table.map { "(\($0.quote()))" } ?? "")) - .compactMap { row -> ForeignKeyError? in + .compactMap { (row: [Binding?]) -> ForeignKeyError? in guard let table = row[0] as? String, let rowId = row[1] as? Int64, let target = row[2] as? String else { return nil } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index b557da0a..4e1b42c5 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -52,9 +52,9 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .renameColumn(let from, let to) where version >= .init(major: 3, minor: 25): + case .renameColumn(let from, let to) where SQLiteFeature.renameColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .dropColumn(let column) where version >= .init(major: 3, minor: 35): + case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } @@ -128,7 +128,7 @@ public class SchemaChanger: CustomStringConvertible { init(connection: Connection, version: SQLiteVersion) { self.connection = connection - schemaReader = connection.schemaReader + schemaReader = connection.schema self.version = version } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index fe2e3931..4159b35b 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -245,8 +245,9 @@ public struct IndexDefinition: Equatable { } func orders(sql: String) -> [String: IndexDefinition.Order] { - IndexDefinition.orderRe.matches(in: sql, range: NSRange(location: 0, length: sql.count)) - .reduce([String: IndexDefinition.Order]()) { (memo, result) in + IndexDefinition.orderRe + .matches(in: sql, range: NSRange(location: 0, length: sql.count)) + .reduce([String: IndexDefinition.Order]()) { (memo, result) in var memo2 = memo let column = (sql as NSString).substring(with: result.range(at: 1)) memo2[column] = .DESC diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index bfa2e887..b1292471 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -38,7 +38,7 @@ public class SchemaReader { public func objectDefinitions(name: String? = nil, type: ObjectDefinition.ObjectType? = nil, temp: Bool = false) throws -> [ObjectDefinition] { - var query: QueryType = connection.schemaTable(temp: temp) + var query: QueryType = SchemaTable.get(for: connection, temp: temp) if let name = name { query = query.where(SchemaTable.nameColumn == name) } @@ -125,14 +125,22 @@ public class SchemaReader { } } -private class SchemaTable { - internal static let name = Table("sqlite_schema", database: "main") - internal static let tempName = Table("sqlite_schema", database: "temp") +private enum SchemaTable { + private static let name = Table("sqlite_schema", database: "main") + private static let tempName = Table("sqlite_schema", database: "temp") + // legacy names (< 3.33.0) + private static let masterName = Table("sqlite_master") + private static let tempMasterName = Table("sqlite_temp_master") - // legacy table names - internal static let masterName = Table("sqlite_master") - internal static let tempMasterName = Table("sqlite_temp_master") + static func get(for connection: Connection, temp: Bool = false) -> Table { + if connection.supports(.sqliteSchemaTable) { + return temp ? SchemaTable.tempName : SchemaTable.name + } else { + return temp ? SchemaTable.tempMasterName : SchemaTable.masterName + } + } + // columns static let typeColumn = Expression("type") static let nameColumn = Expression("name") static let tableNameColumn = Expression("tbl_name") @@ -140,7 +148,7 @@ private class SchemaTable { static let sqlColumn = Expression("sql") } -private class TableInfoTable { +private enum TableInfoTable { static let idColumn = Expression("cid") static let nameColumn = Expression("name") static let typeColumn = Expression("type") @@ -149,7 +157,7 @@ private class TableInfoTable { static let primaryKeyColumn = Expression("pk") } -private class IndexInfoTable { +private enum IndexInfoTable { // The rank of the column within the index. (0 means left-most.) static let seqnoColumn = Expression("seqno") // The rank of the column within the table being indexed. @@ -159,7 +167,7 @@ private class IndexInfoTable { static let nameColumn = Expression("name") } -private class IndexListTable { +private enum IndexListTable { // A sequence number assigned to each index for internal tracking purposes. static let seqColumn = Expression("seq") // The name of the index @@ -174,7 +182,7 @@ private class IndexListTable { static let partialColumn = Expression("partial") } -private class ForeignKeyListTable { +private enum ForeignKeyListTable { static let idColumn = Expression("id") static let seqColumn = Expression("seq") static let tableColumn = Expression("table") @@ -184,13 +192,3 @@ private class ForeignKeyListTable { static let onDeleteColumn = Expression("on_delete") static let matchColumn = Expression("match") } - -private extension Connection { - func schemaTable(temp: Bool = false) -> Table { - if supports(.sqliteSchemaTable) { - return temp ? SchemaTable.tempName : SchemaTable.name - } else { - return temp ? SchemaTable.tempMasterName : SchemaTable.masterName - } - } -} diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 165dbc28..95dba921 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -8,7 +8,7 @@ class SchemaReaderTests: SQLiteTestCase { try super.setUpWithError() try createUsersTable() - schemaReader = db.schemaReader + schemaReader = db.schema } func test_columnDefinitions() throws { @@ -168,6 +168,7 @@ class SchemaReaderTests: SQLiteTestCase { XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ ["users", "users", "table"] ]) + XCTAssertTrue((try schemaReader.objectDefinitions(type: .trigger)).isEmpty) } func test_objectDefinitionsFilterByName() throws { @@ -176,5 +177,6 @@ class SchemaReaderTests: SQLiteTestCase { XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ ["users", "users", "table"] ]) + XCTAssertTrue((try schemaReader.objectDefinitions(name: "xxx")).isEmpty) } } From 0f6c3e30245d09bdd1fd50d5e0ac0d162131426c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 01:11:08 +0200 Subject: [PATCH 085/216] Add documentation --- Documentation/Index.md | 82 ++++++++++++++++++- Sources/SQLite/Schema/SchemaChanger.swift | 2 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 4 + Sources/SQLite/Schema/SchemaReader.swift | 2 +- .../Schema/SchemaChangerTests.swift | 34 ++++---- .../Schema/SchemaReaderTests.swift | 34 +++++++- 6 files changed, 134 insertions(+), 24 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 29b18c96..e40f786d 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1371,6 +1371,37 @@ try db.transaction { > _Note:_ Transactions run in a serial queue. +## Querying the Schema + +We can obtain generic information about objects in the current schema with a `SchemaReader`: + +```swift +let schema = db.schema +``` + +To query the data: + +```swift +let indexes = try schema.objectDefinitions(type: .index) +let tables = try schema.objectDefinitions(type: .table) +let triggers = try schema.objectDefinitions(type: .trigger) +``` + +### Indexes and Columns + +Specialized methods are available to get more detailed information: + +```swift +let indexes = try schema.indexDefinitions("users") +let columns = try schema.columnDefinitions("users") + +for index in indexes { + print("\(index.name) columns:\(index.columns))") +} +for column in columns { + print("\(column.name) pk:\(column.primaryKey) nullable: \(column.nullable)") +} +``` ## Altering the Schema @@ -1454,11 +1485,56 @@ tables](#creating-a-table). ### Renaming Columns -Added in SQLite 3.25.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) +We can rename columns with the help of the `SchemaChanger` class: + +```swift +let schemaChanger = SchemaChanger(connection: db) +try schemaChanger.alter(table: "users") { table in + table.rename("old_name", to: "new_name") +} +``` ### Dropping Columns -Added in SQLite 3.35.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) +```swift +let schemaChanger = SchemaChanger(connection: db) +try schemaChanger.alter(table: "users") { table in + table.drop("column") +} +``` + +These operations will work with all versions of SQLite and use modern SQL +operations such as `DROP COLUMN` when available. + +### Adding Columns (SchemaChanger) + +The `SchemaChanger` provides an alternative API to add new columns: + +```swift +let newColumn = ColumnDefinition( + name: "new_text_column", + type: .TEXT, + nullable: true, + defaultValue: .stringLiteral("foo") +) + +let schemaChanger = SchemaChanger(connection: db) + +try schemaChanger.alter(table: "users") { table in + table.add(newColumn) +} +``` + +### Renaming/dropping Tables (SchemaChanger) + +The `SchemaChanger` provides an alternative API to rename and drop tables: + +```swift +let schemaChanger = SchemaChanger(connection: db) + +try schemaChanger.rename(table: "users", to: "users_new") +try schemaChanger.drop(table: "emails") +``` ### Indexes @@ -1515,7 +1591,6 @@ try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` - ### Dropping Tables We can build @@ -1535,7 +1610,6 @@ try db.run(users.drop(ifExists: true)) // DROP TABLE IF EXISTS "users" ``` - ### Migrations and Schema Versioning You can use the convenience property on `Connection` to query and set the diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 4e1b42c5..d2ada22c 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -99,7 +99,7 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.addColumn(column)) } - public func remove(_ column: String) { + public func drop(_ column: String) { operations.append(.dropColumn(column)) } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 4159b35b..a2700dd2 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -31,6 +31,10 @@ public struct ObjectDefinition: Equatable { // SQL text that describes the object (NULL for the internal indexes) public let sql: String? + + public var isInternal: Bool { + name.starts(with: "sqlite_") || sql == nil + } } // https://sqlite.org/syntax/column-def.html diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index b1292471..1c26fdd6 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -52,7 +52,7 @@ public class SchemaReader { return ObjectDefinition( type: type, name: row[SchemaTable.nameColumn], - tableName: row[SchemaTable.nameColumn], + tableName: row[SchemaTable.tableNameColumn], rootpage: row[SchemaTable.rootPageColumn] ?? 0, sql: row[SchemaTable.sqlColumn] ) diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index af8b6565..d023b8e5 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -3,7 +3,7 @@ import XCTest class SchemaChangerTests: SQLiteTestCase { var schemaChanger: SchemaChanger! - var schemaReader: SchemaReader! + var schema: SchemaReader! override func setUpWithError() throws { try super.setUpWithError() @@ -11,33 +11,33 @@ class SchemaChangerTests: SQLiteTestCase { try insertUsers("bob") - schemaReader = SchemaReader(connection: db) + schema = SchemaReader(connection: db) schemaChanger = SchemaChanger(connection: db) } func test_empty_migration_does_not_change_column_definitions() throws { - let previous = try schemaReader.columnDefinitions(table: "users") + let previous = try schema.columnDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try schemaReader.columnDefinitions(table: "users") + let current = try schema.columnDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_index_definitions() throws { - let previous = try schemaReader.indexDefinitions(table: "users") + let previous = try schema.indexDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try schemaReader.indexDefinitions(table: "users") + let current = try schema.indexDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_foreign_key_definitions() throws { - let previous = try schemaReader.foreignKeys(table: "users") + let previous = try schema.foreignKeys(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try schemaReader.foreignKeys(table: "users") + let current = try schema.foreignKeys(table: "users") XCTAssertEqual(previous, current) } @@ -51,21 +51,21 @@ class SchemaChangerTests: SQLiteTestCase { XCTAssertEqual(previous, current) } - func test_remove_column() throws { + func test_drop_column() throws { try schemaChanger.alter(table: "users") { table in - table.remove("age") + table.drop("age") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } - func test_remove_column_legacy() throws { + func test_drop_column_legacy() throws { schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // DROP COLUMN introduced in 3.35.0 try schemaChanger.alter(table: "users") { table in - table.remove("age") + table.drop("age") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } @@ -74,7 +74,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -86,7 +86,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -102,7 +102,7 @@ class SchemaChangerTests: SQLiteTestCase { table.add(newColumn) } - let columns = try schemaReader.columnDefinitions(table: "users") + let columns = try schema.columnDefinitions(table: "users") XCTAssertTrue(columns.contains(newColumn)) XCTAssertEqual(try db.pluck(users.select(column))?[column], "foo") diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 95dba921..dd5ae103 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -146,7 +146,7 @@ class SchemaReaderTests: SQLiteTestCase { XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ ["users", "users", "table"], - ["sqlite_autoindex_users_1", "sqlite_autoindex_users_1", "index"] + ["sqlite_autoindex_users_1", "users", "index"] ]) } @@ -162,6 +162,38 @@ class SchemaReaderTests: SQLiteTestCase { ]) } + func test_objectDefinitions_indexes() throws { + let emailIndex = users.createIndex(Expression("email"), unique: false, ifNotExists: true) + try db.run(emailIndex) + + let indexes = try schemaReader.objectDefinitions(type: .index) + .filter { !$0.isInternal } + + XCTAssertEqual(indexes.map { index in [index.name, index.tableName, index.type.rawValue, index.sql]}, [ + ["index_users_on_email", + "users", + "index", + "CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")"] + ]) + } + + func test_objectDefinitions_triggers() throws { + let trigger = """ + CREATE TRIGGER test_trigger + AFTER INSERT ON users BEGIN + UPDATE USERS SET name = "update" WHERE id = NEW.rowid; + END; + """ + + try db.run(trigger) + + let triggers = try schemaReader.objectDefinitions(type: .trigger) + + XCTAssertEqual(triggers.map { trigger in [trigger.name, trigger.tableName, trigger.type.rawValue]}, [ + ["test_trigger", "users", "trigger"] + ]) + } + func test_objectDefinitionsFilterByType() throws { let tables = try schemaReader.objectDefinitions(type: .table) From 137788e332f07e04d0a7d07150a043cc04c0d5e5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 01:17:31 +0200 Subject: [PATCH 086/216] Named param for rename/drop --- Documentation/Index.md | 4 ++-- Sources/SQLite/Schema/SchemaChanger.swift | 4 ++-- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index e40f786d..68e66457 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1490,7 +1490,7 @@ We can rename columns with the help of the `SchemaChanger` class: ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.rename("old_name", to: "new_name") + table.rename(column: "old_name", to: "new_name") } ``` @@ -1499,7 +1499,7 @@ try schemaChanger.alter(table: "users") { table in ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.drop("column") + table.drop(column: "email") } ``` diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index d2ada22c..67f2b0d3 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -99,11 +99,11 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.addColumn(column)) } - public func drop(_ column: String) { + public func drop(column: String) { operations.append(.dropColumn(column)) } - public func rename(_ column: String, to: String) { + public func rename(column: String, to: String) { operations.append(.renameColumn(column, to)) } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index d023b8e5..2894e5ce 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -53,7 +53,7 @@ class SchemaChangerTests: SQLiteTestCase { func test_drop_column() throws { try schemaChanger.alter(table: "users") { table in - table.drop("age") + table.drop(column: "age") } let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) @@ -63,7 +63,7 @@ class SchemaChangerTests: SQLiteTestCase { schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // DROP COLUMN introduced in 3.35.0 try schemaChanger.alter(table: "users") { table in - table.drop("age") + table.drop(column: "age") } let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) @@ -71,7 +71,7 @@ class SchemaChangerTests: SQLiteTestCase { func test_rename_column() throws { try schemaChanger.alter(table: "users") { table in - table.rename("age", to: "age2") + table.rename(column: "age", to: "age2") } let columns = try schema.columnDefinitions(table: "users").map(\.name) @@ -83,7 +83,7 @@ class SchemaChangerTests: SQLiteTestCase { schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // RENAME COLUMN introduced in 3.25.0 try schemaChanger.alter(table: "users") { table in - table.rename("age", to: "age2") + table.rename(column: "age", to: "age2") } let columns = try schema.columnDefinitions(table: "users").map(\.name) From f2c8bdaf71c57a5087efabf447da33f874ae5787 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 01:25:48 +0200 Subject: [PATCH 087/216] Changelog --- CHANGELOG.md | 4 ++++ Documentation/Index.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3f78b4..3a12f377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.14.0 (tbd), [diff][diff-0.14.0] ======================================== +* Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) @@ -160,6 +161,7 @@ [#866]: https://github.com/stephencelis/SQLite.swift/pull/866 [#881]: https://github.com/stephencelis/SQLite.swift/pull/881 [#919]: https://github.com/stephencelis/SQLite.swift/pull/919 +[#1073]: https://github.com/stephencelis/SQLite.swift/issues/1073 [#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 @@ -185,3 +187,5 @@ [#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141 [#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 [#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144 +[#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146 +[#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148 diff --git a/Documentation/Index.md b/Documentation/Index.md index 68e66457..d275db8e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1514,7 +1514,7 @@ The `SchemaChanger` provides an alternative API to add new columns: let newColumn = ColumnDefinition( name: "new_text_column", type: .TEXT, - nullable: true, + nullable: true, defaultValue: .stringLiteral("foo") ) From 140134d68fd82190972f30232e33363c8ae58a79 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 02:06:21 +0200 Subject: [PATCH 088/216] Add playground examples --- SQLite.playground/Contents.swift | 30 ++++++++++++++++++- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- .../Schema/SchemaDefinitionsTests.swift | 10 +++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index c089076d..73091735 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -99,5 +99,33 @@ db.createAggregation("customConcat", initialValue: "users:", reduce: reduce, result: { $0 }) -let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +let result = try db.prepare("SELECT customConcat(email) FROM users").scalar() as! String print(result) + +/// schema queries +let schema = db.schema +let objects = try schema.objectDefinitions() +print(objects) + +let columns = try schema.columnDefinitions(table: "users") +print(columns) + +/// schema alteration + +let schemaChanger = SchemaChanger(connection: db) +try schemaChanger.alter(table: "users") { table in + table.add(.init(name: "age", type: .INTEGER)) + table.rename(column: "email", to: "electronic_mail") + table.drop(column: "name") +} + +let changedColumns = try schema.columnDefinitions(table: "users") +print(changedColumns) + +let age = Expression("age") +let electronicMail = Expression("electronic_mail") + +let newRowid = try db.run(users.insert( + electronicMail <- "carol@mac.com", + age <- 33 +)) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index a2700dd2..b06ddfc7 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -122,7 +122,7 @@ public struct ColumnDefinition: Equatable { public init(name: String, primaryKey: PrimaryKey? = nil, type: Affinity, - nullable: Bool = false, + nullable: Bool = true, defaultValue: LiteralValue = .NULL, references: ForeignKey? = nil) { self.name = name diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 384aab3f..ef97b981 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -49,6 +49,16 @@ class ColumnDefinitionTests: XCTestCase { XCTAssertEqual(definition.toSQL(), expected) } #endif + + func testNullableByDefault() { + let test = ColumnDefinition(name: "test", type: .REAL) + XCTAssertEqual(test.name, "test") + XCTAssertTrue(test.nullable) + XCTAssertEqual(test.defaultValue, .NULL) + XCTAssertEqual(test.type, .REAL) + XCTAssertNil(test.references) + XCTAssertNil(test.primaryKey) + } } class AffinityTests: XCTestCase { From 020ec7aabbda32a384d29fcc4ecf42e52ef48190 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 02:40:18 +0200 Subject: [PATCH 089/216] Better example --- SQLite.playground/Contents.swift | 2 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 73091735..5a7ea379 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -114,7 +114,7 @@ print(columns) let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.add(.init(name: "age", type: .INTEGER)) + table.add(ColumnDefinition(name: "age", type: .INTEGER)) table.rename(column: "email", to: "electronic_mail") table.drop(column: "name") } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index b06ddfc7..284fc4c3 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -154,7 +154,6 @@ public enum LiteralValue: Equatable, CustomStringConvertible { // If there is no explicit DEFAULT clause attached to a column definition, then the default value of the // column is NULL - // swiftlint:disable identifier_name case NULL // Beginning with SQLite 3.23.0 (2018-04-02), SQLite recognizes the identifiers "TRUE" and @@ -164,6 +163,7 @@ public enum LiteralValue: Equatable, CustomStringConvertible { // The boolean identifiers TRUE and FALSE are usually just aliases for the integer values 1 and 0, respectively. case TRUE case FALSE + // swiftlint:disable identifier_name case CURRENT_TIME case CURRENT_DATE case CURRENT_TIMESTAMP From 773ac2611faaeeb16cedd83406ed3b281ffcf8ea Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 21:39:32 +0200 Subject: [PATCH 090/216] Reactivate #416 --- Sources/SQLite/Core/Blob.swift | 51 ++++++++++++++++----- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Foundation.swift | 6 +-- Tests/SQLiteTests/Core/BlobTests.swift | 10 +++- Tests/SQLiteTests/Core/StatementTests.swift | 4 +- Tests/SQLiteTests/FoundationTests.swift | 2 +- 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index cd31483b..e1ce5d9c 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -21,26 +21,55 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // +import Foundation -public struct Blob { +public final class Blob { - public let bytes: [UInt8] + public let data: NSData - public init(bytes: [UInt8]) { - self.bytes = bytes + public var bytes: UnsafeRawPointer { + data.bytes } - public init(bytes: UnsafeRawPointer, length: Int) { - let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length) - self.init(bytes: [UInt8](i8bufptr)) + public var length: Int { + data.count } - public func toHex() -> String { - bytes.map { - ($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false) - }.joined(separator: "") + public convenience init(bytes: [UInt8]) { + let buffer = UnsafeMutablePointer.allocate(capacity: bytes.count) + for idx in 0.. String { + let bytes = bytes.assumingMemoryBound(to: UInt8.self) + + var hex = "" + for idx in 0.. Data { - Data(dataValue.bytes) + dataValue.data as Data } public var datatypeValue: Blob { - withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in - Blob(bytes: pointer.baseAddress!, length: count) - } + Blob(data: self as NSData) } } diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index 87eb5709..463ef10a 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -11,13 +11,19 @@ class BlobTests: XCTestCase { func test_init_array() { let blob = Blob(bytes: [42, 42, 42]) - XCTAssertEqual(blob.bytes, [42, 42, 42]) + XCTAssertEqual(blob.byteArray, [42, 42, 42]) } func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) - XCTAssertEqual(blob.bytes, [42, 42, 42]) + XCTAssertEqual(blob.byteArray, [42, 42, 42]) + } +} + +extension Blob { + var byteArray: [UInt8] { + [UInt8](data) } } diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index eeb513fe..28772ae1 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -22,7 +22,7 @@ class StatementTests: SQLiteTestCase { let statement = try db.prepare("SELECT email FROM users") XCTAssert(try statement.step()) let blob = statement.row[0] as Blob - XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) + XCTAssertEqual("alice@example.com", String(data: blob.data as Data, encoding: .utf8)!) } func test_zero_sized_blob_returns_null() throws { @@ -31,7 +31,7 @@ class StatementTests: SQLiteTestCase { try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) - XCTAssertEqual([], blobValue.bytes) + XCTAssertEqual([], blobValue.byteArray) } func test_prepareRowIterator() throws { diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 453febcd..cca15cc1 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -5,7 +5,7 @@ class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue - XCTAssertEqual([1, 2, 3], blob.bytes) + XCTAssertEqual([1, 2, 3], blob.byteArray) } func testBlobToData() { From 3b91a00c71b34cc3f4343b2c622bf400717a7c3d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 22:13:21 +0200 Subject: [PATCH 091/216] Fix test --- Sources/SQLite/Core/Statement.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index ec06a5bb..df4ceebf 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -100,21 +100,24 @@ public final class Statement { } fileprivate func bind(_ value: Binding?, atIndex idx: Int) { - if value == nil { + switch value { + case .none: sqlite3_bind_null(handle, Int32(idx)) - } else if let value = value as? Blob { + case let value as Blob where value.length == 0: + sqlite3_bind_zeroblob(handle, Int32(idx), 0) + case let value as Blob: sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT) - } else if let value = value as? Double { + case let value as Double: sqlite3_bind_double(handle, Int32(idx), value) - } else if let value = value as? Int64 { + case let value as Int64: sqlite3_bind_int64(handle, Int32(idx), value) - } else if let value = value as? String { + case let value as String: sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT) - } else if let value = value as? Int { + case let value as Int: self.bind(value.datatypeValue, atIndex: idx) - } else if let value = value as? Bool { + case let value as Bool: self.bind(value.datatypeValue, atIndex: idx) - } else if let value = value { + case .some(let value): fatalError("tried to bind unexpected value \(value)") } } From 0715e9512daf86fe42e592b54eb633a049adb4b7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 22:26:46 +0200 Subject: [PATCH 092/216] Fix blob equality --- Sources/SQLite/Core/Blob.swift | 8 +++++++- Tests/SQLiteTests/Core/BlobTests.swift | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index e1ce5d9c..83b78c2c 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -36,6 +36,10 @@ public final class Blob { } public convenience init(bytes: [UInt8]) { + guard bytes.count > 0 else { + self.init(data: NSData()) + return + } let buffer = UnsafeMutablePointer.allocate(capacity: bytes.count) for idx in 0.. String { + guard length > 0 else { return "" } let bytes = bytes.assumingMemoryBound(to: UInt8.self) var hex = "" @@ -85,5 +91,5 @@ extension Blob: Equatable { } public func ==(lhs: Blob, rhs: Blob) -> Bool { - lhs.bytes == rhs.bytes + lhs.data == rhs.data } diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index 463ef10a..9c8700cc 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -5,10 +5,14 @@ class BlobTests: XCTestCase { func test_toHex() { let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255]) - XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") } + func test_toHex_empty() { + let blob = Blob(bytes: []) + XCTAssertEqual(blob.toHex(), "") + } + func test_init_array() { let blob = Blob(bytes: [42, 42, 42]) XCTAssertEqual(blob.byteArray, [42, 42, 42]) @@ -20,6 +24,16 @@ class BlobTests: XCTestCase { let blob = Blob(bytes: pointer, length: 3) XCTAssertEqual(blob.byteArray, [42, 42, 42]) } + + func test_equality() { + let blob1 = Blob(bytes: [42, 42, 42]) + let blob2 = Blob(bytes: [42, 42, 42]) + let blob3 = Blob(bytes: [42, 42, 43]) + + XCTAssertEqual(Blob(bytes: []), Blob(bytes: [])) + XCTAssertEqual(blob1, blob2) + XCTAssertNotEqual(blob1, blob3) + } } extension Blob { From 0ce78d92aebea37bc68585ae4d0ead17641d86b1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 22:29:40 +0200 Subject: [PATCH 093/216] Remove helper --- Tests/SQLiteTests/Core/BlobTests.swift | 10 ++-------- Tests/SQLiteTests/Core/StatementTests.swift | 3 +-- Tests/SQLiteTests/FoundationTests.swift | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index 9c8700cc..01f38197 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -15,14 +15,14 @@ class BlobTests: XCTestCase { func test_init_array() { let blob = Blob(bytes: [42, 42, 42]) - XCTAssertEqual(blob.byteArray, [42, 42, 42]) + XCTAssertEqual([UInt8](blob.data), [42, 42, 42]) } func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) - XCTAssertEqual(blob.byteArray, [42, 42, 42]) + XCTAssertEqual([UInt8](blob.data), [42, 42, 42]) } func test_equality() { @@ -35,9 +35,3 @@ class BlobTests: XCTestCase { XCTAssertNotEqual(blob1, blob3) } } - -extension Blob { - var byteArray: [UInt8] { - [UInt8](data) - } -} diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 28772ae1..2ab84c09 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -31,7 +31,7 @@ class StatementTests: SQLiteTestCase { try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) - XCTAssertEqual([], blobValue.byteArray) + XCTAssertEqual([], [UInt8](blobValue.data)) } func test_prepareRowIterator() throws { @@ -71,5 +71,4 @@ class StatementTests: SQLiteTestCase { // truncate succeeds try db.run("DROP TABLE users") } - } diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index cca15cc1..ffd3fae1 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -5,7 +5,7 @@ class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue - XCTAssertEqual([1, 2, 3], blob.byteArray) + XCTAssertEqual([1, 2, 3], [UInt8](blob.data)) } func testBlobToData() { From 932d580deae26f04004bcd0aefa536ef021219c8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 23:08:55 +0200 Subject: [PATCH 094/216] Fix SQLCipher subspec --- Sources/SQLite/Core/Blob.swift | 5 ++--- Sources/SQLite/Extensions/Cipher.swift | 6 +++--- Tests/SQLiteTests/Core/BlobTests.swift | 4 ++++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index 83b78c2c..fb7ab11b 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -27,8 +27,8 @@ public final class Blob { public let data: NSData - public var bytes: UnsafeRawPointer { - data.bytes + public var bytes: UnsafePointer { + data.bytes.assumingMemoryBound(to: UInt8.self) } public var length: Int { @@ -64,7 +64,6 @@ public final class Blob { public func toHex() -> String { guard length > 0 else { return "" } - let bytes = bytes.assumingMemoryBound(to: UInt8.self) var hex = "" for idx in 0.. Date: Sun, 23 Oct 2022 23:47:17 +0200 Subject: [PATCH 095/216] Avoid NSMutableData --- Tests/SQLiteTests/Extensions/CipherTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/SQLiteTests/Extensions/CipherTests.swift b/Tests/SQLiteTests/Extensions/CipherTests.swift index cc43272e..bd7f2042 100644 --- a/Tests/SQLiteTests/Extensions/CipherTests.swift +++ b/Tests/SQLiteTests/Extensions/CipherTests.swift @@ -19,7 +19,7 @@ class CipherTests: XCTestCase { // db2 let key2 = keyData() - try db2.key(Blob(bytes: key2.bytes, length: key2.length)) + try db2.key(Blob(data: key2)) try db2.run("CREATE TABLE foo (bar TEXT)") try db2.run("INSERT INTO foo (bar) VALUES ('world')") @@ -47,7 +47,7 @@ class CipherTests: XCTestCase { func test_data_rekey() throws { let newKey = keyData() - try db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) + try db2.rekey(Blob(data: newKey)) XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } @@ -108,12 +108,12 @@ class CipherTests: XCTestCase { XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) } - private func keyData(length: Int = 64) -> NSMutableData { + private func keyData(length: Int = 64) -> NSData { let keyData = NSMutableData(length: length)! let result = SecRandomCopyBytes(kSecRandomDefault, length, keyData.mutableBytes.assumingMemoryBound(to: UInt8.self)) XCTAssertEqual(0, result) - return keyData + return NSData(data: keyData) } } #endif From bf021c503067675295a18f69ecfc6eb3903c8195 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 23:51:43 +0200 Subject: [PATCH 096/216] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a12f377..49590fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ * Fix `insertMany([Encodable])` ([#1130][], [#1138][]) * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) -* Performance improvements ([#1109][], [#1115][], [#1132][]) +* Blob performance improvements ([#416][], [#1167][]) +* Various performance improvements ([#1109][], [#1115][], [#1132][]) * Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144][]) 0.13.3 (27-03-2022), [diff][diff-0.13.3] @@ -130,6 +131,7 @@ [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 +[#416]: https://github.com/stephencelis/SQLite.swift/pull/416 [#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 @@ -189,3 +191,4 @@ [#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144 [#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146 [#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148 +[#1167]: https://github.com/stephencelis/SQLite.swift/pull/1167 From 06e55e794ffb49eda3f97e0ca627fb76ed2f98f6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 24 Oct 2022 12:10:54 +0200 Subject: [PATCH 097/216] Simplify --- Sources/SQLite/Core/Blob.swift | 12 +----------- Tests/SQLiteTests/Core/BlobTests.swift | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index fb7ab11b..c8bd2c15 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -40,17 +40,7 @@ public final class Blob { self.init(data: NSData()) return } - let buffer = UnsafeMutablePointer.allocate(capacity: bytes.count) - for idx in 0.. Date: Wed, 26 Oct 2022 01:04:29 +0200 Subject: [PATCH 098/216] prepareRowIterator should be internal --- Documentation/Index.md | 8 -------- Sources/SQLite/Core/Statement.swift | 4 ++-- Tests/SQLiteTests/Core/StatementTests.swift | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3d0639b3..fa91f7b7 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -2125,15 +2125,7 @@ using the following functions. } } ``` - Statements with results may be iterated over, using a `RowIterator` if - useful. - ```swift - let emailColumn = Expression("email") - let stmt = try db.prepare("SELECT id, email FROM users") - let emails = try! stmt.prepareRowIterator().map { $0[emailColumn] } - ``` - - `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. diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index df4ceebf..80450258 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -236,8 +236,8 @@ extension Statement: FailableIterator { } extension Statement { - public func prepareRowIterator() -> RowIterator { - return RowIterator(statement: self, columnNames: self.columnNameMap) + func prepareRowIterator() -> RowIterator { + RowIterator(statement: self, columnNames: columnNameMap) } var columnNameMap: [String: Int] { diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 2ab84c09..0d82e890 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite #if SQLITE_SWIFT_STANDALONE import sqlite3 From 11bfa260eef892b0a187a6b77ffe8a94d4e97641 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 23:50:13 +0200 Subject: [PATCH 099/216] Mention query parameter in changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49590fbe..01b7de5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) +* Expose connection flags (via `URIQueryParameter`) to open db ([#1074][])) * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) @@ -164,6 +165,7 @@ [#881]: https://github.com/stephencelis/SQLite.swift/pull/881 [#919]: https://github.com/stephencelis/SQLite.swift/pull/919 [#1073]: https://github.com/stephencelis/SQLite.swift/issues/1073 +[#1074]: https://github.com/stephencelis/SQLite.swift/issues/1074 [#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 From ff3c4a2d4cc8533c45addba6bbd2d0d90e8833d8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 21 Oct 2022 12:20:24 +0200 Subject: [PATCH 100/216] Remove outdated/unavailable project refs --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 1b04c7c8..cdc11dd5 100644 --- a/README.md +++ b/README.md @@ -267,19 +267,14 @@ These projects enhance or use SQLite.swift: - [SQLiteMigrationManager.swift][] (inspired by [FMDBMigrationManager][]) - - [Delta: Math helper](https://apps.apple.com/app/delta-math-helper/id1436506800) - (see [Delta/Utils/Database.swift](https://github.com/GroupeMINASTE/Delta-iOS/blob/master/Delta/Utils/Database.swift) for production implementation example) - ## Alternatives Looking for something else? Try another Swift wrapper (or [FMDB][]): - - [Camembert](https://github.com/remirobert/Camembert) - [GRDB](https://github.com/groue/GRDB.swift) - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - [Squeal](https://github.com/nerdyc/Squeal) - - [SwiftData](https://github.com/ryanfowler/SwiftData) [Swift]: https://swift.org/ [SQLite3]: https://www.sqlite.org From dad179a555d0d2714ab1fcf69ba938073d17ff54 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 21 Oct 2022 18:47:32 +0200 Subject: [PATCH 101/216] Bump version number --- Documentation/Index.md | 12 ++++++------ README.md | 8 ++++---- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- Sources/SQLite/Typed/Expression.swift | 3 +-- Tests/SPM/Package.swift | 2 +- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index fa91f7b7..65cf5114 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -83,7 +83,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") ] ``` @@ -104,7 +104,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.3 + github "stephencelis/SQLite.swift" ~> 0.14.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -134,7 +134,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.3' + pod 'SQLite.swift', '~> 0.14.0' end ``` @@ -148,7 +148,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.3' + pod 'SQLite.swift/standalone', '~> 0.14.0' end ``` @@ -158,7 +158,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.3' + pod 'SQLite.swift/standalone', '~> 0.14.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -174,7 +174,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.13.3' + pod 'SQLite.swift/SQLCipher', '~> 0.14.0' end ``` diff --git a/README.md b/README.md index cdc11dd5..ab1b67f5 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.3 + github "stephencelis/SQLite.swift" ~> 0.14.0 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.3' + pod 'SQLite.swift', '~> 0.14.0' end ``` @@ -250,7 +250,7 @@ device: [Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork -## Author +## Original author - [Stephen Celis](mailto:stephen@stephencelis.com) ([@stephencelis](https://twitter.com/stephencelis)) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index d36d0330..c30aa765 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.13.3" + s.version = "0.14.0" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 6fe89283..3d62e980 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1497,7 +1497,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.3; + MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1520,7 +1520,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.3; + MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 95cdd3b9..de5afe2e 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: make internal (0.13.0) - public func asSQL() -> String { + func asSQL() -> String { let expressed = expression var idx = 0 return expressed.template.reduce("") { template, character in diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index dd1cd45c..d5d2e9ff 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") ], targets: [ .target( From 136a5c5c07d0084d459bb6fa466eba1e564c3e9f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 22 Oct 2022 23:51:34 +0200 Subject: [PATCH 102/216] Add ExpressionTests --- Sources/SQLite/Typed/Expression.swift | 20 +++++++------- Tests/SQLiteTests/TestHelpers.swift | 2 +- Tests/SQLiteTests/Typed/ExpressionTests.swift | 27 ++++++++++++++++++- .../Typed/QueryIntegrationTests.swift | 7 ++++- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index de5afe2e..af125cc2 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -64,31 +64,31 @@ public struct Expression: ExpressionType { } -public protocol Expressible { +public protocol Expressible: CustomStringConvertible { var expression: Expression { get } } extension Expressible { + public var description: String { + asSQL() + } // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE func asSQL() -> String { let expressed = expression - var idx = 0 - return expressed.template.reduce("") { template, character in - let transcoded: String + return expressed.template.reduce(("", 0)) { memo, character in + let (template, index) = memo if character == "?" { - transcoded = transcode(expressed.bindings[idx]) - idx += 1 + precondition(index < expressed.bindings.count, "not enough bindings for expression") + return (template + transcode(expressed.bindings[index]), index + 1) } else { - transcoded = String(character) + return (template + String(character), index) } - return template + transcoded - } + }.0 } - } extension ExpressionType { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 2a8c7e39..65cff8bb 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -12,7 +12,7 @@ class SQLiteTestCase: XCTestCase { trace = [String: Int]() db.trace { SQL in - print(SQL) + // print("SQL: \(SQL)") self.trace[SQL, default: 0] += 1 } } diff --git a/Tests/SQLiteTests/Typed/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift index 32100d4d..9155a45c 100644 --- a/Tests/SQLiteTests/Typed/ExpressionTests.swift +++ b/Tests/SQLiteTests/Typed/ExpressionTests.swift @@ -1,5 +1,30 @@ import XCTest -import SQLite +@testable import SQLite class ExpressionTests: XCTestCase { + + func test_asSQL_expression_bindings() { + let expression = Expression("foo ? bar", ["baz"]) + XCTAssertEqual(expression.asSQL(), "foo 'baz' bar") + } + + func test_asSQL_expression_bindings_quoting() { + let expression = Expression("foo ? bar", ["'baz'"]) + XCTAssertEqual(expression.asSQL(), "foo '''baz''' bar") + } + + func test_expression_custom_string_convertible() { + let expression = Expression("foo ? bar", ["baz"]) + XCTAssertEqual(expression.asSQL(), expression.description) + } + + func test_init_literal() { + let expression = Expression(literal: "literal") + XCTAssertEqual(expression.template, "literal") + } + + func test_init_identifier() { + let expression = Expression("identifier") + XCTAssertEqual(expression.template, "\"identifier\"") + } } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 27b78c0a..3fd388e9 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -192,7 +192,12 @@ class QueryIntegrationTests: SQLiteTestCase { 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 sql = query3.union(query4).order(Expression(literal: "weight")).asSQL() + XCTAssertEqual(sql, + """ + SELECT "users".*, 1 AS weight FROM "users" WHERE ("email" = 'sally@example.com') UNION \ + SELECT "users".*, 2 AS weight FROM "users" WHERE ("email" = 'alice@example.com') ORDER BY weight + """) let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) From fc8f8df40b8434337338633fc5ae853736c1adad Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 24 Oct 2022 10:40:21 +0200 Subject: [PATCH 103/216] Remove outdated docs/links --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ab1b67f5..0d9396c6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ syntax _and_ intent. - [Well-documented][See Documentation] - Extensively tested - [SQLCipher][] support via CocoaPods + - [Schema query/migration][] - Works on [Linux](Documentation/Linux.md) (with some limitations) - Active support at [StackOverflow](https://stackoverflow.com/questions/tagged/sqlite.swift), @@ -27,6 +28,7 @@ syntax _and_ intent. [SQLCipher]: https://www.zetetic.net/sqlcipher/ [Full-text search]: Documentation/Index.md#full-text-search +[Schema query/migration]: Documentation/Index.md#querying-the-schema [See Documentation]: Documentation/Index.md#sqliteswift-documentation @@ -115,17 +117,10 @@ interactively, from the Xcode project’s playground. ![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) -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]: https://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html -[SQLiteDataAccessLayer2]: https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master - ## Installation -> _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. +> _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. +> Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. ### Swift Package Manager From 0eb58d8fa82e864d1071c92a2fc88747484d3f48 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 26 Oct 2022 23:34:17 +0200 Subject: [PATCH 104/216] Doc updates --- CHANGELOG.md | 1 + Documentation/Index.md | 90 +++++++++---------- Documentation/Release.md | 1 + Documentation/Upgrading.md | 11 +++ SQLite.playground/Contents.swift | 1 + Sources/SQLite/Schema/SchemaChanger.swift | 10 +-- .../Schema/SchemaChangerTests.swift | 14 +++ 7 files changed, 78 insertions(+), 50 deletions(-) create mode 100644 Documentation/Upgrading.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b7de5a..92c89e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 0.14.0 (tbd), [diff][diff-0.14.0] ======================================== +For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). * Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) diff --git a/Documentation/Index.md b/Documentation/Index.md index 65cf5114..bdd1da28 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -41,14 +41,19 @@ - [Updating Rows](#updating-rows) - [Deleting Rows](#deleting-rows) - [Transactions and Savepoints](#transactions-and-savepoints) + - [Querying the Schema](#querying-the-schema) - [Altering the Schema](#altering-the-schema) - [Renaming Tables](#renaming-tables) + - [Dropping Tables](#dropping-tables) - [Adding Columns](#adding-columns) - [Added Column Constraints](#added-column-constraints) + - [Schema Changer](#schemachanger) + - [Renaming Columns](#renaming-columns) + - [Dropping Columns](#dropping-columns) + - [Renaming/dropping tables](#renamingdropping-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) - - [Dropping Tables](#dropping-tables) - [Migrations and Schema Versioning](#migrations-and-schema-versioning) - [Custom Types](#custom-types) - [Date-Time Values](#date-time-values) @@ -1409,7 +1414,6 @@ for column in columns { 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` @@ -1420,6 +1424,24 @@ try db.run(users.rename(Table("users_old"))) // ALTER TABLE "users" RENAME TO "users_old" ``` +### Dropping Tables + +We can build +[`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) +by calling the `dropTable` function on a `SchemaType`. + +```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. + +```swift +try db.run(users.drop(ifExists: true)) +// DROP TABLE IF EXISTS "users" +``` ### Adding Columns @@ -1484,57 +1506,54 @@ tables](#creating-a-table). // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` -### Renaming Columns +### SchemaChanger -We can rename columns with the help of the `SchemaChanger` class: +Version 0.14.0 introduces `SchemaChanger`, an alternative API to perform more complex +migrations such as renaming columns. These operations work with all versions of +SQLite but use SQL statements such as `ALTER TABLE RENAME COLUMN` when available. + +#### Adding Columns ```swift +let newColumn = ColumnDefinition( + name: "new_text_column", + type: .TEXT, + nullable: true, + defaultValue: .stringLiteral("foo") +) + let schemaChanger = SchemaChanger(connection: db) + try schemaChanger.alter(table: "users") { table in - table.rename(column: "old_name", to: "new_name") + table.add(newColumn) } ``` -### Dropping Columns +#### Renaming Columns ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.drop(column: "email") + table.rename(column: "old_name", to: "new_name") } ``` -These operations will work with all versions of SQLite and use modern SQL -operations such as `DROP COLUMN` when available. - -### Adding Columns (SchemaChanger) - -The `SchemaChanger` provides an alternative API to add new columns: +#### Dropping Columns ```swift -let newColumn = ColumnDefinition( - name: "new_text_column", - type: .TEXT, - nullable: true, - defaultValue: .stringLiteral("foo") -) - let schemaChanger = SchemaChanger(connection: db) - try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.drop(column: "email") } ``` -### Renaming/dropping Tables (SchemaChanger) - -The `SchemaChanger` provides an alternative API to rename and drop tables: +#### Renaming/dropping Tables ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.rename(table: "users", to: "users_new") -try schemaChanger.drop(table: "emails") +try schemaChanger.drop(table: "emails", ifExists: false) ``` ### Indexes @@ -1592,25 +1611,6 @@ try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` -### Dropping Tables - -We can build -[`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) -by calling the `dropTable` function on a `SchemaType`. - -```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. - -```swift -try db.run(users.drop(ifExists: true)) -// DROP TABLE IF EXISTS "users" -``` - ### Migrations and Schema Versioning You can use the convenience property on `Connection` to query and set the diff --git a/Documentation/Release.md b/Documentation/Release.md index 87d715f6..8ec67970 100644 --- a/Documentation/Release.md +++ b/Documentation/Release.md @@ -3,6 +3,7 @@ * [ ] Make sure current master branch has a green build * [ ] Make sure `SQLite.playground` runs without errors * [ ] Make sure `CHANGELOG.md` is up-to-date +* [ ] Add content to `Documentation/Upgrading.md` if needed * [ ] Update the version number in `SQLite.swift.podspec` * [ ] Run `pod lib lint` locally * [ ] Update the version numbers mentioned in `README.md`, `Documentation/Index.md` diff --git a/Documentation/Upgrading.md b/Documentation/Upgrading.md new file mode 100644 index 00000000..8c398467 --- /dev/null +++ b/Documentation/Upgrading.md @@ -0,0 +1,11 @@ +# Upgrading + +## 0.13 → 0.14 + +- `Expression.asSQL()` is no longer available. Expressions now implement `CustomStringConvertible`, + where `description` returns the SQL. +- `Statement.prepareRowIterator()` is now longer available. Instead, use the methods + of the same name on `Connection`. +- `Blob` no longer wraps byte arrays and now uses `NSData`, which enables memory and + performance improvements. +- `Connection.registerTokenizer` is no longer available to register custom FTS4 tokenizers. diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 5a7ea379..0eafdd0d 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -52,6 +52,7 @@ for user in try Array(rowIterator) { /// also with `map()` let mapRowIterator = try db.prepareRowIterator(users) + let userIds = try mapRowIterator.map { $0[id] } /// using `failableNext()` on `RowIterator` diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 67f2b0d3..af710908 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -141,8 +141,8 @@ public class SchemaChanger: CustomStringConvertible { } } - public func drop(table: String) throws { - try dropTable(table) + public func drop(table: String, ifExists: Bool = true) throws { + try dropTable(table, ifExists: ifExists) } // Beginning with release 3.25.0 (2018-09-15), references to the table within trigger bodies and @@ -192,7 +192,7 @@ public class SchemaChanger: CustomStringConvertible { private func moveTable(from: String, to: String, options: Options = .default, operation: Operation? = nil) throws { try copyTable(from: from, to: to, options: options, operation: operation) - try dropTable(from) + try dropTable(from, ifExists: true) } private func copyTable(from: String, to: String, options: Options = .default, operation: Operation?) throws { @@ -225,8 +225,8 @@ public class SchemaChanger: CustomStringConvertible { } } - private func dropTable(_ table: String) throws { - try connection.run("DROP TABLE IF EXISTS \(table.quote())") + private func dropTable(_ table: String, ifExists: Bool) throws { + try connection.run("DROP TABLE \(ifExists ? "IF EXISTS" : "") \(table.quote())") } private func copyTableContents(from: TableDefinition, to: TableDefinition) throws { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 2894e5ce..40f65b62 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -135,6 +135,20 @@ class SchemaChangerTests: SQLiteTestCase { } } + func test_drop_table_if_exists_true() throws { + try schemaChanger.drop(table: "xxx", ifExists: true) + } + + func test_drop_table_if_exists_false() throws { + XCTAssertThrowsError(try schemaChanger.drop(table: "xxx", ifExists: false)) { error in + if case Result.error(let message, _, _) = error { + XCTAssertEqual(message, "no such table: xxx") + } else { + XCTFail("unexpected error \(error)") + } + } + } + func test_rename_table() throws { try schemaChanger.rename(table: "users", to: "users_new") let users_new = Table("users_new") From 25708c9804d906d039a293538e45877e9262d433 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 26 Oct 2022 23:48:00 +0200 Subject: [PATCH 105/216] Updates --- CHANGELOG.md | 2 +- Documentation/Index.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c89e9d..2783a67d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -0.14.0 (tbd), [diff][diff-0.14.0] +0.14.0 (27-10-2022), [diff][diff-0.14.0] ======================================== For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). diff --git a/Documentation/Index.md b/Documentation/Index.md index bdd1da28..20469037 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -50,7 +50,7 @@ - [Schema Changer](#schemachanger) - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - - [Renaming/dropping tables](#renamingdropping-tables) + - [Renaming/dropping Tables](#renamingdropping-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) @@ -173,7 +173,7 @@ 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: +`SQLCipher` subspec in your Podfile (SPM is not supported yet, see [#1084](/issues/1084)): ```ruby target 'YourAppTargetName' do @@ -2183,7 +2183,7 @@ try db.detach("external") // DETACH DATABASE 'external' ``` -When compiled for SQLCipher, you can additionally pass a `key` parameter to `attach`: +When compiled for SQLCipher, we can additionally pass a `key` parameter to `attach`: ```swift try db.attach(.uri("encrypted.sqlite"), as: "encrypted", key: "secret") From ac09389994bb6549a3f978d996f2d3a06b5a0071 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 00:04:22 +0200 Subject: [PATCH 106/216] Mention group container problems Closes #1042 --- Documentation/Index.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 20469037..363443d6 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -9,6 +9,7 @@ - [Connecting to a Database](#connecting-to-a-database) - [Read-Write Databases](#read-write-databases) - [Read-Only Databases](#read-only-databases) + - [In a Shared Group Container](#in-a-shared-group-container) - [In-Memory Databases](#in-memory-databases) - [URI parameters](#uri-parameters) - [Thread-Safety](#thread-safety) @@ -173,7 +174,7 @@ 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 (SPM is not supported yet, see [#1084](/issues/1084)): +`SQLCipher` subspec in your Podfile (SPM is not supported yet, see [#1084](https://github.com/stephencelis/SQLite.swift/issues/1084)): ```ruby target 'YourAppTargetName' do @@ -330,6 +331,13 @@ let db = try Connection(path, readonly: true) > We welcome changes to the above sample code to show how to successfully copy and use a bundled "seed" > database for writing in an app. +#### In a shared group container + +It is not recommend to store databases in a [shared group container], +some users have reported crashes ([#1042](https://github.com/stephencelis/SQLite.swift/issues/1042)). + +[shared group container]: https://developer.apple.com/documentation/foundation/filemanager/1412643-containerurl# + #### In-Memory Databases If you omit the path, SQLite.swift will provision an [in-memory From 194d99addcfbf76795d00c072470623b4ded5ad8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 00:51:19 +0200 Subject: [PATCH 107/216] Fix ambiguous use of description --- Sources/SQLite/Extensions/FTS4.swift | 50 ++++++++----------- Sources/SQLite/Extensions/FTS5.swift | 18 +++---- Sources/SQLite/Typed/Expression.swift | 10 ++-- Tests/SQLiteTests/Typed/ExpressionTests.swift | 5 ++ 4 files changed, 39 insertions(+), 44 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index e9e65746..8e91e453 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -117,7 +117,7 @@ public struct Tokenizer { // https://sqlite.org/fts5.html#the_experimental_trigram_tokenizer public static func Trigram(caseSensitive: Bool = false) -> Tokenizer { - return Tokenizer("trigram", ["case_sensitive", caseSensitive ? "1" : "0"]) + Tokenizer("trigram", ["case_sensitive", caseSensitive ? "1" : "0"]) } public static func Custom(_ name: String) -> Tokenizer { @@ -236,18 +236,12 @@ open class FTSConfig { } } - @discardableResult mutating func append(_ key: String, value: CustomStringConvertible?) -> Options { - append(key, value: value?.description) + @discardableResult mutating func append(_ key: String, value: String) -> Options { + append(key, value: Expression(value)) } - @discardableResult mutating func append(_ key: String, value: String?) -> Options { - append(key, value: value.map { Expression($0) }) - } - - @discardableResult mutating func append(_ key: String, value: Expressible?) -> Options { - if let value = value { - arguments.append("=".join([Expression(literal: key), value])) - } + @discardableResult mutating func append(_ key: String, value: Expressible) -> Options { + arguments.append("=".join([Expression(literal: key), value])) return self } } @@ -256,26 +250,16 @@ open class FTSConfig { /// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension. open class FTS4Config: FTSConfig { /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) - public enum MatchInfo: CustomStringConvertible { + public enum MatchInfo: String { case fts3 - public var description: String { - "fts3" - } } /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) - public enum Order: CustomStringConvertible { + public enum Order: String { /// Data structures are optimized for returning results in ascending order by docid (default) case asc /// FTS4 stores its data in such a way as to optimize returning results in descending order by docid. case desc - - public var description: String { - switch self { - case .asc: return "asc" - case .desc: return "desc" - } - } } var compressFunction: String? @@ -322,11 +306,21 @@ open class FTS4Config: FTSConfig { for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { options.append("notindexed", value: column) } - options.append("languageid", value: languageId) - options.append("compress", value: compressFunction) - options.append("uncompress", value: uncompressFunction) - options.append("matchinfo", value: matchInfo) - options.append("order", value: order) + if let languageId = languageId { + options.append("languageid", value: languageId) + } + if let compressFunction = compressFunction { + options.append("compress", value: compressFunction) + } + if let uncompressFunction = uncompressFunction { + options.append("uncompress", value: uncompressFunction) + } + if let matchInfo = matchInfo { + options.append("matchinfo", value: matchInfo.rawValue) + } + if let order = order { + options.append("order", value: order.rawValue) + } return options } } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index f108bbec..2e4f65fb 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -33,21 +33,13 @@ extension Module { /// **Note:** this is currently only applicable when using SQLite.swift together with a FTS5-enabled version /// of SQLite. open class FTS5Config: FTSConfig { - public enum Detail: CustomStringConvertible { + public enum Detail: String { /// store rowid, column number, term offset case full /// store rowid, column number case column /// store rowid case none - - public var description: String { - switch self { - case .full: return "full" - case .column: return "column" - case .none: return "none" - } - } } var detail: Detail? @@ -77,11 +69,15 @@ open class FTS5Config: FTSConfig { override func options() -> Options { var options = super.options() - options.append("content_rowid", value: contentRowId) + if let contentRowId = contentRowId { + options.append("content_rowid", value: contentRowId) + } if let columnSize = columnSize { options.append("columnsize", value: Expression(value: columnSize)) } - options.append("detail", value: detail) + if let detail = detail { + options.append("detail", value: detail.rawValue) + } return options } diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index af125cc2..dcc44fe4 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public protocol ExpressionType: Expressible { // extensions cannot have inheritance clauses +public protocol ExpressionType: Expressible, CustomStringConvertible { // extensions cannot have inheritance clauses associatedtype UnderlyingType = Void @@ -47,6 +47,9 @@ extension ExpressionType { self.init(expression.template, expression.bindings) } + public var description: String { + asSQL() + } } /// An `Expression` represents a raw SQL fragment and any associated bindings. @@ -64,16 +67,13 @@ public struct Expression: ExpressionType { } -public protocol Expressible: CustomStringConvertible { +public protocol Expressible { var expression: Expression { get } } extension Expressible { - public var description: String { - asSQL() - } // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE func asSQL() -> String { diff --git a/Tests/SQLiteTests/Typed/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift index 9155a45c..147d62e9 100644 --- a/Tests/SQLiteTests/Typed/ExpressionTests.swift +++ b/Tests/SQLiteTests/Typed/ExpressionTests.swift @@ -18,6 +18,11 @@ class ExpressionTests: XCTestCase { XCTAssertEqual(expression.asSQL(), expression.description) } + func test_builtin_unambiguously_custom_string_convertible() { + let integer: Int = 45 + XCTAssertEqual(integer.description, "45") + } + func test_init_literal() { let expression = Expression(literal: "literal") XCTAssertEqual(expression.template, "literal") From 367f6aaee8228f71eedcfcc34e1433e79c46c632 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 10:17:46 +0200 Subject: [PATCH 108/216] add(_ column:) => add(column:) --- Documentation/Index.md | 2 +- SQLite.playground/Contents.swift | 2 +- Sources/SQLite/Schema/SchemaChanger.swift | 2 +- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 363443d6..c518c739 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1533,7 +1533,7 @@ let newColumn = ColumnDefinition( let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.add(column: newColumn) } ``` diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 0eafdd0d..f651cbc1 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -115,7 +115,7 @@ print(columns) let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.add(ColumnDefinition(name: "age", type: .INTEGER)) + table.add(column: ColumnDefinition(name: "age", type: .INTEGER)) table.rename(column: "email", to: "electronic_mail") table.drop(column: "name") } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index af710908..af7b5e27 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -95,7 +95,7 @@ public class SchemaChanger: CustomStringConvertible { self.name = name } - public func add(_ column: ColumnDefinition) { + public func add(column: ColumnDefinition) { operations.append(.addColumn(column)) } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 40f65b62..9f8e21f4 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -99,7 +99,7 @@ class SchemaChangerTests: SQLiteTestCase { defaultValue: .stringLiteral("foo")) try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.add(column: newColumn) } let columns = try schema.columnDefinitions(table: "users") @@ -114,7 +114,7 @@ class SchemaChangerTests: SQLiteTestCase { type: .TEXT) XCTAssertThrowsError(try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.add(column: newColumn) }) { error in if case SchemaChanger.Error.invalidColumnDefinition(_) = error { XCTAssertEqual("Invalid column definition: can not add primary key column", error.localizedDescription) From 2c4af8526e112e3a23df141e988d08a28e475ecd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 10:21:13 +0200 Subject: [PATCH 109/216] Update docs --- Documentation/Index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index c518c739..ada9b7c3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -49,9 +49,10 @@ - [Adding Columns](#adding-columns) - [Added Column Constraints](#added-column-constraints) - [Schema Changer](#schemachanger) + - [Adding Columns](#adding-columns) - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - - [Renaming/dropping Tables](#renamingdropping-tables) + - [Renaming/Dropping Tables](#renamingdropping-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) @@ -1555,7 +1556,7 @@ try schemaChanger.alter(table: "users") { table in } ``` -#### Renaming/dropping Tables +#### Renaming/Dropping Tables ```swift let schemaChanger = SchemaChanger(connection: db) From 3161f06acd378ff3e5e91c0c590c9dc2edba1ce5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 12:06:14 +0200 Subject: [PATCH 110/216] Use direct links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d9396c6..ac0c2a7a 100644 --- a/README.md +++ b/README.md @@ -277,10 +277,10 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitHubActionBadge]: https://img.shields.io/github/workflow/status/stephencelis/SQLite.swift/Build%20and%20test -[CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png +[CocoaPodsVersionBadge]: https://img.shields.io/cocoapods/v/SQLite.swift.svg?style=flat [CocoaPodsVersionLink]: https://cocoapods.org/pods/SQLite.swift -[PlatformBadge]: https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png +[PlatformBadge]: https://img.shields.io/cocoapods/p/SQLite.swift.svg?style=flat [PlatformLink]: https://cocoapods.org/pods/SQLite.swift [CartagheBadge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat From 1235d44cd3e8596e842a47c03efbcbdb61797024 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 1 Nov 2022 22:28:21 +0100 Subject: [PATCH 111/216] Revert changes to Blob --- CHANGELOG.md | 6 +++ Documentation/Index.md | 12 ++--- README.md | 4 +- SQLite.swift.podspec | 2 +- Sources/SQLite/Core/Blob.swift | 50 ++++--------------- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 4 +- Sources/SQLite/Extensions/Cipher.swift | 6 +-- Sources/SQLite/Foundation.swift | 6 ++- Tests/SPM/Package.swift | 2 +- Tests/SQLiteTests/Core/BlobTests.swift | 8 +-- Tests/SQLiteTests/Core/StatementTests.swift | 4 +- .../SQLiteTests/Extensions/CipherTests.swift | 11 ++-- Tests/SQLiteTests/FoundationTests.swift | 2 +- 14 files changed, 47 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2783a67d..fc759118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.14.1 (01-11-2022), [diff][diff-0.14.1] +======================================== + +* Reverted `Blob` changes (See [#1167][] for rationale). + 0.14.0 (27-10-2022), [diff][diff-0.14.0] ======================================== For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). @@ -129,6 +134,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.13.2]: https://github.com/stephencelis/SQLite.swift/compare/0.13.1...0.13.2 [diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 [diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 +[diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 diff --git a/Documentation/Index.md b/Documentation/Index.md index ada9b7c3..730fe30b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -90,7 +90,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ] ``` @@ -111,7 +111,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.0 + github "stephencelis/SQLite.swift" ~> 0.14.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -141,7 +141,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.14.0' + pod 'SQLite.swift', '~> 0.14.1' end ``` @@ -155,7 +155,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.0' + pod 'SQLite.swift/standalone', '~> 0.14.1' end ``` @@ -165,7 +165,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.0' + pod 'SQLite.swift/standalone', '~> 0.14.1' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -181,7 +181,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.14.0' + pod 'SQLite.swift/SQLCipher', '~> 0.14.1' end ``` diff --git a/README.md b/README.md index ac0c2a7a..4cf72d03 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ] ``` @@ -155,7 +155,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.0 + github "stephencelis/SQLite.swift" ~> 0.14.1 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index c30aa765..f32282d3 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.14.0" + s.version = "0.14.1" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index c8bd2c15..a709fb42 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -21,64 +21,36 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -import Foundation -public final class Blob { +public struct Blob { - public let data: NSData + public let bytes: [UInt8] - public var bytes: UnsafePointer { - data.bytes.assumingMemoryBound(to: UInt8.self) + public init(bytes: [UInt8]) { + self.bytes = bytes } - public var length: Int { - data.count - } - - public convenience init(bytes: [UInt8]) { - guard bytes.count > 0 else { - self.init(data: NSData()) - return - } - self.init(data: NSData(bytes: bytes, length: bytes.count)) - } - - public convenience init(bytes: UnsafeRawPointer, length: Int) { - self.init(data: NSData(bytes: bytes, length: length)) - } - - public init(data: NSData) { - precondition(!(data is NSMutableData), "Blob cannot be initialized with mutable data") - self.data = data + public init(bytes: UnsafeRawPointer, length: Int) { + let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length) + self.init(bytes: [UInt8](i8bufptr)) } public func toHex() -> String { - guard length > 0 else { return "" } - - var hex = "" - for idx in 0.. Bool { - lhs.data == rhs.data + lhs.bytes == rhs.bytes } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 2a7ec487..0e51e651 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -741,7 +741,7 @@ extension Context { func set(result: Binding?) { switch result { case let blob as Blob: - sqlite3_result_blob(self, blob.bytes, Int32(blob.length), nil) + sqlite3_result_blob(self, blob.bytes, Int32(blob.bytes.count), nil) case let double as Double: sqlite3_result_double(self, double) case let int as Int64: diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 80450258..6cb2e5d3 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -103,10 +103,10 @@ public final class Statement { switch value { case .none: sqlite3_bind_null(handle, Int32(idx)) - case let value as Blob where value.length == 0: + case let value as Blob where value.bytes.count == 0: sqlite3_bind_zeroblob(handle, Int32(idx), 0) case let value as Blob: - sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT) + sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.bytes.count), SQLITE_TRANSIENT) case let value as Double: sqlite3_bind_double(handle, Int32(idx), value) case let value as Int64: diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 71889ae5..8af04df9 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -32,7 +32,7 @@ extension Connection { } public func key(_ key: Blob, db: String = "main") throws { - try _key_v2(db: db, keyPointer: key.bytes, keySize: key.length) + try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } /// Same as `key(_ key: String, db: String = "main")`, running "PRAGMA cipher_migrate;" @@ -53,7 +53,7 @@ extension Connection { /// Same as `[`keyAndMigrate(_ key: String, db: String = "main")` accepting byte array as key public func keyAndMigrate(_ key: Blob, db: String = "main") throws { - try _key_v2(db: db, keyPointer: key.bytes, keySize: key.length, migrate: true) + try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count, migrate: true) } /// Change the key on an open database. NB: only works if the database is already encrypted. @@ -68,7 +68,7 @@ extension Connection { } public func rekey(_ key: Blob, db: String = "main") throws { - try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.length) + try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } /// Converts a non-encrypted database to an encrypted one. diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index e81c4964..44a31736 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -31,11 +31,13 @@ extension Data: Value { } public static func fromDatatypeValue(_ dataValue: Blob) -> Data { - dataValue.data as Data + Data(dataValue.bytes) } public var datatypeValue: Blob { - Blob(data: self as NSData) + withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in + Blob(bytes: pointer.baseAddress!, length: count) + } } } diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index d5d2e9ff..3e329552 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ], targets: [ .target( diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index bef4bb83..f2e9435e 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -25,14 +25,14 @@ class BlobTests: XCTestCase { func test_init_array() { let blob = Blob(bytes: [42, 43, 44]) - XCTAssertEqual([UInt8](blob.data), [42, 43, 44]) + XCTAssertEqual(blob.bytes, [42, 43, 44]) } func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) - XCTAssertEqual([UInt8](blob.data), [42, 42, 42]) + XCTAssertEqual(blob.bytes, [42, 42, 42]) } func test_equality() { @@ -44,8 +44,4 @@ class BlobTests: XCTestCase { XCTAssertEqual(blob1, blob2) XCTAssertNotEqual(blob1, blob3) } - - func XXX_test_init_with_mutable_data_fails() { - _ = Blob(data: NSMutableData()) - } } diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 0d82e890..5f212505 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -22,7 +22,7 @@ class StatementTests: SQLiteTestCase { let statement = try db.prepare("SELECT email FROM users") XCTAssert(try statement.step()) let blob = statement.row[0] as Blob - XCTAssertEqual("alice@example.com", String(data: blob.data as Data, encoding: .utf8)!) + XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) } func test_zero_sized_blob_returns_null() throws { @@ -31,7 +31,7 @@ class StatementTests: SQLiteTestCase { try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) - XCTAssertEqual([], [UInt8](blobValue.data)) + XCTAssertEqual([], blobValue.bytes) } func test_prepareRowIterator() throws { diff --git a/Tests/SQLiteTests/Extensions/CipherTests.swift b/Tests/SQLiteTests/Extensions/CipherTests.swift index bd7f2042..bc89cfa2 100644 --- a/Tests/SQLiteTests/Extensions/CipherTests.swift +++ b/Tests/SQLiteTests/Extensions/CipherTests.swift @@ -19,7 +19,7 @@ class CipherTests: XCTestCase { // db2 let key2 = keyData() - try db2.key(Blob(data: key2)) + try db2.key(Blob(bytes: key2.bytes, length: key2.length)) try db2.run("CREATE TABLE foo (bar TEXT)") try db2.run("INSERT INTO foo (bar) VALUES ('world')") @@ -47,7 +47,7 @@ class CipherTests: XCTestCase { func test_data_rekey() throws { let newKey = keyData() - try db2.rekey(Blob(data: newKey)) + try db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } @@ -79,12 +79,11 @@ class CipherTests: XCTestCase { // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); guard let cipherVersion: String = db1.cipherVersion, - cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") - else { return } + cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") else { return } let encryptedFile = cipherVersion.starts(with: "3.") ? - fixture("encrypted-3.x", withExtension: "sqlite") : - fixture("encrypted-4.x", withExtension: "sqlite") + fixture("encrypted-3.x", withExtension: "sqlite") : + fixture("encrypted-4.x", withExtension: "sqlite") try FileManager.default.setAttributes([FileAttributeKey.immutable: 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index ffd3fae1..453febcd 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -5,7 +5,7 @@ class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue - XCTAssertEqual([1, 2, 3], [UInt8](blob.data)) + XCTAssertEqual([1, 2, 3], blob.bytes) } func testBlobToData() { From f06b8df5bc0fb73d6ac46adaf5f047fded953f79 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 2 Nov 2022 09:04:08 +0100 Subject: [PATCH 112/216] Removed --- Documentation/Upgrading.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/Upgrading.md b/Documentation/Upgrading.md index 8c398467..f2cc2ecb 100644 --- a/Documentation/Upgrading.md +++ b/Documentation/Upgrading.md @@ -6,6 +6,4 @@ where `description` returns the SQL. - `Statement.prepareRowIterator()` is now longer available. Instead, use the methods of the same name on `Connection`. -- `Blob` no longer wraps byte arrays and now uses `NSData`, which enables memory and - performance improvements. - `Connection.registerTokenizer` is no longer available to register custom FTS4 tokenizers. From d926517c090042ee3268048f1d46020f94b39d0c Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Tue, 20 Dec 2022 12:18:53 -0800 Subject: [PATCH 113/216] adds support for extended error codes --- Sources/SQLite/Core/Connection.swift | 7 ++++++ Sources/SQLite/Core/Result.swift | 24 ++++++++++++++++++- Tests/SQLiteTests/Core/ConnectionTests.swift | 4 ++++ Tests/SQLiteTests/Core/ResultTests.swift | 13 ++++++++++ .../Typed/QueryIntegrationTests.swift | 13 ++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 0e51e651..f74ed82c 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -156,6 +156,13 @@ public final class Connection { Int(sqlite3_total_changes(handle)) } + /// Whether or not the database will return extended error codes when errors are handled. + public var usesExtendedErrorCodes: Bool = false { + didSet { + sqlite3_extended_result_codes(handle, usesExtendedErrorCodes ? 1 : 0) + } + } + // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 3de4d25d..06f9cc74 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -21,11 +21,27 @@ public enum Result: Error { /// - statement: the statement which produced the error case error(message: String, code: Int32, statement: Statement?) + /// Represents a SQLite specific [extended error code] (https://sqlite.org/rescode.html#primary_result_codes_versus_extended_result_codes) + /// + /// - message: English-language text that describes the error + /// + /// - extendedCode: SQLite [extended error code](https://sqlite.org/rescode.html#extended_result_code_list) + /// + /// - statement: the statement which produced the error + case extendedError(message: String, extendedCode: Int32, statement: Statement?) + init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String(cString: sqlite3_errmsg(connection.handle)) - self = .error(message: message, code: errorCode, statement: statement) + + guard connection.usesExtendedErrorCodes else { + self = .error(message: message, code: errorCode, statement: statement) + return + } + + let extendedErrorCode = sqlite3_extended_errcode(connection.handle) + self = .extendedError(message: message, extendedCode: extendedErrorCode, statement: statement) } } @@ -40,6 +56,12 @@ extension Result: CustomStringConvertible { } else { return "\(message) (code: \(errorCode))" } + case let .extendedError(message, extendedCode, statement): + if let statement = statement { + return "\(message) (\(statement)) (extended code: \(extendedCode))" + } else { + return "\(message) (extended code: \(extendedCode))" + } } } } diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 9d623f3f..6a1d94ae 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -111,6 +111,10 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.totalChanges) } + func test_useExtendedErrorCodes_returnsFalseDefault() throws { + XCTAssertFalse(db.usesExtendedErrorCodes) + } + func test_prepare_preparesAndReturnsStatements() throws { _ = try db.prepare("SELECT * FROM users WHERE admin = 0") _ = try db.prepare("SELECT * FROM users WHERE admin = ?", 0) diff --git a/Tests/SQLiteTests/Core/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index fab4a0bb..d3c8bb1f 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -53,4 +53,17 @@ class ResultTests: XCTestCase { XCTAssertEqual("not an error (SELECT 1) (code: 21)", Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) } + + func test_init_extended_with_other_code_returns_error() { + connection.usesExtendedErrorCodes = true + if case .some(.extendedError(let message, let extendedCode, let statement)) = + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { + XCTAssertEqual("not an error", message) + XCTAssertEqual(extendedCode, 0) + XCTAssertNil(statement) + XCTAssert(connection === connection) + } else { + XCTFail("no error") + } + } } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 3fd388e9..a86708ec 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -229,6 +229,19 @@ class QueryIntegrationTests: SQLiteTestCase { } } + func test_extendedErrorCodes_catchConstraintError() throws { + db.usesExtendedErrorCodes = true + 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.extendedError(_, extendedCode, _) where extendedCode == 2_067 { + // SQLITE_CONSTRAINT_UNIQUE expected + } catch let error { + XCTFail("unexpected error: \(error)") + } + } + // https://github.com/stephencelis/SQLite.swift/issues/285 func test_order_by_random() throws { try insertUsers(["a", "b", "c'"]) From 0fc4b7b223e5402fd497c5929e220bbec02608d5 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Tue, 20 Dec 2022 12:21:24 -0800 Subject: [PATCH 114/216] lint --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Result.swift | 2 +- Tests/SQLiteTests/Typed/QueryIntegrationTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f74ed82c..26cadea4 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -162,7 +162,7 @@ public final class Connection { sqlite3_extended_result_codes(handle, usesExtendedErrorCodes ? 1 : 0) } } - + // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 06f9cc74..9fe47ada 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -56,7 +56,7 @@ extension Result: CustomStringConvertible { } else { return "\(message) (code: \(errorCode))" } - case let .extendedError(message, extendedCode, statement): + case let .extendedError(message, extendedCode, statement): if let statement = statement { return "\(message) (\(statement)) (extended code: \(extendedCode))" } else { diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index a86708ec..3b973fa0 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -235,7 +235,7 @@ class QueryIntegrationTests: SQLiteTestCase { do { try db.run(users.insert(email <- "alice@example.com")) XCTFail("expected error") - } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { + } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { // SQLITE_CONSTRAINT_UNIQUE expected } catch let error { XCTFail("unexpected error: \(error)") From 4b9ea97872a241fad38c221afad7a701fc987868 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 23 Dec 2022 00:37:03 -0800 Subject: [PATCH 115/216] Fix incorrect column names for SELECT * preceded by a WITH In #1139 I introduced support for the `WITH` clause. My implementation contains a bug: the statement preparer doesn't produce the correct result column names for queries containing a `SELECT *` preceded by a `WITH`. For example, consider the following statement: ``` WITH temp AS ( SELECT id, email from users) SELECT * from temp ``` An error would be thrown when preparing this statement because the glob expansion procedure would try to look up the column names for the result by looking up the column names for the query `SELECT * from temp`. This does not work because `temp` is a temporary view defined in the `WITH` clause. To fix this, I modified the glob expansion procedure to include the `WITH` clause in the query used to look up the result column names. --- Sources/SQLite/Typed/Query.swift | 19 +++++++++++ .../Typed/QueryIntegrationTests.swift | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 04665f39..feff2f69 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1036,10 +1036,29 @@ extension Connection { let column = names.removeLast() let namespace = names.joined(separator: ".") + // Return a copy of the input "with" clause stripping all subclauses besides "select", "join", and "with". + func strip(_ with: WithClauses) -> WithClauses { + var stripped = WithClauses() + stripped.recursive = with.recursive + for subclause in with.clauses { + let query = subclause.query + var strippedQuery = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) + strippedQuery.clauses.select = query.clauses.select + strippedQuery.clauses.join = query.clauses.join + strippedQuery.clauses.with = strip(query.clauses.with) + + var strippedSubclause = WithClauses.Clause(alias: subclause.alias, query: strippedQuery) + strippedSubclause.columns = subclause.columns + stripped.clauses.append(strippedSubclause) + } + return stripped + } + func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { { (queryType: QueryType) throws -> Void in var query = type(of: queryType).init(queryType.clauses.from.name, database: queryType.clauses.from.database) query.clauses.select = queryType.clauses.select + query.clauses.with = strip(queryType.clauses.with) let expression = query.expression var names = try self.prepare(expression.template, expression.bindings).columnNames.map { $0.quote() } if namespace { names = names.map { "\(queryType.tableName().expression.template).\($0)" } } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 3fd388e9..d99b8457 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -276,6 +276,39 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(21, sum) } + + /// Verify that `*` is properly expanded in a SELECT statement following a WITH clause. + func test_with_glob_expansion() throws { + let names = Table("names") + let name = Expression("name") + try db.run(names.create { builder in + builder.column(email) + builder.column(name) + }) + + try db.run(users.insert(email <- "alice@example.com")) + try db.run(names.insert(email <- "alice@example.com", name <- "Alice")) + + // WITH intermediate AS ( SELECT ... ) SELECT * FROM intermediate + let intermediate = Table("intermediate") + let rows = try db.prepare( + intermediate + .with(intermediate, + as: users + .select([id, users[email], name]) + .join(names, on: names[email] == users[email]) + .where(users[email] == "alice@example.com") + )) + + // There should be at least one row in the result. + let row = try XCTUnwrap(rows.makeIterator().next()) + + // Verify the column names + XCTAssertEqual(row.columnNames.count, 3) + XCTAssertNotNil(row[id]) + XCTAssertNotNil(row[name]) + XCTAssertNotNil(row[email]) + } } extension Connection { From bdc3be7fdf9b6289ee0e0ea5c28002839d8cf54d Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 23 Dec 2022 00:45:34 -0800 Subject: [PATCH 116/216] linter error --- Tests/SQLiteTests/Typed/QueryIntegrationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index d99b8457..f5105a09 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -302,7 +302,7 @@ class QueryIntegrationTests: SQLiteTestCase { // There should be at least one row in the result. let row = try XCTUnwrap(rows.makeIterator().next()) - + // Verify the column names XCTAssertEqual(row.columnNames.count, 3) XCTAssertNotNil(row[id]) From b7c6fd6f94eedf6995ff607ae1625f71041377aa Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Sun, 8 Jan 2023 14:07:12 +0200 Subject: [PATCH 117/216] Fix typos --- Documentation/Index.md | 2 +- SQLite.playground/Contents.swift | 2 +- Sources/SQLite/Extensions/Cipher.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 730fe30b..583d5def 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -691,7 +691,7 @@ do { } ``` -Multiple rows can be inserted at once by similarily calling `insertMany` with an array of +Multiple rows can be inserted at once by similarly calling `insertMany` with an array of per-row [setters](#setters). ```swift diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index f651cbc1..d893e696 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -65,7 +65,7 @@ do { // Handle error } -/// define a virtual tabe for the FTS index +/// define a virtual table for the FTS index let emails = VirtualTable("emails") let subject = Expression("subject") diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 8af04df9..03194ef1 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -93,7 +93,7 @@ extension Connection { // per recommendation of SQLCipher authors let migrateResult = try scalar("PRAGMA cipher_migrate;") if (migrateResult as? String) != "0" { - // "0" is the result of successfull migration + // "0" is the result of successful migration throw Result.error(message: "Error in cipher migration, result \(migrateResult.debugDescription)", code: 1, statement: nil) } } From 0d905d788e386854effc7133f1faf31fe9a3c9e2 Mon Sep 17 00:00:00 2001 From: "Pongsakorn Onsri(Ken)" Date: Fri, 17 Mar 2023 17:21:31 +0700 Subject: [PATCH 118/216] fix Xcode build error --- Sources/SQLite/Core/Connection+Pragmas.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection+Pragmas.swift b/Sources/SQLite/Core/Connection+Pragmas.swift index 8f6d854b..2c4f0efb 100644 --- a/Sources/SQLite/Core/Connection+Pragmas.swift +++ b/Sources/SQLite/Core/Connection+Pragmas.swift @@ -7,7 +7,7 @@ public extension Connection { /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) var userVersion: UserVersion? { get { - (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) + (try? scalar("PRAGMA user_version") as? Int64)?.map(Int32.init) } set { _ = try? run("PRAGMA user_version = \(newValue ?? 0)") From 5f96ca46c1843f26c040b467d9f87519f03a0888 Mon Sep 17 00:00:00 2001 From: Stefan Saasen Date: Fri, 7 Apr 2023 15:41:11 +0200 Subject: [PATCH 119/216] Make ithe IndexDefinition properties public The [documentation](https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#querying-the-schema) suggests that the index schema information can be queried. That requires the `IndexDefinition` properties to be public. --- Sources/SQLite/Schema/SchemaDefinitions.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 284fc4c3..b314e5c0 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -266,12 +266,12 @@ public struct IndexDefinition: Equatable { orders: indexSQL.flatMap(orders)) } - let table: String - let name: String - let unique: Bool - let columns: [String] - let `where`: String? - let orders: [String: Order]? + public let table: String + public let name: String + public let unique: Bool + public let columns: [String] + public let `where`: String? + public let orders: [String: Order]? enum IndexError: LocalizedError { case tooLong(String, String) From 0718088b7885dfb08f8930111e13aa12fa9a55c6 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 21 Apr 2023 16:10:41 +0200 Subject: [PATCH 120/216] Fix GitHub Actions build badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cf72d03..90333b16 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [SQLite3]: https://www.sqlite.org [SQLite.swift]: https://github.com/stephencelis/SQLite.swift -[GitHubActionBadge]: https://img.shields.io/github/workflow/status/stephencelis/SQLite.swift/Build%20and%20test +[GitHubActionBadge]: https://img.shields.io/github/actions/workflow/status/stephencelis/SQLite.swift/build.yml?branch=master [CocoaPodsVersionBadge]: https://img.shields.io/cocoapods/v/SQLite.swift.svg?style=flat [CocoaPodsVersionLink]: https://cocoapods.org/pods/SQLite.swift From 481b531c6906e11d8efcc7c36ea89b7e572e6ce7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 22:14:04 +0200 Subject: [PATCH 121/216] Run on macOS 12 Bump deployment targets Disable watchOS validation CocoaPods/CocoaPods#11558 Adjust deployment targets for Xcode 14 --- .github/workflows/build.yml | 4 ++-- Package.swift | 8 ++++---- SQLite.swift.podspec | 8 ++++---- SQLite.xcodeproj/project.pbxproj | 30 ++++++++++++++++++++++-------- Tests/SPM/Package.swift | 8 ++++---- run-tests.sh | 4 ++-- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1659d9d..beec5ca2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,10 +2,10 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: "15.2" + IOS_VERSION: "16.0" jobs: build: - runs-on: macos-11 + runs-on: macos-12 steps: - uses: actions/checkout@v2 - name: Install diff --git a/Package.swift b/Package.swift index 9664e99a..de40367a 100644 --- a/Package.swift +++ b/Package.swift @@ -4,10 +4,10 @@ import PackageDescription let package = Package( name: "SQLite.swift", platforms: [ - .iOS(.v9), - .macOS(.v10_10), - .watchOS(.v3), - .tvOS(.v9) + .iOS(.v11), + .macOS(.v10_13), + .watchOS(.v4), + .tvOS(.v11) ], products: [ .library( diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index f32282d3..4edddd67 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,10 +18,10 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '9.0' - tvos_deployment_target = '9.1' - osx_deployment_target = '10.10' - watchos_deployment_target = '3.0' + ios_deployment_target = '11.0' + tvos_deployment_target = '11.0' + osx_deployment_target = '10.13' + watchos_deployment_target = '4.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3d62e980..def32855 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1263,6 +1263,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Debug; }; @@ -1284,6 +1285,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Release; }; @@ -1297,6 +1299,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Debug; }; @@ -1310,6 +1313,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Release; }; @@ -1333,6 +1337,7 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Debug; }; @@ -1356,6 +1361,7 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Release; }; @@ -1408,8 +1414,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1417,10 +1423,10 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 3.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Debug; }; @@ -1467,19 +1473,19 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 11.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 3.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Release; }; @@ -1496,6 +1502,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1519,6 +1526,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1533,6 +1541,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1545,6 +1554,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1567,6 +1577,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1591,6 +1602,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1608,6 +1620,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1623,6 +1636,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 3e329552..51f176f4 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -6,10 +6,10 @@ import PackageDescription let package = Package( name: "test", platforms: [ - .iOS(.v9), - .macOS(.v10_10), - .watchOS(.v3), - .tvOS(.v9) + .iOS(.v11), + .macOS(.v10_13), + .watchOS(.v4), + .tvOS(.v11) ], dependencies: [ // for testing from same repository diff --git a/run-tests.sh b/run-tests.sh index 49330c12..c54300ee 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,9 +8,9 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - pod lib lint --no-subspecs --fail-fast + pod lib lint --no-subspecs --fail-fast --platforms=ios,osx,tvos else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=ios,osx,tvos fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From a2897d5512f897e566247808bf7d87b2f033b5e7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 12:45:26 +0200 Subject: [PATCH 122/216] Run on macos-13 --- .github/workflows/build.yml | 4 ++-- .gitignore | 1 + Package.swift | 2 +- Tests/SPM/Package.swift | 7 ++----- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index beec5ca2..ae806ecc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,10 +2,10 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: "16.0" + IOS_VERSION: "16.4" jobs: build: - runs-on: macos-12 + runs-on: macos-13 steps: - uses: actions/checkout@v2 - name: Install diff --git a/.gitignore b/.gitignore index 5882e0cb..b7d43ec9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ DerivedData # Swift Package Manager .build Packages/ +.swiftpm/ diff --git a/Package.swift b/Package.swift index de40367a..70bde7ef 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.7 import PackageDescription let package = Package( diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 51f176f4..3c446ae6 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -18,9 +18,6 @@ let package = Package( // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ], targets: [ - .target( - name: "test", - dependencies: [.product(name: "SQLite", package: "SQLite.swift")] - ) + .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) ] ) From 85f1d443e9b758aaeea3fc5c9b3abef67675931b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:09:30 +0200 Subject: [PATCH 123/216] Automatically download swiftlint / xcbeautifier, lint fixes --- .gitignore | 3 + .swiftlint.yml | 5 +- Makefile | 81 +++++++++++++------ Sources/SQLite/Core/Backup.swift | 2 +- Sources/SQLite/Helpers.swift | 1 + Sources/SQLite/Schema/SchemaReader.swift | 2 +- Sources/SQLite/Typed/Query.swift | 2 +- Tests/.swiftlint.yml | 4 +- Tests/SQLiteTests/Schema/SchemaTests.swift | 2 + .../Typed/CustomFunctionsTests.swift | 16 ++-- 10 files changed, 80 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index b7d43ec9..e7b2ad4d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ DerivedData # Carthage /Carthage/ +# Makefile +bin/ + # Swift Package Manager .build Packages/ diff --git a/.swiftlint.yml b/.swiftlint.yml index d40be28e..e728e191 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -4,6 +4,7 @@ disabled_rules: # rule identifiers to exclude from running - large_tuple - closure_parameter_position - inclusive_language # sqlite_master etc. + - blanket_disable_command included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`. - Sources - Tests @@ -26,8 +27,8 @@ identifier_name: - SQLITE_TRANSIENT type_body_length: - warning: 260 - error: 260 + warning: 350 + error: 350 function_body_length: warning: 60 diff --git a/Makefile b/Makefile index 74bf5d18..73b33f97 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,76 @@ -BUILD_TOOL = xcodebuild +XCODEBUILD = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone 12 -IOS_VERSION = 15.0 +IOS_SIMULATOR = iPhone 14 +IOS_VERSION = 16.4 + +# tool settings +SWIFTLINT_VERSION=0.52.2 +SWIFTLINT=bin/swiftlint-$(SWIFTLINT_VERSION) +SWIFTLINT_URL=https://github.com/realm/SwiftLint/releases/download/$(SWIFTLINT_VERSION)/portable_swiftlint.zip +XCBEAUTIFY_VERSION=0.20.0 +XCBEAUTIFY=bin/xcbeautify-$(XCBEAUTIFY_VERSION) +ifeq ($(shell uname), Linux) + XCBEAUTIFY_PLATFORM=x86_64-unknown-linux-gnu.tar.xz +else + XCBEAUTIFY_PLATFORM=universal-apple-macosx.zip +endif +XCBEAUTIFY_URL=https://github.com/tuist/xcbeautify/releases/download/$(XCBEAUTIFY_VERSION)/xcbeautify-$(XCBEAUTIFY_VERSION)-$(XCBEAUTIFY_PLATFORM) +CURL_OPTS=--fail --silent -L --retry 3 + ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" endif -XCPRETTY := $(shell command -v xcpretty) -TEST_ACTIONS := clean build build-for-testing test-without-building +test: $(XCBEAUTIFY) + set -o pipefail; \ + $(XCODEBUILD) $(BUILD_ARGUMENTS) test | $(XCBEAUTIFY) -default: test +build: $(XCBEAUTIFY) + set -o pipefail; \ + $(XCODEBUILD) $(BUILD_ARGUMENTS) | $(XCBEAUTIFY) -build: - $(BUILD_TOOL) $(BUILD_ARGUMENTS) +lint: $(SWIFTLINT) + $< --strict -lint: - swiftlint --strict -lint-fix: - swiftlint lint fix - -test: -ifdef XCPRETTY - @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) | $(XCPRETTY) -c -else - $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) -endif +lint-fix: $(SWIFTLINT) + $< lint fix clean: - $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean + $(XCODEBUILD) $(BUILD_ARGUMENTS) clean repl: - @$(BUILD_TOOL) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \ - swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' + @$(XCODEBUILD) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \ + swift repl -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' sloc: - @zsh -c "grep -vE '^ *//|^$$' Sources/**/*.{swift,h,m} | wc -l" + @zsh -c "grep -vE '^ *//|^$$' Sources/**/*.{swift,h} | wc -l" + +$(SWIFTLINT): + set -e ; \ + curl $(CURL_OPTS) $(SWIFTLINT_URL) -o swiftlint.zip; \ + unzip -o swiftlint.zip swiftlint; \ + mkdir -p bin; \ + mv swiftlint $@ && rm -f swiftlint.zip + +$(XCBEAUTIFY): + set -e; \ + FILE=$(XCBEAUTIFY_PLATFORM); \ + curl $(CURL_OPTS) $(XCBEAUTIFY_URL) -o $$FILE; \ + case "$${FILE#*.}" in \ + "zip") \ + unzip -o $$FILE xcbeautify; \ + ;; \ + "tar.xz") \ + tar -xvf $$FILE xcbeautify; \ + ;; \ + *) \ + echo "unknown extension $${FILE#*.}!"; \ + exit 1; \ + ;; \ + esac; \ + mkdir -p bin; \ + mv xcbeautify $@ && rm -f $$FILE; .PHONY: test clean repl sloc diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 84ebf1e0..0eebbdd5 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -140,7 +140,7 @@ public final class Backup { /// - Parameter pagesToCopy: The maximal number of pages to copy in one step /// /// - Throws: `Result.Error` if step fails. - // + /// /// See: public func step(pagesToCopy pages: Pages = .all) throws { let status = sqlite3_backup_step(handle, pages.number) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index e3d37e11..79d057d6 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -38,6 +38,7 @@ public func *(_: Expression?, _: Expression?) -> Expression [ColumnDefinition] { + public func columnDefinitions(table: String) throws -> [ColumnDefinition] { func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { try createTableSQL(name: table).flatMap { .init(sql: $0) } } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index feff2f69..c59b728f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1105,7 +1105,7 @@ extension Connection { return value(try scalar(expression.template, expression.bindings)) } - public func scalar(_ query: Select) throws -> V.ValueType? { + public func scalar(_ query: Select) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) diff --git a/Tests/.swiftlint.yml b/Tests/.swiftlint.yml index 3537085c..81d6c314 100644 --- a/Tests/.swiftlint.yml +++ b/Tests/.swiftlint.yml @@ -6,8 +6,8 @@ disabled_rules: - identifier_name type_body_length: - warning: 800 - error: 800 + warning: 1000 + error: 1000 function_body_length: warning: 200 diff --git a/Tests/SQLiteTests/Schema/SchemaTests.swift b/Tests/SQLiteTests/Schema/SchemaTests.swift index a7de8bcf..4f4a49d1 100644 --- a/Tests/SQLiteTests/Schema/SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaTests.swift @@ -72,6 +72,7 @@ class SchemaTests: XCTestCase { } // thoroughness test for ambiguity + // swiftlint:disable:next function_body_length func test_column_compilesColumnDefinitionExpression() { XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL)", @@ -390,6 +391,7 @@ class SchemaTests: XCTestCase { ) } + // swiftlint:disable:next function_body_length func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() { XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL COLLATE RTRIM)", diff --git a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift index c4eb51e6..0f46b380 100644 --- a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift @@ -5,8 +5,8 @@ import SQLite #if !os(Linux) class CustomFunctionNoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = () -> Expression - typealias FunctionResultOptional = () -> Expression + typealias FunctionNoOptional = () -> Expression + typealias FunctionResultOptional = () -> Expression func testFunctionNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { @@ -26,9 +26,9 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { } class CustomFunctionWithOneArgTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression) -> Expression + typealias FunctionNoOptional = (Expression) -> Expression typealias FunctionLeftOptional = (Expression) -> Expression - typealias FunctionResultOptional = (Expression) -> Expression + typealias FunctionResultOptional = (Expression) -> Expression typealias FunctionLeftResultOptional = (Expression) -> Expression func testFunctionNoOptional() throws { @@ -65,12 +65,12 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { } class CustomFunctionWithTwoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression, Expression) -> Expression - typealias FunctionLeftOptional = (Expression, Expression) -> Expression + typealias FunctionNoOptional = (Expression, Expression) -> Expression + typealias FunctionLeftOptional = (Expression, Expression) -> Expression typealias FunctionRightOptional = (Expression, Expression) -> Expression - typealias FunctionResultOptional = (Expression, Expression) -> Expression + typealias FunctionResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightOptional = (Expression, Expression) -> Expression - typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression + typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression typealias FunctionRightResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression From 4a0ab2a7445712e71ff803e020b74f9557ae64a8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:14:36 +0200 Subject: [PATCH 124/216] Fix simulator version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae806ecc..ac705e4e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: iPhone 12 + IOS_SIMULATOR: "iPhone 14" IOS_VERSION: "16.4" jobs: build: From e4f4ddff5f124652421d7cd99512181bed86b0e7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:22:26 +0200 Subject: [PATCH 125/216] Use newer Xcode --- .github/workflows/build.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac705e4e..fbe4ec49 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,13 +8,11 @@ jobs: runs-on: macos-13 steps: - uses: actions/checkout@v2 - - name: Install + - name: "Select Xcode" run: | - gem update bundler - gem install xcpretty --no-document - brew update - brew outdated carthage || brew upgrade carthage - brew outdated swiftlint || brew upgrade swiftlint + xcode-select -p + xcode-select -s /Applications/Xcode-14.3.app/Contents/Developer + xcode-select -p - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 1bc58a5477cc47856d1e6a31b834f1cc0f66c00b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:28:43 +0200 Subject: [PATCH 126/216] Fix path --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbe4ec49..8c893c77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - name: "Select Xcode" run: | xcode-select -p - xcode-select -s /Applications/Xcode-14.3.app/Contents/Developer + xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer xcode-select -p - name: "Lint" run: make lint From 79bab4610b74cc7a8dee118a6b9ebce769aaec61 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:33:04 +0200 Subject: [PATCH 127/216] sudo --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c893c77..15f325ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - name: "Select Xcode" run: | xcode-select -p - xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer + sudo xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer xcode-select -p - name: "Lint" run: make lint From b028fcdda1489a3bb0c738f8809154f9c4be7f5b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 17:48:05 +0200 Subject: [PATCH 128/216] Try Xcode 14.2 --- .github/workflows/build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15f325ed..242a9021 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: "iPhone 14" - IOS_VERSION: "16.4" + IOS_VERSION: "16.2" jobs: build: runs-on: macos-13 @@ -11,8 +11,7 @@ jobs: - name: "Select Xcode" run: | xcode-select -p - sudo xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer - xcode-select -p + sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 101109848902bb2c7061286c86f15a638d6d5a17 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 22:28:40 +0200 Subject: [PATCH 129/216] Update docs --- .github/workflows/build.yml | 2 ++ CHANGELOG.md | 5 +++++ Documentation/Index.md | 31 ++++++++++++++----------------- README.md | 5 +---- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 242a9021..9cc2f56e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,8 @@ jobs: steps: - uses: actions/checkout@v2 - name: "Select Xcode" + # Currently only works with Xcode 14.2: + # https://github.com/CocoaPods/CocoaPods/issues/11839 run: | xcode-select -p sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer diff --git a/CHANGELOG.md b/CHANGELOG.md index fc759118..970b4ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.15.0 (unreleased) +======================================== + +* New minimum deployment targets: iOS/tvOS 11.0, watchOS 4.0 + 0.14.1 (01-11-2022), [diff][diff-0.14.1] ======================================== diff --git a/Documentation/Index.md b/Documentation/Index.md index 583d5def..5d72aa4b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -77,9 +77,6 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and -> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. - ### Swift Package Manager The [Swift Package Manager][] is a tool for managing the distribution of @@ -1142,11 +1139,11 @@ let query = managers chain.with(chain, recursive: true, as: query) // WITH RECURSIVE // "chain" AS ( -// SELECT * FROM "managers" WHERE "id" = 8 -// UNION -// SELECT * from "chain" +// SELECT * FROM "managers" WHERE "id" = 8 +// UNION +// SELECT * from "chain" // JOIN "managers" ON "chain"."manager_id" = "managers"."id" -// ) +// ) // SELECT * FROM "chain" ``` @@ -1156,7 +1153,7 @@ Column names and a materialization hint can optionally be provided. // Add a "level" column to the query representing manager's position in the chain let level = Expression("level") -let queryWithLevel = +let queryWithLevel = managers .select(id, managerId, 0) .where(id == 8) @@ -1166,18 +1163,18 @@ let queryWithLevel = .join(managers, on: chain[managerId] == managers[id]) ) -chain.with(chain, - columns: [id, managerId, level], +chain.with(chain, + columns: [id, managerId, level], recursive: true, hint: .materialize, as: queryWithLevel) // WITH RECURSIVE // "chain" ("id", "manager_id", "level") AS MATERIALIZED ( -// SELECT ("id", "manager_id", 0) FROM "managers" WHERE "id" = 8 -// UNION -// SELECT ("manager"."id", "manager"."manager_id", "level" + 1) FROM "chain" +// SELECT ("id", "manager_id", 0) FROM "managers" WHERE "id" = 8 +// UNION +// SELECT ("manager"."id", "manager"."manager_id", "level" + 1) FROM "chain" // JOIN "managers" ON "chain"."manager_id" = "managers"."id" -// ) +// ) // SELECT * FROM "chain" ``` @@ -1266,7 +1263,7 @@ let count = try db.scalar(users.filter(name != nil).count) We can upsert rows into a table by calling a [query’s](#queries) `upsert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each -joined by the `<-` operator. Upserting is like inserting, except if there is a +joined by the `<-` operator. Upserting is like inserting, except if there is a conflict on the specified column value, SQLite will perform an update on the row instead. ```swift @@ -1957,7 +1954,7 @@ for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` > _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either [implicit or explicit](https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions)) will be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. -> +> > ```swift > someObj.statement = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") > for row in someObj.statement.bind(kUTTypeImage) { /* ... */ } @@ -2134,7 +2131,7 @@ using the following functions. } } ``` - + - `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. diff --git a/README.md b/README.md index 90333b16..e6e5a894 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ API. // Wrap everything in a do...catch to handle errors do { // ... - + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") for email in ["betty@icloud.com", "cathy@icloud.com"] { try stmt.run(email) @@ -119,9 +119,6 @@ interactively, from the Xcode project’s playground. ## Installation -> _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. -> Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. - ### Swift Package Manager The [Swift Package Manager][] is a tool for managing the distribution of From ab3c598db29a7a2db3e06e9642736cb18e670a0e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 23:59:35 +0200 Subject: [PATCH 130/216] Use shorthand optional binding --- .swiftlint.yml | 2 ++ Sources/SQLite/Core/Connection+Attach.swift | 2 +- Sources/SQLite/Core/Connection.swift | 18 +++++------ Sources/SQLite/Core/Result.swift | 4 +-- Sources/SQLite/Extensions/FTS4.swift | 16 +++++----- Sources/SQLite/Extensions/FTS5.swift | 6 ++-- Sources/SQLite/Helpers.swift | 2 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- Sources/SQLite/Schema/SchemaReader.swift | 4 +-- Sources/SQLite/Typed/Coding.swift | 12 ++++---- Sources/SQLite/Typed/CoreFunctions.swift | 30 +++++++++---------- Sources/SQLite/Typed/Operators.swift | 16 +++++----- 12 files changed, 58 insertions(+), 56 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index e728e191..1bb21cd5 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,5 @@ +opt_in_rules: + - shorthand_optional_binding disabled_rules: # rule identifiers to exclude from running - todo - operator_whitespace diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index 47e6af5e..8a25e51d 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -13,7 +13,7 @@ extension Connection { #if SQLITE_SWIFT_SQLCIPHER /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#attach public func attach(_ location: Location, as schemaName: String, key: String? = nil) throws { - if let key = key { + if let key { try run("ATTACH DATABASE ? AS ? KEY ?", location.description, schemaName, key) } else { try run("ATTACH DATABASE ? AS ?", location.description, schemaName) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 26cadea4..f2c3b781 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -414,7 +414,7 @@ public final class Connection { /// times it’s been called for this lock. If it returns `true`, it will /// try again. If it returns `false`, no further attempts will be made. public func busyHandler(_ callback: ((_ tries: Int) -> Bool)?) { - guard let callback = callback else { + guard let callback else { sqlite3_busy_handler(handle, nil, nil) busyHandler = nil return @@ -449,7 +449,7 @@ public final class Connection { @available(watchOS, deprecated: 3.0) @available(tvOS, deprecated: 10.0) fileprivate func trace_v1(_ callback: ((String) -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_trace(handle, nil /* xCallback */, nil /* pCtx */) trace = nil return @@ -458,7 +458,7 @@ public final class Connection { callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) } sqlite3_trace(handle, { (context: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in - if let context = context, let SQL = SQL { + if let context, let SQL { unsafeBitCast(context, to: Trace.self)(SQL) } }, @@ -469,7 +469,7 @@ public final class Connection { @available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) fileprivate func trace_v2(_ callback: ((String) -> Void)?) { - guard let callback = callback else { + guard let callback else { // If the X callback is NULL or if the M mask is zero, then tracing is disabled. sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */) trace = nil @@ -485,7 +485,7 @@ public final class Connection { // callback was invoked. The C argument is a copy of the context pointer. // The P and X arguments are pointers whose meanings depend on T. (_: UInt32, context: UnsafeMutableRawPointer?, pointer: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in - if let pointer = pointer, + if let pointer, let expandedSQL = sqlite3_expanded_sql(OpaquePointer(pointer)) { unsafeBitCast(context, to: Trace.self)(expandedSQL) sqlite3_free(expandedSQL) @@ -507,7 +507,7 @@ public final class Connection { /// `.Insert`, `.Update`, or `.Delete`), database name, table name, and /// rowid. public func updateHook(_ callback: ((_ operation: Operation, _ db: String, _ table: String, _ rowid: Int64) -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_update_hook(handle, nil, nil) updateHook = nil return @@ -535,7 +535,7 @@ public final class Connection { /// committed. If this callback throws, the transaction will be rolled /// back. public func commitHook(_ callback: (() throws -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_commit_hook(handle, nil, nil) commitHook = nil return @@ -562,7 +562,7 @@ public final class Connection { /// - Parameter callback: A callback invoked when a transaction is rolled /// back. public func rollbackHook(_ callback: (() -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_rollback_hook(handle, nil, nil) rollbackHook = nil return @@ -650,7 +650,7 @@ public final class Connection { try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { (callback: UnsafeMutableRawPointer?, _, lhs: UnsafeRawPointer?, _, rhs: UnsafeRawPointer?) in /* xCompare */ - if let lhs = lhs, let rhs = rhs { + if let lhs, let rhs { return unsafeBitCast(callback, to: Collation.self)(lhs, rhs) } else { fatalError("sqlite3_create_collation_v2 callback called with NULL pointer") diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 9fe47ada..ee59e5d1 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -51,13 +51,13 @@ extension Result: CustomStringConvertible { public var description: String { switch self { case let .error(message, errorCode, statement): - if let statement = statement { + if let statement { return "\(message) (\(statement)) (code: \(errorCode))" } else { return "\(message) (code: \(errorCode))" } case let .extendedError(message, extendedCode, statement): - if let statement = statement { + if let statement { return "\(message) (\(statement)) (extended code: \(extendedCode))" } else { return "\(message) (extended code: \(extendedCode))" diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 8e91e453..c02cfdc1 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -98,7 +98,7 @@ public struct Tokenizer { separators: Set = []) -> Tokenizer { var arguments = [String]() - if let removeDiacritics = removeDiacritics { + if let removeDiacritics { arguments.append("remove_diacritics=\(removeDiacritics ? 1 : 0)".quote()) } @@ -208,13 +208,13 @@ open class FTSConfig { func options() -> Options { var options = Options() options.append(formatColumnDefinitions()) - if let tokenizer = tokenizer { + if let tokenizer { options.append("tokenize", value: Expression(literal: tokenizer.description)) } options.appendCommaSeparated("prefix", values: prefixes.sorted().map { String($0) }) if isContentless { options.append("content", value: "") - } else if let externalContentSchema = externalContentSchema { + } else if let externalContentSchema { options.append("content", value: externalContentSchema.tableName()) } return options @@ -306,19 +306,19 @@ open class FTS4Config: FTSConfig { for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { options.append("notindexed", value: column) } - if let languageId = languageId { + if let languageId { options.append("languageid", value: languageId) } - if let compressFunction = compressFunction { + if let compressFunction { options.append("compress", value: compressFunction) } - if let uncompressFunction = uncompressFunction { + if let uncompressFunction { options.append("uncompress", value: uncompressFunction) } - if let matchInfo = matchInfo { + if let matchInfo { options.append("matchinfo", value: matchInfo.rawValue) } - if let order = order { + if let order { options.append("order", value: order.rawValue) } return options diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index 2e4f65fb..3e84e171 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -69,13 +69,13 @@ open class FTS5Config: FTSConfig { override func options() -> Options { var options = super.options() - if let contentRowId = contentRowId { + if let contentRowId { options.append("content_rowid", value: contentRowId) } - if let columnSize = columnSize { + if let columnSize { options.append("columnsize", value: Expression(value: columnSize)) } - if let detail = detail { + if let detail { options.append("detail", value: detail.rawValue) } return options diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 79d057d6..adfadc8a 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -109,7 +109,7 @@ extension String { } func transcode(_ literal: Binding?) -> String { - guard let literal = literal else { return "NULL" } + guard let literal else { return "NULL" } switch literal { case let blob as Blob: diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index b314e5c0..2d38e1fb 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -170,7 +170,7 @@ public enum LiteralValue: Equatable, CustomStringConvertible { // swiftlint:enable identifier_name init(_ string: String?) { - guard let string = string else { + guard let string else { self = .NULL return } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 0c02ba9f..58a9b1bd 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -39,10 +39,10 @@ public class SchemaReader { type: ObjectDefinition.ObjectType? = nil, temp: Bool = false) throws -> [ObjectDefinition] { var query: QueryType = SchemaTable.get(for: connection, temp: temp) - if let name = name { + if let name { query = query.where(SchemaTable.nameColumn == name) } - if let type = type { + if let type { query = query.where(SchemaTable.typeColumn == type.rawValue) } return try connection.prepare(query).map { row -> ObjectDefinition in diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index b96bc64e..e7db03fb 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -215,7 +215,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Int?, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -223,7 +223,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -231,7 +231,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Float?, forKey key: Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -239,7 +239,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Double?, forKey key: Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -247,7 +247,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: String?, forKey key: MyKey) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -270,7 +270,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: T?, forKey key: Key) throws where T: Swift.Encodable { - guard let value = value else { + guard let value else { guard forcingNilValueSetters else { return } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index dc6a1044..4429bb5f 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -100,7 +100,7 @@ extension ExpressionType where UnderlyingType == Double { /// /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { - guard let precision = precision else { + guard let precision else { return Function.round.wrap([self]) } return Function.round.wrap([self, Int(precision)]) @@ -120,7 +120,7 @@ extension ExpressionType where UnderlyingType == Double? { /// /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { - guard let precision = precision else { + guard let precision else { return Function.round.wrap(self) } return Function.round.wrap([self, Int(precision)]) @@ -250,7 +250,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { - guard let character = character else { + guard let character else { return "LIKE".infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) @@ -274,7 +274,7 @@ extension ExpressionType where UnderlyingType == String { /// - 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 { + guard let character else { return Function.like.infix(self, pattern) } let like: Expression = Function.like.infix(self, pattern, wrap: false) @@ -349,7 +349,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.ltrim.wrap(self) } return Function.ltrim.wrap([self, String(characters)]) @@ -367,7 +367,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.rtrim.wrap(self) } return Function.rtrim.wrap([self, String(characters)]) @@ -385,7 +385,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.trim.wrap([self]) } return Function.trim.wrap([self, String(characters)]) @@ -409,7 +409,7 @@ extension ExpressionType where UnderlyingType == String { } public func substring(_ location: Int, length: Int? = nil) -> Expression { - guard let length = length else { + guard let length else { return Function.substr.wrap([self, location]) } return Function.substr.wrap([self, location, length]) @@ -475,7 +475,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { - guard let character = character else { + guard let character else { return Function.like.infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) @@ -499,7 +499,7 @@ extension ExpressionType where UnderlyingType == String? { /// - 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 { + guard let character else { return Function.like.infix(self, pattern) } let like: Expression = Function.like.infix(self, pattern, wrap: false) @@ -574,7 +574,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.ltrim.wrap(self) } return Function.ltrim.wrap([self, String(characters)]) @@ -592,7 +592,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.rtrim.wrap(self) } return Function.rtrim.wrap([self, String(characters)]) @@ -610,7 +610,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.trim.wrap(self) } return Function.trim.wrap([self, String(characters)]) @@ -649,7 +649,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `substr` function. public func substring(_ location: Int, length: Int? = nil) -> Expression { - guard let length = length else { + guard let length else { return Function.substr.wrap([self, location]) } return Function.substr.wrap([self, location, length]) @@ -726,7 +726,7 @@ extension String { /// - 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 { + guard let character else { return Function.like.infix(self, pattern) } let like: Expression = Function.like.infix(self, pattern, wrap: false) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 5ffbbceb..1c611cbc 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -367,14 +367,14 @@ public func ==(lhs: Expression, rhs: V) -> Expression where V Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS".infix(lhs, Expression(value: nil)) } return Operator.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { Operator.eq.infix(lhs, rhs) } public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS".infix(Expression(value: nil), rhs) } return Operator.eq.infix(lhs, rhs) } @@ -394,14 +394,14 @@ public func ===(lhs: Expression, rhs: V) -> Expression where "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS".infix(lhs, Expression(value: nil)) } return "IS".infix(lhs, rhs) } public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { "IS".infix(lhs, rhs) } public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS".infix(Expression(value: nil), rhs) } return "IS".infix(lhs, rhs) } @@ -421,14 +421,14 @@ public func !=(lhs: Expression, rhs: V) -> Expression where V Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return Operator.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { Operator.neq.infix(lhs, rhs) } public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return Operator.neq.infix(lhs, rhs) } @@ -448,14 +448,14 @@ public func !==(lhs: Expression, rhs: V) -> Expression where "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return "IS NOT".infix(lhs, rhs) } public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { "IS NOT".infix(lhs, rhs) } public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return "IS NOT".infix(lhs, rhs) } From 6aab9fcdc27c97f04b4153589908ec871b17ba55 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 22 May 2023 00:56:52 +0200 Subject: [PATCH 131/216] Re-enable watch tests --- run-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index c54300ee..49330c12 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,9 +8,9 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - pod lib lint --no-subspecs --fail-fast --platforms=ios,osx,tvos + pod lib lint --no-subspecs --fail-fast else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=ios,osx,tvos + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From ff93ebc831c01daa0117be6c19e0960347e001a6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 27 May 2023 23:09:08 +0200 Subject: [PATCH 132/216] Handle FK definitions w/o key references Closes #1199 --- SQLite.xcodeproj/project.pbxproj | 8 ++++ Sources/SQLite/Schema/SchemaDefinitions.swift | 4 +- Sources/SQLite/Schema/SchemaReader.swift | 2 +- .../Schema/SchemaReaderTests.swift | 38 +++++++++++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..e6bb4fc9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,6 +211,9 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.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 */; }; @@ -340,6 +343,7 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -483,6 +487,7 @@ 19A17B56FBA20E7245BC8AC0 /* Schema */ = { isa = PBXGroup; children = ( + DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */, 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */, 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */, 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */, @@ -998,6 +1003,7 @@ 19A17FACE8E4D54A50BA934E /* FTS5Tests.swift in Sources */, 19A177909023B7B940C5805E /* FTSIntegrationTests.swift in Sources */, 19A17E1DD976D5CE80018749 /* FTS4Tests.swift in Sources */, + DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */, 19A17411403D60640467209E /* ExpressionTests.swift in Sources */, 19A17CA4D7B63D845428A9C5 /* StatementTests.swift in Sources */, 19A17885B646CB0201BE4BD5 /* QueryTests.swift in Sources */, @@ -1119,6 +1125,7 @@ 19A178DA2BB5970778CCAF13 /* FTS5Tests.swift in Sources */, 19A1755C49154C87304C9146 /* FTSIntegrationTests.swift in Sources */, 19A17444861E1443143DEB44 /* FTS4Tests.swift in Sources */, + DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */, 19A17DD33C2E43DD6EE05A60 /* ExpressionTests.swift in Sources */, 19A17D6EC40BC35A5DC81BA8 /* StatementTests.swift in Sources */, 19A17E3F47DA087E2B76D087 /* QueryTests.swift in Sources */, @@ -1199,6 +1206,7 @@ 19A1776BD5127DFDF847FF1F /* FTS5Tests.swift in Sources */, 19A173088B85A7E18E8582A7 /* FTSIntegrationTests.swift in Sources */, 19A178767223229E61C5066F /* FTS4Tests.swift in Sources */, + DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */, 19A1781CBA8968ABD3E00877 /* ExpressionTests.swift in Sources */, 19A17923494236793893BF72 /* StatementTests.swift in Sources */, 19A17A52BF29D27C9AA229E7 /* QueryTests.swift in Sources */, diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 2d38e1fb..9f4c126f 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -107,7 +107,7 @@ public struct ColumnDefinition: Equatable { public struct ForeignKey: Equatable { let table: String let column: String - let primaryKey: String + let primaryKey: String? let onUpdate: String? let onDelete: String? } @@ -365,7 +365,7 @@ extension ColumnDefinition.ForeignKey { ([ "REFERENCES", table.quote(), - "(\(primaryKey.quote()))", + primaryKey.map { "(\($0.quote()))" }, onUpdate.map { "ON UPDATE \($0)" }, onDelete.map { "ON DELETE \($0)" } ] as [String?]).compactMap { $0 } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 58a9b1bd..eb31888b 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -187,7 +187,7 @@ private enum ForeignKeyListTable { static let seqColumn = Expression("seq") static let tableColumn = Expression("table") static let fromColumn = Expression("from") - static let toColumn = Expression("to") + static let toColumn = Expression("to") // when null, use primary key static let onUpdateColumn = Expression("on_update") static let onDeleteColumn = Expression("on_delete") static let matchColumn = Expression("match") diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index dd5ae103..e90578e7 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -135,6 +135,44 @@ class SchemaReaderTests: SQLiteTestCase { ]) } + func test_foreignKeys_references_column() throws { + let sql = """ + CREATE TABLE artist( + artistid INTEGER PRIMARY KEY, + artistname TEXT + ); + CREATE TABLE track( + trackid INTEGER, + trackname TEXT, + trackartist INTEGER REFERENCES artist(artistid) + ); + """ + try db.execute(sql) + let trackColumns = try db.schema.foreignKeys(table: "track") + XCTAssertEqual(trackColumns.map { $0.toSQL() }.joined(separator: "\n"), """ + REFERENCES "artist" ("artistid") + """) + } + + func test_foreignKeys_references_null_column() throws { + let sql = """ + CREATE TABLE artist( + artistid INTEGER PRIMARY KEY, + artistname TEXT + ); + CREATE TABLE track( + trackid INTEGER, + trackname TEXT, + trackartist INTEGER REFERENCES artist + ); + """ + try db.execute(sql) + let trackColumns = try db.schema.foreignKeys(table: "track") + XCTAssertEqual(trackColumns.map { $0.toSQL() }.joined(separator: "\n"), """ + REFERENCES "artist" + """) + } + func test_tableDefinitions() throws { let tables = try schemaReader.tableDefinitions() XCTAssertEqual(tables.count, 1) From 7997c85cbf6c0a1f8caf300b483f04028548e40e Mon Sep 17 00:00:00 2001 From: Stefan Saasen Date: Wed, 7 Jun 2023 15:45:35 +0200 Subject: [PATCH 133/216] SchemaReader: return the correct column definition for a composite primary key The `PRAGMA` `table_info` that is used to return the column definitions, returns one row for each defined column. The `pk` column contains: > ... either zero for columns that are not part of the primary key, or the 1-based index of the column within the primary key). See https://www.sqlite.org/pragma.html#pragma_table_info Checking whether the `pk` column equals 1 only detects a single primary key and ignores other columns that are part of a composite primary key. --- Sources/SQLite/Schema/SchemaReader.swift | 2 +- .../Schema/SchemaReaderTests.swift | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 58a9b1bd..2be15f29 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -25,7 +25,7 @@ public class SchemaReader { .map { (row: Row) -> ColumnDefinition in ColumnDefinition( name: row[TableInfoTable.nameColumn], - primaryKey: row[TableInfoTable.primaryKeyColumn] == 1 ? + primaryKey: (row[TableInfoTable.primaryKeyColumn] ?? 0) > 0 ? try parsePrimaryKey(column: row[TableInfoTable.nameColumn]) : nil, type: ColumnDefinition.Affinity(row[TableInfoTable.typeColumn]), nullable: row[TableInfoTable.notNullColumn] == 0, diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index dd5ae103..49871c10 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -90,6 +90,43 @@ class SchemaReaderTests: SQLiteTestCase { ) } + func test_columnDefinitions_composite_primary_keys() throws { + try db.run(""" + CREATE TABLE t ( + col1 INTEGER, + col2 INTEGER, + col3 INTEGER, + PRIMARY KEY (col1, col2) + ); + """) + + XCTAssertEqual( + try schemaReader.columnDefinitions(table: "t"), [ + ColumnDefinition( + name: "col1", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition( + name: "col2", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition( + name: "col3", + primaryKey: nil, + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + func test_indexDefinitions_no_index() throws { let indexes = try schemaReader.indexDefinitions(table: "users") XCTAssertTrue(indexes.isEmpty) From 656ca871e357df8ef45b7344c777102cdbf7dfc6 Mon Sep 17 00:00:00 2001 From: Stefan Saasen Date: Tue, 30 May 2023 16:20:29 +0200 Subject: [PATCH 134/216] Fix column affinity parsing to match how SQLite determines affinity See https://www.sqlite.org/datatype3.html#determination_of_column_affinity for how SQLite determines column affinity. --- Sources/SQLite/Schema/SchemaDefinitions.swift | 14 +++- .../Schema/SchemaDefinitionsTests.swift | 64 ++++++++++++++++++- .../Schema/SchemaReaderTests.swift | 4 +- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 2d38e1fb..3c6b0523 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -57,7 +57,19 @@ public struct ColumnDefinition: Equatable { } init(_ string: String) { - self = Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? .TEXT + let test = string.uppercased() + // https://sqlite.org/datatype3.html#determination_of_column_affinity + if test.contains("INT") { // Rule 1 + self = .INTEGER + } else if ["CHAR", "CLOB", "TEXT"].first(where: {test.contains($0)}) != nil { // Rule 2 + self = .TEXT + } else if string.contains("BLOB") { // Rule 3 + self = .BLOB + } else if ["REAL", "FLOA", "DOUB"].first(where: {test.contains($0)}) != nil { // Rule 4 + self = .REAL + } else { // Rule 5 + self = .NUMERIC + } } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index ef97b981..8b7e27e3 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -71,8 +71,68 @@ class AffinityTests: XCTestCase { XCTAssertEqual(ColumnDefinition.Affinity("NUMERIC"), .NUMERIC) } - func test_returns_TEXT_for_unknown_type() { - XCTAssertEqual(ColumnDefinition.Affinity("baz"), .TEXT) + // [Determination Of Column Affinity](https://sqlite.org/datatype3.html#determination_of_column_affinity) + // Rule 1 + func testIntegerAffinity() { + let declared = [ + "INT", + "INTEGER", + "TINYINT", + "SMALLINT", + "MEDIUMINT", + "BIGINT", + "UNSIGNED BIG INT", + "INT2", + "INT8" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .INTEGER})) + } + + // Rule 2 + func testTextAffinity() { + let declared = [ + "CHARACTER(20)", + "VARCHAR(255)", + "VARYING CHARACTER(255)", + "NCHAR(55)", + "NATIVE CHARACTER(70)", + "NVARCHAR(100)", + "TEXT", + "CLOB" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .TEXT})) + } + + // Rule 3 + func testBlobAffinity() { + XCTAssertEqual(ColumnDefinition.Affinity("BLOB"), .BLOB) + } + + // Rule 4 + func testRealAffinity() { + let declared = [ + "REAL", + "DOUBLE", + "DOUBLE PRECISION", + "FLOAT" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .REAL})) + } + + // Rule 5 + func testNumericAffinity() { + let declared = [ + "NUMERIC", + "DECIMAL(10,5)", + "BOOLEAN", + "DATE", + "DATETIME" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .NUMERIC})) + } + + func test_returns_NUMERIC_for_unknown_type() { + XCTAssertEqual(ColumnDefinition.Affinity("baz"), .NUMERIC) } } diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index dd5ae103..b03045de 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -40,7 +40,7 @@ class SchemaReaderTests: SQLiteTestCase { references: nil), ColumnDefinition(name: "admin", primaryKey: nil, - type: .TEXT, + type: .NUMERIC, nullable: false, defaultValue: .numericLiteral("0"), references: nil), @@ -51,7 +51,7 @@ class SchemaReaderTests: SQLiteTestCase { references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, - type: .TEXT, + type: .NUMERIC, nullable: true, defaultValue: .NULL, references: nil) From 3023a1f63336f70c1470bdef59ac2dfded258206 Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Mon, 16 Oct 2023 07:33:54 -0500 Subject: [PATCH 135/216] Add optional support for decoding --- Sources/SQLite/Typed/Coding.swift | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index e7db03fb..8c7755c3 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -471,6 +471,54 @@ private class SQLiteDecoder: Decoder { } } + func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: Int.Type, forKey key: Key) throws -> Int? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: Int64.Type, forKey key: Key) throws -> Int64? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: Float.Type, forKey key: Key) throws -> Float? { + try? Float(row.get(Expression(key.stringValue))) + } + + func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> Double? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> String? { + try? row.get(Expression(key.stringValue)) + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { + switch type { + case is Data.Type: + let data = try row.get(Expression(key.stringValue)) + return data as? T + case is Date.Type: + let date = try row.get(Expression(key.stringValue)) + return date as? T + case is UUID.Type: + let uuid = try row.get(Expression(key.stringValue)) + return uuid as? T + default: + guard let JSONString = try row.get(Expression(key.stringValue)) else { + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, + debugDescription: "an unsupported type was found")) + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: 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.dataCorrupted(DecodingError.Context(codingPath: codingPath, From e7c259226373e9469d9166b842e5395dae302b6e Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Thu, 19 Oct 2023 07:13:41 -0500 Subject: [PATCH 136/216] Fix copypasta --- Sources/SQLite/Typed/Coding.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 8c7755c3..f62f5b6b 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -495,19 +495,22 @@ private class SQLiteDecoder: Decoder { try? row.get(Expression(key.stringValue)) } - func decode(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { + func decodeIfPresent(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { switch type { case is Data.Type: - let data = try row.get(Expression(key.stringValue)) - return data as? T + if let data = try? row.get(Expression(key.stringValue)) { + return data as? T + } case is Date.Type: - let date = try row.get(Expression(key.stringValue)) - return date as? T + if let date = try? row.get(Expression(key.stringValue)) { + return date as? T + } case is UUID.Type: - let uuid = try row.get(Expression(key.stringValue)) - return uuid as? T + if let uuid = try? row.get(Expression(key.stringValue)) { + return uuid as? T + } default: - guard let JSONString = try row.get(Expression(key.stringValue)) else { + guard let JSONString = try? row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "an unsupported type was found")) } From 128246f30a396ef864318e3a7ca851e90b3e84a1 Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Thu, 19 Oct 2023 11:04:58 -0500 Subject: [PATCH 137/216] Fix missing return --- 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 f62f5b6b..52612063 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -520,6 +520,8 @@ private class SQLiteDecoder: Decoder { } return try JSONDecoder().decode(type, from: data) } + + return nil } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws From 1c6bf76948de5b6ab1ef7a45629788e3c5c70d0a Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Fri, 20 Oct 2023 07:20:58 -0500 Subject: [PATCH 138/216] Fix smoothbrain --- Sources/SQLite/Typed/Coding.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 52612063..bfddc5ee 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -498,17 +498,11 @@ private class SQLiteDecoder: Decoder { func decodeIfPresent(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { switch type { case is Data.Type: - if let data = try? row.get(Expression(key.stringValue)) { - return data as? T - } + return try? row.get(Expression(key.stringValue)) as? T case is Date.Type: - if let date = try? row.get(Expression(key.stringValue)) { - return date as? T - } + return try? row.get(Expression(key.stringValue)) as? T case is UUID.Type: - if let uuid = try? row.get(Expression(key.stringValue)) { - return uuid as? T - } + return try? row.get(Expression(key.stringValue)) as? T default: guard let JSONString = try? row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, @@ -520,8 +514,6 @@ private class SQLiteDecoder: Decoder { } return try JSONDecoder().decode(type, from: data) } - - return nil } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws From fc96d30b72db986b7f930d951eca14c16e32b9b5 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Sat, 18 Nov 2023 18:30:28 -0800 Subject: [PATCH 139/216] Implements built-in window functions --- SQLite.xcodeproj/project.pbxproj | 18 +++ Sources/SQLite/Typed/AggregateFunctions.swift | 6 +- Sources/SQLite/Typed/WindowFunctions.swift | 145 ++++++++++++++++++ .../Typed/QueryIntegrationTests.swift | 95 ++++++++++++ .../Typed/WindowFunctionsTests.swift | 58 +++++++ 5 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 Sources/SQLite/Typed/WindowFunctions.swift create mode 100644 Tests/SQLiteTests/Typed/WindowFunctionsTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..95ed6806 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -195,6 +195,13 @@ 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 */; }; + 64A8EE432B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64A8EE442B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64A8EE452B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64A8EE462B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64B8E1702B09748000545AFB /* WindowFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */; }; + 64B8E1712B09748000545AFB /* WindowFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */; }; + 64B8E1722B09748000545AFB /* WindowFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */; }; 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; @@ -335,6 +342,8 @@ 3DF7B79B2884C901005DD8CA /* Planning.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Planning.md; sourceTree = ""; }; 3DFC0B862886C239001C8FC9 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; + 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowFunctions.swift; sourceTree = ""; }; + 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowFunctionsTests.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; @@ -476,6 +485,7 @@ 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */, 19A1709D5BDD2691BA160012 /* SetterTests.swift */, 19A174FE5B47A97937A27276 /* RowTests.swift */, + 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */, ); path = Typed; sourceTree = ""; @@ -607,6 +617,7 @@ isa = PBXGroup; children = ( EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */, + 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */, EE247AFB1C3F06E900AE3E12 /* Collation.swift */, EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */, EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */, @@ -960,6 +971,7 @@ 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */, 03A65E811C6BB2FB0062603F /* CustomFunctions.swift in Sources */, 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */, + 64A8EE452B095FBB00F583F7 /* WindowFunctions.swift in Sources */, 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */, 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */, @@ -1014,6 +1026,7 @@ 19A17746150A815944A6820B /* SelectTests.swift in Sources */, 19A1766135CE9786B1878603 /* ValueTests.swift in Sources */, 19A177D5C6542E2D572162E5 /* QueryIntegrationTests.swift in Sources */, + 64B8E1722B09748000545AFB /* WindowFunctionsTests.swift in Sources */, 19A178DF5A96CFEFF1E271F6 /* AggregateFunctionsTests.swift in Sources */, 19A17437659BD7FD787D94A6 /* CustomAggregationTests.swift in Sources */, 19A17F907258E524B3CA2FAE /* SetterTests.swift in Sources */, @@ -1057,6 +1070,7 @@ 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */, + 64A8EE462B095FBB00F583F7 /* WindowFunctions.swift in Sources */, 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */, 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */, 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */, @@ -1081,6 +1095,7 @@ EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */, 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B101C3F06E900AE3E12 /* CustomFunctions.swift in Sources */, + 64A8EE432B095FBB00F583F7 /* WindowFunctions.swift in Sources */, EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */, EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, @@ -1135,6 +1150,7 @@ 19A17F7977364EC8CD33C3C3 /* SelectTests.swift in Sources */, 19A17FD22EF43DF428DD93BA /* ValueTests.swift in Sources */, 19A177AA5922527BBDC77CF9 /* QueryIntegrationTests.swift in Sources */, + 64B8E1702B09748000545AFB /* WindowFunctionsTests.swift in Sources */, 19A179786A6826D58A70F8BC /* AggregateFunctionsTests.swift in Sources */, 19A1793972BDDDB027C113BB /* CustomAggregationTests.swift in Sources */, 19A1773155AC2BF2CA86A473 /* SetterTests.swift in Sources */, @@ -1161,6 +1177,7 @@ 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B701C3F3FEC00AE3E12 /* CustomFunctions.swift in Sources */, EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */, + 64A8EE442B095FBB00F583F7 /* WindowFunctions.swift in Sources */, EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */, EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */, @@ -1215,6 +1232,7 @@ 19A17DE1FCDB5695702AD24D /* SelectTests.swift in Sources */, 19A1726002D24C14F876C8FE /* ValueTests.swift in Sources */, 19A173389E53CB24DFA8CEDD /* QueryIntegrationTests.swift in Sources */, + 64B8E1712B09748000545AFB /* WindowFunctionsTests.swift in Sources */, 19A170C56745F9D722A73D77 /* AggregateFunctionsTests.swift in Sources */, 19A1772EBE65173EDFB1AFCA /* CustomAggregationTests.swift in Sources */, 19A17E0ABA6C415F014CD51C /* SetterTests.swift in Sources */, diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index bf4fb8fc..17fc4a20 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -166,7 +166,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// salary.average /// // avg("salary") /// - /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// - Returns: A copy of the expression wrapped with the `avg` aggregate /// function. public var average: Expression { Function.avg.wrap(self) @@ -179,7 +179,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// salary.sum /// // sum("salary") /// - /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// - Returns: A copy of the expression wrapped with the `sum` aggregate /// function. public var sum: Expression { Function.sum.wrap(self) @@ -192,7 +192,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// salary.total /// // total("salary") /// - /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// - Returns: A copy of the expression wrapped with the `total` aggregate /// function. public var total: Expression { Function.total.wrap(self) diff --git a/Sources/SQLite/Typed/WindowFunctions.swift b/Sources/SQLite/Typed/WindowFunctions.swift new file mode 100644 index 00000000..e71e1ada --- /dev/null +++ b/Sources/SQLite/Typed/WindowFunctions.swift @@ -0,0 +1,145 @@ +import Foundation + +// see https://www.sqlite.org/windowfunctions.html#builtins +private enum WindowFunction: String { + // swiftlint:disable identifier_name + case ntile + case row_number + case rank + case dense_rank + case percent_rank + case cume_dist + case lag + case lead + case first_value + case last_value + case nth_value + // swiftlint:enable identifier_name + + func wrap(_ value: Int? = nil) -> Expression { + if let value { + return self.rawValue.wrap(Expression(value: value)) + } + return Expression(literal: "\(rawValue)()") + } + + func over(value: Int? = nil, _ orderBy: Expressible) -> Expression { + return Expression(" ".join([ + self.wrap(value), + Expression("OVER (ORDER BY \(orderBy.expression.template))", orderBy.expression.bindings) + ]).expression) + } + + func over(valueExpr: Expressible, _ orderBy: Expressible) -> Expression { + return Expression(" ".join([ + self.rawValue.wrap(valueExpr), + Expression("OVER (ORDER BY \(orderBy.expression.template))", orderBy.expression.bindings) + ]).expression) + } +} + +extension ExpressionType where UnderlyingType: Value { + /// Builds a copy of the expression with `lag(self, offset, default) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `lag(self, offset, default) OVER (ORDER BY {orderBy})` window function + public func lag(offset: Int = 0, default: Expressible? = nil, _ orderBy: Expressible) -> Expression { + if let defaultExpression = `default` { + return Expression( + "lag(\(template), \(offset), \(defaultExpression.asSQL())) OVER (ORDER BY \(orderBy.expression.template))", + bindings + orderBy.expression.bindings + ) + + } + return Expression("lag(\(template), \(offset)) OVER (ORDER BY \(orderBy.expression.template))", bindings + orderBy.expression.bindings) + } + + /// Builds a copy of the expression with `lead(self, offset, default) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `lead(self, offset, default) OVER (ORDER BY {orderBy})` window function + public func lead(offset: Int = 0, default: Expressible? = nil, _ orderBy: Expressible) -> Expression { + if let defaultExpression = `default` { + return Expression( + "lead(\(template), \(offset), \(defaultExpression.asSQL())) OVER (ORDER BY \(orderBy.expression.template))", + bindings + orderBy.expression.bindings) + + } + return Expression("lead(\(template), \(offset)) OVER (ORDER BY \(orderBy.expression.template))", bindings + orderBy.expression.bindings) + } + + /// Builds a copy of the expression with `first_value(self) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `first_value(self) OVER (ORDER BY {orderBy})` window function + public func firstValue(_ orderBy: Expressible) -> Expression { + WindowFunction.first_value.over(valueExpr: self, orderBy) + } + + /// Builds a copy of the expression with `last_value(self) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `last_value(self) OVER (ORDER BY {orderBy})` window function + public func lastValue(_ orderBy: Expressible) -> Expression { + WindowFunction.last_value.over(valueExpr: self, orderBy) + } + + /// Builds a copy of the expression with `nth_value(self) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter index: Row N of the window frame to return + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `nth_value(self) OVER (ORDER BY {orderBy})` window function + public func value(_ index: Int, _ orderBy: Expressible) -> Expression { + Expression("nth_value(\(template), \(index)) OVER (ORDER BY \(orderBy.expression.template))", bindings + orderBy.expression.bindings) + } +} + +/// Builds an expression representing `ntile(size) OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `ntile(size) OVER (ORDER BY {orderBy})` +public func ntile(_ size: Int, _ orderBy: Expressible) -> Expression { +// Expression.ntile(size, orderBy) + + WindowFunction.ntile.over(value: size, orderBy) +} + +/// Builds an expression representing `row_count() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `row_count() OVER (ORDER BY {orderBy})` +public func rowNumber(_ orderBy: Expressible) -> Expression { + WindowFunction.row_number.over(orderBy) +} + +/// Builds an expression representing `rank() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `rank() OVER (ORDER BY {orderBy})` +public func rank(_ orderBy: Expressible) -> Expression { + WindowFunction.rank.over(orderBy) +} + +/// Builds an expression representing `dense_rank() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `dense_rank() OVER ('over')` +public func denseRank(_ orderBy: Expressible) -> Expression { + WindowFunction.dense_rank.over(orderBy) +} + +/// Builds an expression representing `percent_rank() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `percent_rank() OVER (ORDER BY {orderBy})` +public func percentRank(_ orderBy: Expressible) -> Expression { + WindowFunction.percent_rank.over(orderBy) +} + +/// Builds an expression representing `cume_dist() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `cume_dist() OVER (ORDER BY {orderBy})` +public func cumeDist(_ orderBy: Expressible) -> Expression { + WindowFunction.cume_dist.over(orderBy) +} diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index d8d31a79..aa45cafe 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -322,6 +322,101 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertNotNil(row[name]) XCTAssertNotNil(row[email]) } + + func test_select_ntile_function() throws { + let users = Table("users") + + try insertUser("Joey") + try insertUser("Timmy") + try insertUser("Jimmy") + try insertUser("Billy") + + let bucket = ntile(1, id.asc) + try db.prepare(users.select(id, bucket)).forEach { + XCTAssertEqual($0[bucket], 1) // only 1 window + } + } + + func test_select_cume_dist_function() throws { + let users = Table("users") + + try insertUser("Joey") + try insertUser("Timmy") + try insertUser("Jimmy") + try insertUser("Billy") + + let cumeDist = cumeDist(email) + let results = try db.prepare(users.select(id, cumeDist)).map { + $0[cumeDist] + } + XCTAssertEqual([0.25, 0.5, 0.75, 1], results) + } + + func test_select_window_row_number() throws { + let users = Table("users") + + try insertUser("Billy") + try insertUser("Jimmy") + try insertUser("Joey") + try insertUser("Timmy") + + let rowNumber = rowNumber(email.asc) + var expectedRowNum = 1 + try db.prepare(users.select(id, rowNumber)).forEach { + // should retrieve row numbers in order of INSERT above + XCTAssertEqual($0[rowNumber], expectedRowNum) + expectedRowNum += 1 + } + } + + func test_select_window_ranking() throws { + let users = Table("users") + + try insertUser("Billy") + try insertUser("Jimmy") + try insertUser("Joey") + try insertUser("Timmy") + + let percentRank = percentRank(email) + let actualPercentRank: [Int] = try db.prepare(users.select(id, percentRank)).map { + Int($0[percentRank] * 100) + } + XCTAssertEqual([0, 33, 66, 100], actualPercentRank) + + let rank = rank(email) + let actualRank: [Int] = try db.prepare(users.select(id, rank)).map { + $0[rank] + } + XCTAssertEqual([1, 2, 3, 4], actualRank) + + let denseRank = denseRank(email) + let actualDenseRank: [Int] = try db.prepare(users.select(id, denseRank)).map { + $0[denseRank] + } + XCTAssertEqual([1, 2, 3, 4], actualDenseRank) + } + + func test_select_window_values() throws { + let users = Table("users") + + try insertUser("Billy") + try insertUser("Jimmy") + try insertUser("Joey") + try insertUser("Timmy") + + let firstValue = email.firstValue(email.desc) + try db.prepare(users.select(id, firstValue)).forEach { + XCTAssertEqual($0[firstValue], "Timmy@example.com") // should grab last email alphabetically + } + + let lastValue = email.lastValue(email.asc) + var row = try db.pluck(users.select(id, lastValue))! + XCTAssertEqual(row[lastValue], "Billy@example.com") + + let nthValue = email.value(1, email.asc) + row = try db.pluck(users.select(id, nthValue))! + XCTAssertEqual(row[nthValue], "Billy@example.com") + } } extension Connection { diff --git a/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift new file mode 100644 index 00000000..6ded152b --- /dev/null +++ b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift @@ -0,0 +1,58 @@ +import XCTest +import SQLite + +class WindowFunctionsTests: XCTestCase { + + func test_ntile_wrapsExpressionWithOverClause() { + assertSQL("ntile(1) OVER (ORDER BY \"int\" DESC)", ntile(1, int.desc)) + assertSQL("ntile(20) OVER (ORDER BY \"intOptional\" ASC)", ntile(20, intOptional.asc)) + assertSQL("ntile(20) OVER (ORDER BY \"double\" ASC)", ntile(20, double.asc)) + assertSQL("ntile(1) OVER (ORDER BY \"doubleOptional\" ASC)", ntile(1, doubleOptional.asc)) + assertSQL("ntile(1) OVER (ORDER BY \"int\" DESC)", ntile(1, int.desc)) + } + + func test_row_number_wrapsExpressionWithOverClause() { + assertSQL("row_number() OVER (ORDER BY \"int\" DESC)", rowNumber(int.desc)) + } + + func test_rank_wrapsExpressionWithOverClause() { + assertSQL("rank() OVER (ORDER BY \"int\" DESC)", rank(int.desc)) + } + + func test_dense_rank_wrapsExpressionWithOverClause() { + assertSQL("dense_rank() OVER (ORDER BY \"int\" DESC)", denseRank(int.desc)) + } + + func test_percent_rank_wrapsExpressionWithOverClause() { + assertSQL("percent_rank() OVER (ORDER BY \"int\" DESC)", percentRank(int.desc)) + } + + func test_cume_dist_wrapsExpressionWithOverClause() { + assertSQL("cume_dist() OVER (ORDER BY \"int\" DESC)", cumeDist(int.desc)) + } + + func test_lag_wrapsExpressionWithOverClause() { + assertSQL("lag(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lag(int.desc)) + assertSQL("lag(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 7, int.desc)) + assertSQL("lag(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 1, default: Expression(value: 3), int.desc)) + } + + func test_lead_wrapsExpressionWithOverClause() { + assertSQL("lead(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lead(int.desc)) + assertSQL("lead(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 7, int.desc)) + assertSQL("lead(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 1, default: Expression(value: 3), int.desc)) + } + + func test_firstValue_wrapsExpressionWithOverClause() { + assertSQL("first_value(\"int\") OVER (ORDER BY \"int\" DESC)", int.firstValue(int.desc)) + assertSQL("first_value(\"double\") OVER (ORDER BY \"int\" DESC)", double.firstValue(int.desc)) + } + + func test_lastValue_wrapsExpressionWithOverClause() { + assertSQL("last_value(\"int\") OVER (ORDER BY \"int\" DESC)", int.lastValue(int.desc)) + } + + func test_nth_value_wrapsExpressionWithOverClause() { + assertSQL("nth_value(\"int\", 3) OVER (ORDER BY \"int\" DESC)", int.value(3, int.desc)) + } +} From 2fc62a96f03112f77604285cd0ad24b60558c22a Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Sat, 18 Nov 2023 18:42:41 -0800 Subject: [PATCH 140/216] update docs --- Documentation/Index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 5d72aa4b..a03c26a3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -64,6 +64,7 @@ - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) + - [Window SQLite Functions](#window-sqlite-functions) - [Date and Time Functions](#date-and-time-functions) - [Custom SQL Functions](#custom-sql-functions) - [Custom Collations](#custom-collations) @@ -1871,6 +1872,11 @@ Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift. +## Window SQLite Functions + +Most of SQLite's [window functions](https://www.sqlite.org/windowfunctions.html) have been +surfaced in and type-audited for SQLite.swift. Currently only `OVER (ORDER BY ...)` windowing is possible. + ## Date and Time functions SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) From dedef242d9253e5d6169dfe91d9e67ba4597291b Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 4 Jan 2024 11:22:10 -0800 Subject: [PATCH 141/216] Add privacy manifest --- SQLite.xcodeproj/project.pbxproj | 16 ++++++++++++++++ Sources/SQLite/PrivacyInfo.xcprivacy | 14 ++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 Sources/SQLite/PrivacyInfo.xcprivacy diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..8eb1df66 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,6 +211,13 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; 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 */; }; @@ -340,6 +347,7 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -546,6 +554,7 @@ EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, + EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */, EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, EE247AF91C3F06E900AE3E12 /* Typed */, @@ -892,6 +901,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -899,6 +909,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79828846FED005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -907,6 +918,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -914,6 +926,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -921,6 +934,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79628846FCC005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -929,6 +943,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -936,6 +951,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79928847055005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/SQLite/PrivacyInfo.xcprivacy b/Sources/SQLite/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..987771fa --- /dev/null +++ b/Sources/SQLite/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + NSPrivacyTracking + + + From e7c4212a213f8dec5d68ec6e7acadeed8f4fb912 Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Wed, 24 Jan 2024 15:18:34 -0800 Subject: [PATCH 142/216] Add visionOS support to Package.swift --- Package.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 70bde7ef..238661ae 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 import PackageDescription let package = Package( @@ -7,7 +7,8 @@ let package = Package( .iOS(.v11), .macOS(.v10_13), .watchOS(.v4), - .tvOS(.v11) + .tvOS(.v11), + .visionOS(.v1) ], products: [ .library( From 240f0802792cce82ebd0c27300cd1748b322e200 Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Wed, 24 Jan 2024 15:18:43 -0800 Subject: [PATCH 143/216] Create visionOS target and test plan --- SQLite.xcodeproj/project.pbxproj | 344 +++++++++++++++++- .../xcschemes/SQLite visionOS.xcscheme | 71 ++++ Tests/SQLite visionOS.xctestplan | 24 ++ 3 files changed, 435 insertions(+), 4 deletions(-) create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme create mode 100644 Tests/SQLite visionOS.xctestplan diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..857c6fd9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,6 +211,76 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DEB306BA2B61CEF500F9D46B /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DEB306BC2B61CEF500F9D46B /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; + DEB306BD2B61CEF500F9D46B /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + DEB306BE2B61CEF500F9D46B /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* RTree.swift */; }; + DEB306BF2B61CEF500F9D46B /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; + DEB306C02B61CEF500F9D46B /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + DEB306C12B61CEF500F9D46B /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; + DEB306C22B61CEF500F9D46B /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; + DEB306C32B61CEF500F9D46B /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFE1C3F06E900AE3E12 /* Expression.swift */; }; + DEB306C42B61CEF500F9D46B /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; + DEB306C52B61CEF500F9D46B /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; + DEB306C62B61CEF500F9D46B /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; + DEB306C72B61CEF500F9D46B /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + DEB306C82B61CEF500F9D46B /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */; }; + DEB306C92B61CEF500F9D46B /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; + DEB306CA2B61CEF500F9D46B /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; + DEB306CB2B61CEF500F9D46B /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFF1C3F06E900AE3E12 /* Operators.swift */; }; + DEB306CC2B61CEF500F9D46B /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; + DEB306CD2B61CEF500F9D46B /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; + DEB306CE2B61CEF500F9D46B /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; + DEB306CF2B61CEF500F9D46B /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; + DEB306D02B61CEF500F9D46B /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + DEB306D12B61CEF500F9D46B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + DEB306D22B61CEF500F9D46B /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + DEB306D32B61CEF500F9D46B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + DEB306D42B61CEF500F9D46B /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + DEB306D52B61CEF500F9D46B /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DEB306D62B61CEF500F9D46B /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + DEB306D72B61CEF500F9D46B /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + DEB306D82B61CEF500F9D46B /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; + DEB306D92B61CEF500F9D46B /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DEB306DA2B61CEF500F9D46B /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + DEB306DB2B61CEF500F9D46B /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + DEB306DC2B61CEF500F9D46B /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + DEB306DD2B61CEF500F9D46B /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; + DEB306DE2B61CEF500F9D46B /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DEB306E02B61CEF500F9D46B /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; + DEB306EB2B61CF9500F9D46B /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; + DEB306EC2B61CF9500F9D46B /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + DEB306ED2B61CF9500F9D46B /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + DEB306EE2B61CF9500F9D46B /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; + DEB306EF2B61CF9500F9D46B /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; + DEB306F02B61CF9500F9D46B /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */; }; + DEB306F12B61CF9500F9D46B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; + DEB306F22B61CF9500F9D46B /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; + DEB306F32B61CF9500F9D46B /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; + DEB306F42B61CF9500F9D46B /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + DEB306F52B61CF9500F9D46B /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; + DEB306F62B61CF9500F9D46B /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; + DEB306F72B61CF9500F9D46B /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; + DEB306F82B61CF9500F9D46B /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; + DEB306F92B61CF9500F9D46B /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; + DEB306FA2B61CF9500F9D46B /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; + DEB306FB2B61CF9500F9D46B /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; + DEB306FC2B61CF9500F9D46B /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; + DEB306FD2B61CF9500F9D46B /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.swift */; }; + DEB306FE2B61CF9500F9D46B /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; + DEB306FF2B61CF9500F9D46B /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; + DEB307002B61CF9500F9D46B /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; + DEB307012B61CF9500F9D46B /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + DEB307022B61CF9500F9D46B /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; + DEB307032B61CF9500F9D46B /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; + DEB307042B61CF9500F9D46B /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; + DEB307052B61CF9500F9D46B /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + DEB307062B61CF9500F9D46B /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; + DEB307072B61CF9500F9D46B /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; + DEB307082B61CF9500F9D46B /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; + DEB307092B61CF9500F9D46B /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; + DEB3070B2B61CF9500F9D46B /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; + DEB3070D2B61CF9500F9D46B /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; 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 */; }; @@ -263,6 +333,13 @@ remoteGlobalIDString = 03A65E591C6BB0F50062603F; remoteInfo = "SQLite tvOS"; }; + DEB307142B61D07F00F9D46B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DEB306B82B61CEF500F9D46B; + remoteInfo = "SQLite visionOS"; + }; EE247ADF1C3F04ED00AE3E12 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; @@ -340,6 +417,9 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + DEB306E52B61CEF500F9D46B /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests visionOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DEB307132B61D04500F9D46B /* SQLite visionOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "SQLite visionOS.xctestplan"; path = "Tests/SQLite visionOS.xctestplan"; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -401,6 +481,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306DF2B61CEF500F9D46B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306E02B61CEF500F9D46B /* libsqlite3.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DEB3070A2B61CF9500F9D46B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB3070B2B61CF9500F9D46B /* SQLite.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACF1C3F04ED00AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -517,6 +613,7 @@ 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, + DEB307132B61D04500F9D46B /* SQLite visionOS.xctestplan */, EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, 3D67B3E41DB2469200A4F4C6 /* Frameworks */, @@ -535,6 +632,8 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */, 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */, A121AC451CA35C79005A31D1 /* SQLite.framework */, + DEB306E52B61CEF500F9D46B /* SQLite.framework */, + DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */, ); name = Products; sourceTree = ""; @@ -679,6 +778,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306B92B61CEF500F9D46B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306BA2B61CEF500F9D46B /* SQLite.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD01C3F04ED00AE3E12 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -752,6 +859,42 @@ productReference = A121AC451CA35C79005A31D1 /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; + DEB306B82B61CEF500F9D46B /* SQLite visionOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = DEB306E22B61CEF500F9D46B /* Build configuration list for PBXNativeTarget "SQLite visionOS" */; + buildPhases = ( + DEB306B92B61CEF500F9D46B /* Headers */, + DEB306BB2B61CEF500F9D46B /* Sources */, + DEB306DF2B61CEF500F9D46B /* Frameworks */, + DEB306E12B61CEF500F9D46B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "SQLite visionOS"; + productName = SQLite; + productReference = DEB306E52B61CEF500F9D46B /* SQLite.framework */; + productType = "com.apple.product-type.framework"; + }; + DEB306E72B61CF9500F9D46B /* SQLiteTests visionOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = DEB3070E2B61CF9500F9D46B /* Build configuration list for PBXNativeTarget "SQLiteTests visionOS" */; + buildPhases = ( + DEB306EA2B61CF9500F9D46B /* Sources */, + DEB3070A2B61CF9500F9D46B /* Frameworks */, + DEB3070C2B61CF9500F9D46B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DEB307152B61D07F00F9D46B /* PBXTargetDependency */, + ); + name = "SQLiteTests visionOS"; + productName = "SQLite tvOSTests"; + productReference = DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; EE247AD21C3F04ED00AE3E12 /* SQLite iOS */ = { isa = PBXNativeTarget; buildConfigurationList = EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */; @@ -883,6 +1026,8 @@ 03A65E591C6BB0F50062603F /* SQLite tvOS */, 03A65E621C6BB0F60062603F /* SQLiteTests tvOS */, A121AC441CA35C79005A31D1 /* SQLite watchOS */, + DEB306B82B61CEF500F9D46B /* SQLite visionOS */, + DEB306E72B61CF9500F9D46B /* SQLiteTests visionOS */, ); }; /* End PBXProject section */ @@ -910,6 +1055,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306E12B61CEF500F9D46B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DEB3070C2B61CF9500F9D46B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB3070D2B61CF9500F9D46B /* Resources in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD11C3F04ED00AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1064,6 +1224,86 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306BB2B61CEF500F9D46B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306BC2B61CEF500F9D46B /* CoreFunctions.swift in Sources */, + DEB306BD2B61CEF500F9D46B /* Coding.swift in Sources */, + DEB306BE2B61CEF500F9D46B /* RTree.swift in Sources */, + DEB306BF2B61CEF500F9D46B /* Blob.swift in Sources */, + DEB306C02B61CEF500F9D46B /* URIQueryParameter.swift in Sources */, + DEB306C12B61CEF500F9D46B /* Foundation.swift in Sources */, + DEB306C22B61CEF500F9D46B /* Connection.swift in Sources */, + DEB306C32B61CEF500F9D46B /* Expression.swift in Sources */, + DEB306C42B61CEF500F9D46B /* Helpers.swift in Sources */, + DEB306C52B61CEF500F9D46B /* Collation.swift in Sources */, + DEB306C62B61CEF500F9D46B /* Setter.swift in Sources */, + DEB306C72B61CEF500F9D46B /* Connection+Attach.swift in Sources */, + DEB306C82B61CEF500F9D46B /* CustomFunctions.swift in Sources */, + DEB306C92B61CEF500F9D46B /* FTS4.swift in Sources */, + DEB306CA2B61CEF500F9D46B /* Value.swift in Sources */, + DEB306CB2B61CEF500F9D46B /* Operators.swift in Sources */, + DEB306CC2B61CEF500F9D46B /* Schema.swift in Sources */, + DEB306CD2B61CEF500F9D46B /* Query.swift in Sources */, + DEB306CE2B61CEF500F9D46B /* Statement.swift in Sources */, + DEB306CF2B61CEF500F9D46B /* AggregateFunctions.swift in Sources */, + DEB306D02B61CEF500F9D46B /* FTS5.swift in Sources */, + DEB306D12B61CEF500F9D46B /* Cipher.swift in Sources */, + DEB306D22B61CEF500F9D46B /* Backup.swift in Sources */, + DEB306D32B61CEF500F9D46B /* Errors.swift in Sources */, + DEB306D42B61CEF500F9D46B /* DateAndTimeFunctions.swift in Sources */, + DEB306D52B61CEF500F9D46B /* SQLiteVersion.swift in Sources */, + DEB306D62B61CEF500F9D46B /* Result.swift in Sources */, + DEB306D72B61CEF500F9D46B /* Query+with.swift in Sources */, + DEB306D82B61CEF500F9D46B /* Connection+Aggregation.swift in Sources */, + DEB306D92B61CEF500F9D46B /* SQLiteFeature.swift in Sources */, + DEB306DA2B61CEF500F9D46B /* SchemaChanger.swift in Sources */, + DEB306DB2B61CEF500F9D46B /* SchemaDefinitions.swift in Sources */, + DEB306DC2B61CEF500F9D46B /* Connection+Schema.swift in Sources */, + DEB306DD2B61CEF500F9D46B /* Connection+Pragmas.swift in Sources */, + DEB306DE2B61CEF500F9D46B /* SchemaReader.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DEB306EA2B61CF9500F9D46B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306EB2B61CF9500F9D46B /* TestHelpers.swift in Sources */, + DEB306EC2B61CF9500F9D46B /* FoundationTests.swift in Sources */, + DEB306ED2B61CF9500F9D46B /* Fixtures.swift in Sources */, + DEB306EE2B61CF9500F9D46B /* SchemaDefinitionsTests.swift in Sources */, + DEB306EF2B61CF9500F9D46B /* SchemaChangerTests.swift in Sources */, + DEB306F02B61CF9500F9D46B /* Connection+SchemaTests.swift in Sources */, + DEB306F12B61CF9500F9D46B /* FTS5Tests.swift in Sources */, + DEB306F22B61CF9500F9D46B /* FTSIntegrationTests.swift in Sources */, + DEB306F32B61CF9500F9D46B /* FTS4Tests.swift in Sources */, + DEB306F42B61CF9500F9D46B /* ExpressionTests.swift in Sources */, + DEB306F52B61CF9500F9D46B /* StatementTests.swift in Sources */, + DEB306F62B61CF9500F9D46B /* QueryTests.swift in Sources */, + DEB306F72B61CF9500F9D46B /* CipherTests.swift in Sources */, + DEB306F82B61CF9500F9D46B /* BlobTests.swift in Sources */, + DEB306F92B61CF9500F9D46B /* ConnectionTests.swift in Sources */, + DEB306FA2B61CF9500F9D46B /* CoreFunctionsTests.swift in Sources */, + DEB306FB2B61CF9500F9D46B /* DateAndTimeFunctionTests.swift in Sources */, + DEB306FC2B61CF9500F9D46B /* CustomFunctionsTests.swift in Sources */, + DEB306FD2B61CF9500F9D46B /* OperatorsTests.swift in Sources */, + DEB306FE2B61CF9500F9D46B /* ResultTests.swift in Sources */, + DEB306FF2B61CF9500F9D46B /* RTreeTests.swift in Sources */, + DEB307002B61CF9500F9D46B /* SchemaTests.swift in Sources */, + DEB307012B61CF9500F9D46B /* SelectTests.swift in Sources */, + DEB307022B61CF9500F9D46B /* ValueTests.swift in Sources */, + DEB307032B61CF9500F9D46B /* QueryIntegrationTests.swift in Sources */, + DEB307042B61CF9500F9D46B /* AggregateFunctionsTests.swift in Sources */, + DEB307052B61CF9500F9D46B /* CustomAggregationTests.swift in Sources */, + DEB307062B61CF9500F9D46B /* SetterTests.swift in Sources */, + DEB307072B61CF9500F9D46B /* RowTests.swift in Sources */, + DEB307082B61CF9500F9D46B /* Connection+PragmaTests.swift in Sources */, + DEB307092B61CF9500F9D46B /* Connection+AttachTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACE1C3F04ED00AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1232,6 +1472,11 @@ target = 03A65E591C6BB0F50062603F /* SQLite tvOS */; targetProxy = 03A65E651C6BB0F60062603F /* PBXContainerItemProxy */; }; + DEB307152B61D07F00F9D46B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DEB306B82B61CEF500F9D46B /* SQLite visionOS */; + targetProxy = DEB307142B61D07F00F9D46B /* PBXContainerItemProxy */; + }; EE247AE01C3F04ED00AE3E12 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = EE247AD21C3F04ED00AE3E12 /* SQLite iOS */; @@ -1365,6 +1610,83 @@ }; name = Release; }; + DEB306E32B61CEF500F9D46B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.14.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = xros; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Debug; + }; + DEB306E42B61CEF500F9D46B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.14.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = xros; + SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Release; + }; + DEB3070F2B61CF9500F9D46B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = xros; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Debug; + }; + DEB307102B61CF9500F9D46B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = xros; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Release; + }; EE247AE51C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1502,7 +1824,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1526,7 +1847,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1541,7 +1861,6 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1554,7 +1873,6 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1674,6 +1992,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DEB306E22B61CEF500F9D46B /* Build configuration list for PBXNativeTarget "SQLite visionOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEB306E32B61CEF500F9D46B /* Debug */, + DEB306E42B61CEF500F9D46B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DEB3070E2B61CF9500F9D46B /* Build configuration list for PBXNativeTarget "SQLiteTests visionOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEB3070F2B61CF9500F9D46B /* Debug */, + DEB307102B61CF9500F9D46B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme new file mode 100644 index 00000000..58783573 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/SQLite visionOS.xctestplan b/Tests/SQLite visionOS.xctestplan new file mode 100644 index 00000000..9b4e90ed --- /dev/null +++ b/Tests/SQLite visionOS.xctestplan @@ -0,0 +1,24 @@ +{ + "configurations" : [ + { + "id" : "72B91FD4-441C-4C06-9E92-CAEDCB7325AB", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:SQLite.xcodeproj", + "identifier" : "DEB306E72B61CF9500F9D46B", + "name" : "SQLiteTests visionOS" + } + } + ], + "version" : 1 +} From 9043277c83fcddf18d325192616ec5214c3a50df Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 6 Feb 2024 18:54:57 +0100 Subject: [PATCH 144/216] Using macOS 14 to build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cc2f56e..d4fc87d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ env: IOS_VERSION: "16.2" jobs: build: - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v2 - name: "Select Xcode" From 623ac53da9a23e6e8d53acdcc07291b8496580c5 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 6 Feb 2024 18:56:12 +0100 Subject: [PATCH 145/216] Xcode 15? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4fc87d8..ec25e7bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: # https://github.com/CocoaPods/CocoaPods/issues/11839 run: | xcode-select -p - sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer + sudo xcode-select -s /Applications/Xcode_15.0.1.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 0c68b99da0e8378a0d43c7ad6911b98e437dd8c1 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 6 Feb 2024 19:00:02 +0100 Subject: [PATCH 146/216] Testing using iOS 17 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec25e7bf..7be89270 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "iPhone 14" - IOS_VERSION: "16.2" + IOS_SIMULATOR: "iPhone 15" + IOS_VERSION: "17.2" jobs: build: runs-on: macos-14 From 03724bf3d28a16ac0197027d47956b8039c42e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elfred=20Pag=C3=A1n?= Date: Tue, 20 Feb 2024 20:06:37 -0600 Subject: [PATCH 147/216] make fromDatatypeValue throw When using custom types, sometimes decoding can fail, say due to changes in the type structure. In this case decoding would fail and the only way to handle it is forcing a crash. This change allows you to use `try row.get()` instead. Givng you the chance to handle the mismatch. --- Sources/SQLite/Core/Value.swift | 2 +- Sources/SQLite/Helpers.swift | 4 ++-- Sources/SQLite/Typed/Query.swift | 12 +++++------ Tests/SQLiteTests/Typed/RowTests.swift | 30 ++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Sources/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift index 9c463f0c..249a7728 100644 --- a/Sources/SQLite/Core/Value.swift +++ b/Sources/SQLite/Core/Value.swift @@ -39,7 +39,7 @@ public protocol Value: Expressible { // extensions cannot have inheritance claus static var declaredDatatype: String { get } - static func fromDatatypeValue(_ datatypeValue: Datatype) -> ValueType + static func fromDatatypeValue(_ datatypeValue: Datatype) throws -> ValueType var datatypeValue: Datatype { get } diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index adfadc8a..c27ccf05 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -121,9 +121,9 @@ func transcode(_ literal: Binding?) -> String { } } -// swiftlint:disable force_cast +// swiftlint:disable force_cast force_try func value(_ binding: Binding) -> A { - A.fromDatatypeValue(binding as! A.Datatype) as! A + try! A.fromDatatypeValue(binding as! A.Datatype) as! A } func value(_ binding: Binding?) -> A { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c59b728f..2a83665c 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1097,7 +1097,7 @@ extension Connection { public func scalar(_ query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) + return try V.fromDatatypeValue(value) } public func scalar(_ query: Select) throws -> V { @@ -1108,7 +1108,7 @@ extension Connection { public func scalar(_ query: Select) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) + return try V.fromDatatypeValue(value) } public func pluck(_ query: QueryType) throws -> Row? { @@ -1200,9 +1200,9 @@ public struct Row { } public func get(_ column: Expression) throws -> V? { - func valueAtIndex(_ idx: Int) -> V? { + func valueAtIndex(_ idx: Int) throws -> V? { guard let value = values[idx] as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) as? V + return try V.fromDatatypeValue(value) as? V } guard let idx = columnNames[column.template] else { @@ -1224,10 +1224,10 @@ public struct Row { similar: columnNames.keys.filter(similar).sorted() ) } - return valueAtIndex(columnNames[firstIndex].value) + return try valueAtIndex(columnNames[firstIndex].value) } - return valueAtIndex(idx) + return try valueAtIndex(idx) } public subscript(column: Expression) -> T { diff --git a/Tests/SQLiteTests/Typed/RowTests.swift b/Tests/SQLiteTests/Typed/RowTests.swift index 506f6b10..14fa373b 100644 --- a/Tests/SQLiteTests/Typed/RowTests.swift +++ b/Tests/SQLiteTests/Typed/RowTests.swift @@ -85,4 +85,34 @@ class RowTests: XCTestCase { } } } + + public func test_get_datatype_throws() { + // swiftlint:disable nesting + struct MyType: Value { + enum MyError: Error { + case failed + } + + public static var declaredDatatype: String { + Blob.declaredDatatype + } + + public static func fromDatatypeValue(_ dataValue: Blob) throws -> Data { + throw MyError.failed + } + + public var datatypeValue: Blob { + return Blob(bytes: []) + } + } + + let row = Row(["\"foo\"": 0], [Blob(bytes: [])]) + XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + if case MyType.MyError.failed = error { + XCTAssertTrue(true) + } else { + XCTFail("unexpected error: \(error)") + } + } + } } From 5bfaddf455a152a276e534b24c98ff08c67d3f63 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 23 Feb 2024 13:50:42 +0100 Subject: [PATCH 148/216] fix: json tests (due to a swift upgrade) --- Tests/SQLiteTests/TestHelpers.swift | 10 ++++++++++ Tests/SQLiteTests/Typed/QueryTests.swift | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 65cff8bb..e2da5927 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -105,6 +105,16 @@ func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclo XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } +func extractAndReplace(_ value: String, regex: String, with replacement: String) -> (String, String) { + // We cannot use `Regex` because it is not available before iOS 16 :( + let regex = try! NSRegularExpression(pattern: regex) + let valueRange = NSRange(location: 0, length: value.utf16.count) + let match = regex.firstMatch(in: value, options: [], range: valueRange)!.range + let range = Range(match, in: value)! + let extractedValue = String(value[range]) + return (value.replacingCharacters(in: range, with: replacement), extractedValue) +} + let table = Table("table") let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index 82a33808..f698c621 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -364,13 +364,22 @@ class QueryTests: XCTestCase { let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - assertSQL( + + let expectedSQL = """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', '\(encodedJSONString)') - """.replacingOccurrences(of: "\n", with: ""), - insert + """.replacingOccurrences(of: "\n", with: "") + + // As JSON serialization gives a different result each time, we extract JSON and compare it by deserializing it + // and keep comparing the query but with the json replaced by the `JSON` string + let (expectedQuery, expectedJSON) = extractAndReplace(expectedSQL, regex: "\\{.*\\}", with: "JSON") + let (actualQuery, actualJSON) = extractAndReplace(insert.asSQL(), regex: "\\{.*\\}", with: "JSON") + XCTAssertEqual(expectedQuery, actualQuery) + XCTAssertEqual( + try JSONDecoder().decode(TestCodable.self, from: expectedJSON.data(using: .utf8)!), + try JSONDecoder().decode(TestCodable.self, from: actualJSON.data(using: .utf8)!) ) } #endif From 485b677b8991287bf9b91de2b7d18f3eff21c38e Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Fri, 23 Feb 2024 14:26:22 +0100 Subject: [PATCH 149/216] Revert "Add privacy manifest" --- SQLite.xcodeproj/project.pbxproj | 16 ---------------- Sources/SQLite/PrivacyInfo.xcprivacy | 14 -------------- 2 files changed, 30 deletions(-) delete mode 100644 Sources/SQLite/PrivacyInfo.xcprivacy diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 8eb1df66..def32855 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,13 +211,6 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; - EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; 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 */; }; @@ -347,7 +340,6 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; - EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -554,7 +546,6 @@ EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, - EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */, EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, EE247AF91C3F06E900AE3E12 /* Typed */, @@ -901,7 +892,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -909,7 +899,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79828846FED005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -918,7 +907,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -926,7 +914,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -934,7 +921,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79628846FCC005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -943,7 +929,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -951,7 +936,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79928847055005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/SQLite/PrivacyInfo.xcprivacy b/Sources/SQLite/PrivacyInfo.xcprivacy deleted file mode 100644 index 987771fa..00000000 --- a/Sources/SQLite/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,14 +0,0 @@ - - - - - NSPrivacyTrackingDomains - - NSPrivacyCollectedDataTypes - - NSPrivacyAccessedAPITypes - - NSPrivacyTracking - - - From fec58b90b7a340377d16dfcce89027a6b6f81e26 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 23 Feb 2024 14:47:00 +0100 Subject: [PATCH 150/216] fix: ios deployment target --- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/xcschemes/SQLite visionOS.xcscheme | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4edddd67..b8aaaa8f 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '11.0' + ios_deployment_target = '17.0' tvos_deployment_target = '11.0' osx_deployment_target = '10.13' watchos_deployment_target = '4.0' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 857c6fd9..d23b6d64 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1736,7 +1736,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1795,7 +1795,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme index 58783573..c1536b66 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme @@ -15,7 +15,7 @@ @@ -55,7 +55,7 @@ From ce125ce64c03e9fcd142f18aca254637c223be8c Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 23 Feb 2024 15:37:04 +0100 Subject: [PATCH 151/216] fix: deployment targets? --- SQLite.swift.podspec | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b8aaaa8f..2a75f169 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -19,9 +19,9 @@ Pod::Spec.new do |s| s.swift_versions = ['5'] ios_deployment_target = '17.0' - tvos_deployment_target = '11.0' - osx_deployment_target = '10.13' - watchos_deployment_target = '4.0' + tvos_deployment_target = '17.0' + osx_deployment_target = '14.0' + watchos_deployment_target = '10.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target @@ -32,6 +32,11 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.library = 'sqlite3' + + ss.ios.deployment_target = ios_deployment_target + ss.tvos.deployment_target = tvos_deployment_target + ss.osx.deployment_target = osx_deployment_target + ss.watchos.deployment_target = watchos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -51,6 +56,11 @@ Pod::Spec.new do |s| 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_SWIFT_STANDALONE=1' } ss.dependency 'sqlite3' + + ss.ios.deployment_target = ios_deployment_target + ss.tvos.deployment_target = tvos_deployment_target + ss.osx.deployment_target = osx_deployment_target + ss.watchos.deployment_target = watchos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -68,6 +78,11 @@ Pod::Spec.new do |s| 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' } ss.dependency 'SQLCipher', '>= 4.0.0' + + ss.ios.deployment_target = ios_deployment_target + ss.tvos.deployment_target = tvos_deployment_target + ss.osx.deployment_target = osx_deployment_target + ss.watchos.deployment_target = watchos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' From e7813517c80c5bd79d4e7029ec8a89121f5afe60 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Feb 2024 12:51:28 -0800 Subject: [PATCH 152/216] Add privacy manifest - fixes build issue https://github.com/stephencelis/SQLite.swift/actions/runs/8019552944/job/21907598856 --- SQLite.xcodeproj/project.pbxproj | 10 ++++++++++ Sources/SQLite/PrivacyInfo.xcprivacy | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 Sources/SQLite/PrivacyInfo.xcprivacy diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 37c8a802..0b246a00 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -221,6 +221,10 @@ DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + EAE5A0372B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; + EAE5A0382B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; + EAE5A0392B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; + EAE5A03A2B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; 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 */; }; @@ -353,6 +357,7 @@ DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; + EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -560,6 +565,7 @@ EE247AD61C3F04ED00AE3E12 /* SQLite.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, + EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, @@ -908,6 +914,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A0392B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -923,6 +930,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A03A2B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -930,6 +938,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A0372B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -945,6 +954,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A0382B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/PrivacyInfo.xcprivacy b/Sources/SQLite/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..987771fa --- /dev/null +++ b/Sources/SQLite/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + NSPrivacyTracking + + + From e78ae0220e17525a15ac68c697a155eb7a672a8e Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 24 Feb 2024 11:51:41 +0100 Subject: [PATCH 153/216] changelogs --- CHANGELOG.md | 33 ++++++++++++++++++++++++++++++-- Documentation/Index.md | 42 ++++++++++++++++++++++++++++++----------- README.md | 4 ++-- SQLite.swift.podspec | 2 +- Tests/SPM/Package.swift | 2 +- 5 files changed, 66 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 970b4ca1..4c5c1fba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ -0.15.0 (unreleased) +0.15.0 (24-02-2024), [diff][diff-0.15.0] ======================================== +* Fix incorrect behavior when preparing `SELECT *` preceded by a `WITH` ([#1179][]) +* Adds support for returning extended error codes ([#1178][]) +* Fix typos ([#1182][]) +* fix Xcode build error ([#1192][]) +* Make the IndexDefinition properties public ([#1196][]) +* Fix GitHub Actions build badge ([#1200][]) +* Run CI on macOS 13 ([#1206][]) +* SchemaReader: return the correct column definition for a composite primary key ([#1217][]) +* Add optional support for decoding ([#1224][]) +* make fromDatatypeValue throw ([#1242][]) +* Implements built-in window functions ([#1228][]) +* Fix column affinity parsing to match how SQLite determines affinity ([#1218][]) +* Handle FK definitions w/o key references ([#1210][]) +* Add privacy manifest ([#1245][]) * New minimum deployment targets: iOS/tvOS 11.0, watchOS 4.0 0.14.1 (01-11-2022), [diff][diff-0.14.1] @@ -14,7 +28,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). * Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) -* Expose connection flags (via `URIQueryParameter`) to open db ([#1074][])) +* Expose connection flags (via `URIQueryParameter`) to open db ([#1074][]) * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) @@ -140,6 +154,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 [diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 [diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 +[diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -206,3 +221,17 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146 [#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148 [#1167]: https://github.com/stephencelis/SQLite.swift/pull/1167 +[#1179]: https://github.com/stephencelis/SQLite.swift/pull/1179 +[#1178]: https://github.com/stephencelis/SQLite.swift/pull/1178 +[#1182]: https://github.com/stephencelis/SQLite.swift/pull/1182 +[#1192]: https://github.com/stephencelis/SQLite.swift/pull/1192 +[#1196]: https://github.com/stephencelis/SQLite.swift/pull/1196 +[#1200]: https://github.com/stephencelis/SQLite.swift/pull/1200 +[#1206]: https://github.com/stephencelis/SQLite.swift/pull/1206 +[#1217]: https://github.com/stephencelis/SQLite.swift/pull/1217 +[#1224]: https://github.com/stephencelis/SQLite.swift/pull/1224 +[#1242]: https://github.com/stephencelis/SQLite.swift/pull/1242 +[#1228]: https://github.com/stephencelis/SQLite.swift/pull/1228 +[#1218]: https://github.com/stephencelis/SQLite.swift/pull/1218 +[#1210]: https://github.com/stephencelis/SQLite.swift/pull/1210 +[#1245]: https://github.com/stephencelis/SQLite.swift/pull/1245 diff --git a/Documentation/Index.md b/Documentation/Index.md index a03c26a3..7f900bc5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1,21 +1,24 @@ # SQLite.swift Documentation +- [SQLite.swift Documentation](#sqliteswift-documentation) - [Installation](#installation) - [Swift Package Manager](#swift-package-manager) - [Carthage](#carthage) - [CocoaPods](#cocoapods) + - [Requiring a specific version of SQLite](#requiring-a-specific-version-of-sqlite) + - [Using SQLite.swift with SQLCipher](#using-sqliteswift-with-sqlcipher) - [Manual](#manual) - [Getting Started](#getting-started) - [Connecting to a Database](#connecting-to-a-database) - [Read-Write Databases](#read-write-databases) - [Read-Only Databases](#read-only-databases) - - [In a Shared Group Container](#in-a-shared-group-container) + - [In a shared group container](#in-a-shared-group-container) - [In-Memory Databases](#in-memory-databases) - [URI parameters](#uri-parameters) - [Thread-Safety](#thread-safety) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) - - [Compound Expressions](#compound-expressions) + - [Compound Expressions](#compound-expressions) - [Queries](#queries) - [Creating a Table](#creating-a-table) - [Create Table Options](#create-table-options) @@ -24,8 +27,11 @@ - [Inserting Rows](#inserting-rows) - [Handling SQLite errors](#handling-sqlite-errors) - [Setters](#setters) + - [Infix Setters](#infix-setters) + - [Postfix Setters](#postfix-setters) - [Selecting Rows](#selecting-rows) - [Iterating and Accessing Values](#iterating-and-accessing-values) + - [Failable iteration](#failable-iteration) - [Plucking Rows](#plucking-rows) - [Building Complex Queries](#building-complex-queries) - [Selecting Columns](#selecting-columns) @@ -34,6 +40,9 @@ - [Table Aliasing](#table-aliasing) - [Filtering Rows](#filtering-rows) - [Filter Operators and Functions](#filter-operators-and-functions) + - [Infix Filter Operators](#infix-filter-operators) + - [Prefix Filter Operators](#prefix-filter-operators) + - [Filtering Functions](#filtering-functions) - [Sorting Rows](#sorting-rows) - [Limiting and Paging Results](#limiting-and-paging-results) - [Recursive and Hierarchical Queries](#recursive-and-hierarchical-queries) @@ -43,13 +52,14 @@ - [Deleting Rows](#deleting-rows) - [Transactions and Savepoints](#transactions-and-savepoints) - [Querying the Schema](#querying-the-schema) + - [Indexes and Columns](#indexes-and-columns) - [Altering the Schema](#altering-the-schema) - [Renaming Tables](#renaming-tables) - [Dropping Tables](#dropping-tables) - [Adding Columns](#adding-columns) - [Added Column Constraints](#added-column-constraints) - - [Schema Changer](#schemachanger) - - [Adding Columns](#adding-columns) + - [SchemaChanger](#schemachanger) + - [Adding Columns](#adding-columns-1) - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - [Renaming/Dropping Tables](#renamingdropping-tables) @@ -61,17 +71,27 @@ - [Date-Time Values](#date-time-values) - [Binary Data](#binary-data) - [Codable Types](#codable-types) + - [Inserting Codable Types](#inserting-codable-types) + - [Updating Codable Types](#updating-codable-types) + - [Retrieving Codable Types](#retrieving-codable-types) + - [Restrictions](#restrictions) - [Other Operators](#other-operators) + - [Other Infix Operators](#other-infix-operators) + - [Other Prefix Operators](#other-prefix-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) - [Window SQLite Functions](#window-sqlite-functions) - - [Date and Time Functions](#date-and-time-functions) + - [Date and Time functions](#date-and-time-functions) - [Custom SQL Functions](#custom-sql-functions) + - [Custom Aggregations](#custom-aggregations) - [Custom Collations](#custom-collations) - [Full-text Search](#full-text-search) + - [FTS5](#fts5) - [Executing Arbitrary SQL](#executing-arbitrary-sql) + - [Online Database Backup](#online-database-backup) - [Attaching and detaching databases](#attaching-and-detaching-databases) - [Logging](#logging) + - [Vacuum](#vacuum) [↩]: #sqliteswift-documentation @@ -88,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") ] ``` @@ -109,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.1 + github "stephencelis/SQLite.swift" ~> 0.15.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -139,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.14.1' + pod 'SQLite.swift', '~> 0.15.0' end ``` @@ -153,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.1' + pod 'SQLite.swift/standalone', '~> 0.15.0' end ``` @@ -163,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.1' + pod 'SQLite.swift/standalone', '~> 0.15.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -179,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.14.1' + pod 'SQLite.swift/SQLCipher', '~> 0.15.0' end ``` diff --git a/README.md b/README.md index e6e5a894..117edb5a 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.1 + github "stephencelis/SQLite.swift" ~> 0.15.0 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4edddd67..b691aeab 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.14.1" + s.version = "0.15.0" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 3c446ae6..c82b3b9e 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 20ebe9b334ff557c1166e26ca6c89e94a77a7f2d Mon Sep 17 00:00:00 2001 From: Chris Stockbridge Date: Mon, 4 Mar 2024 20:07:22 -0500 Subject: [PATCH 154/216] Adding failing test that includes an optional struct This test passes with release 0.14.1, but fails starting with 0.15 --- .../Typed/QueryIntegrationTests.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index aa45cafe..7ac72d0c 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -152,6 +152,33 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(values.count, 2) } + func test_insert_custom_encodable_type() throws { + struct TestTypeWithOptionalArray: Codable { + var myInt: Int + var myString: String + var myOptionalArray: [Int]? + } + + let table = Table("custom_codable") + try db.run(table.create { builder in + builder.column(Expression("myInt")) + builder.column(Expression("myString")) + builder.column(Expression("myOptionalArray")) + }) + + let customType = TestTypeWithOptionalArray(myInt: 13, myString: "foo", myOptionalArray: [1, 2, 3]) + try db.run(table.insert(customType)) + let rows = try db.prepare(table) + let values: [TestTypeWithOptionalArray] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 1, "return one optional custom type") + + let customTypeWithNil = TestTypeWithOptionalArray(myInt: 123, myString: "String", myOptionalArray: nil) + try db.run(table.insert(customTypeWithNil)) + let rowsNil = try db.prepare(table) + let valuesNil: [TestTypeWithOptionalArray] = try rowsNil.map({ try $0.decode() }) + XCTAssertEqual(valuesNil.count, 2, "return two custom objects, including one that contains a nil optional") + } + func test_upsert() throws { try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 24)) let fetchAge = { () throws -> Int? in From bad16cb5d1a09e93bbdb40f50df5f222920a10e3 Mon Sep 17 00:00:00 2001 From: Chris Stockbridge Date: Tue, 5 Mar 2024 11:11:50 -0500 Subject: [PATCH 155/216] Bugfix: returning nil when decoding an optional should not throw an error --- Sources/SQLite/Typed/Coding.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index bfddc5ee..d0061851 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -504,9 +504,8 @@ private class SQLiteDecoder: Decoder { case is UUID.Type: return try? row.get(Expression(key.stringValue)) as? T default: - guard let JSONString = try? row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, - debugDescription: "an unsupported type was found")) + guard let JSONString = try row.get(Expression(key.stringValue)) else { + return nil } guard let data = JSONString.data(using: .utf8) else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, From cda8a1274a5c426c043bea6d381fddab630dad95 Mon Sep 17 00:00:00 2001 From: Steven Date: Sun, 17 Mar 2024 01:10:17 +0800 Subject: [PATCH 156/216] Update CoreFunctions.swift fix typo fix lower to upper --- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 4429bb5f..c4359d8b 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -224,7 +224,7 @@ extension ExpressionType where UnderlyingType == String { /// /// let name = Expression("name") /// name.uppercaseString - /// // lower("name") + /// // upper("name") /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { From 6d821f83499500cb7bb7f258655fe83ac6f890ed Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Sat, 13 Apr 2024 08:23:49 +0530 Subject: [PATCH 157/216] Add dependency on custom cocoapods fork --- Gemfile | 1 + Gemfile.lock | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..2f97c391 --- /dev/null +++ b/Gemfile @@ -0,0 +1 @@ +gem 'cocoapods', :git => 'https://github.com/SagarSDagdu/CocoaPods.git', tag: '1.15.2.1-sagard' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..75374a30 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,116 @@ +GIT + remote: https://github.com/SagarSDagdu/CocoaPods.git + revision: d96f491f79abd2804d1359c5228cce404dd365b7 + tag: 1.15.2.1-sagard + specs: + cocoapods (1.15.2.1.pre.sagard) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.15.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + +GEM + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.1.3.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + base64 (0.2.0) + bigdecimal (3.1.5) + claide (1.1.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + drb (2.2.0) + ruby2_keywords + escape (0.0.4) + ethon (0.16.0) + ffi (>= 1.15.0) + ffi (1.16.3) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (1.14.4) + concurrent-ruby (~> 1.0) + json (2.7.1) + minitest (5.22.0) + molinillo (0.8.0) + mutex_m (0.2.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + nkf (0.1.3) + public_suffix (4.0.7) + rexml (3.2.6) + ruby-macho (2.5.1) + ruby2_keywords (0.0.5) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + cocoapods! + +BUNDLED WITH + 2.5.4 From 6491e381d7f827ced234e6aa7edca0a92f25eff6 Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Sat, 13 Apr 2024 08:24:03 +0530 Subject: [PATCH 158/216] Update deployment targets for Xcode 15 --- SQLite.swift.podspec | 4 ++-- SQLite.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b691aeab..4a7a390c 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,8 +18,8 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '11.0' - tvos_deployment_target = '11.0' + ios_deployment_target = '12.0' + tvos_deployment_target = '12.0' osx_deployment_target = '10.13' watchos_deployment_target = '4.0' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 0b246a00..c5215807 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1299,7 +1299,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1321,7 +1321,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1335,7 +1335,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1349,7 +1349,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1450,7 +1450,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1459,7 +1459,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -1509,7 +1509,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; @@ -1517,7 +1517,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1538,7 +1538,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1562,7 +1562,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1577,7 +1577,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1590,7 +1590,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; From c4ec236be4b089984c8239869309bd4c637642b6 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 15:17:15 +0200 Subject: [PATCH 159/216] using temp cocoapods (fix xcode 15 build, to be reverted later) --- run-tests.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 49330c12..1bf8de96 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,10 +7,11 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then + bundle install if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - pod lib lint --no-subspecs --fail-fast + bundle exec pod lib lint --no-subspecs --fail-fast else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From a6e7697bac187dd6ee5ee8026e7fca91235e5b73 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 15:31:08 +0200 Subject: [PATCH 160/216] fixing bundle versions? --- Gemfile.lock | 2 +- run-tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 75374a30..159cbf9c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,7 +82,7 @@ GEM i18n (1.14.4) concurrent-ruby (~> 1.0) json (2.7.1) - minitest (5.22.0) + minitest (5.22.3) molinillo (0.8.0) mutex_m (0.2.0) nanaimo (0.3.0) diff --git a/run-tests.sh b/run-tests.sh index 1bf8de96..13975f0e 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,7 +7,7 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then - bundle install + bundle install --deployment if [ "$VALIDATOR_SUBSPEC" == "none" ]; then bundle exec pod lib lint --no-subspecs --fail-fast else From 9bed2450cbd38352a5a3316b2817bec8baf4f5d3 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 15:38:17 +0200 Subject: [PATCH 161/216] adding source for gems as otherwise it does not resolve correctly in CI --- Gemfile | 2 ++ Gemfile.lock | 11 +++++------ run-tests.sh | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 2f97c391..eb6036cd 100644 --- a/Gemfile +++ b/Gemfile @@ -1 +1,3 @@ +source "https://rubygems.org" + gem 'cocoapods', :git => 'https://github.com/SagarSDagdu/CocoaPods.git', tag: '1.15.2.1-sagard' diff --git a/Gemfile.lock b/Gemfile.lock index 159cbf9c..a58783ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,6 +23,7 @@ GIT xcodeproj (>= 1.23.0, < 2.0) GEM + remote: https://rubygems.org/ specs: CFPropertyList (3.0.7) base64 @@ -45,7 +46,7 @@ GEM json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.5) + bigdecimal (3.1.7) claide (1.1.0) cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) @@ -69,8 +70,7 @@ GEM colored2 (3.1.2) concurrent-ruby (1.2.3) connection_pool (2.4.1) - drb (2.2.0) - ruby2_keywords + drb (2.2.1) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) @@ -81,18 +81,17 @@ GEM httpclient (2.8.3) i18n (1.14.4) concurrent-ruby (~> 1.0) - json (2.7.1) + json (2.7.2) minitest (5.22.3) molinillo (0.8.0) mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - nkf (0.1.3) + nkf (0.2.0) public_suffix (4.0.7) rexml (3.2.6) ruby-macho (2.5.1) - ruby2_keywords (0.0.5) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) diff --git a/run-tests.sh b/run-tests.sh index 13975f0e..1bf8de96 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,7 +7,7 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then - bundle install --deployment + bundle install if [ "$VALIDATOR_SUBSPEC" == "none" ]; then bundle exec pod lib lint --no-subspecs --fail-fast else From 6be8ca943138e3b3d77bb14b74c1b498d75b0cf4 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 16:30:09 +0200 Subject: [PATCH 162/216] fix carthage location? --- Tests/Carthage/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index b44d85b9..fe708ab0 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -1,4 +1,4 @@ -CARTHAGE := /usr/local/bin/carthage +CARTHAGE := $(shell which carthage) CARTHAGE_PLATFORM := iOS CARTHAGE_CONFIGURATION := Release CARTHAGE_DIR := Carthage From a2a550e6266e80ebe4d896d1360cff4ef9ad202a Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 17:18:49 +0200 Subject: [PATCH 163/216] bump xcode version + carthage visionOS tests --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7be89270..9fbba1c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: # https://github.com/CocoaPods/CocoaPods/issues/11839 run: | xcode-select -p - sudo xcode-select -s /Applications/Xcode_15.0.1.app/Contents/Developer + sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" @@ -64,6 +64,10 @@ jobs: env: CARTHAGE_PLATFORM: tvOS run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: visionOS)" + env: + CARTHAGE_PLATFORM: visionOS + run: ./run-tests.sh build-linux: runs-on: ubuntu-latest steps: From f0af5e0a2b6917ae38e3019e3456fd0fa9365864 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 18:55:58 +0200 Subject: [PATCH 164/216] Changelogs v0.15.1 --- CHANGELOG.md | 13 +++++++++++++ Documentation/Index.md | 12 ++++++------ README.md | 4 ++-- SQLite.swift.podspec | 2 +- Tests/SPM/Package.swift | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c5c1fba..79085ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +0.15.1 (14-04-2024), [diff][diff-0.15.1] +======================================== + +* Update CoreFunctions.swift fix typo ([#1249][]) +* Fix #1247 support nil case when decoding optionals ([#1248][]) +* Change deployment targets for Xcode 15 and add dependency on custom Cocoapods fork ([#1255][]) +* Add VisionOS support ([#1237][]) + 0.15.0 (24-02-2024), [diff][diff-0.15.0] ======================================== @@ -155,6 +163,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 [diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 [diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 +[diff-0.15.1]: https://github.com/stephencelis/SQLite.swift/compare/0.15.0...0.15.1 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -235,3 +244,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1218]: https://github.com/stephencelis/SQLite.swift/pull/1218 [#1210]: https://github.com/stephencelis/SQLite.swift/pull/1210 [#1245]: https://github.com/stephencelis/SQLite.swift/pull/1245 +[#1249]: https://github.com/stephencelis/SQLite.swift/pull/1249 +[#1248]: https://github.com/stephencelis/SQLite.swift/pull/1248 +[#1255]: https://github.com/stephencelis/SQLite.swift/pull/1255 +[#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 diff --git a/Documentation/Index.md b/Documentation/Index.md index 7f900bc5..edb9dab3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -108,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") ] ``` @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.0 + github "stephencelis/SQLite.swift" ~> 0.15.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.0' + pod 'SQLite.swift', '~> 0.15.1' end ``` @@ -173,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.0' + pod 'SQLite.swift/standalone', '~> 0.15.1' end ``` @@ -183,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.0' + pod 'SQLite.swift/standalone', '~> 0.15.1' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -199,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.0' + pod 'SQLite.swift/SQLCipher', '~> 0.15.1' end ``` diff --git a/README.md b/README.md index 117edb5a..51090e5d 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.0 + github "stephencelis/SQLite.swift" ~> 0.15.1 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 860fe3d4..035fffb0 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.15.0" + s.version = "0.15.1" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index c82b3b9e..5a96daff 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From fc236997c8e1b21be4ecaaa547496857483dcf6b Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Tue, 16 Apr 2024 05:40:47 +0200 Subject: [PATCH 165/216] fix: visionos to cocoapods --- CHANGELOG.md | 6 ++++++ Documentation/Index.md | 12 ++++++------ README.md | 4 ++-- SQLite.swift.podspec | 7 ++++++- Tests/SPM/Package.swift | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79085ce3..376c19d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.15.2 (16-04-2024), [diff][diff-0.15.2] +======================================== +* fix: visionos to cocoapods ([#1260][]) + 0.15.1 (14-04-2024), [diff][diff-0.15.1] ======================================== @@ -164,6 +168,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 [diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 [diff-0.15.1]: https://github.com/stephencelis/SQLite.swift/compare/0.15.0...0.15.1 +[diff-0.15.2]: https://github.com/stephencelis/SQLite.swift/compare/0.15.1...0.15.2 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -248,3 +253,4 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1248]: https://github.com/stephencelis/SQLite.swift/pull/1248 [#1255]: https://github.com/stephencelis/SQLite.swift/pull/1255 [#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 +[#1260]: https://github.com/stephencelis/SQLite.swift/pull/1260 diff --git a/Documentation/Index.md b/Documentation/Index.md index edb9dab3..adbbff8b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -108,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") ] ``` @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.1 + github "stephencelis/SQLite.swift" ~> 0.15.2 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.1' + pod 'SQLite.swift', '~> 0.15.2' end ``` @@ -173,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.1' + pod 'SQLite.swift/standalone', '~> 0.15.2' end ``` @@ -183,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.1' + pod 'SQLite.swift/standalone', '~> 0.15.2' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -199,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.1' + pod 'SQLite.swift/SQLCipher', '~> 0.15.2' end ``` diff --git a/README.md b/README.md index 51090e5d..a87ab937 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.1 + github "stephencelis/SQLite.swift" ~> 0.15.2 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 035fffb0..efed67c1 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.15.1" + s.version = "0.15.2" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC @@ -22,11 +22,13 @@ Pod::Spec.new do |s| tvos_deployment_target = '12.0' osx_deployment_target = '10.13' watchos_deployment_target = '4.0' + visionos_deployment_target = '1.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target s.osx.deployment_target = osx_deployment_target s.watchos.deployment_target = watchos_deployment_target + s.visionos.deployment_target = visionos_deployment_target s.subspec 'standard' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' @@ -37,6 +39,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target + ss.visionos.deployment_target = visionos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -61,6 +64,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target + ss.visionos.deployment_target = visionos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -83,6 +87,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target + ss.visionos.deployment_target = visionos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 5a96daff..b73ecd83 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 8f75f4609221c55901cf60b1438b7a8441af145e Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Tue, 16 Apr 2024 06:06:25 +0200 Subject: [PATCH 166/216] gonna fix that later --- SQLite.swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index efed67c1..88713c9e 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -87,7 +87,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target - ss.visionos.deployment_target = visionos_deployment_target + #ss.visionos.deployment_target = visionos_deployment_target # Not supported by SQLCipher for now ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' From 15fe07e3d5d0555a7f03966114ce5a9390823af7 Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Thu, 18 Apr 2024 18:57:43 +0530 Subject: [PATCH 167/216] Update the marketing version to the version we will be releasing next For recent releases, the `Info.plist` shipped inside the `xcframeworks` do not match the release version. Fixing that to reflect the marketing version we will be releasing next. --- SQLite.xcodeproj/project.pbxproj | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 79de22e7..a2d7edbe 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -218,6 +218,9 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; DEB306BA2B61CEF500F9D46B /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; DEB306BC2B61CEF500F9D46B /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; DEB306BD2B61CEF500F9D46B /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; @@ -288,9 +291,6 @@ DEB307092B61CF9500F9D46B /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; DEB3070B2B61CF9500F9D46B /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; DEB3070D2B61CF9500F9D46B /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; - DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; - DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; - DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; EAE5A0372B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; EAE5A0382B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; EAE5A0392B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; @@ -433,10 +433,10 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; DEB306E52B61CEF500F9D46B /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests visionOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DEB307132B61D04500F9D46B /* SQLite visionOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "SQLite visionOS.xctestplan"; path = "Tests/SQLite visionOS.xctestplan"; sourceTree = ""; }; - DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -1539,6 +1539,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1561,6 +1562,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1612,6 +1614,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1636,6 +1639,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1660,7 +1664,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1685,7 +1689,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1862,7 +1866,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1886,7 +1890,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1936,6 +1940,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1961,6 +1966,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; From 5d3db22d9b1e979a3436d199f10bc7dddd4d3a39 Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Thu, 18 Apr 2024 21:26:22 +0530 Subject: [PATCH 168/216] Update `podspec` to include privacy manifest Update `podspec` to include privacy manifest --- SQLite.swift.podspec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 88713c9e..e6aa2c6a 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -34,6 +34,7 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.library = 'sqlite3' + ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } ss.ios.deployment_target = ios_deployment_target ss.tvos.deployment_target = tvos_deployment_target @@ -53,6 +54,7 @@ Pod::Spec.new do |s| s.subspec 'standalone' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' + ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', @@ -77,6 +79,8 @@ Pod::Spec.new do |s| s.subspec 'SQLCipher' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' + ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } + ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' From a95fc6df17d108bd99210db5e8a9bac90fe984b8 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 19 Apr 2024 02:50:43 +0200 Subject: [PATCH 169/216] v0.15.3 (oups) --- CHANGELOG.md | 5 +++++ Documentation/Index.md | 12 ++++++------ README.md | 4 ++-- SQLite.swift.podspec | 2 +- Tests/SPM/Package.swift | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 376c19d8..c96724fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.15.3 (19-04-2024), [diff][diff-0.15.3] +======================================== +* Update `podspec` to include privacy manifest ([#1265][]) + 0.15.2 (16-04-2024), [diff][diff-0.15.2] ======================================== * fix: visionos to cocoapods ([#1260][]) @@ -254,3 +258,4 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1255]: https://github.com/stephencelis/SQLite.swift/pull/1255 [#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 [#1260]: https://github.com/stephencelis/SQLite.swift/pull/1260 +[#1265]: https://github.com/stephencelis/SQLite.swift/pull/1265 diff --git a/Documentation/Index.md b/Documentation/Index.md index adbbff8b..fcd2d642 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -108,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") ] ``` @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.2 + github "stephencelis/SQLite.swift" ~> 0.15.3 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.2' + pod 'SQLite.swift', '~> 0.15.3' end ``` @@ -173,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.2' + pod 'SQLite.swift/standalone', '~> 0.15.3' end ``` @@ -183,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.2' + pod 'SQLite.swift/standalone', '~> 0.15.3' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -199,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.2' + pod 'SQLite.swift/SQLCipher', '~> 0.15.3' end ``` diff --git a/README.md b/README.md index a87ab937..a4d713b6 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.2 + github "stephencelis/SQLite.swift" ~> 0.15.3 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index e6aa2c6a..184ce23e 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.15.2" + s.version = "0.15.3" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index b73ecd83..6521211a 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 363141ac6c070799239593cf6dcad63fe33edd17 Mon Sep 17 00:00:00 2001 From: YiKai Deng Date: Thu, 26 Sep 2024 17:33:15 +0800 Subject: [PATCH 170/216] Add `CustomStringConvertible` for Setter --- Documentation/Upgrading.md | 4 +- Sources/SQLite/Typed/Setter.swift | 6 ++ .../Core/Connection+AttachTests.swift | 4 +- .../SQLiteTests/Core/CoreFunctionsTests.swift | 16 ++--- Tests/SQLiteTests/Core/StatementTests.swift | 4 +- Tests/SQLiteTests/Extensions/FTS4Tests.swift | 6 +- .../Extensions/FTSIntegrationTests.swift | 2 +- .../Schema/SchemaChangerTests.swift | 2 +- .../Schema/SchemaReaderTests.swift | 4 +- Tests/SQLiteTests/TestHelpers.swift | 32 ++++----- .../Typed/CustomFunctionsTests.swift | 28 ++++---- Tests/SQLiteTests/Typed/ExpressionTests.swift | 10 +-- Tests/SQLiteTests/Typed/OperatorsTests.swift | 2 +- .../Typed/QueryIntegrationTests.swift | 70 +++++++++---------- Tests/SQLiteTests/Typed/QueryTests.swift | 22 +++--- Tests/SQLiteTests/Typed/RowTests.swift | 22 +++--- Tests/SQLiteTests/Typed/SelectTests.swift | 8 +-- Tests/SQLiteTests/Typed/SetterTests.swift | 3 + .../Typed/WindowFunctionsTests.swift | 4 +- 19 files changed, 130 insertions(+), 119 deletions(-) diff --git a/Documentation/Upgrading.md b/Documentation/Upgrading.md index f2cc2ecb..0e12aacf 100644 --- a/Documentation/Upgrading.md +++ b/Documentation/Upgrading.md @@ -4,6 +4,8 @@ - `Expression.asSQL()` is no longer available. Expressions now implement `CustomStringConvertible`, where `description` returns the SQL. -- `Statement.prepareRowIterator()` is now longer available. Instead, use the methods +- `Statement.prepareRowIterator()` is no longer available. Instead, use the methods of the same name on `Connection`. - `Connection.registerTokenizer` is no longer available to register custom FTS4 tokenizers. +- `Setter.asSQL()` is no longer available. Instead, Setter now implement `CustomStringConvertible`, + where `description` returns the SQL. diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 7910cab8..8dc8a0e0 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -75,6 +75,12 @@ extension Setter: Expressible { } +extension Setter: CustomStringConvertible { + public var description: String { + asSQL() + } +} + public func <-(column: Expression, value: Expression) -> Setter { Setter(column: column, value: value) } diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift index 940a30ca..f37300ca 100644 --- a/Tests/SQLiteTests/Core/Connection+AttachTests.swift +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -19,7 +19,7 @@ class ConnectionAttachTests: SQLiteTestCase { try db.attach(.inMemory, as: schemaName) let table = Table("attached_users", database: schemaName) - let name = Expression("string") + let name = SQLite.Expression("string") // create a table, insert some data try db.run(table.create { builder in @@ -41,7 +41,7 @@ class ConnectionAttachTests: SQLiteTestCase { try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) let table = Table("tests", database: schemaName) - let email = Expression("email") + let email = SQLite.Expression("email") let rows = try db.prepare(table.select(email)).map { $0[email] } XCTAssertEqual(["foo@bar.com"], rows) diff --git a/Tests/SQLiteTests/Core/CoreFunctionsTests.swift b/Tests/SQLiteTests/Core/CoreFunctionsTests.swift index e03e3769..b866c4e9 100644 --- a/Tests/SQLiteTests/Core/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/Core/CoreFunctionsTests.swift @@ -12,8 +12,8 @@ class CoreFunctionsTests: XCTestCase { } func test_random_generatesExpressionWithRandomFunction() { - assertSQL("random()", Expression.random()) - assertSQL("random()", Expression.random()) + assertSQL("random()", SQLite.Expression.random()) + assertSQL("random()", SQLite.Expression.random()) } func test_length_wrapsStringExpressionWithLengthFunction() { @@ -38,14 +38,14 @@ 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\")", string.like(SQLite.Expression("a"))) + assertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(SQLite.Expression("a"))) - assertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) - assertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) + assertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(SQLite.Expression("a"), escape: "\\")) + assertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(SQLite.Expression("a"), escape: "\\")) - assertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) - assertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) + assertSQL("('string' LIKE \"a\")", "string".like(SQLite.Expression("a"))) + assertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(SQLite.Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 5f212505..dbf99d7c 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -27,7 +27,7 @@ class StatementTests: SQLiteTestCase { func test_zero_sized_blob_returns_null() throws { let blobs = Table("blobs") - let blobColumn = Expression("blob_column") + let blobColumn = SQLite.Expression("blob_column") try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) @@ -38,7 +38,7 @@ class StatementTests: SQLiteTestCase { let names = ["a", "b", "c"] try insertUsers(names) - let emailColumn = Expression("email") + let emailColumn = SQLite.Expression("email") let statement = try db.prepare("SELECT email FROM users") let emails = try statement.prepareRowIterator().map { $0[emailColumn] } diff --git a/Tests/SQLiteTests/Extensions/FTS4Tests.swift b/Tests/SQLiteTests/Extensions/FTS4Tests.swift index 5e595007..f7258fb5 100644 --- a/Tests/SQLiteTests/Extensions/FTS4Tests.swift +++ b/Tests/SQLiteTests/Extensions/FTS4Tests.swift @@ -35,9 +35,9 @@ class FTS4Tests: XCTestCase { } func test_match_onVirtualTableAsExpression_compilesMatchExpression() { - assertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as Expression) - assertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression) - assertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression) + assertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as SQLite.Expression) + assertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as SQLite.Expression) + assertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as SQLite.Expression) } func test_match_onVirtualTableAsQueryType_compilesMatchExpression() { diff --git a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift index da9ba8dc..1129ae08 100644 --- a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift @@ -11,7 +11,7 @@ import SQLite3 @testable import SQLite class FTSIntegrationTests: SQLiteTestCase { - let email = Expression("email") + let email = SQLite.Expression("email") let index = VirtualTable("index") private func createIndex() throws { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 9f8e21f4..2bec6a06 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -92,7 +92,7 @@ class SchemaChangerTests: SQLiteTestCase { } func test_add_column() throws { - let column = Expression("new_column") + let column = SQLite.Expression("new_column") let newColumn = ColumnDefinition(name: "new_column", type: .TEXT, nullable: true, diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index d57ebff7..8c033e41 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -163,7 +163,7 @@ class SchemaReaderTests: SQLiteTestCase { try db.run(linkTable.create(block: { definition in definition.column(idColumn, primaryKey: .autoincrement) - definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) + definition.column(testIdColumn, unique: false, check: nil, references: users, SQLite.Expression("id")) })) let foreignKeys = try schemaReader.foreignKeys(table: "test_links") @@ -238,7 +238,7 @@ class SchemaReaderTests: SQLiteTestCase { } func test_objectDefinitions_indexes() throws { - let emailIndex = users.createIndex(Expression("email"), unique: false, ifNotExists: true) + let emailIndex = users.createIndex(SQLite.Expression("email"), unique: false, ifNotExists: true) try db.run(emailIndex) let indexes = try schemaReader.objectDefinitions(type: .index) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index e2da5927..56415a59 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -74,29 +74,29 @@ class SQLiteTestCase: XCTestCase { } -let bool = Expression("bool") -let boolOptional = Expression("boolOptional") +let bool = SQLite.Expression("bool") +let boolOptional = SQLite.Expression("boolOptional") -let data = Expression("blob") -let dataOptional = Expression("blobOptional") +let data = SQLite.Expression("blob") +let dataOptional = SQLite.Expression("blobOptional") -let date = Expression("date") -let dateOptional = Expression("dateOptional") +let date = SQLite.Expression("date") +let dateOptional = SQLite.Expression("dateOptional") -let double = Expression("double") -let doubleOptional = Expression("doubleOptional") +let double = SQLite.Expression("double") +let doubleOptional = SQLite.Expression("doubleOptional") -let int = Expression("int") -let intOptional = Expression("intOptional") +let int = SQLite.Expression("int") +let intOptional = SQLite.Expression("intOptional") -let int64 = Expression("int64") -let int64Optional = Expression("int64Optional") +let int64 = SQLite.Expression("int64") +let int64Optional = SQLite.Expression("int64Optional") -let string = Expression("string") -let stringOptional = Expression("stringOptional") +let string = SQLite.Expression("string") +let stringOptional = SQLite.Expression("stringOptional") -let uuid = Expression("uuid") -let uuidOptional = Expression("uuidOptional") +let uuid = SQLite.Expression("uuid") +let uuidOptional = SQLite.Expression("uuidOptional") let testUUIDValue = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! diff --git a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift index 0f46b380..8598b6fb 100644 --- a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift @@ -5,8 +5,8 @@ import SQLite #if !os(Linux) class CustomFunctionNoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = () -> Expression - typealias FunctionResultOptional = () -> Expression + typealias FunctionNoOptional = () -> SQLite.Expression + typealias FunctionResultOptional = () -> SQLite.Expression func testFunctionNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { @@ -26,10 +26,10 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { } class CustomFunctionWithOneArgTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression) -> Expression - typealias FunctionLeftOptional = (Expression) -> Expression - typealias FunctionResultOptional = (Expression) -> Expression - typealias FunctionLeftResultOptional = (Expression) -> Expression + typealias FunctionNoOptional = (SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftOptional = (SQLite.Expression) -> SQLite.Expression + typealias FunctionResultOptional = (SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftResultOptional = (SQLite.Expression) -> SQLite.Expression func testFunctionNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a in @@ -65,14 +65,14 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { } class CustomFunctionWithTwoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression, Expression) -> Expression - typealias FunctionLeftOptional = (Expression, Expression) -> Expression - typealias FunctionRightOptional = (Expression, Expression) -> Expression - typealias FunctionResultOptional = (Expression, Expression) -> Expression - typealias FunctionLeftRightOptional = (Expression, Expression) -> Expression - typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression - typealias FunctionRightResultOptional = (Expression, Expression) -> Expression - typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression + typealias FunctionNoOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionRightOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftRightOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionRightResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftRightResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression func testNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a, b in diff --git a/Tests/SQLiteTests/Typed/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift index 147d62e9..1b97fe94 100644 --- a/Tests/SQLiteTests/Typed/ExpressionTests.swift +++ b/Tests/SQLiteTests/Typed/ExpressionTests.swift @@ -4,17 +4,17 @@ import XCTest class ExpressionTests: XCTestCase { func test_asSQL_expression_bindings() { - let expression = Expression("foo ? bar", ["baz"]) + let expression = SQLite.Expression("foo ? bar", ["baz"]) XCTAssertEqual(expression.asSQL(), "foo 'baz' bar") } func test_asSQL_expression_bindings_quoting() { - let expression = Expression("foo ? bar", ["'baz'"]) + let expression = SQLite.Expression("foo ? bar", ["'baz'"]) XCTAssertEqual(expression.asSQL(), "foo '''baz''' bar") } func test_expression_custom_string_convertible() { - let expression = Expression("foo ? bar", ["baz"]) + let expression = SQLite.Expression("foo ? bar", ["baz"]) XCTAssertEqual(expression.asSQL(), expression.description) } @@ -24,12 +24,12 @@ class ExpressionTests: XCTestCase { } func test_init_literal() { - let expression = Expression(literal: "literal") + let expression = SQLite.Expression(literal: "literal") XCTAssertEqual(expression.template, "literal") } func test_init_identifier() { - let expression = Expression("identifier") + let expression = SQLite.Expression("identifier") XCTAssertEqual(expression.template, "\"identifier\"") } } diff --git a/Tests/SQLiteTests/Typed/OperatorsTests.swift b/Tests/SQLiteTests/Typed/OperatorsTests.swift index 370b910b..7a5d83da 100644 --- a/Tests/SQLiteTests/Typed/OperatorsTests.swift +++ b/Tests/SQLiteTests/Typed/OperatorsTests.swift @@ -356,7 +356,7 @@ class OperatorsTests: XCTestCase { } func test_precedencePreserved() { - let n = Expression(value: 1) + let n = SQLite.Expression(value: 1) assertSQL("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) assertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 7ac72d0c..6be98ca0 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -12,9 +12,9 @@ import SQLite3 class QueryIntegrationTests: SQLiteTestCase { - let id = Expression("id") - let email = Expression("email") - let age = Expression("age") + let id = SQLite.Expression("id") + let email = SQLite.Expression("email") + let age = SQLite.Expression("age") override func setUpWithError() throws { try super.setUpWithError() @@ -24,7 +24,7 @@ class QueryIntegrationTests: SQLiteTestCase { // MARK: - func test_select() throws { - let managerId = Expression("manager_id") + let managerId = SQLite.Expression("manager_id") let managers = users.alias("managers") let alice = try db.run(users.insert(email <- "alice@example.com")) @@ -39,7 +39,7 @@ class QueryIntegrationTests: SQLiteTestCase { let names = ["a", "b", "c"] try insertUsers(names) - let emailColumn = Expression("email") + let emailColumn = SQLite.Expression("email") let emails = try db.prepareRowIterator(users).map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) @@ -55,7 +55,7 @@ class QueryIntegrationTests: SQLiteTestCase { } func test_select_optional() throws { - let managerId = Expression("manager_id") + let managerId = SQLite.Expression("manager_id") let managers = users.alias("managers") let alice = try db.run(users.insert(email <- "alice@example.com")) @@ -69,15 +69,15 @@ 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("date")) - builder.column(Expression("uuid")) - builder.column(Expression("optional")) - builder.column(Expression("sub")) + builder.column(SQLite.Expression("int")) + builder.column(SQLite.Expression("string")) + builder.column(SQLite.Expression("bool")) + builder.column(SQLite.Expression("float")) + builder.column(SQLite.Expression("double")) + builder.column(SQLite.Expression("date")) + builder.column(SQLite.Expression("uuid")) + builder.column(SQLite.Expression("optional")) + builder.column(SQLite.Expression("sub")) }) let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, @@ -133,13 +133,13 @@ class QueryIntegrationTests: SQLiteTestCase { func test_insert_many_encodables() 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("date")) - builder.column(Expression("uuid")) + builder.column(SQLite.Expression("int")) + builder.column(SQLite.Expression("string")) + builder.column(SQLite.Expression("bool")) + builder.column(SQLite.Expression("float")) + builder.column(SQLite.Expression("double")) + builder.column(SQLite.Expression("date")) + builder.column(SQLite.Expression("uuid")) }) let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, @@ -161,9 +161,9 @@ class QueryIntegrationTests: SQLiteTestCase { let table = Table("custom_codable") try db.run(table.create { builder in - builder.column(Expression("myInt")) - builder.column(Expression("myString")) - builder.column(Expression("myOptionalArray")) + builder.column(SQLite.Expression("myInt")) + builder.column(SQLite.Expression("myString")) + builder.column(SQLite.Expression("myOptionalArray")) }) let customType = TestTypeWithOptionalArray(myInt: 13, myString: "foo", myOptionalArray: [1, 2, 3]) @@ -216,22 +216,22 @@ class QueryIntegrationTests: SQLiteTestCase { 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") + let query3 = users.select(users[*], SQLite.Expression(literal: "1 AS weight")).filter(email == "sally@example.com") + let query4 = users.select(users[*], SQLite.Expression(literal: "2 AS weight")).filter(email == "alice@example.com") - let sql = query3.union(query4).order(Expression(literal: "weight")).asSQL() + let sql = query3.union(query4).order(SQLite.Expression(literal: "weight")).asSQL() XCTAssertEqual(sql, """ SELECT "users".*, 1 AS weight FROM "users" WHERE ("email" = 'sally@example.com') UNION \ SELECT "users".*, 2 AS weight FROM "users" WHERE ("email" = 'alice@example.com') ORDER BY weight """) - let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } + let orderedIDs = try db.prepare(query3.union(query4).order(SQLite.Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) } func test_no_such_column() throws { - let doesNotExist = Expression("doesNotExist") + let doesNotExist = SQLite.Expression("doesNotExist") try insertUser("alice") let row = try db.pluck(users.filter(email == "alice@example.com"))! @@ -272,15 +272,15 @@ class QueryIntegrationTests: SQLiteTestCase { // https://github.com/stephencelis/SQLite.swift/issues/285 func test_order_by_random() throws { try insertUsers(["a", "b", "c'"]) - let result = Array(try db.prepare(users.select(email).order(Expression.random()).limit(1))) + let result = Array(try db.prepare(users.select(email).order(SQLite.Expression.random()).limit(1))) XCTAssertEqual(1, result.count) } func test_with_recursive() throws { let nodes = Table("nodes") - let id = Expression("id") - let parent = Expression("parent") - let value = Expression("value") + let id = SQLite.Expression("id") + let parent = SQLite.Expression("parent") + let value = SQLite.Expression("value") try db.run(nodes.create { builder in builder.column(id) @@ -320,7 +320,7 @@ class QueryIntegrationTests: SQLiteTestCase { /// Verify that `*` is properly expanded in a SELECT statement following a WITH clause. func test_with_glob_expansion() throws { let names = Table("names") - let name = Expression("name") + let name = SQLite.Expression("name") try db.run(names.create { builder in builder.column(email) builder.column(name) diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index f698c621..f018f097 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -13,19 +13,19 @@ import SQLite3 class QueryTests: XCTestCase { let users = Table("users") - let id = Expression("id") - let email = Expression("email") - let age = Expression("age") - let admin = Expression("admin") - let optionalAdmin = Expression("admin") + let id = SQLite.Expression("id") + let email = SQLite.Expression("email") + let age = SQLite.Expression("age") + let admin = SQLite.Expression("admin") + let optionalAdmin = SQLite.Expression("admin") let posts = Table("posts") - let userId = Expression("user_id") - let categoryId = Expression("category_id") - let published = Expression("published") + let userId = SQLite.Expression("user_id") + let categoryId = SQLite.Expression("category_id") + let published = SQLite.Expression("published") let categories = Table("categories") - let tag = Expression("tag") + let tag = SQLite.Expression("tag") func test_select_withExpression_compilesSelectClause() { assertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) @@ -217,7 +217,7 @@ class QueryTests: XCTestCase { } func test_alias_aliasesTable() { - let managerId = Expression("manager_id") + let managerId = SQLite.Expression("manager_id") let managers = users.alias("managers") @@ -422,7 +422,7 @@ class QueryTests: XCTestCase { func test_upsert_encodable() throws { let emails = Table("emails") - let string = Expression("string") + let string = SQLite.Expression("string") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) diff --git a/Tests/SQLiteTests/Typed/RowTests.swift b/Tests/SQLiteTests/Typed/RowTests.swift index 14fa373b..1aed00a7 100644 --- a/Tests/SQLiteTests/Typed/RowTests.swift +++ b/Tests/SQLiteTests/Typed/RowTests.swift @@ -5,49 +5,49 @@ class RowTests: XCTestCase { public func test_get_value() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.Expression("foo")) XCTAssertEqual("value", result) } public func test_get_value_subscript() { let row = Row(["\"foo\"": 0], ["value"]) - let result = row[Expression("foo")] + let result = row[SQLite.Expression("foo")] XCTAssertEqual("value", result) } public func test_get_value_optional() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.Expression("foo")) XCTAssertEqual("value", result) } public func test_get_value_optional_subscript() { let row = Row(["\"foo\"": 0], ["value"]) - let result = row[Expression("foo")] + let result = row[SQLite.Expression("foo")] XCTAssertEqual("value", result) } public func test_get_value_optional_nil() throws { let row = Row(["\"foo\"": 0], [nil]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.Expression("foo")) XCTAssertNil(result) } public func test_get_value_optional_nil_subscript() { let row = Row(["\"foo\"": 0], [nil]) - let result = row[Expression("foo")] + let result = row[SQLite.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 + XCTAssertThrowsError(try row.get(SQLite.Expression("foo"))) { error in if case QueryError.unexpectedNullValue(let name) = error { XCTAssertEqual("\"foo\"", name) } else { @@ -58,13 +58,13 @@ class RowTests: XCTestCase { public func test_get_type_mismatch_optional_returns_nil() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.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 + XCTAssertThrowsError(try row.get(SQLite.Expression("bar"))) { error in if case QueryError.noSuchColumn(let name, let columns) = error { XCTAssertEqual("\"bar\"", name) XCTAssertEqual(["\"foo\""], columns) @@ -76,7 +76,7 @@ class RowTests: XCTestCase { 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 + XCTAssertThrowsError(try row.get(SQLite.Expression("foo"))) { error in if case QueryError.ambiguousColumn(let name, let columns) = error { XCTAssertEqual("\"foo\"", name) XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns.sorted()) @@ -107,7 +107,7 @@ class RowTests: XCTestCase { } let row = Row(["\"foo\"": 0], [Blob(bytes: [])]) - XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + XCTAssertThrowsError(try row.get(SQLite.Expression("foo"))) { error in if case MyType.MyError.failed = error { XCTAssertTrue(true) } else { diff --git a/Tests/SQLiteTests/Typed/SelectTests.swift b/Tests/SQLiteTests/Typed/SelectTests.swift index 52d5bb6b..5fa3cd30 100644 --- a/Tests/SQLiteTests/Typed/SelectTests.swift +++ b/Tests/SQLiteTests/Typed/SelectTests.swift @@ -24,10 +24,10 @@ class SelectTests: SQLiteTestCase { let usersData = Table("users_name") let users = Table("users") - let name = Expression("name") - let id = Expression("id") - let userID = Expression("user_id") - let email = Expression("email") + let name = SQLite.Expression("name") + let id = SQLite.Expression("id") + let userID = SQLite.Expression("user_id") + let email = SQLite.Expression("email") try insertUser("Joey") try db.run(usersData.insert( diff --git a/Tests/SQLiteTests/Typed/SetterTests.swift b/Tests/SQLiteTests/Typed/SetterTests.swift index 938dd013..05da57a4 100644 --- a/Tests/SQLiteTests/Typed/SetterTests.swift +++ b/Tests/SQLiteTests/Typed/SetterTests.swift @@ -134,4 +134,7 @@ class SetterTests: XCTestCase { assertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) } + func test_setter_custom_string_convertible() { + XCTAssertEqual("\"int\" = \"int\"", (int <- int).description) + } } diff --git a/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift index 6ded152b..01e88297 100644 --- a/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift @@ -34,13 +34,13 @@ class WindowFunctionsTests: XCTestCase { func test_lag_wrapsExpressionWithOverClause() { assertSQL("lag(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lag(int.desc)) assertSQL("lag(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 7, int.desc)) - assertSQL("lag(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 1, default: Expression(value: 3), int.desc)) + assertSQL("lag(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 1, default: SQLite.Expression(value: 3), int.desc)) } func test_lead_wrapsExpressionWithOverClause() { assertSQL("lead(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lead(int.desc)) assertSQL("lead(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 7, int.desc)) - assertSQL("lead(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 1, default: Expression(value: 3), int.desc)) + assertSQL("lead(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 1, default: SQLite.Expression(value: 3), int.desc)) } func test_firstValue_wrapsExpressionWithOverClause() { From 4a5eda39a5aa63b4151b54e13ac9cd6ff3390a7e Mon Sep 17 00:00:00 2001 From: Cary Clark Date: Fri, 4 Oct 2024 15:54:10 -0700 Subject: [PATCH 171/216] Update oldest supported platform versions --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 238661ae..12a2f11c 100644 --- a/Package.swift +++ b/Package.swift @@ -4,10 +4,10 @@ import PackageDescription let package = Package( name: "SQLite.swift", platforms: [ - .iOS(.v11), + .iOS(.v12), .macOS(.v10_13), .watchOS(.v4), - .tvOS(.v11), + .tvOS(.v12), .visionOS(.v1) ], products: [ From 748dcba3312485151fa91b551a4564238f2b69b9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 18:35:08 +0200 Subject: [PATCH 172/216] Support creating tables in schema changer --- Makefile | 2 +- Sources/SQLite/Schema/SchemaChanger.swift | 49 +++++++++++++++++++ Sources/SQLite/Schema/SchemaDefinitions.swift | 5 +- .../Schema/SchemaChangerTests.swift | 46 +++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 73b33f97..52a25a12 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ lint: $(SWIFTLINT) $< --strict lint-fix: $(SWIFTLINT) - $< lint fix + $< --fix clean: $(XCODEBUILD) $(BUILD_ARGUMENTS) clean diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index af7b5e27..b6ed7312 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -43,19 +43,31 @@ public class SchemaChanger: CustomStringConvertible { public enum Operation { case addColumn(ColumnDefinition) + case addIndex(IndexDefinition) case dropColumn(String) case renameColumn(String, String) case renameTable(String) + case createTable(columns: [ColumnDefinition]) /// Returns non-nil if the operation can be executed with a simple SQL statement func toSQL(_ table: String, version: SQLiteVersion) -> String? { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + case .addIndex(let definition): + let unique = definition.unique ? "UNIQUE" : "" + let columns = definition.columns.joined(separator: ", ") + let `where` = definition.where.map { " WHERE " + $0 } ?? "" + + return "CREATE \(unique) INDEX \(definition.name) ON \(definition.table) (\(columns)) \(`where`)" case .renameColumn(let from, let to) where SQLiteFeature.renameColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" + case .createTable(let columns): + return "CREATE TABLE \(table.quote()) (" + + columns.map { $0.toSQL() }.joined(separator: ", ") + + ")" default: return nil } } @@ -108,12 +120,39 @@ public class SchemaChanger: CustomStringConvertible { } } + public class CreateTableDefinition { + fileprivate var columnDefinitions: [ColumnDefinition] = [] + fileprivate var indexDefinitions: [IndexDefinition] = [] + + let name: String + + init(name: String) { + self.name = name + } + + public func add(column: ColumnDefinition) { + columnDefinitions.append(column) + } + + public func add(index: IndexDefinition) { + indexDefinitions.append(index) + } + + var operations: [Operation] { + precondition(!columnDefinitions.isEmpty) + return [ + .createTable(columns: columnDefinitions) + ] + indexDefinitions.map { .addIndex($0) } + } + } + private let connection: Connection private let schemaReader: SchemaReader private let version: SQLiteVersion static let tempPrefix = "tmp_" typealias Block = () throws -> Void public typealias AlterTableDefinitionBlock = (AlterTableDefinition) -> Void + public typealias CreateTableDefinitionBlock = (CreateTableDefinition) -> Void struct Options: OptionSet { let rawValue: Int @@ -141,6 +180,15 @@ public class SchemaChanger: CustomStringConvertible { } } + public func create(table: String, ifNotExists: Bool = false, block: CreateTableDefinitionBlock) throws { + let createTableDefinition = CreateTableDefinition(name: table) + block(createTableDefinition) + + for operation in createTableDefinition.operations { + try run(table: table, operation: operation) + } + } + public func drop(table: String, ifExists: Bool = true) throws { try dropTable(table, ifExists: ifExists) } @@ -263,6 +311,7 @@ extension TableDefinition { func apply(_ operation: SchemaChanger.Operation?) -> TableDefinition { switch operation { case .none: return self + case .createTable, .addIndex: fatalError() case .addColumn: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") case .dropColumn(let column): return TableDefinition(name: name, diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 80f9e199..60837244 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -270,12 +270,15 @@ public struct IndexDefinition: Equatable { return memo2 } } + + let orders = indexSQL.flatMap(orders) + self.init(table: table, name: name, unique: unique, columns: columns, where: indexSQL.flatMap(wherePart), - orders: indexSQL.flatMap(orders)) + orders: (orders?.isEmpty ?? false) ? nil : orders) } public let table: String diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 2bec6a06..125ef09e 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -154,4 +154,50 @@ class SchemaChangerTests: SQLiteTestCase { let users_new = Table("users_new") XCTAssertEqual((try db.scalar(users_new.count)) as Int, 1) } + + func test_create_table() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "name", type: .TEXT, nullable: false)) + table.add(column: .init(name: "age", type: .INTEGER)) + + table.add(index: .init(table: table.name, + name: "nameIndex", + unique: true, + columns: ["name"], + where: nil, + orders: nil)) + } + + // make sure new table can be queried + let foo = Table("foo") + XCTAssertEqual((try db.scalar(foo.count)) as Int, 0) + + let columns = try schema.columnDefinitions(table: "foo") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: true, onConflict: nil), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "name", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ]) + + let indexes = try schema.indexDefinitions(table: "foo") + XCTAssertEqual(indexes, [ + IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil) + ]) + } } From 261f98ae268f986ec6e54822d45d9bda263bdd50 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 22:17:45 +0200 Subject: [PATCH 173/216] Update docs --- Documentation/Index.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index fcd2d642..bc62c791 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -63,6 +63,7 @@ - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - [Renaming/Dropping Tables](#renamingdropping-tables) + - [Creating Tables](#creating-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) @@ -1583,6 +1584,16 @@ try schemaChanger.rename(table: "users", to: "users_new") try schemaChanger.drop(table: "emails", ifExists: false) ``` +#### Creating Tables + +```swift +let schemaChanger = SchemaChanger(connection: db) + +try schemaChanger.create(table: "users") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "name", type: .TEXT, nullable: false)) +} + ### Indexes From e21fde28f0f96cc452151aaa17413b198f3718a9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 22:18:37 +0200 Subject: [PATCH 174/216] Revert to standard cocoapods --- Gemfile | 2 +- Gemfile.lock | 93 ++++++++++++++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/Gemfile b/Gemfile index eb6036cd..1f2ebb87 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem 'cocoapods', :git => 'https://github.com/SagarSDagdu/CocoaPods.git', tag: '1.15.2.1-sagard' +gem 'cocoapods' diff --git a/Gemfile.lock b/Gemfile.lock index a58783ea..464ef06b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,3 @@ -GIT - remote: https://github.com/SagarSDagdu/CocoaPods.git - revision: d96f491f79abd2804d1359c5228cce404dd365b7 - tag: 1.15.2.1-sagard - specs: - cocoapods (1.15.2.1.pre.sagard) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.6.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) - GEM remote: https://rubygems.org/ specs: @@ -29,26 +5,47 @@ GEM base64 nkf rexml - activesupport (7.1.3.2) + activesupport (7.2.2.1) base64 + benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.7) + benchmark (0.4.0) + bigdecimal (3.1.9) claide (1.1.0) - cocoapods-core (1.15.2) + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -68,48 +65,52 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.3) - connection_pool (2.4.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) drb (2.2.1) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.16.3) + ffi (1.17.2) + ffi (1.17.2-arm64-darwin) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.14.4) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.7.2) - minitest (5.22.3) + json (2.12.0) + logger (1.7.0) + minitest (5.25.5) molinillo (0.8.0) - mutex_m (0.2.0) - nanaimo (0.3.0) + mutex_m (0.3.0) + nanaimo (0.4.0) nap (1.1.0) netrc (0.11.0) nkf (0.2.0) public_suffix (4.0.7) - rexml (3.2.6) + rexml (3.4.1) ruby-macho (2.5.1) + securerandom (0.4.1) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.24.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) PLATFORMS arm64-darwin-23 ruby DEPENDENCIES - cocoapods! + cocoapods BUNDLED WITH 2.5.4 From 5ef20d1887e4b8763df890c4f1ffc256b3f79b99 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 22:39:15 +0200 Subject: [PATCH 175/216] Don't lint against visionOS --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 1bf8de96..cb68a022 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -11,7 +11,7 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then if [ "$VALIDATOR_SUBSPEC" == "none" ]; then bundle exec pod lib lint --no-subspecs --fail-fast else - bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From 017929080c146c2085b21adcce95ff67b9e82d2e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 23:14:13 +0200 Subject: [PATCH 176/216] Disable visionos just for standalone --- run-tests.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index cb68a022..94f643e4 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,15 +8,21 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then bundle install - if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - bundle exec pod lib lint --no-subspecs --fail-fast - else - bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos - fi + case "$VALIDATOR_SUBSPEC" in + none) + bundle exec pod lib lint --no-subspecs --fail-fast + ;; + standalone) + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos + ;; + *) + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + ;; + esac elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "$SPM" ]; then - cd Tests/SPM && swift ${SPM} + cd Tests/SPM && swift "${SPM}" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift ${PACKAGE_MANAGER_COMMAND} + swift "${PACKAGE_MANAGER_COMMAND}" fi From 023c5039780b1c086f1d4bbea1c6d5a683e3041f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 23:18:21 +0200 Subject: [PATCH 177/216] Enable word splitting --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 94f643e4..7d5c26d2 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -24,5 +24,5 @@ elif [ -n "$CARTHAGE_PLATFORM" ]; then elif [ -n "$SPM" ]; then cd Tests/SPM && swift "${SPM}" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift "${PACKAGE_MANAGER_COMMAND}" + swift ${PACKAGE_MANAGER_COMMAND} fi From 673367b772b04a8df0be64e757b2979da8798939 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 23:36:41 +0200 Subject: [PATCH 178/216] Public init --- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 60837244..897f8557 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -93,7 +93,7 @@ public struct ColumnDefinition: Equatable { // swiftlint:disable:next force_try static let pattern = try! NSRegularExpression(pattern: "PRIMARY KEY\\s*(?:ASC|DESC)?\\s*(?:ON CONFLICT (\\w+)?)?\\s*(AUTOINCREMENT)?") - init(autoIncrement: Bool = true, onConflict: OnConflict? = nil) { + public init(autoIncrement: Bool = true, onConflict: OnConflict? = nil) { self.autoIncrement = autoIncrement self.onConflict = onConflict } From 5de1420d58a87e082bb3659da9b59a19687cb867 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 14:23:31 +0200 Subject: [PATCH 179/216] Support ifNotExists --- Sources/SQLite/Schema/SchemaChanger.swift | 14 +++++++----- .../Schema/SchemaChangerTests.swift | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index b6ed7312..e2e3d5fa 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -47,7 +47,7 @@ public class SchemaChanger: CustomStringConvertible { case dropColumn(String) case renameColumn(String, String) case renameTable(String) - case createTable(columns: [ColumnDefinition]) + case createTable(columns: [ColumnDefinition], ifNotExists: Bool) /// Returns non-nil if the operation can be executed with a simple SQL statement func toSQL(_ table: String, version: SQLiteVersion) -> String? { @@ -64,8 +64,8 @@ public class SchemaChanger: CustomStringConvertible { return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" - case .createTable(let columns): - return "CREATE TABLE \(table.quote()) (" + + case .createTable(let columns, let ifNotExists): + return "CREATE TABLE \(ifNotExists ? " IF NOT EXISTS " : "") \(table.quote()) (" + columns.map { $0.toSQL() }.joined(separator: ", ") + ")" default: return nil @@ -125,9 +125,11 @@ public class SchemaChanger: CustomStringConvertible { fileprivate var indexDefinitions: [IndexDefinition] = [] let name: String + let ifNotExists: Bool - init(name: String) { + init(name: String, ifNotExists: Bool) { self.name = name + self.ifNotExists = ifNotExists } public func add(column: ColumnDefinition) { @@ -141,7 +143,7 @@ public class SchemaChanger: CustomStringConvertible { var operations: [Operation] { precondition(!columnDefinitions.isEmpty) return [ - .createTable(columns: columnDefinitions) + .createTable(columns: columnDefinitions, ifNotExists: ifNotExists) ] + indexDefinitions.map { .addIndex($0) } } } @@ -181,7 +183,7 @@ public class SchemaChanger: CustomStringConvertible { } public func create(table: String, ifNotExists: Bool = false, block: CreateTableDefinitionBlock) throws { - let createTableDefinition = CreateTableDefinition(name: table) + let createTableDefinition = CreateTableDefinition(name: table, ifNotExists: ifNotExists) block(createTableDefinition) for operation in createTableDefinition.operations { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 125ef09e..bba16e08 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -200,4 +200,26 @@ class SchemaChangerTests: SQLiteTestCase { IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil) ]) } + + func test_create_table_if_not_exists() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + + try schemaChanger.create(table: "foo", ifNotExists: true) { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + + XCTAssertThrowsError( + try schemaChanger.create(table: "foo", ifNotExists: false) { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + ) { error in + if case Result.error(_, let code, _) = error { + XCTAssertEqual(code, 1) + } else { + XCTFail("unexpected error: \(error)") + } + } + } } From f2a86841f527f756610797d56b6c49a46c947f41 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 17:24:57 +0200 Subject: [PATCH 180/216] Add columns via Expressions --- Sources/SQLite/Schema/SchemaChanger.swift | 25 ++++++++++++++++ .../Schema/SchemaChangerTests.swift | 30 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index e2e3d5fa..d3342032 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -136,6 +136,14 @@ public class SchemaChanger: CustomStringConvertible { columnDefinitions.append(column) } + public func add(expression: Expression) where T: Value { + add(column: .init(name: columnName(for: expression), type: .init(expression: expression), nullable: false)) + } + + public func add(expression: Expression) where T: Value { + add(column: .init(name: columnName(for: expression), type: .init(expression: expression), nullable: true)) + } + public func add(index: IndexDefinition) { indexDefinitions.append(index) } @@ -146,6 +154,13 @@ public class SchemaChanger: CustomStringConvertible { .createTable(columns: columnDefinitions, ifNotExists: ifNotExists) ] + indexDefinitions.map { .addIndex($0) } } + + private func columnName(for expression: Expression) -> String { + switch LiteralValue(expression.template) { + case .stringLiteral(let string): return string + default: fatalError("expression is not a literal string value") + } + } } private let connection: Connection @@ -331,3 +346,13 @@ extension TableDefinition { } } } + +extension ColumnDefinition.Affinity { + init(expression: Expression) where T: Value { + self.init(T.declaredDatatype) + } + + init(expression: Expression) where T: Value { + self.init(T.declaredDatatype) + } +} diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index bba16e08..6b53c9ce 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -201,6 +201,36 @@ class SchemaChangerTests: SQLiteTestCase { ]) } + func test_create_table_add_column_expression() throws { + try schemaChanger.create(table: "foo") { table in + table.add(expression: Expression("name")) + table.add(expression: Expression("age")) + table.add(expression: Expression("salary")) + } + + let columns = try schema.columnDefinitions(table: "foo") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "name", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "salary", + primaryKey: nil, + type: .REAL, + nullable: true, + defaultValue: .NULL, + references: nil) + ]) + } + func test_create_table_if_not_exists() throws { try schemaChanger.create(table: "foo") { table in table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) From cdaade15b93a43a5db8038da05da1c30c997ea3d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 18:45:26 +0200 Subject: [PATCH 181/216] Respect ifNotExists for index --- Sources/SQLite/Schema/SchemaChanger.swift | 12 ++++-------- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index d3342032..13222dab 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -43,7 +43,7 @@ public class SchemaChanger: CustomStringConvertible { public enum Operation { case addColumn(ColumnDefinition) - case addIndex(IndexDefinition) + case addIndex(IndexDefinition, ifNotExists: Bool) case dropColumn(String) case renameColumn(String, String) case renameTable(String) @@ -54,12 +54,8 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .addIndex(let definition): - let unique = definition.unique ? "UNIQUE" : "" - let columns = definition.columns.joined(separator: ", ") - let `where` = definition.where.map { " WHERE " + $0 } ?? "" - - return "CREATE \(unique) INDEX \(definition.name) ON \(definition.table) (\(columns)) \(`where`)" + case .addIndex(let definition, let ifNotExists): + return definition.toSQL(ifNotExists: ifNotExists) case .renameColumn(let from, let to) where SQLiteFeature.renameColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): @@ -152,7 +148,7 @@ public class SchemaChanger: CustomStringConvertible { precondition(!columnDefinitions.isEmpty) return [ .createTable(columns: columnDefinitions, ifNotExists: ifNotExists) - ] + indexDefinitions.map { .addIndex($0) } + ] + indexDefinitions.map { .addIndex($0, ifNotExists: ifNotExists) } } private func columnName(for expression: Expression) -> String { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 6b53c9ce..02a82267 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -252,4 +252,18 @@ class SchemaChangerTests: SQLiteTestCase { } } } + + func test_create_table_if_not_exists_with_index() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "name", type: .TEXT)) + table.add(index: .init(table: "foo", name: "name_index", unique: true, columns: ["name"], indexSQL: nil)) + } + + // ifNotExists needs to apply to index creation as well + try schemaChanger.create(table: "foo", ifNotExists: true) { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(index: .init(table: "foo", name: "name_index", unique: true, columns: ["name"], indexSQL: nil)) + } + } } From a0136544e9627b941008d7c59836b61feaf1f142 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 22:17:39 +0200 Subject: [PATCH 182/216] New visionOS0-compatible pod has been release --- run-tests.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 7d5c26d2..3ffba810 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -12,9 +12,6 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then none) bundle exec pod lib lint --no-subspecs --fail-fast ;; - standalone) - bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos - ;; *) bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast ;; From 77b493e661cff1498892dbaa48aa2205b673ce46 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 22:42:20 +0200 Subject: [PATCH 183/216] Use inheritance --- SQLite.swift.podspec | 43 +++++-------------------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 184ce23e..4cbd91bb 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,17 +18,11 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '12.0' - tvos_deployment_target = '12.0' - osx_deployment_target = '10.13' - watchos_deployment_target = '4.0' - visionos_deployment_target = '1.0' - - s.ios.deployment_target = ios_deployment_target - s.tvos.deployment_target = tvos_deployment_target - s.osx.deployment_target = osx_deployment_target - s.watchos.deployment_target = watchos_deployment_target - s.visionos.deployment_target = visionos_deployment_target + s.ios.deployment_target = '12.0' + s.tvos.deployment_target = '12.0' + s.osx.deployment_target = '10.13' + s.watchos.deployment_target = '4.0' + s.visionos.deployment_target = '1.0' s.subspec 'standard' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' @@ -36,18 +30,9 @@ Pod::Spec.new do |s| ss.library = 'sqlite3' ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } - ss.ios.deployment_target = ios_deployment_target - ss.tvos.deployment_target = tvos_deployment_target - ss.osx.deployment_target = osx_deployment_target - ss.watchos.deployment_target = watchos_deployment_target - ss.visionos.deployment_target = visionos_deployment_target - ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' - test_spec.ios.deployment_target = ios_deployment_target - test_spec.tvos.deployment_target = tvos_deployment_target - test_spec.osx.deployment_target = osx_deployment_target end end @@ -62,18 +47,9 @@ Pod::Spec.new do |s| } ss.dependency 'sqlite3' - ss.ios.deployment_target = ios_deployment_target - ss.tvos.deployment_target = tvos_deployment_target - ss.osx.deployment_target = osx_deployment_target - ss.watchos.deployment_target = watchos_deployment_target - ss.visionos.deployment_target = visionos_deployment_target - ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' - test_spec.ios.deployment_target = ios_deployment_target - test_spec.tvos.deployment_target = tvos_deployment_target - test_spec.osx.deployment_target = osx_deployment_target end end @@ -87,18 +63,9 @@ Pod::Spec.new do |s| } ss.dependency 'SQLCipher', '>= 4.0.0' - ss.ios.deployment_target = ios_deployment_target - ss.tvos.deployment_target = tvos_deployment_target - ss.osx.deployment_target = osx_deployment_target - ss.watchos.deployment_target = watchos_deployment_target - #ss.visionos.deployment_target = visionos_deployment_target # Not supported by SQLCipher for now - ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' - test_spec.ios.deployment_target = ios_deployment_target - test_spec.tvos.deployment_target = tvos_deployment_target - test_spec.osx.deployment_target = osx_deployment_target end end end From 7309b4337a0649d53d8ebd703f582117c1dc9e91 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 23:04:30 +0200 Subject: [PATCH 184/216] Use newer Xcode --- .github/workflows/build.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fbba1c5..5b6b74a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,15 +5,9 @@ env: IOS_VERSION: "17.2" jobs: build: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v2 - - name: "Select Xcode" - # Currently only works with Xcode 14.2: - # https://github.com/CocoaPods/CocoaPods/issues/11839 - run: | - xcode-select -p - sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 4166f86e844f5d1e87e25264eb9a5194dbaf3b08 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 23:10:36 +0200 Subject: [PATCH 185/216] iOS 17.5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b6b74a6..a0274321 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: "iPhone 15" - IOS_VERSION: "17.2" + IOS_VERSION: "17.5" jobs: build: runs-on: macos-15 From f312ab4d12b344f7e541df5cb0363070a0f964ba Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 23:29:27 +0200 Subject: [PATCH 186/216] Update bundler --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 464ef06b..9f71603b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -67,7 +67,7 @@ GEM colored2 (3.1.2) concurrent-ruby (1.3.5) connection_pool (2.5.3) - drb (2.2.1) + drb (2.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) @@ -113,4 +113,4 @@ DEPENDENCIES cocoapods BUNDLED WITH - 2.5.4 + 2.6.9 From 9dc478c9f1f6eaae43f1aa4286e3bf574bb5976d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 10:02:48 +0200 Subject: [PATCH 187/216] SchemaReader: parse and create unique constraints --- Sources/SQLite/Schema/SchemaDefinitions.swift | 27 ++++++++-- Sources/SQLite/Schema/SchemaReader.swift | 52 ++++++++++++++----- .../Schema/SchemaChangerTests.swift | 9 ++-- .../Schema/SchemaReaderTests.swift | 32 ++++++++++-- 4 files changed, 98 insertions(+), 22 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 897f8557..bb4ce82b 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -128,6 +128,7 @@ public struct ColumnDefinition: Equatable { public let primaryKey: PrimaryKey? public let type: Affinity public let nullable: Bool + public let unique: Bool public let defaultValue: LiteralValue public let references: ForeignKey? @@ -135,12 +136,14 @@ public struct ColumnDefinition: Equatable { primaryKey: PrimaryKey? = nil, type: Affinity, nullable: Bool = true, + unique: Bool = false, defaultValue: LiteralValue = .NULL, references: ForeignKey? = nil) { self.name = name self.primaryKey = primaryKey self.type = type self.nullable = nullable + self.unique = unique self.defaultValue = defaultValue self.references = references } @@ -244,16 +247,18 @@ public struct IndexDefinition: Equatable { public enum Order: String { case ASC, DESC } - public init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { + public init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, + orders: [String: Order]? = nil, origin: Origin? = nil) { self.table = table self.name = name self.unique = unique self.columns = columns self.where = `where` self.orders = orders + self.origin = origin } - init (table: String, name: String, unique: Bool, columns: [String], indexSQL: String?) { + init (table: String, name: String, unique: Bool, columns: [String], indexSQL: String?, origin: Origin? = nil) { func wherePart(sql: String) -> String? { IndexDefinition.whereRe.firstMatch(in: sql, options: [], range: NSRange(location: 0, length: sql.count)).map { (sql as NSString).substring(with: $0.range(at: 1)) @@ -278,7 +283,8 @@ public struct IndexDefinition: Equatable { unique: unique, columns: columns, where: indexSQL.flatMap(wherePart), - orders: (orders?.isEmpty ?? false) ? nil : orders) + orders: (orders?.isEmpty ?? false) ? nil : orders, + origin: origin) } public let table: String @@ -287,6 +293,13 @@ public struct IndexDefinition: Equatable { public let columns: [String] public let `where`: String? public let orders: [String: Order]? + public let origin: Origin? + + public enum Origin: String { + case uniqueConstraint = "u" // index created from a "CREATE TABLE (... UNIQUE)" column constraint + case createIndex = "c" // index created explicitly via "CREATE INDEX ..." + case primaryKey = "pk" // index created from a "CREATE TABLE PRIMARY KEY" column constraint + } enum IndexError: LocalizedError { case tooLong(String, String) @@ -300,6 +313,13 @@ public struct IndexDefinition: Equatable { } } + // Indices with names of the form "sqlite_autoindex_TABLE_N" that are used to implement UNIQUE and PRIMARY KEY + // constraints on ordinary tables. + // https://sqlite.org/fileformat2.html#intschema + var isInternal: Bool { + name.starts(with: "sqlite_autoindex_") + } + func validate() throws { if name.count > IndexDefinition.maxIndexLength { throw IndexError.tooLong(name, table) @@ -348,6 +368,7 @@ extension ColumnDefinition { defaultValue.map { "DEFAULT \($0)" }, primaryKey.map { $0.toSQL() }, nullable ? nil : "NOT NULL", + unique ? "UNIQUE" : nil, references.map { $0.toSQL() } ].compactMap { $0 } .joined(separator: " ") diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 1989ad6f..a67fb94f 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -21,7 +21,7 @@ public class SchemaReader { let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = Dictionary(grouping: try foreignKeys(table: table), by: { $0.column }) - return try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") + let columnDefinitions = try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") .map { (row: Row) -> ColumnDefinition in ColumnDefinition( name: row[TableInfoTable.nameColumn], @@ -29,10 +29,27 @@ public class SchemaReader { try parsePrimaryKey(column: row[TableInfoTable.nameColumn]) : nil, type: ColumnDefinition.Affinity(row[TableInfoTable.typeColumn]), nullable: row[TableInfoTable.notNullColumn] == 0, + unique: false, defaultValue: LiteralValue(row[TableInfoTable.defaultValueColumn]), references: foreignKeys[row[TableInfoTable.nameColumn]]?.first ) } + + let internalIndexes = try indexDefinitions(table: table).filter { $0.isInternal } + return columnDefinitions.map { definition in + if let index = internalIndexes.first(where: { $0.columns.contains(definition.name) }), index.origin == .uniqueConstraint { + + ColumnDefinition(name: definition.name, + primaryKey: definition.primaryKey, + type: definition.type, + nullable: definition.nullable, + unique: true, + defaultValue: definition.defaultValue, + references: definition.references) + } else { + definition + } + } } public func objectDefinitions(name: String? = nil, @@ -66,27 +83,26 @@ public class SchemaReader { .first } - func columns(name: String) throws -> [String] { + func indexInfos(name: String) throws -> [IndexInfo] { try connection.prepareRowIterator("PRAGMA index_info(\(name.quote()))") .compactMap { row in - row[IndexInfoTable.nameColumn] + IndexInfo(name: row[IndexInfoTable.nameColumn], + columnRank: row[IndexInfoTable.seqnoColumn], + columnRankWithinTable: row[IndexInfoTable.cidColumn]) + } } return try connection.prepareRowIterator("PRAGMA index_list(\(table.quote()))") .compactMap { row -> IndexDefinition? in let name = row[IndexListTable.nameColumn] - guard !name.starts(with: "sqlite_") else { - // Indexes SQLite creates implicitly for internal use start with "sqlite_". - // See https://www.sqlite.org/fileformat2.html#intschema - return nil - } return IndexDefinition( table: table, name: name, unique: row[IndexListTable.uniqueColumn] == 1, - columns: try columns(name: name), - indexSQL: try indexSQL(name: name) + columns: try indexInfos(name: name).compactMap { $0.name }, + indexSQL: try indexSQL(name: name), + origin: IndexDefinition.Origin(rawValue: row[IndexListTable.originColumn]) ) } } @@ -123,6 +139,15 @@ public class SchemaReader { objectDefinitions(name: name, type: .table, temp: true) ).compactMap(\.sql).first } + + struct IndexInfo { + let name: String? + // The rank of the column within the index. (0 means left-most.) + let columnRank: Int + // The rank of the column within the table being indexed. + // A value of -1 means rowid and a value of -2 means that an expression is being used + let columnRankWithinTable: Int + } } private enum SchemaTable { @@ -159,11 +184,12 @@ private enum TableInfoTable { private enum IndexInfoTable { // The rank of the column within the index. (0 means left-most.) - static let seqnoColumn = Expression("seqno") + static let seqnoColumn = Expression("seqno") // The rank of the column within the table being indexed. // A value of -1 means rowid and a value of -2 means that an expression is being used. - static let cidColumn = Expression("cid") - // The name of the column being indexed. This columns is NULL if the column is the rowid or an expression. + static let cidColumn = Expression("cid") + // The name of the column being indexed. + // This columns is NULL if the column is the rowid or an expression. static let nameColumn = Expression("name") } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 02a82267..724c2d0c 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -158,7 +158,7 @@ class SchemaChangerTests: SQLiteTestCase { func test_create_table() throws { try schemaChanger.create(table: "foo") { table in table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) - table.add(column: .init(name: "name", type: .TEXT, nullable: false)) + table.add(column: .init(name: "name", type: .TEXT, nullable: false, unique: true)) table.add(column: .init(name: "age", type: .INTEGER)) table.add(index: .init(table: table.name, @@ -179,25 +179,28 @@ class SchemaChangerTests: SQLiteTestCase { primaryKey: .init(autoIncrement: true, onConflict: nil), type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "name", primaryKey: nil, type: .TEXT, nullable: false, + unique: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "age", primaryKey: nil, type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil) ]) - let indexes = try schema.indexDefinitions(table: "foo") + let indexes = try schema.indexDefinitions(table: "foo").filter { !$0.isInternal } XCTAssertEqual(indexes, [ - IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil) + IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil, origin: .createIndex) ]) } diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 8c033e41..01047c9c 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -18,41 +18,48 @@ class SchemaReaderTests: SQLiteTestCase { primaryKey: .init(autoIncrement: false, onConflict: nil), type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "email", primaryKey: nil, type: .TEXT, nullable: false, + unique: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "age", primaryKey: nil, type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "salary", primaryKey: nil, type: .REAL, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "admin", primaryKey: nil, type: .NUMERIC, nullable: false, + unique: false, defaultValue: .numericLiteral("0"), references: nil), ColumnDefinition(name: "manager_id", primaryKey: nil, type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, type: .NUMERIC, nullable: true, + unique: false, defaultValue: .NULL, references: nil) ]) @@ -68,6 +75,24 @@ class SchemaReaderTests: SQLiteTestCase { primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), type: .INTEGER, nullable: true, + unique: false, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_columnDefinitions_parses_unique() throws { + try db.run("CREATE TABLE t (name TEXT UNIQUE)") + + let columns = try schemaReader.columnDefinitions(table: "t") + XCTAssertEqual(columns, [ + ColumnDefinition( + name: "name", + primaryKey: nil, + type: .TEXT, + nullable: true, + unique: true, defaultValue: .NULL, references: nil) ] @@ -128,13 +153,13 @@ class SchemaReaderTests: SQLiteTestCase { } func test_indexDefinitions_no_index() throws { - let indexes = try schemaReader.indexDefinitions(table: "users") + let indexes = try schemaReader.indexDefinitions(table: "users").filter { !$0.isInternal } XCTAssertTrue(indexes.isEmpty) } func test_indexDefinitions_with_index() throws { try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") - let indexes = try schemaReader.indexDefinitions(table: "users") + let indexes = try schemaReader.indexDefinitions(table: "users").filter { !$0.isInternal } XCTAssertEqual(indexes, [ IndexDefinition( @@ -143,7 +168,8 @@ class SchemaReaderTests: SQLiteTestCase { unique: true, columns: ["age"], where: "age IS NOT NULL", - orders: ["age": .DESC] + orders: ["age": .DESC], + origin: .createIndex ) ]) } From 374d99da2107656adf12670feea4f167853321d9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 10:53:36 +0200 Subject: [PATCH 188/216] Use cocoapods with watchOS fix --- Gemfile | 2 +- Gemfile.lock | 46 ++++++++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 1f2ebb87..2bb803ca 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem 'cocoapods' +gem 'cocoapods', :git => 'https://github.com/jberkel/CocoaPods.git', branch: 'watchos-fourflusher' diff --git a/Gemfile.lock b/Gemfile.lock index 9f71603b..20ed2aad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,27 @@ +GIT + remote: https://github.com/jberkel/CocoaPods.git + revision: 899f273f298ea20de2378687ea55331004b39371 + branch: watchos-fourflusher + specs: + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (~> 4.1.0) + xcodeproj (>= 1.27.0, < 2.0) + GEM remote: https://rubygems.org/ specs: @@ -27,24 +51,6 @@ GEM benchmark (0.4.0) bigdecimal (3.1.9) claide (1.1.0) - cocoapods (1.16.2) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.16.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.6.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.27.0, < 2.0) cocoapods-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) @@ -91,7 +97,7 @@ GEM nkf (0.2.0) public_suffix (4.0.7) rexml (3.4.1) - ruby-macho (2.5.1) + ruby-macho (4.1.0) securerandom (0.4.1) typhoeus (1.4.1) ethon (>= 0.9.0) @@ -110,7 +116,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods + cocoapods! BUNDLED WITH 2.6.9 From 108431244de611050f25c20efd02bcfe68687b28 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 11:52:52 +0200 Subject: [PATCH 189/216] Disable visionOS for SQLCipher --- SQLite.swift.podspec | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4cbd91bb..d0ccb136 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -54,6 +54,13 @@ Pod::Spec.new do |s| end s.subspec 'SQLCipher' do |ss| + # Disable unsupported visionOS + # https://github.com/sqlcipher/sqlcipher/issues/483 + ss.ios.deployment_target = s.deployment_target(:ios) + ss.tvos.deployment_target = s.deployment_target(:tvos) + ss.osx.deployment_target = s.deployment_target(:osx) + ss.watchos.deployment_target = s.deployment_target(:watchos) + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } From 718c7988780f803ede87c03ef2cdce58ea762656 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 12:21:19 +0200 Subject: [PATCH 190/216] Add comment --- Gemfile | 1 + Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 2bb803ca..2770e85d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ source "https://rubygems.org" +# https://github.com/CocoaPods/CocoaPods/pull/12816 gem 'cocoapods', :git => 'https://github.com/jberkel/CocoaPods.git', branch: 'watchos-fourflusher' diff --git a/Gemfile.lock b/Gemfile.lock index 20ed2aad..7dac2c18 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/jberkel/CocoaPods.git - revision: 899f273f298ea20de2378687ea55331004b39371 + revision: 32a90c184bc5dc9ec8b7b9b8ad08e98b7253dec2 branch: watchos-fourflusher specs: cocoapods (1.16.2) From c812caf9c432bc196f87648b56551dff2c0017e3 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 13:30:36 +0200 Subject: [PATCH 191/216] Make ForeignKey public, rename fields for clarity --- Sources/SQLite/Schema/SchemaDefinitions.swift | 23 ++++++++++--- Sources/SQLite/Schema/SchemaReader.swift | 8 ++--- .../Schema/SchemaChangerTests.swift | 34 +++++++++++++++++++ .../Schema/SchemaDefinitionsTests.swift | 8 ++--- .../Schema/SchemaReaderTests.swift | 4 +-- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index bb4ce82b..d7803999 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -117,11 +117,24 @@ public struct ColumnDefinition: Equatable { } public struct ForeignKey: Equatable { - let table: String - let column: String - let primaryKey: String? + let fromColumn: String + let toTable: String + // when null, use primary key of "toTable" + let toColumn: String? let onUpdate: String? let onDelete: String? + + public init(toTable: String, toColumn: String? = nil, onUpdate: String? = nil, onDelete: String? = nil) { + self.init(fromColumn: "", toTable: toTable, toColumn: toColumn, onUpdate: onUpdate, onDelete: onDelete) + } + + public init(fromColumn: String, toTable: String, toColumn: String? = nil, onUpdate: String? = nil, onDelete: String? = nil) { + self.fromColumn = fromColumn + self.toTable = toTable + self.toColumn = toColumn + self.onUpdate = onUpdate + self.onDelete = onDelete + } } public let name: String @@ -400,8 +413,8 @@ extension ColumnDefinition.ForeignKey { func toSQL() -> String { ([ "REFERENCES", - table.quote(), - primaryKey.map { "(\($0.quote()))" }, + toTable.quote(), + toColumn.map { "(\($0.quote()))" }, onUpdate.map { "ON UPDATE \($0)" }, onDelete.map { "ON DELETE \($0)" } ] as [String?]).compactMap { $0 } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index a67fb94f..995cd4d7 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -19,7 +19,7 @@ public class SchemaReader { } let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = - Dictionary(grouping: try foreignKeys(table: table), by: { $0.column }) + Dictionary(grouping: try foreignKeys(table: table), by: { $0.fromColumn }) let columnDefinitions = try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") .map { (row: Row) -> ColumnDefinition in @@ -111,9 +111,9 @@ public class SchemaReader { try connection.prepareRowIterator("PRAGMA foreign_key_list(\(table.quote()))") .map { row in ColumnDefinition.ForeignKey( - table: row[ForeignKeyListTable.tableColumn], - column: row[ForeignKeyListTable.fromColumn], - primaryKey: row[ForeignKeyListTable.toColumn], + fromColumn: row[ForeignKeyListTable.fromColumn], + toTable: row[ForeignKeyListTable.tableColumn], + toColumn: row[ForeignKeyListTable.toColumn], onUpdate: row[ForeignKeyListTable.onUpdateColumn] == TableBuilder.Dependency.noAction.rawValue ? nil : row[ForeignKeyListTable.onUpdateColumn], onDelete: row[ForeignKeyListTable.onDeleteColumn] == TableBuilder.Dependency.noAction.rawValue diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 724c2d0c..b08344fc 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -269,4 +269,38 @@ class SchemaChangerTests: SQLiteTestCase { table.add(index: .init(table: "foo", name: "name_index", unique: true, columns: ["name"], indexSQL: nil)) } } + + func test_create_table_with_foreign_key_reference() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + + try schemaChanger.create(table: "bars") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "foo_id", + type: .INTEGER, + nullable: false, + references: .init(toTable: "foo", toColumn: "id"))) + } + + let barColumns = try schema.columnDefinitions(table: "bars") + + XCTAssertEqual([ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: true, onConflict: nil), + type: .INTEGER, + nullable: true, + unique: false, + defaultValue: .NULL, + references: nil), + + ColumnDefinition(name: "foo_id", + primaryKey: nil, + type: .INTEGER, + nullable: false, + unique: false, + defaultValue: .NULL, + references: .init(fromColumn: "foo_id", toTable: "foo", toColumn: "id", onUpdate: nil, onDelete: nil)) + ], barColumns) + } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 8b7e27e3..1c648bd8 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -11,7 +11,7 @@ class ColumnDefinitionTests: XCTestCase { ("\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")", ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, - references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil))), + references: .init(fromColumn: "", toTable: "other_table", toColumn: "some_id", onUpdate: nil, onDelete: nil))), ("\"text\" TEXT", ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil)), @@ -245,9 +245,9 @@ class ForeignKeyDefinitionTests: XCTestCase { func test_toSQL() { XCTAssertEqual( ColumnDefinition.ForeignKey( - table: "foo", - column: "bar", - primaryKey: "bar_id", + fromColumn: "bar", + toTable: "foo", + toColumn: "bar_id", onUpdate: nil, onDelete: "SET NULL" ).toSQL(), """ diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 01047c9c..8963070f 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -54,7 +54,7 @@ class SchemaReaderTests: SQLiteTestCase { nullable: true, unique: false, defaultValue: .NULL, - references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), + references: .init(fromColumn: "manager_id", toTable: "users", toColumn: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, type: .NUMERIC, @@ -194,7 +194,7 @@ class SchemaReaderTests: SQLiteTestCase { let foreignKeys = try schemaReader.foreignKeys(table: "test_links") XCTAssertEqual(foreignKeys, [ - .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + .init(fromColumn: "test_id", toTable: "users", toColumn: "id", onUpdate: nil, onDelete: nil) ]) } From 1c440766b22019190a9c78731f0c69b78f5d177b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:06:41 +0200 Subject: [PATCH 192/216] Add drop(index:) --- Sources/SQLite/Schema/SchemaChanger.swift | 10 ++++- .../Schema/SchemaChangerTests.swift | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 13222dab..7ba458c1 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -45,6 +45,7 @@ public class SchemaChanger: CustomStringConvertible { case addColumn(ColumnDefinition) case addIndex(IndexDefinition, ifNotExists: Bool) case dropColumn(String) + case dropIndex(String, ifExists: Bool) case renameColumn(String, String) case renameTable(String) case createTable(columns: [ColumnDefinition], ifNotExists: Bool) @@ -60,6 +61,8 @@ public class SchemaChanger: CustomStringConvertible { return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" + case .dropIndex(let name, let ifExists): + return "DROP INDEX \(ifExists ? " IF EXISTS " : "") \(name.quote())" case .createTable(let columns, let ifNotExists): return "CREATE TABLE \(ifNotExists ? " IF NOT EXISTS " : "") \(table.quote()) (" + columns.map { $0.toSQL() }.joined(separator: ", ") + @@ -111,6 +114,10 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.dropColumn(column)) } + public func drop(index: String, ifExists: Bool = false) { + operations.append(.dropIndex(index, ifExists: ifExists)) + } + public func rename(column: String, to: String) { operations.append(.renameColumn(column, to)) } @@ -324,8 +331,9 @@ extension TableDefinition { func apply(_ operation: SchemaChanger.Operation?) -> TableDefinition { switch operation { case .none: return self - case .createTable, .addIndex: fatalError() + case .createTable, .addIndex, .dropIndex: fatalError() case .addColumn: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") + case .dropColumn(let column): return TableDefinition(name: name, columns: columns.filter { $0.name != column }, diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index b08344fc..75331a97 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -124,6 +124,44 @@ class SchemaChangerTests: SQLiteTestCase { } } + func test_drop_index() throws { + try db.execute(""" + CREATE INDEX age_index ON users(age) + """) + + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index") + } + let indexes = try schema.indexDefinitions(table: "users").filter { !$0.isInternal } + XCTAssertEqual(0, indexes.count) + } + + func test_drop_index_if_exists() throws { + try db.execute(""" + CREATE INDEX age_index ON users(age) + """) + + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index") + } + + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index", ifExists: true) + } + + XCTAssertThrowsError( + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index", ifExists: false) + } + ) { error in + if case Result.error(let message, _, _) = error { + XCTAssertEqual(message, "no such index: age_index") + } else { + XCTFail("unexpected error \(error)") + } + } + } + func test_drop_table() throws { try schemaChanger.drop(table: "users") XCTAssertThrowsError(try db.scalar(users.count)) { error in From 08bed022dc12289d64278cc5ad2e9a63868fb12f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:23:02 +0200 Subject: [PATCH 193/216] Implement add(index:) --- Sources/SQLite/Schema/SchemaChanger.swift | 4 +++ .../Schema/SchemaChangerTests.swift | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 7ba458c1..3badb8d8 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -110,6 +110,10 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.addColumn(column)) } + public func add(index: IndexDefinition, ifNotExists: Bool = false) { + operations.append(.addIndex(index, ifNotExists: ifNotExists)) + } + public func drop(column: String) { operations.append(.dropColumn(column)) } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 75331a97..dafdea54 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -124,6 +124,40 @@ class SchemaChangerTests: SQLiteTestCase { } } + func test_add_index() throws { + try schemaChanger.alter(table: "users") { table in + table.add(index: .init(table: table.name, name: "age_index", unique: false, columns: ["age"], indexSQL: nil)) + } + + let indexes = try schema.indexDefinitions(table: "users").filter { !$0.isInternal } + XCTAssertEqual([ + IndexDefinition(table: "users", + name: "age_index", + unique: false, + columns: ["age"], + where: nil, + orders: nil, + origin: .createIndex) + ], indexes) + } + + func test_add_index_if_not_exists() throws { + let index = IndexDefinition(table: "users", name: "age_index", unique: false, columns: ["age"], indexSQL: nil) + try schemaChanger.alter(table: "users") { table in + table.add(index: index) + } + + try schemaChanger.alter(table: "users") { table in + table.add(index: index, ifNotExists: true) + } + + XCTAssertThrowsError( + try schemaChanger.alter(table: "users") { table in + table.add(index: index, ifNotExists: false) + } + ) + } + func test_drop_index() throws { try db.execute(""" CREATE INDEX age_index ON users(age) From 3def7d60f70b109e0f74f6d42bac61d631de56dd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:45:33 +0200 Subject: [PATCH 194/216] Make name public, expose plain SQL run --- Sources/SQLite/Schema/SchemaChanger.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 3badb8d8..3e146df7 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -100,7 +100,7 @@ public class SchemaChanger: CustomStringConvertible { public class AlterTableDefinition { fileprivate var operations: [Operation] = [] - let name: String + public let name: String init(name: String) { self.name = name @@ -223,6 +223,11 @@ public class SchemaChanger: CustomStringConvertible { try connection.run("ALTER TABLE \(table.quote()) RENAME TO \(to.quote())") } + // Runs arbitrary SQL. Should only be used if no predefined operations exist. + public func run(sql: String) throws { + try connection.run(sql) + } + private func run(table: String, operation: Operation) throws { try operation.validate() From 25bd06392f65d7ca75ffc481471de05905ec1c79 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:53:46 +0200 Subject: [PATCH 195/216] Better API, test --- Sources/SQLite/Schema/SchemaChanger.swift | 5 +++-- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 3e146df7..6fae532a 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -224,8 +224,9 @@ public class SchemaChanger: CustomStringConvertible { } // Runs arbitrary SQL. Should only be used if no predefined operations exist. - public func run(sql: String) throws { - try connection.run(sql) + @discardableResult + public func run(_ sql: String, _ bindings: Binding?...) throws -> Statement { + return try connection.run(sql, bindings) } private func run(table: String, operation: Operation) throws { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index dafdea54..d942fba4 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -375,4 +375,9 @@ class SchemaChangerTests: SQLiteTestCase { references: .init(fromColumn: "foo_id", toTable: "foo", toColumn: "id", onUpdate: nil, onDelete: nil)) ], barColumns) } + + func test_run_arbitrary_sql() throws { + try schemaChanger.run("DROP TABLE users") + XCTAssertEqual(0, try schema.objectDefinitions(name: "users", type: .table).count) + } } From 8938697549dccc21803c5cbef9f2e5fec7ee274a Mon Sep 17 00:00:00 2001 From: ha100 Date: Sun, 8 Jun 2025 05:38:36 +0200 Subject: [PATCH 196/216] change CSQLite dep to SwiftToolchainCSQLite to allow swift sdk cross compilation --- Package.swift | 69 +++++++++++-------- Sources/SQLite/Core/Backup.swift | 2 +- .../SQLite/Core/Connection+Aggregation.swift | 2 +- Sources/SQLite/Core/Connection+Attach.swift | 2 +- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Result.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- .../Core/Connection+AttachTests.swift | 2 +- .../Core/Connection+PragmaTests.swift | 2 +- Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- Tests/SQLiteTests/Core/ResultTests.swift | 2 +- Tests/SQLiteTests/Core/StatementTests.swift | 2 +- .../Extensions/FTSIntegrationTests.swift | 2 +- .../Typed/CustomAggregationTests.swift | 2 +- .../Typed/QueryIntegrationTests.swift | 2 +- Tests/SQLiteTests/Typed/QueryTests.swift | 2 +- 17 files changed, 56 insertions(+), 45 deletions(-) diff --git a/Package.swift b/Package.swift index 238661ae..690d41e2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,6 +1,38 @@ // swift-tools-version:5.9 import PackageDescription +let deps: [Package.Dependency] = [ + .github("swiftlang/swift-toolchain-sqlite", exact: "1.0.4") +] + +let targets: [Target] = [ + .target( + name: "SQLite", + dependencies: [ + .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite") + ], + exclude: [ + "Info.plist" + ] + ) +] + +let testTargets: [Target] = [ + .testTarget( + name: "SQLiteTests", + dependencies: [ + "SQLite" + ], + path: "Tests/SQLiteTests", + exclude: [ + "Info.plist" + ], + resources: [ + .copy("Resources") + ] + ) +] + let package = Package( name: "SQLite.swift", platforms: [ @@ -16,34 +48,13 @@ let package = Package( targets: ["SQLite"] ) ], - targets: [ - .target( - name: "SQLite", - exclude: [ - "Info.plist" - ] - ), - .testTarget( - name: "SQLiteTests", - dependencies: [ - "SQLite" - ], - path: "Tests/SQLiteTests", - exclude: [ - "Info.plist" - ], - resources: [ - .copy("Resources") - ] - ) - ] + dependencies: deps, + targets: targets + testTargets ) -#if os(Linux) -package.dependencies = [ - .package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3") -] -package.targets.first?.dependencies += [ - .product(name: "CSQLite", package: "CSQLite") -] -#endif +extension Package.Dependency { + + static func github(_ repo: String, exact ver: Version) -> Package.Dependency { + .package(url: "https://github.com/\(repo)", exact: ver) + } +} diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 0eebbdd5..5e741ecd 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -29,7 +29,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index 4eea76c3..bfe253a7 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index 8a25e51d..32461468 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f2c3b781..188ff80b 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -29,7 +29,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index ee59e5d1..2659ad21 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -3,7 +3,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 6cb2e5d3..458e3d9c 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -27,7 +27,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index c27ccf05..00493f21 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -27,7 +27,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift index f37300ca..68618596 100644 --- a/Tests/SQLiteTests/Core/Connection+AttachTests.swift +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -7,7 +7,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift index 2bcdb6af..bd29bd98 100644 --- a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -7,7 +7,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 6a1d94ae..61083453 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -8,7 +8,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index d3c8bb1f..b6a373f7 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -7,7 +7,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index dbf99d7c..ceaa8813 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -6,7 +6,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift index 1129ae08..8a34e93b 100644 --- a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift index 71cbba9c..8b7ec09a 100644 --- a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift @@ -8,7 +8,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 6be98ca0..899bf354 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index f018f097..7fc3dcf5 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif From fc774a2585965a90f60aa034001e6d69aee42382 Mon Sep 17 00:00:00 2001 From: ha100 Date: Sun, 8 Jun 2025 07:08:47 +0200 Subject: [PATCH 197/216] fix macOS build via platform condition --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 690d41e2..48085178 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let targets: [Target] = [ .target( name: "SQLite", dependencies: [ - .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite") + .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows])) ], exclude: [ "Info.plist" From f3cb9105a80d566575c02bb77ac50e74e5895ccc Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 12:15:34 +0200 Subject: [PATCH 198/216] Fully qualify Expression --- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index d942fba4..f5a6de42 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -278,9 +278,9 @@ class SchemaChangerTests: SQLiteTestCase { func test_create_table_add_column_expression() throws { try schemaChanger.create(table: "foo") { table in - table.add(expression: Expression("name")) - table.add(expression: Expression("age")) - table.add(expression: Expression("salary")) + table.add(expression: SQLite.Expression("name")) + table.add(expression: SQLite.Expression("age")) + table.add(expression: SQLite.Expression("salary")) } let columns = try schema.columnDefinitions(table: "foo") From a7d7e8c0b7529f851fd8927071003095f1e87522 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 12:27:28 +0200 Subject: [PATCH 199/216] 17.5 no longer available --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0274321..33c4dba6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: "iPhone 15" - IOS_VERSION: "17.5" + IOS_VERSION: "18.0" jobs: build: runs-on: macos-15 From 716394d091618047dfe86b7da5106fb21ede412f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 12:34:42 +0200 Subject: [PATCH 200/216] iPhone 16 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33c4dba6..37f75476 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "iPhone 15" + IOS_SIMULATOR: "iPhone 16" IOS_VERSION: "18.0" jobs: build: From 9831a45edb84d492cd5b68ba6f8d53300808499f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 13:17:56 +0200 Subject: [PATCH 201/216] Update Index.md --- Documentation/Index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bc62c791..bd2a7a77 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1592,7 +1592,8 @@ let schemaChanger = SchemaChanger(connection: db) try schemaChanger.create(table: "users") { table in table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) table.add(column: .init(name: "name", type: .TEXT, nullable: false)) -} +} +``` ### Indexes From e3a916637162ece77b0ffd89b9e758669adc62f0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 22:49:43 +0200 Subject: [PATCH 202/216] Update CHANGELOG for release --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c96724fd..b77a8b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +0.15.4 (13-06-2025), [diff][diff-0.15.4] +======================================== +* Fix cross compilation for linux on macOS fails ([#1317][]) +* Support creating tables in schema changer ([#1315][]) +* Update oldest supported platform versions ([#1280][]) +* Add CustomStringConvertible for Setter ([#1279][]) + 0.15.3 (19-04-2024), [diff][diff-0.15.3] ======================================== * Update `podspec` to include privacy manifest ([#1265][]) @@ -173,6 +180,8 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 [diff-0.15.1]: https://github.com/stephencelis/SQLite.swift/compare/0.15.0...0.15.1 [diff-0.15.2]: https://github.com/stephencelis/SQLite.swift/compare/0.15.1...0.15.2 +[diff-0.15.3]: https://github.com/stephencelis/SQLite.swift/compare/0.15.2...0.15.3 +[diff-0.15.4]: https://github.com/stephencelis/SQLite.swift/compare/0.15.3...0.15.4 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -259,3 +268,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 [#1260]: https://github.com/stephencelis/SQLite.swift/pull/1260 [#1265]: https://github.com/stephencelis/SQLite.swift/pull/1265 +[#1279]: https://github.com/stephencelis/SQLite.swift/pull/1279 +[#1280]: https://github.com/stephencelis/SQLite.swift/pull/1280 +[#1315]: https://github.com/stephencelis/SQLite.swift/pull/1315 +[#1317]: https://github.com/stephencelis/SQLite.swift/pull/1317 \ No newline at end of file From 02e055c99c4fa19604a95fea4e82fd085391fdf0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 22:51:53 +0200 Subject: [PATCH 203/216] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a4d713b6..3e782f86 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.3 + github "stephencelis/SQLite.swift" ~> 0.15.4 ``` 3. Run `carthage update` and @@ -183,7 +183,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.14.0' + pod 'SQLite.swift', '~> 0.15.0' end ``` From bbc5212c2ae9905d17ae581927c06e2cbad61ff7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 22:53:36 +0200 Subject: [PATCH 204/216] Project looks dead --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3e782f86..db80fd3a 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,6 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [GRDB](https://github.com/groue/GRDB.swift) - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - - [Squeal](https://github.com/nerdyc/Squeal) [Swift]: https://swift.org/ [SQLite3]: https://www.sqlite.org From 4e289bed6f1ed6737ffa963570beae3359a849e8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 23:00:44 +0200 Subject: [PATCH 205/216] No longer maintained --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index db80fd3a..0e83504f 100644 --- a/README.md +++ b/README.md @@ -226,8 +226,6 @@ device: ## Communication -[See the planning document] for a roadmap and existing feature requests. - [Read the contributing guidelines][]. The _TL;DR_ (but please; _R_): - Need **help** or have a **general question**? [Ask on Stack @@ -235,7 +233,6 @@ device: - Found a **bug** or have a **feature request**? [Open an issue][]. - Want to **contribute**? [Submit a pull request][]. -[See the planning document]: /Documentation/Planning.md [Read the contributing guidelines]: ./CONTRIBUTING.md#contributing [Ask on Stack Overflow]: https://stackoverflow.com/questions/tagged/sqlite.swift [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new From 0f4f2c85c5beddeb70f02e1db36785b5f6e2dccf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 23:12:38 +0200 Subject: [PATCH 206/216] Bump version to 0.15.4 --- Documentation/Index.md | 12 ++++++------ SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 12 ++---------- Tests/SPM/Package.swift | 2 +- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bd2a7a77..0e8261f2 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -109,7 +109,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4") ] ``` @@ -130,7 +130,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.3 + github "stephencelis/SQLite.swift" ~> 0.15.4 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.3' + pod 'SQLite.swift', '~> 0.15.4' end ``` @@ -174,7 +174,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.3' + pod 'SQLite.swift/standalone', '~> 0.15.4' end ``` @@ -184,7 +184,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.3' + pod 'SQLite.swift/standalone', '~> 0.15.4' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -200,7 +200,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.3' + pod 'SQLite.swift/SQLCipher', '~> 0.15.4' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index d0ccb136..73c6f705 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.15.3" + s.version = "0.15.4" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a2d7edbe..ec0423e9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1539,7 +1539,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1562,7 +1561,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1614,7 +1612,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1639,7 +1636,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1664,7 +1660,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1689,7 +1684,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1778,6 +1772,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.4; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1837,6 +1832,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.4; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; @@ -1866,7 +1862,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1890,7 +1885,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1940,7 +1934,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1966,7 +1959,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 6521211a..a5a4afc4 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 496a086e5a4d03fb6b0338f63b600dc153be6fa3 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 14 Jul 2025 15:14:14 -0400 Subject: [PATCH 207/216] Build `SwiftToolchainCSQLite` for Android --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index cf3a0b60..56925d18 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let targets: [Target] = [ .target( name: "SQLite", dependencies: [ - .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows])) + .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows, .android])) ], exclude: [ "Info.plist" From e8cfef658cbdbdd297ed7f549d197ec481b6b7a9 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 14 Jul 2025 15:14:34 -0400 Subject: [PATCH 208/216] Fix Android build --- Sources/SQLite/Core/Backup.swift | 2 +- Sources/SQLite/Core/Connection+Aggregation.swift | 2 +- Sources/SQLite/Core/Connection+Attach.swift | 2 +- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Result.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- Tests/SQLiteTests/Core/Connection+AttachTests.swift | 2 +- Tests/SQLiteTests/Core/Connection+PragmaTests.swift | 2 +- Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- Tests/SQLiteTests/Core/ResultTests.swift | 2 +- Tests/SQLiteTests/Core/StatementTests.swift | 2 +- Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift | 2 +- Tests/SQLiteTests/Typed/CustomAggregationTests.swift | 2 +- Tests/SQLiteTests/Typed/QueryIntegrationTests.swift | 2 +- Tests/SQLiteTests/Typed/QueryTests.swift | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 5e741ecd..023acc08 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -28,7 +28,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index bfe253a7..a1abb74a 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -3,7 +3,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index 32461468..0c674ee6 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -3,7 +3,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 188ff80b..57521f83 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 os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 2659ad21..9a72e4c5 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -2,7 +2,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 458e3d9c..82d535b9 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 os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 00493f21..e3c84589 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift index 68618596..0e185da5 100644 --- a/Tests/SQLiteTests/Core/Connection+AttachTests.swift +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -6,7 +6,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift index bd29bd98..d1d4ab04 100644 --- a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -6,7 +6,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 61083453..da50974a 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -7,7 +7,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index b6a373f7..03415f3a 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -6,7 +6,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index ceaa8813..3c90d941 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -5,7 +5,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift index 8a34e93b..b3b9e617 100644 --- a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift @@ -3,7 +3,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift index 8b7ec09a..73a0767f 100644 --- a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift @@ -7,7 +7,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 899bf354..cde5a0c3 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -3,7 +3,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index 7fc3dcf5..257b5245 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -3,7 +3,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 From 91a30a3535e74767372ef605751569299696d8d1 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Tue, 15 Jul 2025 13:54:04 +0200 Subject: [PATCH 209/216] add android ci --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37f75476..f82a9c59 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,3 +76,9 @@ jobs: env: SPM: run run: ./run-tests.sh + build-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run tests + uses: skiptools/swift-android-action@v2 From 22d30c885f6ac1161ff5e88e32f054b4672963d2 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Wed, 16 Jul 2025 15:17:33 +0200 Subject: [PATCH 210/216] add SQLite. prefix for Expression in README --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e83504f..a52b7ae5 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ do { let db = try Connection("path/to/db.sqlite3") let users = Table("users") - let id = Expression("id") - let name = Expression("name") - let email = Expression("email") + let id = SQLite.Expression("id") + let name = SQLite.Expression("name") + let email = SQLite.Expression("email") try db.run(users.create { t in t.column(id, primaryKey: true) @@ -83,6 +83,9 @@ do { } ``` +Note that `Expression` should be written as `SQLite.Expression` to avoid +conflicts with the `SwiftUI.Expression` if you are using SwiftUI too. + SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API. From fee88af870ccf4f574607b1dc8f2835cb2aa0b36 Mon Sep 17 00:00:00 2001 From: hawk0620 Date: Thu, 17 Jul 2025 14:11:06 +0800 Subject: [PATCH 211/216] create index support array as parameters --- Sources/SQLite/Typed/Schema.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index e162cf34..02f5b102 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -135,8 +135,8 @@ extension Table { } // MARK: - CREATE INDEX - - 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"), @@ -146,12 +146,20 @@ extension Table { return " ".join(clauses.compactMap { $0 }).asSQL() } + + public func createIndex(_ columns: Expressible..., unique: Bool = false, ifNotExists: Bool = false) -> String { + return createIndex(Array(columns), unique: unique, ifNotExists: ifNotExists) + } // MARK: - DROP INDEX - - public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { + + public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String { drop("INDEX", indexName(columns), ifExists) } + + public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { + dropIndex(Array(columns), ifExists: ifExists) + } fileprivate func indexName(_ columns: [Expressible]) -> Expressible { let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joined(separator: " ").lowercased() From 98d83a223db0d1a51c2d575f108c66b1b770b725 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Thu, 17 Jul 2025 13:42:49 +0200 Subject: [PATCH 212/216] fix linting --- Sources/SQLite/Typed/Query.swift | 2 +- Sources/SQLite/Typed/Schema.swift | 8 ++++---- Tests/SQLiteTests/TestHelpers.swift | 10 ---------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 2a83665c..6162fcc7 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1055,7 +1055,7 @@ extension Connection { } func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { - { (queryType: QueryType) throws -> Void in + { (queryType: QueryType) throws in var query = type(of: queryType).init(queryType.clauses.from.name, database: queryType.clauses.from.database) query.clauses.select = queryType.clauses.select query.clauses.with = strip(queryType.clauses.with) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 02f5b102..919042ab 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -135,7 +135,7 @@ extension Table { } // MARK: - CREATE INDEX - + public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create("INDEX", indexName(columns), unique ? .unique : nil, ifNotExists), @@ -146,17 +146,17 @@ extension Table { return " ".join(clauses.compactMap { $0 }).asSQL() } - + public func createIndex(_ columns: Expressible..., unique: Bool = false, ifNotExists: Bool = false) -> String { return createIndex(Array(columns), unique: unique, ifNotExists: ifNotExists) } // MARK: - DROP INDEX - + public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String { drop("INDEX", indexName(columns), ifExists) } - + public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { dropIndex(Array(columns), ifExists: ifExists) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 56415a59..e62b2a23 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -164,14 +164,4 @@ struct TestOptionalCodable: Codable, Equatable { let double: Double? let date: Date? let uuid: UUID? - - init(int: Int?, string: String?, bool: Bool?, float: Float?, double: Double?, date: Date?, uuid: UUID?) { - self.int = int - self.string = string - self.bool = bool - self.float = float - self.double = double - self.date = date - self.uuid = uuid - } } From 83c0c9828c0adb80b6ef08d3451fd8c2951398bd Mon Sep 17 00:00:00 2001 From: Nikolay Dzhulay Date: Wed, 13 Aug 2025 15:59:05 +0100 Subject: [PATCH 213/216] Attempt to fix tests build for android --- Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 1c648bd8..f3a0ba83 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -32,7 +32,7 @@ class ColumnDefinitionTests: XCTestCase { defaultValue: .numericLiteral("123.123"), references: nil)) ] - #if !os(Linux) + #if !(os(Linux) || os(Android)) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) @@ -183,7 +183,7 @@ class IndexDefinitionTests: XCTestCase { "CREATE INDEX IF NOT EXISTS \"index_tests\" ON \"tests\" (\"test_column\")") ] - #if !os(Linux) + #if !(os(Linux) || os(Android)) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: IndexDefinitionTests.self) From b7b7923708c684d73d378c2a279268c72afae407 Mon Sep 17 00:00:00 2001 From: Nikolay Dzhulay Date: Sun, 17 Aug 2025 20:45:07 +0100 Subject: [PATCH 214/216] Disable for Android same tests, which disabled for Linux --- Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- Tests/SQLiteTests/Typed/CustomAggregationTests.swift | 2 +- Tests/SQLiteTests/Typed/CustomFunctionsTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index da50974a..abdb3e1b 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -374,7 +374,7 @@ class ConnectionTests: SQLiteTestCase { } // https://github.com/stephencelis/SQLite.swift/issues/1071 - #if !os(Linux) + #if !(os(Linux) || os(Android)) func test_createFunction_withArrayArguments() throws { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } diff --git a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift index 73a0767f..b052d236 100644 --- a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift @@ -14,7 +14,7 @@ import SQLite3 #endif // https://github.com/stephencelis/SQLite.swift/issues/1071 -#if !os(Linux) +#if !(os(Linux) || os(Android)) class CustomAggregationTests: SQLiteTestCase { override func setUpWithError() throws { diff --git a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift index 8598b6fb..fd9ae6b9 100644 --- a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift @@ -2,7 +2,7 @@ import XCTest import SQLite // https://github.com/stephencelis/SQLite.swift/issues/1071 -#if !os(Linux) +#if !(os(Linux) || os(Android)) class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> SQLite.Expression From 4f2fdffc2a5924d807bded85e6fe58378eba3ae6 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 30 Aug 2025 15:10:47 +0200 Subject: [PATCH 215/216] Change iOS simulator device to Any iOS Simulator Device --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f82a9c59..d2ef6aa1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "iPhone 16" + IOS_SIMULATOR: "Any iOS Simulator Device" IOS_VERSION: "18.0" jobs: build: From 9c14b66992517fe3c376b8af6ede3f68aabc3577 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 30 Aug 2025 15:20:21 +0200 Subject: [PATCH 216/216] Trying iPhone 16 with iOS 18.4 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d2ef6aa1..3c6970f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "Any iOS Simulator Device" - IOS_VERSION: "18.0" + IOS_SIMULATOR: "iPhone 16" + IOS_VERSION: "18.4" jobs: build: runs-on: macos-15