From f40ca4da2f02204fdc5e3d317014062fac55326a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Apr 2018 18:04:41 +0200 Subject: [PATCH 001/391] Fix Swift 4.1 warnings --- .travis.yml | 2 +- SQLite.xcodeproj/project.pbxproj | 6 +++++- .../xcshareddata/xcschemes/SQLite Mac.xcscheme | 4 +--- .../xcshareddata/xcschemes/SQLite iOS.xcscheme | 4 +--- .../xcschemes/SQLite tvOS.xcscheme | 4 +--- .../xcschemes/SQLite watchOS.xcscheme | 2 +- Sources/SQLite/Typed/Coding.swift | 2 +- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- Sources/SQLite/Typed/Query.swift | 8 ++++---- Sources/SQLite/Typed/Schema.swift | 18 +++++++++--------- Tests/SQLiteTests/BlobTests.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 2 +- Tests/SQLiteTests/FTS4Tests.swift | 2 +- 13 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f17b26f..41389849 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c rvm: 2.3 -osx_image: xcode9 +osx_image: xcode9.3 env: global: - IOS_SIMULATOR="iPhone 6s" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index df603862..2015b8ff 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -680,7 +680,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 0930; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -1148,12 +1148,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -1206,12 +1208,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index f1fa216c..2691862e 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ Bool { diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 1afe4e87..d7995b95 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -637,7 +637,7 @@ extension ExpressionType where UnderlyingType == String? { } -extension Collection where Iterator.Element : Value, IndexDistance == Int { +extension Collection where Iterator.Element : Value { /// Builds a copy of the expression prepended with an `IN` check against the /// collection. diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 1d04b797..f6ef6df8 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -647,7 +647,7 @@ extension QueryType { whereClause ] - return Insert(" ".join(clauses.flatMap { $0 }).expression) + return Insert(" ".join(clauses.compactMap { $0 }).expression) } /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. @@ -690,7 +690,7 @@ extension QueryType { limitOffsetClause ] - return Update(" ".join(clauses.flatMap { $0 }).expression) + return Update(" ".join(clauses.compactMap { $0 }).expression) } // MARK: DELETE @@ -704,7 +704,7 @@ extension QueryType { limitOffsetClause ] - return Delete(" ".join(clauses.flatMap { $0 }).expression) + return Delete(" ".join(clauses.compactMap { $0 }).expression) } // MARK: EXISTS @@ -789,7 +789,7 @@ extension QueryType { limitOffsetClause ] - return " ".join(clauses.flatMap { $0 }).expression + return " ".join(clauses.compactMap { $0 }).expression } } diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 60b7be8a..62c90702 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -47,7 +47,7 @@ extension Table { withoutRowid ? Expression(literal: "WITHOUT ROWID") : nil ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { @@ -57,7 +57,7 @@ extension Table { query ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - ALTER TABLE … ADD COLUMN @@ -135,7 +135,7 @@ extension Table { "".wrap(columns) as Expression ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - DROP INDEX @@ -174,7 +174,7 @@ extension View { query ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - DROP VIEW @@ -196,7 +196,7 @@ extension VirtualTable { using ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - ALTER TABLE … RENAME TO @@ -405,7 +405,7 @@ public final class TableBuilder { delete.map { Expression(literal: "ON DELETE \($0.rawValue)") } ] - definitions.append(" ".join(clauses.flatMap { $0 })) + definitions.append(" ".join(clauses.compactMap { $0 })) } } @@ -456,7 +456,7 @@ private extension QueryType { name ] - return " ".join(clauses.flatMap { $0 }) + return " ".join(clauses.compactMap { $0 }) } func rename(to: Self) -> String { @@ -475,7 +475,7 @@ private extension QueryType { name ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } } @@ -493,7 +493,7 @@ private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: collate.map { " ".join([Expression(literal: "COLLATE"), $0]) } ] - return " ".join(clauses.flatMap { $0 }) + return " ".join(clauses.compactMap { $0 }) } private func reference(_ primary: (QueryType, Expressible)) -> Expressible { diff --git a/Tests/SQLiteTests/BlobTests.swift b/Tests/SQLiteTests/BlobTests.swift index fbcca9bc..817205d6 100644 --- a/Tests/SQLiteTests/BlobTests.swift +++ b/Tests/SQLiteTests/BlobTests.swift @@ -16,7 +16,7 @@ class BlobTests : XCTestCase { func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) - pointer.initialize(to: 42, count: 3) + pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) XCTAssertEqual(blob.bytes, [42, 42, 42]) } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 7bb86e41..7fc26110 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -157,7 +157,7 @@ class ConnectionTests : SQLiteTestCase { func test_transaction_rollsBackTransactionsIfCommitsFail() { let sqliteVersion = String(describing: try! db.scalar("SELECT sqlite_version()")!) - .split(separator: ".").flatMap { Int($0) } + .split(separator: ".").compactMap { Int($0) } // PRAGMA defer_foreign_keys only supported in SQLite >= 3.8.0 guard sqliteVersion[0] == 3 && sqliteVersion[1] >= 8 else { NSLog("skipping test for SQLite version \(sqliteVersion)") diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index 4373bf8b..79f0a8e2 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -184,7 +184,7 @@ class FTS4IntegrationTests : SQLiteTestCase { let locale = CFLocaleCopyCurrent() let tokenizerName = "tokenizer" - let tokenizer = CFStringTokenizerCreate(nil, "" as CFString!, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + 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 { From 1ed747ec930889de2b6e79451a8c37eea3109326 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Apr 2018 18:43:32 +0200 Subject: [PATCH 002/391] Fix iOS version --- .travis.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 41389849..f278301d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ osx_image: xcode9.3 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="11.0" + - IOS_VERSION="11.3" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/Makefile b/Makefile index ebd38494..cd4afb8d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 11.0 +IOS_VERSION = 11.3 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else From be9fc789c3772edaef83000deda20ab777f607f7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Apr 2018 23:10:41 +0200 Subject: [PATCH 003/391] Update bundle --- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 54 +++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 04f0155a..3b9d6ba5 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.3.1' +gem 'cocoapods', '~> 1.5.0' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index 47a2db58..de03030a 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,73 +1,77 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.5) - activesupport (4.2.9) + CFPropertyList (3.0.0) + activesupport (4.2.10) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + atomos (0.1.2) claide (1.0.2) - cocoapods (1.3.1) + cocoapods (1.5.0) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.3.1) - cocoapods-deintegrate (>= 1.0.1, < 2.0) - cocoapods-downloader (>= 1.1.3, < 2.0) + cocoapods-core (= 1.5.0) + cocoapods-deintegrate (>= 1.0.2, < 2.0) + cocoapods-downloader (>= 1.2.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.2.0, < 2.0) + cocoapods-trunk (>= 1.3.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.5.7) + molinillo (~> 0.6.5) nap (~> 1.0) ruby-macho (~> 1.1) - xcodeproj (>= 1.5.1, < 2.0) - cocoapods-core (1.3.1) + xcodeproj (>= 1.5.7, < 2.0) + cocoapods-core (1.5.0) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.1) - cocoapods-downloader (1.1.3) + cocoapods-deintegrate (1.0.2) + cocoapods-downloader (1.2.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.2.0) + cocoapods-trunk (1.3.0) nap (>= 0.8, < 2.0) - netrc (= 0.7.8) + netrc (~> 0.11) cocoapods-try (1.1.0) colored2 (3.1.2) + concurrent-ruby (1.0.5) escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) - gh_inspector (1.0.3) - i18n (0.8.6) + gh_inspector (1.1.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) minitest (5.10.1) - molinillo (0.5.7) - nanaimo (0.2.3) + molinillo (0.6.5) + nanaimo (0.2.5) nap (1.1.0) - netrc (0.7.8) + netrc (0.11.0) ruby-macho (1.1.0) thread_safe (0.3.6) - tzinfo (1.2.3) + tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.5.1) - CFPropertyList (~> 2.3.3) + xcodeproj (1.5.7) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.2) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.3) + nanaimo (~> 0.2.4) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.3.1) + cocoapods (~> 1.5.0) minitest BUNDLED WITH - 1.13.6 + 1.16.1 From afd58d3ba205c4b0065528f5c1180e0685ee1388 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Apr 2018 01:21:48 +0200 Subject: [PATCH 004/391] =?UTF-8?q?Don=E2=80=99t=20run=20iOS=20tests=20on?= =?UTF-8?q?=20oldest=20sim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “Early unexpected exit, operation never finished bootstrapping” --- Tests/CocoaPods/integration_test.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 9792b570..4bec8d40 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -39,5 +39,32 @@ def test_pod # https://github.com/CocoaPods/CocoaPods/issues/7009 super unless consumer.platform_name == :watchos end + + def xcodebuild(action, scheme, configuration) + require 'fourflusher' + command = %W(clean #{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) + case consumer.platform_name + when :osx, :macos + command += %w(CODE_SIGN_IDENTITY=) + when :ios + command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) + command += Fourflusher::SimControl.new.destination(nil, 'iOS', deployment_target) + when :watchos + command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator) + command += Fourflusher::SimControl.new.destination(:oldest, 'watchOS', deployment_target) + when :tvos + command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) + command += Fourflusher::SimControl.new.destination(:oldest, 'tvOS', deployment_target) + end + + begin + _xcodebuild(command, true) + rescue => e + message = 'Returned an unsuccessful exit code.' + message += ' You can use `--verbose` for more information.' unless config.verbose? + error('xcodebuild', message) + e.message + end + end end end From a1d478eb4325a8c980b99afdfc8e4a29dab0c3e0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Apr 2018 23:05:19 +0200 Subject: [PATCH 005/391] Exclude test on iOS/tvOS 9.x --- SQLite.xcodeproj/project.pbxproj | 4 ++-- Tests/SQLiteTests/ConnectionTests.swift | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2015b8ff..75b38526 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1063,7 +1063,7 @@ 03A65E6D1C6BB0F60062603F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = Tests/SQLite/Info.plist; + 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)"; @@ -1077,7 +1077,7 @@ 03A65E6E1C6BB0F60062603F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = Tests/SQLite/Info.plist; + 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)"; diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 7fc26110..9480e3bb 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -384,6 +384,9 @@ class ConnectionTests : SQLiteTestCase { } func test_concurrent_access_single_connection() { + // 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) From d732db052d98d3ac2ef577c0331e6fb93a3a170f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 14 Apr 2018 00:43:48 +0200 Subject: [PATCH 006/391] Docs --- .gitmodules | 0 .swift-version | 2 +- CHANGELOG.md | 7 +++++++ Documentation/Index.md | 16 ++++++++-------- README.md | 10 +++++----- SQLite.swift.podspec | 4 ++-- 6 files changed, 23 insertions(+), 16 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/.swift-version b/.swift-version index 5186d070..7d5c902e 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.0 +4.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 578b59c2..648cdc07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.11.5 (04-14-2018), [diff][diff-0.11.5] +======================================== + +* Swift 4.1 ([#797][]) + 0.11.4 (30-09-2017), [diff][diff-0.11.4] ======================================== @@ -51,6 +56,7 @@ [diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 +[diff-0.11.5]: https://github.com/stephencelis/SQLite.swift/compare/0.11.4...0.11.5 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -81,3 +87,4 @@ [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 +[#797]: https://github.com/stephencelis/SQLite.swift/pull/797 diff --git a/Documentation/Index.md b/Documentation/Index.md index efb90ae6..56c8c429 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,8 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 4 (and -> [Xcode 9](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 4.1 (and +> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.4 + github "stephencelis/SQLite.swift" ~> 0.11.5 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.4' + pod 'SQLite.swift', '~> 0.11.5' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.4' + pod 'SQLite.swift/standalone', '~> 0.11.5' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.4' + pod 'SQLite.swift/standalone', '~> 0.11.5' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.4' + pod 'SQLite.swift/SQLCipher', '~> 0.11.5' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") ] ``` diff --git a/README.md b/README.md index 3900c0a3..bc43b6ec 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4 (and [Xcode][] 9). +> _Note:_ SQLite.swift requires Swift 4.1 (and [Xcode][] 9.3). ### Carthage @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.4 + github "stephencelis/SQLite.swift" ~> 0.11.5 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.4' + pod 'SQLite.swift', '~> 0.11.5' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") ] ``` @@ -285,7 +285,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-4-orange.svg?style=flat +[Swift4Badge]: https://img.shields.io/badge/swift-4.1-orange.svg?style=flat [Swift4Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4a329338..b03a9ce4 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.4" + s.version = "0.11.5" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.0', + 'SWIFT_VERSION' => '4.1', } s.subspec 'standard' do |ss| From 44097d32651d093e826e16ce5d2fa13d4c774c3a Mon Sep 17 00:00:00 2001 From: Dave DeLong Date: Mon, 16 Apr 2018 16:13:04 +0200 Subject: [PATCH 007/391] Add `and` and `or` functions When you're programmatically building a query to execute, you have times where you build a variable number of clauses by which you filter a table. There isn't a great way to turn an `Array>` into an `Expression`, short of making a large `(a AND (b AND (c AND (d AND e))))` expression. `and` and `or` fix this by allowing you to turn an `Array>` directly into a lower-complexity clause: `(a AND b AND c AND d AND e)` --- Sources/SQLite/Helpers.swift | 6 +++++- Sources/SQLite/Typed/Operators.swift | 12 ++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index ac831667..b4ff360e 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -73,7 +73,11 @@ extension String { } func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - let expression = Expression(" \(self) ".join([lhs, rhs]).expression) + return infix([lhs, rhs]) + } + + func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { + let expression = Expression(" \(self) ".join(terms).expression) guard wrap else { return expression } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index d97e52b9..a6df717f 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -516,6 +516,12 @@ public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expr // MARK: - +public func and(_ terms: Expression...) -> Expression { + return "AND".infix(terms) +} +public func and(_ terms: [Expression]) -> Expression { + return "AND".infix(terms) +} public func &&(lhs: Expression, rhs: Expression) -> Expression { return "AND".infix(lhs, rhs) } @@ -541,6 +547,12 @@ public func &&(lhs: Bool, rhs: Expression) -> Expression { return "AND".infix(lhs, rhs) } +public func or(_ terms: Expression...) -> Expression { + return "OR".infix(terms) +} +public func or(_ terms: [Expression]) -> Expression { + return "OR".infix(terms) +} public func ||(lhs: Expression, rhs: Expression) -> Expression { return "OR".infix(lhs, rhs) } diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 948eb0a4..050b1e39 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -290,6 +290,16 @@ class OperatorsTests : XCTestCase { AssertSQL("(1 AND \"bool\")", true && bool) AssertSQL("(1 AND \"boolOptional\")", true && boolOptional) } + + func test_andFunction_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and([bool, bool, bool])) + AssertSQL("(\"bool\" AND \"bool\")", and([bool, bool])) + AssertSQL("(\"bool\")", and([bool])) + + AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and(bool, bool, bool)) + AssertSQL("(\"bool\" AND \"bool\")", and(bool, bool)) + AssertSQL("(\"bool\")", and(bool)) + } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { AssertSQL("(\"bool\" OR \"bool\")", bool || bool) @@ -301,6 +311,16 @@ class OperatorsTests : XCTestCase { AssertSQL("(1 OR \"bool\")", true || bool) AssertSQL("(1 OR \"boolOptional\")", true || boolOptional) } + + func test_orFunction_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or([bool, bool, bool])) + AssertSQL("(\"bool\" OR \"bool\")", or([bool, bool])) + AssertSQL("(\"bool\")", or([bool])) + + AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or(bool, bool, bool)) + AssertSQL("(\"bool\" OR \"bool\")", or(bool, bool)) + AssertSQL("(\"bool\")", or(bool)) + } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { AssertSQL("NOT (\"bool\")", !bool) From e080a3ac0d2dc8fea15dde5681939bbbc0848b3e Mon Sep 17 00:00:00 2001 From: Dave DeLong Date: Mon, 16 Apr 2018 16:15:09 +0200 Subject: [PATCH 008/391] Forgot to pass on a parameter --- Sources/SQLite/Helpers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index b4ff360e..4b755078 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -73,7 +73,7 @@ extension String { } func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return infix([lhs, rhs]) + return infix([lhs, rhs], wrap: wrap) } func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { From 10d5e85a99e7f0daedccd5a5099bc457f58ea4e4 Mon Sep 17 00:00:00 2001 From: Madalin Mamuleanu Date: Tue, 19 Jun 2018 17:12:28 +0300 Subject: [PATCH 009/391] Fixed one of Executing Arbitrary SQL examples: - corrected method name - added : instead of = --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 56c8c429..9aa84f4a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1854,8 +1854,8 @@ using the following functions. ```swift let stmt = try db.prepare("SELECT id, email FROM users") for row in stmt { - for (index, name) in stmt.columnNames.enumerate() { - print ("\(name)=\(row[index]!)") + for (index, name) in stmt.columnNames.enumerated() { + print ("\(name):\(row[index]!)") // id: Optional(1), email: Optional("alice@mac.com") } } From 1f84745513aa56bbd38e428e0a5db5d55de3381e Mon Sep 17 00:00:00 2001 From: Serg Date: Sun, 15 Jul 2018 23:36:52 +0700 Subject: [PATCH 010/391] Support Int64 coding --- Sources/SQLite/Typed/Coding.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 7c70db33..3e4b5226 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -152,7 +152,7 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: Int64, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int64 is not supported")) + self.encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: UInt, forKey key: Key) throws { @@ -249,7 +249,7 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) + return try self.row.get(Expression(key.stringValue)) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { From 01e152a0672f3934867b86017c290e1f0a62e24a Mon Sep 17 00:00:00 2001 From: Victor Kononov Date: Mon, 6 Aug 2018 17:09:57 +0300 Subject: [PATCH 011/391] non_framewrok_fix: fixed a mistype that prevented building SQLite.swift as non-framework sources. --- 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 7c70db33..c3fb931b 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -184,7 +184,7 @@ fileprivate class SQLiteEncoder: Encoder { } } - fileprivate var setters: [SQLite.Setter] = [] + fileprivate var setters: [Setter] = [] let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] From 0aaddb3b97d8d62d46567e16c305beb34312bad2 Mon Sep 17 00:00:00 2001 From: Otto Suess <37940680+ottosuess@users.noreply.github.com> Date: Thu, 13 Sep 2018 18:33:51 +0200 Subject: [PATCH 012/391] fix markdown table formatting tables containing `|` are breaking github markdown formatter when they are not escaped. --- Documentation/Index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9aa84f4a..628e7464 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -718,7 +718,7 @@ try db.transaction { | `<<=` | `Int -> Int` | | `>>=` | `Int -> Int` | | `&=` | `Int -> Int` | -| `||=` | `Int -> Int` | +| `\|\|=` | `Int -> Int` | | `^=` | `Int -> Int` | | `+=` | `String -> String` | @@ -954,7 +954,7 @@ equate or compare different types will prevent compilation. | `<=` | `Comparable -> Bool` | `<=` | | `~=` | `(Interval, Comparable) -> Bool` | `BETWEEN` | | `&&` | `Bool -> Bool` | `AND` | -| `||` | `Bool -> Bool` | `OR` | +| `\|\|`| `Bool -> Bool` | `OR` | > *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` > accordingly. @@ -1586,8 +1586,8 @@ arithmetic, bitwise operations, and concatenation. | `<<` | `Int -> Int` | `<<` | | `>>` | `Int -> Int` | `>>` | | `&` | `Int -> Int` | `&` | -| `|` | `Int -> Int` | `|` | -| `+` | `String -> String` | `||` | +| `\|` | `Int -> Int` | `\|` | +| `+` | `String -> String` | `\|\|` | > _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which > expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. From 304c39d42f45c07eece380f1d64e4ba40f3fadb1 Mon Sep 17 00:00:00 2001 From: Jason Peterson Date: Sat, 29 Sep 2018 21:27:34 -0500 Subject: [PATCH 013/391] added sample code for copying a 'seed' database --- Documentation/Index.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..12810f94 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -258,6 +258,28 @@ let path = NSSearchPathForDirectoriesInDomains( let db = try Connection("\(path)/db.sqlite3") ``` +If you have bundled it your application, you can use FileManager to copy it to the Documents directory: + +```swift +func copyDatabaseIfNeeded(sourcePath : String) -> Bool { + var destinationPath = NSSearchPathForDirectoriesInDomains( + .documentDirectory, .userDomainMask, true + ).first! + destinationPath = destinationPath + "/db.sqlite3" + let databaseExistsWhereNeeded = FileManager.default.fileExists(atPath: destinationPath) + if (!databaseExistsWhereNeeded) { + do { + try FileManager.default.copyItem(atPath: sourcePath, toPath: destinationPath) + print("db copied") + } + catch { + print("error during file copy: \(error)") + } + } + return true +} +``` + On OS X, you can use your app’s **Application Support** directory: ```swift @@ -295,7 +317,7 @@ let db = try Connection(path, readonly: true) > See these two Stack Overflow questions for more information about iOS apps > with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), > [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). -> We welcome sample code to show how to successfully copy and use a bundled "seed" +> 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-Memory Databases From ad63c37cc044af3d4bc1588faa6c41c5600b71b0 Mon Sep 17 00:00:00 2001 From: Madalin Mamuleanu Date: Tue, 2 Oct 2018 00:21:03 +0300 Subject: [PATCH 014/391] Documentation update. Fixes #841 --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..f214b50a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -266,8 +266,8 @@ var path = NSSearchPathForDirectoriesInDomains( ).first! + "/" + Bundle.main.bundleIdentifier! // create parent directory iff it doesn’t exist -try FileManager.default.createDirectoryAtPath( - path, withIntermediateDirectories: true, attributes: nil +try FileManager.default.createDirectory( +atPath: path, withIntermediateDirectories: true, attributes: nil ) let db = try Connection("\(path)/db.sqlite3") From ab53696ce1aca28a292ecb1b20967a1f687cf7a1 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Mon, 15 Oct 2018 21:52:10 -0700 Subject: [PATCH 015/391] add primary key with 4 columns. --- Sources/SQLite/Typed/Schema.swift | 4 ++++ Tests/SQLiteTests/SchemaTests.swift | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 62c90702..febfe68c 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -341,6 +341,10 @@ public final class TableBuilder { primaryKey([compositeA, b, c]) } + public func primaryKey(_ compositeA: Expression, _ b: Expression, _ c: Expression, _ d: Expression) { + primaryKey([compositeA, b, c, d]) + } + fileprivate func primaryKey(_ composite: [Expressible]) { definitions.append("PRIMARY KEY".prefix(composite)) } diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index b9a08881..30646b98 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -545,6 +545,10 @@ class SchemaTests : XCTestCase { "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\", \"double\"))", table.create { t in t.primaryKey(int64, string, double) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\", \"double\", \"date\"))", + table.create { t in t.primaryKey(int64, string, double, date) } + ) } func test_unique_compilesUniqueExpression() { From cf4183f4ef54c5c9e82176cc46451934c8fdec2a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 19 Dec 2018 12:35:50 +0100 Subject: [PATCH 016/391] Swift 4.2, Xcode 10.1 - fix tests running agains SQLCipher 4.x --- .swift-version | 2 +- .travis.yml | 5 +- CHANGELOG.md | 7 +++ Makefile | 2 +- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 28 +++++------ Sources/SQLite/Extensions/Cipher.swift | 5 ++ Sources/SQLiteObjc/include/SQLite-Bridging.h | 3 +- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 44 +++++++++--------- Tests/CocoaPods/integration_test.rb | 2 +- Tests/SQLiteTests/CipherTests.swift | 10 +++- Tests/SQLiteTests/ConnectionTests.swift | 35 +++++++------- Tests/SQLiteTests/TestHelpers.swift | 9 ---- ...{encrypted.sqlite => encrypted-3.x.sqlite} | Bin .../SQLiteTests/fixtures/encrypted-4.x.sqlite | Bin 0 -> 8192 bytes 16 files changed, 83 insertions(+), 73 deletions(-) rename Tests/SQLiteTests/fixtures/{encrypted.sqlite => encrypted-3.x.sqlite} (100%) create mode 100644 Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite diff --git a/.swift-version b/.swift-version index 7d5c902e..bf77d549 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.1 +4.2 diff --git a/.travis.yml b/.travis.yml index f278301d..909e5672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: objective-c rvm: 2.3 -osx_image: xcode9.3 +# https://docs.travis-ci.com/user/reference/osx +osx_image: xcode10.1 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="11.3" + - IOS_VERSION="12.1" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/CHANGELOG.md b/CHANGELOG.md index 648cdc07..027611ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.11.6 (xxx), [diff][diff-0.11.6] +======================================== + +* Swift 4.2, SQLCipher 4.x ([#866][]) + 0.11.5 (04-14-2018), [diff][diff-0.11.5] ======================================== @@ -57,6 +62,7 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 [diff-0.11.5]: https://github.com/stephencelis/SQLite.swift/compare/0.11.4...0.11.5 +[diff-0.11.6]: https://github.com/stephencelis/SQLite.swift/compare/0.11.5...0.11.6 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -88,3 +94,4 @@ [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 [#797]: https://github.com/stephencelis/SQLite.swift/pull/797 +[#866]: https://github.com/stephencelis/SQLite.swift/pull/866 diff --git a/Makefile b/Makefile index cd4afb8d..d98c8deb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 11.3 +IOS_VERSION = 12.1 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b03a9ce4..c2235aca 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.1', + 'SWIFT_VERSION' => '4.2', } s.subspec 'standard' do |ss| diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 75b38526..a155a115 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1033,7 +1033,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1055,7 +1055,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1069,7 +1069,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1083,7 +1083,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1106,7 +1106,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1130,7 +1130,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1269,7 +1269,7 @@ SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1291,7 +1291,7 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1304,7 +1304,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1317,7 +1317,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1342,7 +1342,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1367,7 +1367,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1383,7 +1383,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1399,7 +1399,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 6c0d4657..71ef1765 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -6,6 +6,11 @@ import SQLCipher /// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/) extension Connection { + /// - Returns: the SQLCipher version + public var cipherVersion: String? { + return (try? scalar("PRAGMA cipher_version")) as? String + } + /// Specify the key for an encrypted database. This routine should be /// called right after sqlite3_open(). /// diff --git a/Sources/SQLiteObjc/include/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLite-Bridging.h index 5b356593..f8c2a3b3 100644 --- a/Sources/SQLiteObjc/include/SQLite-Bridging.h +++ b/Sources/SQLiteObjc/include/SQLite-Bridging.h @@ -23,8 +23,7 @@ // @import Foundation; - -#import "sqlite3.h" +@import SQLite3; NS_ASSUME_NONNULL_BEGIN typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 3b9d6ba5..fd1c8c2e 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.5.0' +gem 'cocoapods', '~> 1.6.0beta2' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index de03030a..b5172144 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -2,76 +2,76 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - activesupport (4.2.10) + activesupport (4.2.11) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - atomos (0.1.2) + atomos (0.1.3) claide (1.0.2) - cocoapods (1.5.0) + cocoapods (1.6.0.beta.2) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.5.0) + cocoapods-core (= 1.6.0.beta.2) cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.0, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.6.5) + molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.1) - xcodeproj (>= 1.5.7, < 2.0) - cocoapods-core (1.5.0) + ruby-macho (~> 1.3, >= 1.3.1) + xcodeproj (>= 1.7.0, < 2.0) + cocoapods-core (1.6.0.beta.2) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.0) + cocoapods-downloader (1.2.2) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.0) + cocoapods-trunk (1.3.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.1.0) colored2 (3.1.2) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.4) escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) i18n (0.9.5) concurrent-ruby (~> 1.0) - minitest (5.10.1) - molinillo (0.6.5) - nanaimo (0.2.5) + minitest (5.11.3) + molinillo (0.6.6) + nanaimo (0.2.6) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.1.0) + ruby-macho (1.3.1) thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.5.7) + xcodeproj (1.7.0) CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.2) + atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.4) + nanaimo (~> 0.2.6) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.5.0) + cocoapods (~> 1.6.0beta2) minitest BUNDLED WITH - 1.16.1 + 1.17.1 diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 4bec8d40..98a539b5 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -42,7 +42,7 @@ def test_pod def xcodebuild(action, scheme, configuration) require 'fourflusher' - command = %W(clean #{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) + command = %W(#{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) case consumer.platform_name when :osx, :macos command += %w(CODE_SIGN_IDENTITY=) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index 3ee0b135..abecffba 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -73,11 +73,17 @@ class CipherTests: XCTestCase { } func test_open_db_encrypted_with_sqlcipher() { - // $ sqlcipher SQLiteTests/fixtures/encrypted.sqlite + // $ sqlcipher Tests/SQLiteTests/fixtures/encrypted-[version].x.sqlite // sqlite> pragma key = 'sqlcipher-test'; // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); - let encryptedFile = fixture("encrypted", withExtension: "sqlite") + guard let cipherVersion:String = db1.cipherVersion, + 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") try! FileManager.default.setAttributes([FileAttributeKey.immutable : 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 9480e3bb..eab3cf00 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -369,18 +369,23 @@ class ConnectionTests : SQLiteTestCase { } func test_interrupt_interruptsLongRunningQuery() { - try! InsertUsers("abcdefghijklmnopqrstuvwxyz".map { String($0) }) + let semaphore = DispatchSemaphore(value: 0) db.createFunction("sleep") { args in - usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) + DispatchQueue.global(qos: .background).async { + self.db.interrupt() + semaphore.signal() + } + semaphore.wait() return nil } - - let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) - try! stmt.run() - - let deadline = DispatchTime.now() + 0.01 - _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) - AssertThrows(try stmt.run()) + let stmt = try! db.prepare("SELECT sleep()") + XCTAssertThrowsError(try stmt.run()) { error in + if case Result.error(_, let code, _) = error { + XCTAssertEqual(code, SQLITE_INTERRUPT) + } else { + XCTFail("unexpected error: \(error)") + } + } } func test_concurrent_access_single_connection() { @@ -391,21 +396,17 @@ class ConnectionTests : SQLiteTestCase { 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 - var reads = Array(repeating: 0, count: nReaders) - var finished = false + let semaphores = Array(repeating: DispatchSemaphore(value: 100), count: nReaders) for index in 0.. 500) } - } + semaphores.forEach { $0.wait() } } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 8d60362c..2e491b01 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -100,15 +100,6 @@ func AssertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclo XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } -func AssertThrows(_ expression: @autoclosure () throws -> T, file: StaticString = #file, line: UInt = #line) { - do { - _ = try expression() - XCTFail("expression expected to throw", file: file, line: line) - } catch { - XCTAssert(true, file: file, line: line) - } -} - let table = Table("table") let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") diff --git a/Tests/SQLiteTests/fixtures/encrypted.sqlite b/Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/encrypted.sqlite rename to Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite diff --git a/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite b/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..36890365583fc0fa1383f4377d7c9b844059872c GIT binary patch literal 8192 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 Date: Fri, 11 Jan 2019 09:05:41 -0600 Subject: [PATCH 017/391] Codable - Add Date support --- Documentation/Index.md | 2 +- Sources/SQLite/Typed/Coding.swift | 7 +++++++ Tests/SQLiteTests/QueryTests.swift | 27 +++++++++++++++------------ Tests/SQLiteTests/TestHelpers.swift | 5 ++++- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..7fc74ea0 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1557,7 +1557,7 @@ Both of the above methods also have the following optional parameter: There are a few restrictions on using Codable types: - The encodable and decodable objects can only use the following types: - - Int, Bool, Float, Double, String + - Int, Bool, Float, Double, String, Date - Nested Codable types that will be encoded as JSON to a single column - These methods will not handle object relationships for you. You must write your own Codable and Decodable implementations if you wish to support this. diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..0eb02a55 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -132,6 +132,9 @@ fileprivate class SQLiteEncoder: Encoder { if let data = value as? Data { self.encoder.setters.append(Expression(key.stringValue) <- data) } + else if let date = value as? Date { + self.encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) @@ -290,6 +293,10 @@ fileprivate class SQLiteDecoder : Decoder { let data = try self.row.get(Expression(key.stringValue)) return data as! T } + else if type == Date.self { + let date = try self.row.get(Expression(key.stringValue)) + return date as! T + } guard let JSONString = try self.row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "an unsupported type was found")) } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2a9e4ecb..8f48264f 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -249,23 +249,23 @@ class QueryTests : XCTestCase { func test_insert_encodable() throws { let emails = Table("emails") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.insert(value) AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0)", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000')", insert ) } func test_insert_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: "optional", sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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) let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', '\(encodedJSONString)')", + "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 ) } @@ -286,23 +286,23 @@ class QueryTests : XCTestCase { func test_update_encodable() throws { let emails = Table("emails") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let update = try emails.update(value) AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0", + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000'", update ) } func test_update_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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) let update = try emails.update(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = '\(encodedJSONString)'", + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)'", update ) } @@ -438,12 +438,13 @@ class QueryIntegrationTests : SQLiteTestCase { builder.column(Expression("bool")) builder.column(Expression("float")) builder.column(Expression("double")) + builder.column(Expression("date")) builder.column(Expression("optional")) builder.column(Expression("sub")) }) - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) - let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, optional: "optional", sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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) try db.run(table.insert(value)) @@ -455,12 +456,14 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(values[0].bool, true) XCTAssertEqual(values[0].float, 7) XCTAssertEqual(values[0].double, 8) + XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) XCTAssertEqual(values[0].optional, "optional") XCTAssertEqual(values[0].sub?.int, 1) XCTAssertEqual(values[0].sub?.string, "2") XCTAssertEqual(values[0].sub?.bool, true) XCTAssertEqual(values[0].sub?.float, 3) XCTAssertEqual(values[0].sub?.double, 4) + XCTAssertEqual(values[0].sub?.date, Date(timeIntervalSince1970: 0)) XCTAssertNil(values[0].sub?.optional) XCTAssertNil(values[0].sub?.sub) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 2e491b01..247013b4 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -26,6 +26,7 @@ class SQLiteTestCase : XCTestCase { 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) ) """ @@ -111,15 +112,17 @@ class TestCodable: Codable { let bool: Bool let float: Float let double: Double + let date: Date let optional: String? let sub: TestCodable? - init(int: Int, string: String, bool: Bool, float: Float, double: Double, optional: String?, sub: TestCodable?) { + init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, optional: String?, sub: TestCodable?) { self.int = int self.string = string self.bool = bool self.float = float self.double = double + self.date = date self.optional = optional self.sub = sub } From 2b82d152e1870d353cedae0bcbfce0b31a511275 Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Thu, 21 Feb 2019 21:08:29 -0800 Subject: [PATCH 018/391] Add prototype implementation for Connection.createAggregation. --- SQLite.xcodeproj/project.pbxproj | 8 ++ Sources/SQLite/Core/Connection.swift | 106 ++++++++++++++++++ .../SQLiteTests/CustomAggregationTests.swift | 73 ++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 Tests/SQLiteTests/CustomAggregationTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a155a115..1635f651 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -81,6 +81,9 @@ 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 */; }; + 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 */; }; @@ -225,6 +228,7 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; + 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -401,6 +405,7 @@ EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, + 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */, EE247B201C3F137700AE3E12 /* ExpressionTests.swift */, EE247B211C3F137700AE3E12 /* FTS4Tests.swift */, EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */, @@ -834,6 +839,7 @@ 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 */, @@ -922,6 +928,7 @@ 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 */, @@ -980,6 +987,7 @@ 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 */, diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1bbf7f73..1fce790f 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -597,8 +597,114 @@ public final class Connection { if functions[function] == nil { self.functions[function] = [:] } functions[function]?[argc] = box } + + /// Creates or redefines a custom SQL aggregate. + /// + /// - Parameters: + /// + /// - aggregate: The name of the aggregate to create or redefine. + /// + /// - argumentCount: The number of arguments that the aggregate takes. If + /// `nil`, the aggregate may take any number of arguments. + /// + /// Default: `nil` + /// + /// - deterministic: Whether or not the aggregate is deterministic (_i.e._ + /// the aggregate always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - step: A block of code to run for each row of an aggregation group. + /// The block is called with an array of raw SQL values mapped to the + /// aggregate’s parameters, and an UnsafeMutablePointer to a state + /// variable. + /// + /// - final: A block of code to run after each row of an aggregation group + /// is processed. The block is called with an UnsafeMutablePointer to a + /// state variable, and should return a raw SQL value (or nil). + /// + /// - state: A block of code to run to produce a fresh state variable for + /// each aggregation group. The block should return an + /// UnsafeMutablePointer to the fresh state variable. + public func createAggregation(_ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, step: @escaping ([Binding?], UnsafeMutablePointer) -> (), final: @escaping (UnsafeMutablePointer) -> Binding?, state: @escaping () -> UnsafeMutablePointer) { + let argc = argumentCount.map { Int($0) } ?? -1 + let box : Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? + let p = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) + if stepFlag > 0 { + let arguments: [Binding?] = (0..?) -> Void fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void fileprivate var functions = [String: [Int: Function]]() + fileprivate var aggregations = [String: [Int: Aggregate]]() /// Defines a new collating sequence. /// diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift new file mode 100644 index 00000000..56c3c011 --- /dev/null +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -0,0 +1,73 @@ +import XCTest +import Foundation +import Dispatch +@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 CustomAggregationTests : SQLiteTestCase { + override func setUp() { + super.setUp() + CreateUsersTable() + try! InsertUser("Alice", age: 30, admin: true) + try! InsertUser("Bob", age: 25, admin: true) + try! InsertUser("Eve", age: 28, admin: false) + } + + func testCustomSum() { + let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in + if let v = bindings[0] as? Int64 { + state.pointee += v + } + } + + let final = { (state: UnsafeMutablePointer) -> Binding? in + let v = state.pointee + let p = UnsafeMutableBufferPointer(start: state, count: 1) + p.deallocate() + return v + } + let _ = db.createAggregation("mySUM", step: step, final: final) { + let v = UnsafeMutableBufferPointer.allocate(capacity: 1) + v[0] = 0 + return v.baseAddress! + } + let result = try! db.prepare("SELECT mySUM(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(83, value) + } + } + + func testCustomSumGrouping() { + let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in + if let v = bindings[0] as? Int64 { + state.pointee += v + } + } + let final = { (state: UnsafeMutablePointer) -> Binding? in + let v = state.pointee + let p = UnsafeMutableBufferPointer(start: state, count: 1) + p.deallocate() + return v + } + let _ = db.createAggregation("mySUM", step: step, final: final) { + let v = UnsafeMutableBufferPointer.allocate(capacity: 1) + v[0] = 0 + return v.baseAddress! + } + let result = try! db.prepare("SELECT mySUM(age) AS s FROM users GROUP BY admin ORDER BY s") + let i = result.columnNames.index(of: "s")! + let values = result.compactMap { $0[i] as? Int64 } + XCTAssertTrue(values.elementsEqual([28, 55])) + } +} From 1e6bfbf2c046606adc08c9ca2ee4d7bc17f7cb2a Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Thu, 21 Feb 2019 21:58:34 -0800 Subject: [PATCH 019/391] Added convenience overloads for Connection.createAggregation. --- Sources/SQLite/Core/Connection.swift | 16 +++-- Sources/SQLite/Typed/CustomFunctions.swift | 65 +++++++++++++++++ .../SQLiteTests/CustomAggregationTests.swift | 71 +++++++++++++++++-- 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1fce790f..349c18b6 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -626,7 +626,15 @@ public final class Connection { /// - state: A block of code to run to produce a fresh state variable for /// each aggregation group. The block should return an /// UnsafeMutablePointer to the fresh state variable. - public func createAggregation(_ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, step: @escaping ([Binding?], UnsafeMutablePointer) -> (), final: @escaping (UnsafeMutablePointer) -> Binding?, state: @escaping () -> UnsafeMutablePointer) { + public func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + step: @escaping ([Binding?], UnsafeMutablePointer) -> (), + final: @escaping (UnsafeMutablePointer) -> Binding?, + state: @escaping () -> UnsafeMutablePointer) { + + let argc = argumentCount.map { Int($0) } ?? -1 let box : Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? @@ -690,17 +698,17 @@ public final class Connection { { context, argc, value in let function = unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self) function(1, context, argc, value) - }, + }, { context in let function = unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self) function(0, context, 0, nil) - }, + }, nil ) if aggregations[aggregate] == nil { self.aggregations[aggregate] = [:] } aggregations[aggregate]?[argc] = box } - + fileprivate typealias Aggregate = @convention(block) (Int, OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void fileprivate var functions = [String: [Int: Function]]() diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 2389901f..375a6b99 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -133,4 +133,69 @@ public extension Connection { } } + // MARK: - + + public func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, ptr) in + let p = ptr.pointee.assumingMemoryBound(to: T.self) + let current = Unmanaged.fromOpaque(p).takeRetainedValue() + let next = reduce(current, bindings) + ptr.pointee = Unmanaged.passRetained(next).toOpaque() + } + + let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in + let p = ptr.pointee.assumingMemoryBound(to: T.self) + let obj = Unmanaged.fromOpaque(p).takeRetainedValue() + let value = result(obj) + ptr.deallocate() + return value + } + + let state: () -> UnsafeMutablePointer = { + let p = UnsafeMutablePointer.allocate(capacity: 1) + p.pointee = Unmanaged.passRetained(initialValue).toOpaque() + return p + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + + public func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, p) in + let current = p.pointee + let next = reduce(current, bindings) + p.pointee = next + } + + let final: (UnsafeMutablePointer) -> Binding? = { (p) in + let v = result(p.pointee) + p.deallocate() + return v + } + + let state: () -> UnsafeMutablePointer = { + let p = UnsafeMutablePointer.allocate(capacity: 1) + p.pointee = initialValue + return p + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 56c3c011..1035f67c 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -22,25 +22,25 @@ class CustomAggregationTests : SQLiteTestCase { try! InsertUser("Eve", age: 28, admin: false) } - func testCustomSum() { + func testUnsafeCustomSum() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v } } - + let final = { (state: UnsafeMutablePointer) -> Binding? in let v = state.pointee let p = UnsafeMutableBufferPointer(start: state, count: 1) p.deallocate() return v } - let _ = db.createAggregation("mySUM", step: step, final: final) { + let _ = db.createAggregation("mySUM1", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM(age) AS s FROM users") + let result = try! db.prepare("SELECT mySUM1(age) AS s FROM users") let i = result.columnNames.index(of: "s")! for row in result { let value = row[i] as? Int64 @@ -48,7 +48,7 @@ class CustomAggregationTests : SQLiteTestCase { } } - func testCustomSumGrouping() { + func testUnsafeCustomSumGrouping() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v @@ -60,14 +60,71 @@ class CustomAggregationTests : SQLiteTestCase { p.deallocate() return v } - let _ = db.createAggregation("mySUM", step: step, final: final) { + let _ = db.createAggregation("mySUM2", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM(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.index(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([28, 55])) } + + func testCustomSum() { + let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let v = (bindings[0] as? Int64) ?? 0 + return last + v + } + let _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) + let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(2083, value) + } + } + + func testCustomSumGrouping() { + let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let v = (bindings[0] as? Int64) ?? 0 + return last + v + } + let _ = 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 i = result.columnNames.index(of: "s")! + let values = result.compactMap { $0[i] as? Int64 } + XCTAssertTrue(values.elementsEqual([3028, 3055])) + } + + func testCustomObjectSum() { + { + let initial = TestObject(value: 1000) + let reduce : (TestObject, [Binding?]) -> TestObject = { (last, bindings) in + let v = (bindings[0] as? Int64) ?? 0 + return TestObject(value: last.value + v) + } + let _ = db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) + // end this scope to ensure that the initial value is retained + // by the createAggregation call. + }() + let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(1083, value) + } + } +} + +/// This class is used to test that aggregation state variables +/// can be reference types and are properly memory managed when +/// crossing the Swift<->C boundary multiple times. +class TestObject { + var value: Int64 + init(value: Int64) { + self.value = value + } + deinit { + } } From b1d3a4036deddac396b46d725866192eb29df0ff Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Fri, 22 Feb 2019 08:25:08 -0800 Subject: [PATCH 020/391] Add extra test for custom aggregation. --- Tests/SQLiteTests/CustomAggregationTests.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 1035f67c..74a19df0 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -97,6 +97,21 @@ class CustomAggregationTests : SQLiteTestCase { XCTAssertTrue(values.elementsEqual([3028, 3055])) } + func testCustomStringAgg() { + let initial = String(repeating: " ", count: 64) + let reduce : (String, [Binding?]) -> String = { (last, bindings) in + let v = (bindings[0] as? String) ?? "" + return last + v + } + let _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) + let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? String + XCTAssertEqual("\(initial)Alice@example.comBob@example.comEve@example.com", value) + } + } + func testCustomObjectSum() { { let initial = TestObject(value: 1000) From 4a28df38c187a428561c12ee9d0a3e3099ba336b Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Fri, 22 Feb 2019 09:05:24 -0800 Subject: [PATCH 021/391] Track retains/releases that occur during custom aggregation testing. --- .../SQLiteTests/CustomAggregationTests.swift | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 74a19df0..f8efc7e4 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -122,13 +122,18 @@ class CustomAggregationTests : SQLiteTestCase { let _ = db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) // end this scope to ensure that the initial value is retained // by the createAggregation call. + }(); + { + XCTAssertEqual(TestObject.inits, 1) + let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(1083, value) + } }() - let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! - for row in result { - let value = row[i] as? Int64 - XCTAssertEqual(1083, value) - } + XCTAssertEqual(TestObject.inits, 4) + XCTAssertEqual(TestObject.deinits, 3) // the initial value is still retained by the aggregate's state block, so deinits is one less than inits } } @@ -136,10 +141,15 @@ class CustomAggregationTests : SQLiteTestCase { /// can be reference types and are properly memory managed when /// crossing the Swift<->C boundary multiple times. class TestObject { + static var inits = 0 + static var deinits = 0 + var value: Int64 init(value: Int64) { self.value = value + TestObject.inits += 1 } deinit { + TestObject.deinits += 1 } } From 0fa531d1118bf88e83ffb893d89b8898bfdd85b8 Mon Sep 17 00:00:00 2001 From: Andrew Finnell Date: Sun, 24 Feb 2019 13:54:35 -0500 Subject: [PATCH 022/391] Implement Upsert [#482] # Problem Apps need the ability to insert or, if the row already exists, update the existing row. This is commonly called "upsert" and is [documented for SQLite here](https://www.sqlite.org/lang_UPSERT.html). The existing "replace" on conflict clause for `insert()` often does not have the desired behavior. It will cascade (with deletes, if specified) along foreign keys if the value exists and has to be replaced ([see Issue 842](https://github.com/stephencelis/SQLite.swift/issues/842)). # Solution Create an `upsert()` method for queries. The basic form allows for the fallback setters to be manually specified. A slightly more ergonomic version will compute the setters from the inserted values by removing the "conflicting" column, and then using the inserted values via the "excluded" qualifier. Additionally, an `Encodable` version of `upsert` is provided. --- Documentation/Index.md | 28 +++++++++++++++++++++ Sources/SQLite/Typed/Coding.swift | 23 ++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 39 ++++++++++++++++++++++++++++++ Sources/SQLite/Typed/Setter.swift | 5 ++++ Tests/SQLiteTests/QueryTests.swift | 35 ++++++++++++++++++++++++++- 5 files changed, 129 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..61c287d6 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -35,6 +35,7 @@ - [Sorting Rows](#sorting-rows) - [Limiting and Paging Results](#limiting-and-paging-results) - [Aggregation](#aggregation) + - [Upserting Rows](#upserting-rows) - [Updating Rows](#updating-rows) - [Deleting Rows](#deleting-rows) - [Transactions and Savepoints](#transactions-and-savepoints) @@ -1098,6 +1099,33 @@ let count = try db.scalar(users.filter(name != nil).count) > // SELECT count(DISTINCT "name") FROM "users" > ``` +## Upserting Rows + +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 +conflict on the specified column value, SQLite will perform an update on the row instead. + +```swift +try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice"), onConflictOf: email) +// INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') ON CONFLICT (\"email\") DO UPDATE SET \"name\" = \"excluded\".\"name\" +``` + +The `upsert` function, when run successfully, returns an `Int64` representing +the inserted row’s [`ROWID`][ROWID]. + +```swift +do { + let rowid = try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice", onConflictOf: email)) + print("inserted id: \(rowid)") +} catch { + print("insertion failed: \(error)") +} +``` + +The [`insert`](#inserting-rows), [`update`](#updating-rows), and [`delete`](#deleting-rows) functions +follow similar patterns. ## Updating Rows diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..6ed8c987 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -45,6 +45,29 @@ extension QueryType { return self.insert(encoder.setters + otherSetters) } + + /// 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 + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - onConflictOf: The column that if conflicts should trigger an update instead of insert. + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func upsert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = [], onConflictOf conflicting: Expressible) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + 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 diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f6ef6df8..8793acae 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -672,6 +672,45 @@ extension QueryType { query.expression ]).expression) } + + // MARK: UPSERT + + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible) -> Insert { + return upsert(insertValues, onConflictOf: conflicting) + } + + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible) -> Insert { + let setValues = insertValues.filter { $0.column.asSQL() != conflicting.asSQL() } + .map { Setter(excluded: $0.column) } + return upsert(insertValues, onConflictOf: conflicting, set: setValues) + } + + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { + return upsert(insertValues, onConflictOf: conflicting, set: setValues) + } + + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { + let insert = insertValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in + (insert.columns + [setter.column], insert.values + [setter.value]) + } + + let clauses: [Expressible?] = [ + Expression(literal: "INSERT"), + Expression(literal: "INTO"), + tableName(), + "".wrap(insert.columns) as Expression, + Expression(literal: "VALUES"), + "".wrap(insert.values) as Expression, + whereClause, + Expression(literal: "ON CONFLICT"), + "".wrap(conflicting) as Expression, + Expression(literal: "DO UPDATE SET"), + ", ".join(setValues.map { $0.expression }) + ] + + return Insert(" ".join(clauses.compactMap { $0 }).expression) + } + // MARK: UPDATE diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 86f16fca..3d3170f6 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -60,6 +60,11 @@ public struct Setter { self.value = Expression(value: value) } + init(excluded column: Expressible) { + let excluded = Expression("excluded") + self.column = column + self.value = ".".join([excluded, column.expression]) + } } extension Setter : Expressible { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2a9e4ecb..694e7f26 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -270,6 +270,24 @@ class QueryTests : XCTestCase { ) } + func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { + AssertSQL( + "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30) ON CONFLICT (\"email\") DO UPDATE SET \"age\" = \"excluded\".\"age\"", + users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email) + ) + } + + func test_upsert_encodable() throws { + let emails = Table("emails") + let string = Expression("string") + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let insert = try emails.upsert(value, onConflictOf: string) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0) ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", + insert + ) + } + func test_update_compilesUpdateExpression() { AssertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", @@ -378,7 +396,8 @@ class QueryIntegrationTests : SQLiteTestCase { let id = Expression("id") let email = Expression("email") - + let age = Expression("age") + override func setUp() { super.setUp() @@ -483,6 +502,20 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(1, id) } + func test_upsert() throws { + let fetchAge = { () throws -> Int? in + return try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } + } + + let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) + XCTAssertEqual(1, id) + XCTAssertEqual(30, try fetchAge()) + + let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) + XCTAssertEqual(1, nextId) + XCTAssertEqual(42, try fetchAge()) + } + func test_update() { let changes = try! db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) From c96a5da4ef8c9ccec7bfac254b56ae17199097f3 Mon Sep 17 00:00:00 2001 From: Otto Suess Date: Tue, 26 Mar 2019 12:53:22 +0100 Subject: [PATCH 023/391] fix xcode-10.2 issues --- Sources/SQLite/Helpers.swift | 12 - Sources/SQLite/Typed/AggregateFunctions.swift | 22 +- Sources/SQLite/Typed/CoreFunctions.swift | 40 +-- Sources/SQLite/Typed/Operators.swift | 232 +++++++++--------- 4 files changed, 147 insertions(+), 159 deletions(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index ac831667..115ea5c6 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -98,18 +98,6 @@ extension String { } -func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = #function) -> Expression { - return function.infix(lhs, rhs, wrap: wrap) -} - -func wrap(_ expression: Expressible, function: String = #function) -> Expression { - return function.wrap(expression) -} - -func wrap(_ expressions: [Expressible], function: String = #function) -> Expression { - return function.wrap(", ".join(expressions)) -} - func transcode(_ literal: Binding?) -> String { guard let literal = literal else { return "NULL" } diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 249bbe60..bb1157fe 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -48,7 +48,7 @@ extension ExpressionType where UnderlyingType : Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return wrap(self) + return "count".wrap(self) } } @@ -79,7 +79,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return wrap(self) + return "count".wrap(self) } } @@ -96,7 +96,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return wrap(self) + return "max".wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -109,7 +109,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return wrap(self) + return "min".wrap(self) } } @@ -126,7 +126,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return wrap(self) + return "max".wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -139,7 +139,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return wrap(self) + return "min".wrap(self) } } @@ -169,7 +169,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return wrap(self) + return "sum".wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -182,7 +182,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return wrap(self) + return "total".wrap(self) } } @@ -212,7 +212,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return wrap(self) + return "sum".wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -225,7 +225,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return wrap(self) + return "total".wrap(self) } } @@ -233,7 +233,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return wrap(star(nil, nil)) + return "count".wrap(star(nil, nil)) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index d7995b95..b7b53c71 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -68,9 +68,9 @@ 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 { - return wrap([self]) + return "round".wrap([self]) } - return wrap([self, Int(precision)]) + return "round".wrap([self, Int(precision)]) } } @@ -88,9 +88,9 @@ 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 { - return wrap(self) + return "round".wrap(self) } - return wrap([self, Int(precision)]) + return "round".wrap([self, Int(precision)]) } } @@ -143,7 +143,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } } @@ -158,7 +158,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } } @@ -173,7 +173,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -317,9 +317,9 @@ 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 { - return wrap(self) + return "ltrim".wrap(self) } - return wrap([self, String(characters)]) + return "ltrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -335,9 +335,9 @@ 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 { - return wrap(self) + return "rtrim".wrap(self) } - return wrap([self, String(characters)]) + return "rtrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -353,9 +353,9 @@ 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 { - return wrap([self]) + return "trim".wrap([self]) } - return wrap([self, String(characters)]) + return "trim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -398,7 +398,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -542,9 +542,9 @@ 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 { - return wrap(self) + return "ltrim".wrap(self) } - return wrap([self, String(characters)]) + return "ltrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -560,9 +560,9 @@ 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 { - return wrap(self) + return "rtrim".wrap(self) } - return wrap([self, String(characters)]) + return "rtrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -578,9 +578,9 @@ 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 { - return wrap(self) + return "trim".wrap(self) } - return wrap([self, String(characters)]) + return "trim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index d97e52b9..5f8be14b 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -53,237 +53,237 @@ public func +(lhs: String, rhs: Expression) -> Expression { // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return wrap(rhs) + return "-".wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return wrap(rhs) + return "-".wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { @@ -312,10 +312,10 @@ public func ^(lhs: V, rhs: Expression) -> Expression where V. } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return wrap(rhs) + return "~".wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return wrap(rhs) + return "~".wrap(rhs) } // MARK: - @@ -348,130 +348,130 @@ public func ==(lhs: V?, rhs: Expression) -> Expression whe } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".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)) } - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".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) } - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { From c5be72bac4a022448b92d75a8970e41c6254b21f Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Tue, 26 Mar 2019 17:54:28 +0200 Subject: [PATCH 024/391] Strings to enum constants --- Sources/SQLite/Typed/AggregateFunctions.swift | 39 ++- Sources/SQLite/Typed/CoreFunctions.swift | 140 +++++--- Sources/SQLite/Typed/Operators.swift | 331 ++++++++++-------- 3 files changed, 294 insertions(+), 216 deletions(-) diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index bb1157fe..056d617e 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -22,6 +22,19 @@ // THE SOFTWARE. // +private enum Functions: String { + case count + case max + case min + case avg + case sum + case total + + func wrap(_ expression: Expressible) -> Expression { + return self.rawValue.wrap(expression) + } +} + extension ExpressionType where UnderlyingType : Value { /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. @@ -48,7 +61,7 @@ extension ExpressionType where UnderlyingType : Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return "count".wrap(self) + return Functions.count.wrap(self) } } @@ -79,7 +92,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return "count".wrap(self) + return Functions.count.wrap(self) } } @@ -96,7 +109,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return "max".wrap(self) + return Functions.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -109,7 +122,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return "min".wrap(self) + return Functions.min.wrap(self) } } @@ -126,7 +139,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return "max".wrap(self) + return Functions.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -139,7 +152,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return "min".wrap(self) + return Functions.min.wrap(self) } } @@ -156,7 +169,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return "avg".wrap(self) + return Functions.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -169,7 +182,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return "sum".wrap(self) + return Functions.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -182,7 +195,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return "total".wrap(self) + return Functions.total.wrap(self) } } @@ -199,7 +212,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return "avg".wrap(self) + return Functions.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -212,7 +225,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return "sum".wrap(self) + return Functions.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -225,7 +238,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return "total".wrap(self) + return Functions.total.wrap(self) } } @@ -233,7 +246,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return "count".wrap(star(nil, nil)) + return Functions.count.wrap(star(nil, nil)) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index b7b53c71..420110ae 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -24,6 +24,40 @@ import Foundation +private enum Functions: String { + case abs + case round + case random + case randomblob + case zeroblob + case length + case lower + case upper + case ltrim + case rtrim + case trim + case replace + case substr + case LIKE + case IN + case GLOB + case MATCH + case REGEXP + case COLLATE + case ifnull + + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { + return self.rawValue.infix(lhs, rhs, wrap: wrap) + } + + func wrap(_ expression: Expressible) -> Expression { + return self.rawValue.wrap(expression) + } + + func wrap(_ expressions: [Expressible]) -> Expression { + return self.rawValue.wrap(", ".join(expressions)) + } +} extension ExpressionType where UnderlyingType : Number { @@ -35,7 +69,7 @@ extension ExpressionType where UnderlyingType : Number { /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return "abs".wrap(self) + return Functions.abs.wrap(self) } } @@ -50,7 +84,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return "abs".wrap(self) + return Functions.abs.wrap(self) } } @@ -68,9 +102,9 @@ 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 { - return "round".wrap([self]) + return Functions.round.wrap([self]) } - return "round".wrap([self, Int(precision)]) + return Functions.round.wrap([self, Int(precision)]) } } @@ -88,9 +122,9 @@ 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 { - return "round".wrap(self) + return Functions.round.wrap(self) } - return "round".wrap([self, Int(precision)]) + return Functions.round.wrap([self, Int(precision)]) } } @@ -104,7 +138,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype = /// /// - Returns: An expression calling the `random` function. public static func random() -> Expression { - return "random".wrap([]) + return Functions.random.wrap([]) } } @@ -120,7 +154,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `randomblob` function. public static func random(_ length: Int) -> Expression { - return "randomblob".wrap([]) + return Functions.randomblob.wrap([]) } /// Builds an expression representing the `zeroblob` function. @@ -132,7 +166,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `zeroblob` function. public static func allZeros(_ length: Int) -> Expression { - return "zeroblob".wrap([]) + return Functions.zeroblob.wrap([]) } /// Builds a copy of the expression wrapped with the `length` function. @@ -143,7 +177,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } } @@ -158,7 +192,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } } @@ -173,7 +207,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -184,7 +218,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return "lower".wrap(self) + return Functions.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -195,7 +229,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return "upper".wrap(self) + return Functions.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -242,9 +276,9 @@ extension ExpressionType where UnderlyingType == String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } - let like: Expression = "LIKE".infix(self, pattern, wrap: false) + let like: Expression = Functions.LIKE.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -260,7 +294,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return "GLOB".infix(self, pattern) + return Functions.GLOB.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -275,7 +309,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return "MATCH".infix(self, pattern) + return Functions.MATCH.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -286,7 +320,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return "REGEXP".infix(self, pattern) + return Functions.REGEXP.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -301,7 +335,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return "COLLATE".infix(self, collation) + return Functions.COLLATE.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -317,9 +351,9 @@ 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 { - return "ltrim".wrap(self) + return Functions.ltrim.wrap(self) } - return "ltrim".wrap([self, String(characters)]) + return Functions.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -335,9 +369,9 @@ 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 { - return "rtrim".wrap(self) + return Functions.rtrim.wrap(self) } - return "rtrim".wrap([self, String(characters)]) + return Functions.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -353,9 +387,9 @@ 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 { - return "trim".wrap([self]) + return Functions.trim.wrap([self]) } - return "trim".wrap([self, String(characters)]) + return Functions.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -372,14 +406,14 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return "replace".wrap([self, pattern, replacement]) + return Functions.replace.wrap([self, pattern, replacement]) } public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { - return "substr".wrap([self, location]) + return Functions.substr.wrap([self, location]) } - return "substr".wrap([self, location, length]) + return Functions.substr.wrap([self, location, length]) } public subscript(range: Range) -> Expression { @@ -398,7 +432,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -409,7 +443,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return "lower".wrap(self) + return Functions.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -420,7 +454,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return "upper".wrap(self) + return Functions.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -443,7 +477,7 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } @@ -467,7 +501,7 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } let like: Expression = "LIKE".infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) @@ -485,7 +519,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return "GLOB".infix(self, pattern) + return Functions.GLOB.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -500,7 +534,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return "MATCH".infix(self, pattern) + return Functions.MATCH.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -511,7 +545,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return "REGEXP".infix(self, pattern) + return Functions.REGEXP.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -526,7 +560,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return "COLLATE".infix(self, collation) + return Functions.COLLATE.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -542,9 +576,9 @@ 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 { - return "ltrim".wrap(self) + return Functions.ltrim.wrap(self) } - return "ltrim".wrap([self, String(characters)]) + return Functions.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -560,9 +594,9 @@ 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 { - return "rtrim".wrap(self) + return Functions.rtrim.wrap(self) } - return "rtrim".wrap([self, String(characters)]) + return Functions.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -578,9 +612,9 @@ 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 { - return "trim".wrap(self) + return Functions.trim.wrap(self) } - return "trim".wrap([self, String(characters)]) + return Functions.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -597,7 +631,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return "replace".wrap([self, pattern, replacement]) + return Functions.replace.wrap([self, pattern, replacement]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -617,9 +651,9 @@ 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 { - return "substr".wrap([self, location]) + return Functions.substr.wrap([self, location]) } - return "substr".wrap([self, location, length]) + return Functions.substr.wrap([self, location, length]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -652,7 +686,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } /// Builds a copy of the expression prepended with an `IN` check against the @@ -668,7 +702,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } } @@ -694,7 +728,7 @@ extension String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } let like: Expression = "LIKE".infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) @@ -718,7 +752,7 @@ extension String { /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: V) -> Expression { - return "ifnull".wrap([optional, defaultValue]) + return Functions.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -738,7 +772,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressi /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return "ifnull".wrap([optional, defaultValue]) + return Functions.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -758,5 +792,5 @@ public func ??(optional: Expression, defaultValue: Expression) /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return "ifnull".wrap([optional, defaultValue]) + return Functions.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 5f8be14b..751215e3 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -24,266 +24,297 @@ // TODO: use `@warn_unused_result` by the time operator functions support it +private enum Operators: String { + case plus = "+" + case minus = "-" + case or = "OR" + case and = "AND" + case not = "NOT " + case mul = "*" + case div = "/" + case mod = "%" + case bitwiseLeft = "<<" + case bitwiseRight = ">>" + case bitwiseAnd = "&" + case bitwiseOr = "|" + case bitwiseXor = "~" + case eq = "=" + case neq = "!=" + case gt = ">" + case lt = "<" + case gte = ">=" + case lte = "<=" + case concatenate = "||" + + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { + return self.rawValue.infix(lhs, rhs, wrap: wrap) + } + + func wrap(_ expression: Expressible) -> Expression { + return self.rawValue.wrap(expression) + } +} + public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return "-".wrap(rhs) + return Operators.minus.wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return "-".wrap(rhs) + return Operators.minus.wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { @@ -312,166 +343,166 @@ public func ^(lhs: V, rhs: Expression) -> Expression where V. } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return "~".wrap(rhs) + return Operators.bitwiseXor.wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return "~".wrap(rhs) + return Operators.bitwiseXor.wrap(rhs) } // MARK: - public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.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)) } - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.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) } - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.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)) } - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.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) } - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { @@ -517,58 +548,58 @@ public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expr // MARK: - public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public prefix func !(rhs: Expression) -> Expression { - return "NOT ".wrap(rhs) + return Operators.not.wrap(rhs) } public prefix func !(rhs: Expression) -> Expression { - return "NOT ".wrap(rhs) + return Operators.not.wrap(rhs) } From bc909add007da81916c8b9118df081d746aea4c4 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 27 Mar 2019 13:48:58 +0200 Subject: [PATCH 025/391] Fixed enums naming --- Sources/SQLite/Typed/AggregateFunctions.swift | 28 +- Sources/SQLite/Typed/CoreFunctions.swift | 124 +++---- Sources/SQLite/Typed/Operators.swift | 302 +++++++++--------- 3 files changed, 227 insertions(+), 227 deletions(-) diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 056d617e..2ec28288 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -private enum Functions: String { +private enum Function: String { case count case max case min @@ -61,7 +61,7 @@ extension ExpressionType where UnderlyingType : Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Functions.count.wrap(self) + return Function.count.wrap(self) } } @@ -92,7 +92,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Functions.count.wrap(self) + return Function.count.wrap(self) } } @@ -109,7 +109,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Functions.max.wrap(self) + return Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -122,7 +122,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Functions.min.wrap(self) + return Function.min.wrap(self) } } @@ -139,7 +139,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Functions.max.wrap(self) + return Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -152,7 +152,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Functions.min.wrap(self) + return Function.min.wrap(self) } } @@ -169,7 +169,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Functions.avg.wrap(self) + return Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -182,7 +182,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Functions.sum.wrap(self) + return Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -195,7 +195,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Functions.total.wrap(self) + return Function.total.wrap(self) } } @@ -212,7 +212,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Functions.avg.wrap(self) + return Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -225,7 +225,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Functions.sum.wrap(self) + return Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -238,7 +238,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Functions.total.wrap(self) + return Function.total.wrap(self) } } @@ -246,7 +246,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return Functions.count.wrap(star(nil, nil)) + return Function.count.wrap(star(nil, nil)) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 420110ae..068dcf02 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -24,7 +24,7 @@ import Foundation -private enum Functions: String { +private enum Function: String { case abs case round case random @@ -38,12 +38,12 @@ private enum Functions: String { case trim case replace case substr - case LIKE - case IN - case GLOB - case MATCH - case REGEXP - case COLLATE + case like = "LIKE" + case `in` = "IN" + case glob = "GLOB" + case match = "MATCH" + case regexp = "REGEXP" + case collate = "COLLATE" case ifnull func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { @@ -69,7 +69,7 @@ extension ExpressionType where UnderlyingType : Number { /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return Functions.abs.wrap(self) + return Function.abs.wrap(self) } } @@ -84,7 +84,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return Functions.abs.wrap(self) + return Function.abs.wrap(self) } } @@ -102,9 +102,9 @@ 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 { - return Functions.round.wrap([self]) + return Function.round.wrap([self]) } - return Functions.round.wrap([self, Int(precision)]) + return Function.round.wrap([self, Int(precision)]) } } @@ -122,9 +122,9 @@ 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 { - return Functions.round.wrap(self) + return Function.round.wrap(self) } - return Functions.round.wrap([self, Int(precision)]) + return Function.round.wrap([self, Int(precision)]) } } @@ -138,7 +138,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype = /// /// - Returns: An expression calling the `random` function. public static func random() -> Expression { - return Functions.random.wrap([]) + return Function.random.wrap([]) } } @@ -154,7 +154,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `randomblob` function. public static func random(_ length: Int) -> Expression { - return Functions.randomblob.wrap([]) + return Function.randomblob.wrap([]) } /// Builds an expression representing the `zeroblob` function. @@ -166,7 +166,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `zeroblob` function. public static func allZeros(_ length: Int) -> Expression { - return Functions.zeroblob.wrap([]) + return Function.zeroblob.wrap([]) } /// Builds a copy of the expression wrapped with the `length` function. @@ -177,7 +177,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } } @@ -192,7 +192,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } } @@ -207,7 +207,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -218,7 +218,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Functions.lower.wrap(self) + return Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -229,7 +229,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Functions.upper.wrap(self) + return Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -276,9 +276,9 @@ extension ExpressionType where UnderlyingType == String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } - let like: Expression = Functions.LIKE.infix(self, pattern, wrap: false) + let like: Expression = Function.like.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -294,7 +294,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Functions.GLOB.infix(self, pattern) + return Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -309,7 +309,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Functions.MATCH.infix(self, pattern) + return Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -320,7 +320,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Functions.REGEXP.infix(self, pattern) + return Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -335,7 +335,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Functions.COLLATE.infix(self, collation) + return Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -351,9 +351,9 @@ 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 { - return Functions.ltrim.wrap(self) + return Function.ltrim.wrap(self) } - return Functions.ltrim.wrap([self, String(characters)]) + return Function.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -369,9 +369,9 @@ 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 { - return Functions.rtrim.wrap(self) + return Function.rtrim.wrap(self) } - return Functions.rtrim.wrap([self, String(characters)]) + return Function.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -387,9 +387,9 @@ 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 { - return Functions.trim.wrap([self]) + return Function.trim.wrap([self]) } - return Functions.trim.wrap([self, String(characters)]) + return Function.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -406,14 +406,14 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Functions.replace.wrap([self, pattern, replacement]) + return Function.replace.wrap([self, pattern, replacement]) } public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { - return Functions.substr.wrap([self, location]) + return Function.substr.wrap([self, location]) } - return Functions.substr.wrap([self, location, length]) + return Function.substr.wrap([self, location, length]) } public subscript(range: Range) -> Expression { @@ -432,7 +432,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -443,7 +443,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Functions.lower.wrap(self) + return Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -454,7 +454,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Functions.upper.wrap(self) + return Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -477,7 +477,7 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } @@ -501,9 +501,9 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } - let like: Expression = "LIKE".infix(self, pattern, wrap: false) + let like: Expression = Function.like.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -519,7 +519,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Functions.GLOB.infix(self, pattern) + return Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -534,7 +534,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Functions.MATCH.infix(self, pattern) + return Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -545,7 +545,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Functions.REGEXP.infix(self, pattern) + return Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -560,7 +560,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Functions.COLLATE.infix(self, collation) + return Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -576,9 +576,9 @@ 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 { - return Functions.ltrim.wrap(self) + return Function.ltrim.wrap(self) } - return Functions.ltrim.wrap([self, String(characters)]) + return Function.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -594,9 +594,9 @@ 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 { - return Functions.rtrim.wrap(self) + return Function.rtrim.wrap(self) } - return Functions.rtrim.wrap([self, String(characters)]) + return Function.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -612,9 +612,9 @@ 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 { - return Functions.trim.wrap(self) + return Function.trim.wrap(self) } - return Functions.trim.wrap([self, String(characters)]) + return Function.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -631,7 +631,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Functions.replace.wrap([self, pattern, replacement]) + return Function.replace.wrap([self, pattern, replacement]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -651,9 +651,9 @@ 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 { - return Functions.substr.wrap([self, location]) + return Function.substr.wrap([self, location]) } - return Functions.substr.wrap([self, location, length]) + return Function.substr.wrap([self, location, length]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -686,7 +686,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Function.in.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } /// Builds a copy of the expression prepended with an `IN` check against the @@ -702,7 +702,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Function.in.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } } @@ -728,9 +728,9 @@ extension String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } - let like: Expression = "LIKE".infix(self, pattern, wrap: false) + let like: Expression = Function.like.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -752,7 +752,7 @@ extension String { /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: V) -> Expression { - return Functions.ifnull.wrap([optional, defaultValue]) + return Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -772,7 +772,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressi /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Functions.ifnull.wrap([optional, defaultValue]) + return Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -792,5 +792,5 @@ public func ??(optional: Expression, defaultValue: Expression) /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Functions.ifnull.wrap([optional, defaultValue]) + return Function.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 751215e3..b5637ea3 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -24,7 +24,7 @@ // TODO: use `@warn_unused_result` by the time operator functions support it -private enum Operators: String { +private enum Operator: String { case plus = "+" case minus = "-" case or = "OR" @@ -56,265 +56,265 @@ private enum Operators: String { } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.wrap(rhs) + return Operator.minus.wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.wrap(rhs) + return Operator.minus.wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { @@ -343,166 +343,166 @@ public func ^(lhs: V, rhs: Expression) -> Expression where V. } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseXor.wrap(rhs) + return Operator.bitwiseXor.wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseXor.wrap(rhs) + return Operator.bitwiseXor.wrap(rhs) } // MARK: - public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return 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)) } - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return 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) } - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return 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)) } - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return 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) } - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { @@ -548,58 +548,58 @@ public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expr // MARK: - public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operators.not.wrap(rhs) + return Operator.not.wrap(rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operators.not.wrap(rhs) + return Operator.not.wrap(rhs) } From 93f55680604889fea660f191fb9b757d43d961b6 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sat, 30 Mar 2019 16:28:40 -0600 Subject: [PATCH 026/391] Updated project to Swift 5, fixed build warnings --- SQLite.xcodeproj/project.pbxproj | 11 +++++--- .../xcschemes/SQLite Mac.xcscheme | 2 +- .../xcschemes/SQLite iOS.xcscheme | 2 +- .../xcschemes/SQLite tvOS.xcscheme | 2 +- .../xcschemes/SQLite watchOS.xcscheme | 2 +- Sources/SQLite/Foundation.swift | 6 ++-- Sources/SQLite/Typed/CustomFunctions.swift | 28 +++++++++---------- 7 files changed, 28 insertions(+), 25 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a155a115..2294065f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -680,7 +680,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1020; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -714,10 +714,11 @@ }; buildConfigurationList = EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = EE247AC91C3F04ED00AE3E12; productRefGroup = EE247AD41C3F04ED00AE3E12 /* Products */; @@ -1140,6 +1141,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -1200,6 +1202,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -1269,7 +1272,7 @@ SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -1291,7 +1294,7 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index 2691862e..4e94e80a 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ Data { - return Data(bytes: dataValue.bytes) + return Data(dataValue.bytes) } public var datatypeValue: Blob { - return withUnsafeBytes { (pointer: UnsafePointer) -> Blob in - return Blob(bytes: pointer, length: count) + return withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in + return Blob(bytes: pointer.baseAddress!, length: count) } } diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 2389901f..8910a24b 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -39,76 +39,76 @@ public extension Connection { /// The assigned types must be explicit. /// /// - Returns: A closure returning an SQL expression to call the function. - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression) { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression) { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } // MARK: - - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } // MARK: - - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } From e28f30c0f861b1e35eae17e9f04e1d0b8c1206d6 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Wed, 3 Apr 2019 11:28:18 -0600 Subject: [PATCH 027/391] Swift 5 - Passed through project language version setting to framework & unit test targets - Bumped version to 0.11.6 in documentation & pod spec - Fixed build warnings in unit test code for Swift 5 --- Documentation/Index.md | 16 ++++++------- README.md | 10 ++++---- SQLite.swift.podspec | 4 ++-- SQLite.xcodeproj/project.pbxproj | 32 +++---------------------- Tests/SQLiteTests/FoundationTests.swift | 4 ++-- 5 files changed, 20 insertions(+), 46 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..9d40bfdd 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,8 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 4.1 (and -> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 5 (and +> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.5 + github "stephencelis/SQLite.swift" ~> 0.11.6 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.5' + pod 'SQLite.swift', '~> 0.11.6' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.5' + pod 'SQLite.swift/standalone', '~> 0.11.6' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.5' + pod 'SQLite.swift/standalone', '~> 0.11.6' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.5' + pod 'SQLite.swift/SQLCipher', '~> 0.11.6' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") ] ``` diff --git a/README.md b/README.md index bc43b6ec..33598c69 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.1 (and [Xcode][] 9.3). +> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). ### Carthage @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.5 + github "stephencelis/SQLite.swift" ~> 0.11.6 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.5' + pod 'SQLite.swift', '~> 0.11.6' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") ] ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index c2235aca..63987b4b 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.5" + s.version = "0.11.6" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.2', + 'SWIFT_VERSION' => '5', } s.subspec 'standard' do |ss| diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2294065f..9d38dbc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -700,7 +700,7 @@ }; EE247ADC1C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; }; EE247B3B1C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; @@ -1033,8 +1033,6 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1055,8 +1053,6 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1069,8 +1065,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1083,8 +1077,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1106,8 +1098,6 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1130,8 +1120,6 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1192,6 +1180,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1246,6 +1235,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -1271,8 +1261,6 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -1293,8 +1281,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; }; name = Release; }; @@ -1306,8 +1292,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1319,8 +1303,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1344,8 +1326,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1369,8 +1349,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1385,8 +1363,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1401,8 +1377,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 0df746d9..dd80afc1 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -3,7 +3,7 @@ import SQLite class FoundationTests : XCTestCase { func testDataFromBlob() { - let data = Data(bytes: [1, 2, 3]) + let data = Data([1, 2, 3]) let blob = data.datatypeValue XCTAssertEqual([1, 2, 3], blob.bytes) } @@ -11,6 +11,6 @@ class FoundationTests : XCTestCase { func testBlobToData() { let blob = Blob(bytes: [1, 2, 3]) let data = Data.fromDatatypeValue(blob) - XCTAssertEqual(Data(bytes: [1, 2, 3]), data) + XCTAssertEqual(Data([1, 2, 3]), data) } } From 7a98a95ebc0a01729a490e6684e8221fd7adf5d8 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Thu, 4 Apr 2019 09:16:01 -0600 Subject: [PATCH 028/391] - Reverted to swift 4.2, will go to 5 in another version update - Updated CI config --- .travis.yml | 4 ++-- SQLite.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 909e5672..f1212412 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c rvm: 2.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.1 +osx_image: xcode10.2 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="12.1" + - IOS_VERSION="12.2" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 9d38dbc4..2eb11fd9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1180,7 +1180,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1235,7 +1235,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; From 291f1842138b034d48b65df7a5b4972cbee920c3 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Thu, 4 Apr 2019 09:24:15 -0600 Subject: [PATCH 029/391] Reverted other instances of swift 5 / Xcode 10.2 in documentation --- Documentation/Index.md | 4 ++-- README.md | 4 ++-- SQLite.swift.podspec | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9d40bfdd..cf4815b9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,8 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and -> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 4.2 (and +> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage diff --git a/README.md b/README.md index 33598c69..9192fe21 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). +> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 9.3). ### Carthage diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 63987b4b..fcdf88c1 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '5', + 'SWIFT_VERSION' => '4.2', } s.subspec 'standard' do |ss| From 7de9ee55694ae2b7df964c27724bb2811fb1ed9f Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 5 Apr 2019 08:53:37 -0600 Subject: [PATCH 030/391] Changed CI config back to Xcode 10.1, updated documents --- .travis.yml | 4 ++-- Documentation/Index.md | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1212412..909e5672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c rvm: 2.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.2 +osx_image: xcode10.1 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="12.2" + - IOS_VERSION="12.1" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/Documentation/Index.md b/Documentation/Index.md index cf4815b9..4925ca77 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -68,7 +68,7 @@ ## Installation > _Note:_ SQLite.swift requires Swift 4.2 (and -> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. +> [Xcode 10](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage diff --git a/README.md b/README.md index 9192fe21..bdb7aeaa 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 9.3). +> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10). ### Carthage From a3ad1f85a5fb8b31b026a73aa17e3925b618ac06 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 5 Apr 2019 08:54:37 -0600 Subject: [PATCH 031/391] Updated Swift badge to 4.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdb7aeaa..d7c87acc 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-4.1-orange.svg?style=flat +[Swift4Badge]: https://img.shields.io/badge/swift-4.2-orange.svg?style=flat [Swift4Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift From 3b4b93f17b90da96b99e636113eb68c71eabfb41 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 5 Apr 2019 09:08:50 -0600 Subject: [PATCH 032/391] Changed Xcode back to 10.2 for CI config and documentation (apparently `withUnsafeBytes` doesn't use UnsafeRawBufferPointer prior to 10.2) --- .travis.yml | 2 +- Documentation/Index.md | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 909e5672..2ce89b05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: objective-c rvm: 2.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.1 +osx_image: xcode10.2 env: global: - IOS_SIMULATOR="iPhone 6s" diff --git a/Documentation/Index.md b/Documentation/Index.md index 4925ca77..651aa991 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -68,7 +68,7 @@ ## Installation > _Note:_ SQLite.swift requires Swift 4.2 (and -> [Xcode 10](https://developer.apple.com/xcode/downloads/)) or greater. +> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage diff --git a/README.md b/README.md index d7c87acc..a2f090e0 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10). +> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10.2). ### Carthage From c31b43fee598eef452083bce3878d8f4bc328bd1 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Tue, 9 Apr 2019 13:02:02 -0600 Subject: [PATCH 033/391] Changed travis CI device name to iPhone X --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2ce89b05..2c474b79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rvm: 2.3 osx_image: xcode10.2 env: global: - - IOS_SIMULATOR="iPhone 6s" + - IOS_SIMULATOR="iPhone X" - IOS_VERSION="12.1" matrix: include: From e926eb1120a8d4ac8cee0a5ee6f84bd664cc5376 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Tue, 9 Apr 2019 14:24:08 -0600 Subject: [PATCH 034/391] - Updated makefile iOS simulator to iPhone X - Updated cocoapods version in gem file --- Makefile | 2 +- Tests/CocoaPods/Gemfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d98c8deb..7792ecdb 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone 6s +IOS_SIMULATOR = iPhone X IOS_VERSION = 12.1 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index fd1c8c2e..77d90eec 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.0beta2' +gem 'cocoapods', '~> 1.6.1' gem 'minitest' From c2487732f539fe2c0217a014206036a037a89c8d Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 09:03:51 -0600 Subject: [PATCH 035/391] Implemented fixes provided by @timshadel --- .travis.yml | 3 +++ Sources/SQLite/Info.plist | 2 +- Tests/CocoaPods/Makefile | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c474b79..d54827b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,4 +25,7 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: +# Workaround for Xcode 10.2/tvOS 9.1 bug +# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 + - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 7347d842..c3f9414b 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.4 + 0.11.6 CFBundleSignature ???? CFBundleVersion diff --git a/Tests/CocoaPods/Makefile b/Tests/CocoaPods/Makefile index 26163fdb..fa87e245 100644 --- a/Tests/CocoaPods/Makefile +++ b/Tests/CocoaPods/Makefile @@ -1,7 +1,9 @@ +XCPRETTY := $(shell command -v xcpretty) + test: install repo_update @set -e; \ for test in *_test.rb; do \ - bundle exec ./$$test; \ + bundle exec ./$$test | $(XCPRETTY) -C; \ done repo_update: From d89c0e895c3a1f956bb024a36a299719bf5f209e Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 09:25:43 -0600 Subject: [PATCH 036/391] Fixed option typo --- Tests/CocoaPods/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CocoaPods/Makefile b/Tests/CocoaPods/Makefile index fa87e245..532dcbff 100644 --- a/Tests/CocoaPods/Makefile +++ b/Tests/CocoaPods/Makefile @@ -3,7 +3,7 @@ XCPRETTY := $(shell command -v xcpretty) test: install repo_update @set -e; \ for test in *_test.rb; do \ - bundle exec ./$$test | $(XCPRETTY) -C; \ + bundle exec ./$$test | $(XCPRETTY) -c; \ done repo_update: From 9ae1a0bfe1f950099042964b0b4e6cb696681075 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 11:27:07 -0600 Subject: [PATCH 037/391] Updated to Swift 5 --- .travis.yml | 4 ++-- Documentation/Index.md | 14 +++++++------- Makefile | 2 +- README.md | 10 +++++----- SQLite.swift.podspec | 4 ++-- SQLite.xcodeproj/project.pbxproj | 4 ++-- Sources/SQLite/Info.plist | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index d54827b2..c8fc5feb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ rvm: 2.3 osx_image: xcode10.2 env: global: - - IOS_SIMULATOR="iPhone X" - - IOS_VERSION="12.1" + - IOS_SIMULATOR="iPhone XS" + - IOS_VERSION="12.2" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/Documentation/Index.md b/Documentation/Index.md index 651aa991..cbb5d49a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,7 +67,7 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and +> _Note:_ SQLite.swift requires Swift 5 (and > [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.6 + github "stephencelis/SQLite.swift" ~> 0.12 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.6' + pod 'SQLite.swift', '~> 0.12' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.6' + pod 'SQLite.swift/standalone', '~> 0.12' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.6' + pod 'SQLite.swift/standalone', '~> 0.12' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.6' + pod 'SQLite.swift/SQLCipher', '~> 0.12' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") ] ``` diff --git a/Makefile b/Makefile index 7792ecdb..5ea45d5d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone X -IOS_VERSION = 12.1 +IOS_VERSION = 12.2 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/README.md b/README.md index a2f090e0..ce45ef4e 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10.2). +> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). ### Carthage @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.6 + github "stephencelis/SQLite.swift" ~> 0.12 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.6' + pod 'SQLite.swift', '~> 0.12' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") ] ``` @@ -285,7 +285,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-4.2-orange.svg?style=flat +[Swift4Badge]: https://img.shields.io/badge/swift-5-orange.svg?style=flat [Swift4Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index fcdf88c1..8b202745 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.6" + s.version = "0.12" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.2', + 'SWIFT_VERSION' => '5', } s.subspec 'standard' do |ss| diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2eb11fd9..9d38dbc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1180,7 +1180,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1235,7 +1235,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index c3f9414b..3a5fc4f6 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.6 + 0.12 CFBundleSignature ???? CFBundleVersion From c0b9eb0dfd9faa6176a1ddbec87d98c076a4f62c Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 11:29:40 -0600 Subject: [PATCH 038/391] Updated iOS simulator to iPhone XS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5ea45d5d..50d07148 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone X +IOS_SIMULATOR = iPhone XS IOS_VERSION = 12.2 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" From aa9821896a17878c0bca089d2e61415a2a806282 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sun, 21 Apr 2019 10:59:15 -0600 Subject: [PATCH 039/391] Documentation - Updated README to clarify Swift 5 vs Swift 4.2 supported versions - Updated minimum CocoaPods version - Updated SPM language versions --- Documentation/Index.md | 2 +- Package.swift | 2 +- README.md | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index cbb5d49a..94ad580b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -96,7 +96,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift - requires version 1.0.0 or greater). + requires version 1.6.1 or greater). ```sh # Using the default Ruby install will require you to use sudo when diff --git a/Package.swift b/Package.swift index c008e482..2d4325f5 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .target(name: "SQLiteObjc"), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - swiftLanguageVersions: [4] + swiftLanguageVersions: [5] ) #if os(Linux) diff --git a/README.md b/README.md index ce45ef4e..6eaa69bb 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). +> _Note:_ Version 0.12 requires Swift 5 (and [Xcode][] 10.2). \nVersion 0.11.6 requires Swift 4.2 (and [Xcode][] 10.1). ### Carthage @@ -142,7 +142,7 @@ install SQLite.swift with Carthage: SQLite.swift with CocoaPods: 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift - requires version 1.0.0 or greater.) + requires version 1.6.1 or greater.) ```sh # Using the default Ruby install will require you to use sudo when @@ -285,8 +285,8 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-5-orange.svg?style=flat -[Swift4Link]: https://developer.apple.com/swift/ +[Swift5Badge]: https://img.shields.io/badge/swift-5-orange.svg?style=flat +[Swift5Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift [FMDB]: https://github.com/ccgus/fmdb From 3afd756e04b90ca40f26c864f7168fd2335c591b Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sun, 21 Apr 2019 11:04:11 -0600 Subject: [PATCH 040/391] Fixed Swift badges and installation note formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6eaa69bb..8efc53d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ Version 0.12 requires Swift 5 (and [Xcode][] 10.2). \nVersion 0.11.6 requires Swift 4.2 (and [Xcode][] 10.1). +> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/)) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1 or greater). ### Carthage From 71dfa68f001c47046958001f4ec8b07ca3a7c7aa Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sun, 21 Apr 2019 11:05:38 -0600 Subject: [PATCH 041/391] Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8efc53d8..41c7ffdc 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/)) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1 or greater). +> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. ### Carthage From f9cb6bdab2aeae1dc119d0177c90ac93ae710fc1 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Mon, 22 Apr 2019 11:15:15 -0600 Subject: [PATCH 042/391] Documentation - Updated swift tools version in Package.swift - Updated "OS X" references to "macOS" --- CONTRIBUTING.md | 2 +- Documentation/Index.md | 2 +- Package.swift | 2 +- SQLite.swift.podspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60c18370..6c367b95 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,7 +74,7 @@ Made it through everything above and still having trouble? Sorry! - Even better: link to a sample project exhibiting the issue. - Include the SQLite.swift commit or branch experiencing the issue. - Include devices and operating systems affected. - - Include build information: the Xcode and OS X versions affected. + - Include build information: the Xcode and macOS versions affected. [installation instructions]: Documentation/Index.md#installation [See Documentation]: Documentation/Index.md#sqliteswift-documentation diff --git a/Documentation/Index.md b/Documentation/Index.md index 94ad580b..211c01a9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -258,7 +258,7 @@ let path = NSSearchPathForDirectoriesInDomains( let db = try Connection("\(path)/db.sqlite3") ``` -On OS X, you can use your app’s **Application Support** directory: +On macOS, you can use your app’s **Application Support** directory: ```swift var path = NSSearchPathForDirectoriesInDomains( diff --git a/Package.swift b/Package.swift index 2d4325f5..6644cd73 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.0 import PackageDescription let package = Package( diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 8b202745..9405c903 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" s.version = "0.12" - s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." + s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC SQLite.swift provides compile-time confidence in SQL statement syntax and From 78089e2bdebbf1f634eaf85876d719981ed3c147 Mon Sep 17 00:00:00 2001 From: Stephan Heilner Date: Thu, 31 May 2018 15:14:02 -0600 Subject: [PATCH 043/391] Fixed bug not allowing columns from multiple tables in the .select --- SQLite.xcodeproj/project.pbxproj | 8 +++++ Sources/SQLite/Typed/Query.swift | 1 - Tests/SQLiteTests/SelectTests.swift | 45 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 Tests/SQLiteTests/SelectTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2eb11fd9..9143d1b2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -108,6 +108,9 @@ 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 */; }; + 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 */; }; @@ -228,6 +231,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; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; @@ -418,6 +422,7 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */, 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, + D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -847,6 +852,7 @@ 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, + D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -935,6 +941,7 @@ 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, + D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -993,6 +1000,7 @@ 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, + D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f6ef6df8..7ec5e581 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -963,7 +963,6 @@ extension Connection { try expandGlob(true)(q) continue column } - throw QueryError.noSuchTable(name: namespace) } throw QueryError.noSuchTable(name: namespace) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift new file mode 100644 index 00000000..bca01092 --- /dev/null +++ b/Tests/SQLiteTests/SelectTests.swift @@ -0,0 +1,45 @@ +import XCTest +@testable import SQLite + +class SelectTests: SQLiteTestCase { + + override func setUp() { + super.setUp() + CreateUsersTable() + CreateUsersDataTable() + } + + func CreateUsersDataTable() { + try! db.execute(""" + CREATE TABLE users_name ( + id INTEGER, + user_id INTEGER REFERENCES users(id), + name TEXT + ) + """ + ) + } + + func test_select_columns_from_multiple_tables() { + 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") + + 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 { + XCTAssertEqual($0[name], "Joey") + XCTAssertEqual($0[email], "Joey@example.com") + } + } + +} From e8ca9c0d138dca152df122053a8a2e181e6c3c7d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Apr 2019 12:29:15 -0600 Subject: [PATCH 044/391] Reverted swift tools version to 4.0 for backwards compatibility Co-Authored-By: sburlewapg <49003548+sburlewapg@users.noreply.github.com> --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 6644cd73..2d4325f5 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:4.0 import PackageDescription let package = Package( From 3e27caa3d898ce366fc29c8a8af9ccb105bd21d1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Apr 2019 12:38:02 -0600 Subject: [PATCH 045/391] Added Swift 4 and 4.2 for CI compatibility Co-Authored-By: sburlewapg <49003548+sburlewapg@users.noreply.github.com> --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 2d4325f5..96266a2c 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .target(name: "SQLiteObjc"), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - swiftLanguageVersions: [5] + swiftLanguageVersions: [.version("5"), .v4_2, .v4] ) #if os(Linux) From a93b4956946f122a7f2202297f2f284da829e476 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Mon, 22 Apr 2019 13:34:16 -0600 Subject: [PATCH 046/391] Changed language versions array to contain Ints to try and get CI working --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 96266a2c..430ae6f2 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .target(name: "SQLiteObjc"), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - swiftLanguageVersions: [.version("5"), .v4_2, .v4] + swiftLanguageVersions: [4, 5] ) #if os(Linux) From 08a1696765940e4fdc6e7fd33ab76d92a87239c7 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 24 Apr 2019 20:52:22 +0300 Subject: [PATCH 047/391] Fixed version --- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 9405c903..57eda40a 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.12" + s.version = "0.12.0" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 3a5fc4f6..db84c711 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12 + 0.12.0 CFBundleSignature ???? CFBundleVersion From 9b2d3e231feeb25f960e36915656b42719435bea Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 24 Apr 2019 21:44:45 +0300 Subject: [PATCH 048/391] Updated version in README and Documentation --- Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- Sources/SQLite/Typed/Expression.swift | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 211c01a9..05967ff4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12 + github "stephencelis/SQLite.swift" ~> 0.12.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12' + pod 'SQLite.swift', '~> 0.12.0' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12' + pod 'SQLite.swift/standalone', '~> 0.12.0' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12' + pod 'SQLite.swift/standalone', '~> 0.12.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.12' + pod 'SQLite.swift/SQLCipher', '~> 0.12.0' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") ] ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 8a3d5a11..5f885de8 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.12.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.12.0) +> the [0.13.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.0) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index 41c7ffdc..b7a18e0b 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12 + github "stephencelis/SQLite.swift" ~> 0.12.0 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12' + pod 'SQLite.swift', '~> 0.12.0' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") ] ``` diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index d89ee6cc..76ba04c4 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -73,7 +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.12.0) + // FIXME: make internal (0.13.0) public func asSQL() -> String { let expressed = expression var idx = 0 From 9a0170ed7a7a9b8a0bcb976783f849025e7f35e9 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Thu, 25 Apr 2019 01:13:04 +0300 Subject: [PATCH 049/391] Removed warning in the Cipher extension --- Sources/SQLite/Extensions/Cipher.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 71ef1765..44919aab 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -60,7 +60,7 @@ extension Connection { // the key provided is incorrect. To test that the database can be successfully opened with the // provided key, it is necessary to perform some operation on the database (i.e. read from it). private func cipher_key_check() throws { - try scalar("SELECT count(*) FROM sqlite_master;") + let _ = try scalar("SELECT count(*) FROM sqlite_master;") } } #endif From 026c5c264f925c0796d1cb72ee4927019c1dec4e Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Fri, 26 Apr 2019 22:27:16 +0200 Subject: [PATCH 050/391] Introduce support for backup --- SQLite.xcodeproj/project.pbxproj | 18 +++ Sources/SQLite/Core/Backup.swift | 176 ++++++++++++++++++++++++++++ Tests/SQLiteTests/BackupTests.swift | 36 ++++++ 3 files changed, 230 insertions(+) create mode 100644 Sources/SQLite/Core/Backup.swift create mode 100644 Tests/SQLiteTests/BackupTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 9d38dbc4..aec22743 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 02A43A9822738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9922738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; + 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; + 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; @@ -211,6 +218,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 02A43A9722738CF100FEC494 /* Backup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backup.swift; sourceTree = ""; }; + 02A43A9C22738E2900FEC494 /* BackupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupTests.swift; sourceTree = ""; }; 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; }; @@ -398,6 +407,7 @@ 19A17E2695737FAB5D6086E3 /* fixtures */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, + 02A43A9C22738E2900FEC494 /* BackupTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, @@ -434,6 +444,7 @@ EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, + 02A43A9722738CF100FEC494 /* Backup.swift */, ); path = Core; sourceTree = ""; @@ -817,6 +828,7 @@ 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */, + 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, ); @@ -839,6 +851,7 @@ 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, + 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */, 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, @@ -875,6 +888,7 @@ 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */, 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */, + 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */, 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, ); @@ -905,6 +919,7 @@ EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */, + 02A43A9822738CF100FEC494 /* Backup.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, ); @@ -927,6 +942,7 @@ EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, + 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, @@ -963,6 +979,7 @@ EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */, + 02A43A9922738CF100FEC494 /* Backup.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, ); @@ -985,6 +1002,7 @@ EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, + 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift new file mode 100644 index 00000000..d001ae7d --- /dev/null +++ b/Sources/SQLite/Core/Backup.swift @@ -0,0 +1,176 @@ +// +// 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 +import Dispatch +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +/// An object representing database backup. +/// +/// See: +public final class Backup { + + /// The name of the database to backup + public enum DatabaseName { + + /// The main database + case main + + /// The temporary database + case temp + + /// A database added to the connection with ATTACH statement + case attached(name: String) + + var name: String { + switch self { + case .main: + return "main" + case .temp: + return "temp" + case .attached(let name): + return name + } + } + } + + /// Number of pages to copy while performing a backup step + public enum Pages { + + /// Indicates all remaining pages should be copied + case all + + /// Indicates the maximal number of pages to be copied in single step + case limited(number: Int32) + + var number: Int32 { + switch self { + case .all: + return -1 + case .limited(let number): + return number + } + } + } + + /// Total number of pages to copy + /// + /// See: + public var pageCount: Int32 { + return handle.map { sqlite3_backup_pagecount($0) } ?? 0 + } + + /// Number of remaining pages to copy. + /// + /// See: + public var remainingPages: Int32 { + return handle.map { sqlite3_backup_remaining($0) } ?? 0 + } + + private let targetConnection: Connection + private let sourceConnection: Connection + + private var handle: OpaquePointer? + + /// Initializes a new SQLite backup. + /// + /// - Parameters: + /// + /// - targetConnection: The connection to the database to save backup into. + /// + /// - targetConnection: The name of the database to save backup into. + /// + /// Default: `.main`. + /// + /// - sourceConnection: The connection to the database to backup. + /// + /// - targetConnection: The name of the database to backup. + /// + /// Default: `.main`. + /// + /// - Returns: A new database backup. + /// + /// See: + public init(targetConnection: Connection, + targetName: DatabaseName = .main, + sourceConnection: Connection, + sourceName: DatabaseName = .main) throws { + + self.targetConnection = targetConnection + self.sourceConnection = sourceConnection + + self.handle = sqlite3_backup_init(targetConnection.handle, + targetName.name.withCString { $0 }, + sourceConnection.handle, + sourceName.name.withCString { $0 }) + + if self.handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), connection: targetConnection) { + throw error + } + } + + /// Performs a backup step. + /// + /// - 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) + + guard status != SQLITE_DONE else { + finish() + return + } + + if let error = Result(errorCode: status, connection: targetConnection) { + throw error + } + } + + /// Finalizes backup. + /// + /// See: + public func finish() { + guard let handle = self.handle else { + return + } + + sqlite3_backup_finish(handle) + self.handle = nil + } + + deinit { + finish() + } +} diff --git a/Tests/SQLiteTests/BackupTests.swift b/Tests/SQLiteTests/BackupTests.swift new file mode 100644 index 00000000..284ba610 --- /dev/null +++ b/Tests/SQLiteTests/BackupTests.swift @@ -0,0 +1,36 @@ +import XCTest +import Foundation +import Dispatch +@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 BackupTests : SQLiteTestCase { + override func setUp() { + super.setUp() + + CreateUsersTable() + } + + func test_backup_copies_database() throws { + let source = db! + let target = try Connection() + + try InsertUsers("alice", "betsy") + + let backup = try Backup(targetConnection: target, sourceConnection: source) + try backup.step() + + let users = try target.prepare("SELECT email FROM users ORDER BY email") + XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) + } +} + From d10c9da458377c245a59f8a817196b1ca221414a Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sat, 27 Apr 2019 00:21:17 +0200 Subject: [PATCH 051/391] Pass swift strings instead of UnsafePointers while initializing backup --- Sources/SQLite/Core/Backup.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index d001ae7d..e4fc6204 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -129,9 +129,9 @@ public final class Backup { self.sourceConnection = sourceConnection self.handle = sqlite3_backup_init(targetConnection.handle, - targetName.name.withCString { $0 }, + targetName.name, sourceConnection.handle, - sourceName.name.withCString { $0 }) + sourceName.name) if self.handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), connection: targetConnection) { throw error From 69ca8fed4574a8fac8f3074b4ca025ed5afdeac5 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Mon, 29 Apr 2019 18:07:45 +0200 Subject: [PATCH 052/391] More convenient API for backup --- SQLite.xcodeproj/project.pbxproj | 8 ------ Sources/SQLite/Core/Backup.swift | 4 +-- Sources/SQLite/Core/Connection.swift | 29 +++++++++++++++++++- Tests/SQLiteTests/BackupTests.swift | 36 ------------------------- Tests/SQLiteTests/ConnectionTests.swift | 12 +++++++++ 5 files changed, 42 insertions(+), 47 deletions(-) delete mode 100644 Tests/SQLiteTests/BackupTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index aec22743..b0bf35e7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -11,9 +11,6 @@ 02A43A9922738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; - 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; - 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; - 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; @@ -219,7 +216,6 @@ /* Begin PBXFileReference section */ 02A43A9722738CF100FEC494 /* Backup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backup.swift; sourceTree = ""; }; - 02A43A9C22738E2900FEC494 /* BackupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupTests.swift; sourceTree = ""; }; 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; }; @@ -407,7 +403,6 @@ 19A17E2695737FAB5D6086E3 /* fixtures */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, - 02A43A9C22738E2900FEC494 /* BackupTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, @@ -851,7 +846,6 @@ 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, - 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */, 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, @@ -942,7 +936,6 @@ EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, - 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, @@ -1002,7 +995,6 @@ EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, - 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index e4fc6204..1ea9637d 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -107,13 +107,13 @@ public final class Backup { /// /// - targetConnection: The connection to the database to save backup into. /// - /// - targetConnection: The name of the database to save backup into. + /// - targetName: The name of the database to save backup into. /// /// Default: `.main`. /// /// - sourceConnection: The connection to the database to backup. /// - /// - targetConnection: The name of the database to backup. + /// - sourceName: The name of the database to backup. /// /// Default: `.main`. /// diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1bbf7f73..c23076af 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -627,7 +627,34 @@ public final class Connection { } fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32 fileprivate var collations = [String: Collation]() - + + // MARK: - Backup + + /// Prepares a new backup for current connection. + /// + /// - Parameters: + /// + /// - databaseName: The name of the database to backup. + /// + /// Default: `.main` + /// + /// - targetConnection: The name of the database to save backup into. + /// + /// - targetDatabaseName: The name of the database to save backup into. + /// + /// Default: `.main`. + /// + /// - Returns: A new database backup. + + public func backup(databaseName: Backup.DatabaseName = .main, + usingConnection targetConnection: Connection, + andDatabaseName targetDatabaseName: Backup.DatabaseName = .main) throws -> Backup { + return try Backup(targetConnection: targetConnection, + targetName: targetDatabaseName, + sourceConnection: self, + sourceName: databaseName) + } + // MARK: - Error Handling func sync(_ block: () throws -> T) rethrows -> T { diff --git a/Tests/SQLiteTests/BackupTests.swift b/Tests/SQLiteTests/BackupTests.swift deleted file mode 100644 index 284ba610..00000000 --- a/Tests/SQLiteTests/BackupTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -import XCTest -import Foundation -import Dispatch -@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 BackupTests : SQLiteTestCase { - override func setUp() { - super.setUp() - - CreateUsersTable() - } - - func test_backup_copies_database() throws { - let source = db! - let target = try Connection() - - try InsertUsers("alice", "betsy") - - let backup = try Backup(targetConnection: target, sourceConnection: source) - try backup.step() - - let users = try target.prepare("SELECT email FROM users ORDER BY email") - XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) - } -} - diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index eab3cf00..bae13971 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -141,6 +141,18 @@ class ConnectionTests : SQLiteTestCase { AssertSQL("BEGIN EXCLUSIVE TRANSACTION") } + + func test_backup_copiesDatabase() throws { + let target = try Connection() + + try InsertUsers("alice", "betsy") + + let backup = try db.backup(usingConnection: target) + try backup.step() + + let users = try target.prepare("SELECT email FROM users ORDER BY email") + 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") From 2509ee37b80b7633a8f6dc8e5c72f34ae60f08dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2EG=C3=B6kay=20Borulday?= Date: Wed, 22 May 2019 17:57:57 +0300 Subject: [PATCH 053/391] Add missing parenthesis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 05967ff4..70d67c2d 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1209,7 +1209,7 @@ We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` function on a `Table` or `VirtualTable`. ```swift -try db.run(users.rename(Table("users_old")) +try db.run(users.rename(Table("users_old"))) // ALTER TABLE "users" RENAME TO "users_old" ``` From c27d53cab370631a21f089a19ac72a183693ab16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=A5gedal=20Reimer?= Date: Thu, 30 May 2019 11:15:11 +0200 Subject: [PATCH 054/391] Rename SQLite-Bridging.h -> SQLiteObjc.h Fixes #925 and makes SwiftPM-generated Xcode projects that use SQLite.swift compile out of the box. --- SQLite.xcodeproj/project.pbxproj | 20 +++++++++---------- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ Sources/SQLite/SQLite.h | 2 +- Sources/SQLiteObjc/SQLite-Bridging.m | 2 +- .../{SQLite-Bridging.h => SQLiteObjc.h} | 0 5 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename Sources/SQLiteObjc/include/{SQLite-Bridging.h => SQLiteObjc.h} (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 9d38dbc4..2d7735db 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; - 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; @@ -101,7 +101,7 @@ 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; - 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; @@ -180,8 +180,8 @@ EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180911C46E9D30038162A /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ @@ -277,7 +277,7 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SQLite-Bridging.h"; path = "../../SQLiteObjc/include/SQLite-Bridging.h"; sourceTree = ""; }; + EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -426,7 +426,7 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - EE91808D1C46E5230038162A /* SQLite-Bridging.h */, + EE91808D1C46E5230038162A /* SQLiteObjc.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, @@ -509,7 +509,7 @@ buildActionMask = 2147483647; files = ( 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, - 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */, + 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -518,7 +518,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */, + 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */, 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, ); @@ -528,7 +528,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */, + EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */, EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); @@ -540,7 +540,7 @@ files = ( EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, - EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */, + EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Sources/SQLite/SQLite.h b/Sources/SQLite/SQLite.h index 693ce323..69382ea0 100644 --- a/Sources/SQLite/SQLite.h +++ b/Sources/SQLite/SQLite.h @@ -3,4 +3,4 @@ FOUNDATION_EXPORT double SQLiteVersionNumber; FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; -#import +#import diff --git a/Sources/SQLiteObjc/SQLite-Bridging.m b/Sources/SQLiteObjc/SQLite-Bridging.m index e00a7315..9f2be0bd 100644 --- a/Sources/SQLiteObjc/SQLite-Bridging.m +++ b/Sources/SQLiteObjc/SQLite-Bridging.m @@ -22,7 +22,7 @@ // THE SOFTWARE. // -#import "SQLite-Bridging.h" +#import "SQLiteObjc.h" #import "fts3_tokenizer.h" #pragma mark - FTS diff --git a/Sources/SQLiteObjc/include/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLiteObjc.h similarity index 100% rename from Sources/SQLiteObjc/include/SQLite-Bridging.h rename to Sources/SQLiteObjc/include/SQLiteObjc.h From dc38f8c2da63aa238b3361e20893714d9c93bf42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=A5gedal=20Reimer?= Date: Thu, 30 May 2019 22:05:11 +0200 Subject: [PATCH 055/391] Rename SQLite-Bridging.m -> SQLiteObjc.m --- SQLite.xcodeproj/project.pbxproj | 20 +++++++++---------- .../{SQLite-Bridging.m => SQLiteObjc.m} | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename Sources/SQLiteObjc/{SQLite-Bridging.m => SQLiteObjc.m} (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2d7735db..30a40214 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ 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 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 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 */; }; @@ -100,7 +100,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 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; @@ -113,7 +113,7 @@ 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 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 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 */; }; @@ -166,7 +166,7 @@ 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 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 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 */; }; @@ -236,7 +236,7 @@ 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 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SQLite-Bridging.m"; path = "../../SQLiteObjc/SQLite-Bridging.m"; 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 = ""; }; @@ -430,7 +430,7 @@ EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, - EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, + EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, @@ -800,7 +800,7 @@ 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */, 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, - 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */, + 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */, 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */, @@ -854,7 +854,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */, + 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, @@ -900,7 +900,7 @@ EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, - EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, + EE247B061C3F06E900AE3E12 /* SQLiteObjc.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, @@ -946,7 +946,7 @@ 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, - EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m 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/SQLiteObjc/SQLite-Bridging.m b/Sources/SQLiteObjc/SQLiteObjc.m similarity index 100% rename from Sources/SQLiteObjc/SQLite-Bridging.m rename to Sources/SQLiteObjc/SQLiteObjc.m From 8dbbf80a28a42847e8b41a649c3a384c37106475 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 19 Jun 2019 17:17:21 +0300 Subject: [PATCH 056/391] version bump --- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 57eda40a..524e721c 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.12.0" + s.version = "0.12.1" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index db84c711..f7f7ce7f 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.0 + 0.12.1 CFBundleSignature ???? CFBundleVersion From 1b1ec920f1e168707b1a6af80d297d8f0d77bbd1 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Thu, 20 Jun 2019 22:52:43 +0300 Subject: [PATCH 057/391] Fixed build with module headers --- SQLite.swift.podspec | 28 +++++++++++++++++++++------- Sources/SQLite/SQLite.h | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 524e721c..cae73653 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -15,14 +15,19 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/stephencelis' s.module_name = 'SQLite' - s.ios.deployment_target = "8.0" - s.tvos.deployment_target = "9.1" - s.osx.deployment_target = "10.10" - s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' - s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '5', - } + s.swift_versions = ['4.2', '5'] + + + ios_deployment_target = '8.0' + tvos_deployment_target = '9.1' + osx_deployment_target = '10.10' + watchos_deployment_target = '2.2' + + 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.subspec 'standard' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' @@ -33,6 +38,9 @@ Pod::Spec.new do |s| ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' 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 @@ -49,6 +57,9 @@ Pod::Spec.new do |s| ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' 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 @@ -64,6 +75,9 @@ Pod::Spec.new do |s| ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' 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 diff --git a/Sources/SQLite/SQLite.h b/Sources/SQLite/SQLite.h index 69382ea0..fe1e3dfe 100644 --- a/Sources/SQLite/SQLite.h +++ b/Sources/SQLite/SQLite.h @@ -3,4 +3,4 @@ FOUNDATION_EXPORT double SQLiteVersionNumber; FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; -#import +#import "SQLiteObjc.h" From d0d802e3f99b78112cfb0b71894eeec021a7436c Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Fri, 21 Jun 2019 13:18:14 +0300 Subject: [PATCH 058/391] version bump --- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index cae73653..2341cfc4 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.12.1" + s.version = "0.12.2" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index f7f7ce7f..2d956da2 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.1 + 0.12.2 CFBundleSignature ???? CFBundleVersion From 4de5dc458788bc09111d26f97c4ea46c8f08d946 Mon Sep 17 00:00:00 2001 From: Guillaume Algis Date: Wed, 14 Aug 2019 12:44:15 +0200 Subject: [PATCH 059/391] Add @discardableResult to all FTSConfig methods returning Self for chaining --- Sources/SQLite/Extensions/FTS4.swift | 18 +++++++++--------- Sources/SQLite/Extensions/FTS5.swift | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 5ef84dd7..f0184565 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -194,25 +194,25 @@ open class FTSConfig { } /// [Tokenizers](https://www.sqlite.org/fts3.html#tokenizer) - open func tokenizer(_ tokenizer: Tokenizer?) -> Self { + @discardableResult open func tokenizer(_ tokenizer: Tokenizer?) -> Self { self.tokenizer = tokenizer return self } /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6) - open func prefix(_ prefix: [Int]) -> Self { + @discardableResult open func prefix(_ prefix: [Int]) -> Self { self.prefixes += prefix return self } /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2) - open func externalContent(_ schema: SchemaType) -> Self { + @discardableResult open func externalContent(_ schema: SchemaType) -> Self { self.externalContentSchema = schema return self } /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1) - open func contentless() -> Self { + @discardableResult open func contentless() -> Self { self.isContentless = true return self } @@ -308,31 +308,31 @@ open class FTS4Config : FTSConfig { } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) - open func compress(_ functionName: String) -> Self { + @discardableResult open func compress(_ functionName: String) -> Self { self.compressFunction = functionName return self } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) - open func uncompress(_ functionName: String) -> Self { + @discardableResult open func uncompress(_ functionName: String) -> Self { self.uncompressFunction = functionName return self } /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3) - open func languageId(_ columnName: String) -> Self { + @discardableResult open func languageId(_ columnName: String) -> Self { self.languageId = columnName return self } /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) - open func matchInfo(_ matchInfo: MatchInfo) -> Self { + @discardableResult open func matchInfo(_ matchInfo: MatchInfo) -> Self { self.matchInfo = matchInfo return self } /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) - open func order(_ order: Order) -> Self { + @discardableResult open func order(_ order: Order) -> Self { self.order = order return self } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index 763927ff..cf13f3d8 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -58,19 +58,19 @@ open class FTS5Config : FTSConfig { } /// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2) - open func contentRowId(_ column: Expressible) -> Self { + @discardableResult open func contentRowId(_ column: Expressible) -> Self { self.contentRowId = column return self } /// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5) - open func columnSize(_ size: Int) -> Self { + @discardableResult open func columnSize(_ size: Int) -> Self { self.columnSize = size return self } /// [The Detail Option](https://www.sqlite.org/fts5.html#section_4_6) - open func detail(_ detail: Detail) -> Self { + @discardableResult open func detail(_ detail: Detail) -> Self { self.detail = detail return self } From 115ad41835b9de8c2f69fa510c222ce85e526a71 Mon Sep 17 00:00:00 2001 From: Johannes Ebeling Date: Sat, 17 Aug 2019 00:15:34 +0200 Subject: [PATCH 060/391] Enable the encodable api to pass the onConflict parameter to the QueryType.insert function --- Sources/SQLite/Typed/Coding.swift | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..d4f99e6e 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -44,6 +44,30 @@ 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 + /// have to provide your own Encodable implementations that encode the correct ids. + /// The onConflict will be passed to the actual insert function to define what should happen + /// when an error occurs during the insert operation. + /// + /// - Parameters: + /// + /// - onConlict: Define what happens when an insert operation fails + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func insert(or onConflict: OnConflict, encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.insert(or: onConflict, encoder.setters + otherSetters) + } /// Creates an `UPDATE` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort From d8193fb00255186802fe5182cd84fa4de041f917 Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 5 Nov 2019 08:40:20 +0100 Subject: [PATCH 061/391] closing brackets/aposthrophes few things wasnt closed properly --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 70d67c2d..c6c54437 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1792,12 +1792,12 @@ let config = FTS5Config() .column(subject) .column(body, [.unindexed]) -try db.run(emails.create(.FTS5(config)) +try db.run(emails.create(.FTS5(config))) // CREATE VIRTUAL TABLE "emails" USING fts5("subject", "body" UNINDEXED) // Note that FTS5 uses a different syntax to select columns, so we need to rewrite // the last FTS4 query above as: -let replies = emails.filter(emails.match("subject:\"Re:\"*)) +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_ From ef3f88f7d02116c95088d7c5dae9a0af42f4dbfc Mon Sep 17 00:00:00 2001 From: Bradley Walters Date: Fri, 15 Nov 2019 09:52:51 -0700 Subject: [PATCH 062/391] Fix building with standalone sqlite3 >= 3.30.0 SQLite 3.30.0 changed the definition of SQLITE_DETERMINISTIC: `-#define SQLITE_DETERMINISTIC 0x800` `+#define SQLITE_DETERMINISTIC 0x000000800` Meaning that the (older) system sqlite3 library and the pod have different definitions, even though they're the same value. We've been importing the system sqlite3 module in SQLiteObjc.h even when linking against standalone sqlite. I added a check to SQLiteObjc.h to import the sqlite3 pod when we're using it, instead of always importing the system module. This leads to there being only one definition in scope. --- SQLite.swift.podspec | 3 ++- Sources/SQLiteObjc/include/SQLiteObjc.h | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 2341cfc4..712ddc36 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -50,7 +50,8 @@ Pod::Spec.new do |s| ss.private_header_files = 'Sources/SQLiteObjc/*.h' ss.xcconfig = { - 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' + 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_SWIFT_STANDALONE=1' } ss.dependency 'sqlite3' diff --git a/Sources/SQLiteObjc/include/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h index f8c2a3b3..e8ba9a7d 100644 --- a/Sources/SQLiteObjc/include/SQLiteObjc.h +++ b/Sources/SQLiteObjc/include/SQLiteObjc.h @@ -23,7 +23,11 @@ // @import Foundation; +#if defined(SQLITE_SWIFT_STANDALONE) +@import sqlite3; +#else @import SQLite3; +#endif NS_ASSUME_NONNULL_BEGIN typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); From 23f4aa42553119aab2e0118afc6b080e70a8d101 Mon Sep 17 00:00:00 2001 From: Svetlana Korosteleva Date: Mon, 2 Dec 2019 15:14:59 -0800 Subject: [PATCH 063/391] Add interface to perform migration to new major SQLCipher immediately after setting a key This change adds `keyAndMigrate` methods which perform "PRAGMA cipher_migrate;" call immediately after calling `sqlite3_key_v2` This call is needed to open the database files created in older major SQLCipher version (e.g. 3.x.x) by newer SQLCipher version (e.g. 4.x.x). These calls should be performed only once, when opening older database files for the first time. 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 --- Sources/SQLite/Extensions/Cipher.swift | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 44919aab..4d8d426b 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -32,6 +32,25 @@ extension Connection { try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } + /// Same as `key(_ key: String, db: String = "main")`, running "PRAGMA cipher_migrate;" + /// immediately after calling `sqlite3_key_v2`, which performs the migration of + /// SQLCipher database created by older major version of SQLCipher, to be able to + /// open this database with new major version of SQLCipher + /// (e.g. to open database created by SQLCipher version 3.x.x with SQLCipher version 4.x.x). + /// 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 + public func keyAndMigrate(_ key: String, db: String = "main") throws { + try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count, migrate: true) + } + + /// 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.bytes.count, migrate: true) + } /// Change the key on an open database. If the current database is not encrypted, this routine /// will encrypt it. @@ -47,8 +66,20 @@ extension Connection { } // MARK: - private - private func _key_v2(db: String, keyPointer: UnsafePointer, keySize: Int) throws { + private func _key_v2(db: String, + keyPointer: UnsafePointer, + keySize: Int, + migrate: Bool = false) throws { try check(sqlite3_key_v2(handle, db, keyPointer, Int32(keySize))) + if migrate { + // Run "PRAGMA cipher_migrate;" immediately after `sqlite3_key_v2` + // per recommendation of SQLCipher authors + let migrateResult = try scalar("PRAGMA cipher_migrate;") + if (migrateResult as? String) != "0" { + // "0" is the result of successfull migration + throw Result.error(message: "Error in cipher migration, result \(migrateResult.debugDescription)", code: 1, statement: nil) + } + } try cipher_key_check() } From 3b57e6963303adbca4cbc435e8ef1c43dee02726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 22 Jan 2020 09:13:54 +0100 Subject: [PATCH 064/391] Add support for storing and retrieving UUID objects Storing them as string for better readability in the database --- Sources/SQLite/Foundation.swift | 16 ++++++++++++++++ Tests/SQLiteTests/FoundationTests.swift | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index cfb79bec..9986f581 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -68,3 +68,19 @@ public var dateFormatter: DateFormatter = { formatter.timeZone = TimeZone(secondsFromGMT: 0) return formatter }() + +extension UUID : Value { + + public static var declaredDatatype: String { + return String.declaredDatatype + } + + public static func fromDatatypeValue(_ stringValue: String) -> UUID { + return UUID(uuidString: stringValue)! + } + + public var datatypeValue: String { + return self.uuidString + } + +} diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index dd80afc1..ba9685b7 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -13,4 +13,16 @@ class FoundationTests : XCTestCase { let data = Data.fromDatatypeValue(blob) XCTAssertEqual(Data([1, 2, 3]), data) } + + func testStringFromUUID() { + let uuid = UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3")! + let string = uuid.datatypeValue + XCTAssertEqual("4ABE10C9-FF12-4CD4-90C1-4B429001BAD3", string) + } + + func testUUIDFromString() { + let string = "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3" + let uuid = UUID.fromDatatypeValue(string) + XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) + } } From 806f03c91f473d5003372a19083e27ab5e8897a6 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 15 Apr 2020 11:29:23 +0200 Subject: [PATCH 065/391] Improving README.md Because of #988 and #983, I added a do...catch in the usage section, and a production implementation example in the related section. --- README.md | 119 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index b7a18e0b..8b0972f6 100644 --- a/README.md +++ b/README.md @@ -34,67 +34,79 @@ syntax _and_ intent. ```swift import SQLite -let db = try Connection("path/to/db.sqlite3") - -let users = Table("users") -let id = Expression("id") -let name = Expression("name") -let email = Expression("email") - -try db.run(users.create { t in - t.column(id, primaryKey: true) - t.column(name) - t.column(email, unique: true) -}) -// CREATE TABLE "users" ( -// "id" INTEGER PRIMARY KEY NOT NULL, -// "name" TEXT, -// "email" TEXT NOT NULL UNIQUE -// ) - -let insert = users.insert(name <- "Alice", email <- "alice@mac.com") -let rowid = try db.run(insert) -// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') - -for user in try db.prepare(users) { - print("id: \(user[id]), name: \(user[name]), email: \(user[email])") - // id: 1, name: Optional("Alice"), email: alice@mac.com +// Wrap everything in a do...catch to handle errors +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") + + try db.run(users.create { t in + t.column(id, primaryKey: true) + t.column(name) + t.column(email, unique: true) + }) + // CREATE TABLE "users" ( + // "id" INTEGER PRIMARY KEY NOT NULL, + // "name" TEXT, + // "email" TEXT NOT NULL UNIQUE + // ) + + let insert = users.insert(name <- "Alice", email <- "alice@mac.com") + let rowid = try db.run(insert) + // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') + + for user in try db.prepare(users) { + print("id: \(user[id]), name: \(user[name]), email: \(user[email])") + // id: 1, name: Optional("Alice"), email: alice@mac.com + } + // SELECT * FROM "users" + + let alice = users.filter(id == rowid) + + try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) + // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') + // WHERE ("id" = 1) + + try db.run(alice.delete()) + // DELETE FROM "users" WHERE ("id" = 1) + + try db.scalar(users.count) // 0 + // SELECT count(*) FROM "users" +} catch { + print (error) } -// SELECT * FROM "users" - -let alice = users.filter(id == rowid) - -try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) -// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') -// WHERE ("id" = 1) - -try db.run(alice.delete()) -// DELETE FROM "users" WHERE ("id" = 1) - -try db.scalar(users.count) // 0 -// SELECT count(*) FROM "users" ``` SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API. ```swift -let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") -for email in ["betty@icloud.com", "cathy@icloud.com"] { - try stmt.run(email) +// 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) + } + + db.totalChanges // 3 + db.changes // 1 + db.lastInsertRowid // 3 + + for row in try db.prepare("SELECT id, email FROM users") { + print("id: \(row[0]), email: \(row[1])") + // id: Optional(2), email: Optional("betty@icloud.com") + // id: Optional(3), email: Optional("cathy@icloud.com") + } + + try db.scalar("SELECT count(*) FROM users") // 2 +} catch { + print (error) } - -db.totalChanges // 3 -db.changes // 1 -db.lastInsertRowid // 3 - -for row in try db.prepare("SELECT id, email FROM users") { - print("id: \(row[0]), email: \(row[1])") - // id: Optional(2), email: Optional("betty@icloud.com") - // id: Optional(3), email: Optional("cathy@icloud.com") -} - -try db.scalar("SELECT count(*) FROM users") // 2 ``` [Read the documentation][See Documentation] or explore more, @@ -253,6 +265,7 @@ 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 From f040e145d328042a4a0f042086ac09b28f4b1b41 Mon Sep 17 00:00:00 2001 From: Daniel Shelley Date: Fri, 17 Jul 2020 10:59:47 -0600 Subject: [PATCH 066/391] set deployment target to iOS 9 to fix Archiving with Xcode 12 --- 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 30a40214..80719c33 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1173,7 +1173,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1229,7 +1229,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; @@ -1255,7 +1255,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1276,7 +1276,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; From 135b2fc8b854f8b528956a56d8af8a89f6708d17 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Thu, 13 Aug 2020 07:09:38 -0400 Subject: [PATCH 067/391] =?UTF-8?q?Added=20=3D=3D=3D=20as=20explicit=20?= =?UTF-8?q?=E2=80=9CIS=E2=80=9D=20operator=20for=20expressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added === as explicit “IS” operator for expressions. Added tests for === operator. --- Sources/SQLite/Typed/Operators.swift | 27 ++++++++++++++++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 14 +++++++++++++ 2 files changed, 41 insertions(+) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index b5637ea3..0a323b90 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -378,6 +378,33 @@ public func ==(lhs: V?, rhs: Expression) -> Expression whe return Operator.eq.infix(lhs, rhs) } +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { + return "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)) } + return "IS".infix(lhs, rhs) +} +public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "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) } + return "IS".infix(lhs, rhs) +} + public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return Operator.neq.infix(lhs, rhs) } diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 948eb0a4..18a1f52e 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -187,6 +187,20 @@ class OperatorsTests : XCTestCase { AssertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) } + func test_isOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" IS \"bool\")", bool === bool) + AssertSQL("(\"bool\" IS \"boolOptional\")", bool === boolOptional) + AssertSQL("(\"boolOptional\" IS \"bool\")", boolOptional === bool) + AssertSQL("(\"boolOptional\" IS \"boolOptional\")", boolOptional === boolOptional) + AssertSQL("(\"bool\" IS 1)", bool === true) + AssertSQL("(\"boolOptional\" IS 1)", boolOptional === true) + AssertSQL("(1 IS \"bool\")", true === bool) + AssertSQL("(1 IS \"boolOptional\")", true === boolOptional) + + AssertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) + AssertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) + } + func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { AssertSQL("(\"bool\" != \"bool\")", bool != bool) AssertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) From 113872d8d01b2b0c927d9b450abfb5fe6a8f312a Mon Sep 17 00:00:00 2001 From: Jake-B Date: Thu, 13 Aug 2020 08:17:00 -0400 Subject: [PATCH 068/391] =?UTF-8?q?Add=20!=3D=3D=20as=20explicit=20?= =?UTF-8?q?=E2=80=9CIS=20NOT=E2=80=9D=20operator=20for=20exprsns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added !== as explicit “IS” operator for expressions. Added tests for !== operator. --- Sources/SQLite/Typed/Operators.swift | 28 ++++++++++++++++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 14 +++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 0a323b90..594f08a0 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -432,6 +432,34 @@ public func !=(lhs: V?, rhs: Expression) -> Expression whe return Operator.neq.infix(lhs, rhs) } +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { + return "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)) } + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "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) } + return "IS NOT".infix(lhs, rhs) +} + + public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return Operator.gt.infix(lhs, rhs) } diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 18a1f52e..c2416844 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -200,6 +200,20 @@ class OperatorsTests : XCTestCase { AssertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) AssertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) } + + func test_isNotOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" IS NOT \"bool\")", bool !== bool) + AssertSQL("(\"bool\" IS NOT \"boolOptional\")", bool !== boolOptional) + AssertSQL("(\"boolOptional\" IS NOT \"bool\")", boolOptional !== bool) + AssertSQL("(\"boolOptional\" IS NOT \"boolOptional\")", boolOptional !== boolOptional) + AssertSQL("(\"bool\" IS NOT 1)", bool !== true) + AssertSQL("(\"boolOptional\" IS NOT 1)", boolOptional !== true) + AssertSQL("(1 IS NOT \"bool\")", true !== bool) + AssertSQL("(1 IS NOT \"boolOptional\")", true !== boolOptional) + + AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional !== nil) + AssertSQL("(NULL IS NOT \"boolOptional\")", nil !== boolOptional) + } func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { AssertSQL("(\"bool\" != \"bool\")", bool != bool) From e3e031df66fe6f8a35f50a6d2f3cfcd1a3439656 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 5 Nov 2020 16:37:47 -0300 Subject: [PATCH 069/391] Removed the force unwrap in the FailableIterator extension for the next () method Although the method accepts an optional return, the function forces unwrap --- SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Info.plist | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 30a40214..9972b3f7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1257,6 +1257,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.12.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1278,6 +1279,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.12.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index dc91d3d8..237d4812 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -208,7 +208,7 @@ public protocol FailableIterator : IteratorProtocol { extension FailableIterator { public func next() -> Element? { - return try! failableNext() + return try? failableNext() } } diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 2d956da2..ca23c84f 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.2 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion From 12ac2fd4928e690957e2d3cad57884d450a8cea7 Mon Sep 17 00:00:00 2001 From: turtlemaster19 <46784000+UInt2048@users.noreply.github.com> Date: Thu, 3 Dec 2020 14:29:36 -0500 Subject: [PATCH 070/391] Fix #920 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7a18e0b..a12fe539 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.6 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. ### Carthage From 140374a1f25df9a50c9e014679844bb2de70542d Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 11:09:45 -0700 Subject: [PATCH 071/391] implement batch insert, insertMany() --- Sources/SQLite/Typed/Coding.swift | 25 +++++++++++++++++++- Sources/SQLite/Typed/Query.swift | 37 ++++++++++++++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 37 ++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..34edbc49 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -38,13 +38,36 @@ extension QueryType { /// /// - otherSetters: Any other setters to include in the insert /// - /// - Returns: An `INSERT` statement fort the encodable object + /// - Returns: An `INSERT` statement for the encodable object public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } + /// Creates 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 + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodables: Encodable objects to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the inserts, per row/object. + /// + /// - 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) + try encodable.encode(to: encoder) + return encoder.setters + otherSetters + } + return self.insertMany(combinedSetters) + } + /// 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 diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f6ef6df8..61deaa1f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -631,6 +631,18 @@ extension QueryType { return insert(onConflict, values) } + public func insertMany( _ values: [[Setter]]) -> Insert { + return insertMany(nil, values) + } + + public func insertMany(or onConflict: OnConflict, _ values: [[Setter]]) -> Insert { + return insertMany(onConflict, values) + } + + public func insertMany(or onConflict: OnConflict, _ values: [Setter]...) -> Insert { + return insertMany(onConflict, values) + } + fileprivate func insert(_ or: OnConflict?, _ values: [Setter]) -> Insert { let insert = values.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in (insert.columns + [setter.column], insert.values + [setter.value]) @@ -650,6 +662,29 @@ extension QueryType { return Insert(" ".join(clauses.compactMap { $0 }).expression) } + fileprivate func insertMany(_ or: OnConflict?, _ values: [[Setter]]) -> Insert { + guard values.count > 0 else { + return insert() + } + let insertRows = values.map { rowValues in + rowValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in + (insert.columns + [setter.column], insert.values + [setter.value]) + } + } + + let clauses: [Expressible?] = [ + Expression(literal: "INSERT"), + or.map { Expression(literal: "OR \($0.rawValue)") }, + Expression(literal: "INTO"), + tableName(), + "".wrap(insertRows[0].columns) as Expression, + Expression(literal: "VALUES"), + ", ".join(insertRows.map(\.values).map({ "".wrap($0) as Expression })), + whereClause + ] + return Insert(" ".join(clauses.compactMap { $0 }).expression) + } + /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. public func insert() -> Insert { return Insert(" ".join([ @@ -1010,6 +1045,8 @@ extension Connection { /// - SeeAlso: `QueryType.insert(value:_:)` /// - SeeAlso: `QueryType.insert(values:)` /// - SeeAlso: `QueryType.insert(or:_:)` + /// - SeeAlso: `QueryType.insertMany(values:)` + /// - SeeAlso: `QueryType.insertMany(or:_:)` /// - SeeAlso: `QueryType.insert()` /// /// - Parameter query: An insert query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2a9e4ecb..f48f49b9 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -247,6 +247,26 @@ class QueryTests : XCTestCase { ) } + func test_insert_many_compilesInsertManyExpression() { + AssertSQL( + "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", + users.insertMany([[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + ) + } + func test_insert_many_compilesInsertManyNoneExpression() { + AssertSQL( + "INSERT INTO \"users\" DEFAULT VALUES", + users.insertMany([]) + ) + } + + func test_insert_many_withOnConflict_compilesInsertManyOrOnConflictExpression() { + AssertSQL( + "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", + users.insertMany(or: .replace, [[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + ) + } + func test_insert_encodable() throws { let emails = Table("emails") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) @@ -270,6 +290,18 @@ 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, optional: nil, sub: nil) + let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, optional: nil, sub: nil) + let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, optional: nil, sub: nil) + let insert = try emails.insertMany([value1, value2, value3]) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0), (2, '3', 1, 3.0, 5.0), (3, '4', 1, 3.0, 6.0)", + insert + ) + } + func test_update_compilesUpdateExpression() { AssertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", @@ -483,6 +515,11 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(1, id) } + func test_insert_many() { + let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) + XCTAssertEqual(2, id) + } + func test_update() { let changes = try! db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) From 087264792a7c75c446106ef4090d9fd669b37e8a Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 11:27:42 -0700 Subject: [PATCH 072/391] Update Index.md --- Documentation/Index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 70d67c2d..2f441c8c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -638,6 +638,18 @@ do { } ``` +Multiple rows can be inserted at once by similarily calling `insertMany` with an array of per-row [setters](#setters). + +```swift +do { + let rowid = try db.run(users.insertMany([mail <- "alice@mac.com"], [email <- "geoff@mac.com"])) + print("inserted id: \(rowid)") +} catch { + print("insertion failed: \(error)") +} +``` + + The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns. From 05c404fcec8df8043d68d38617a9c69f54772f50 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 11:37:22 -0700 Subject: [PATCH 073/391] cleanup sql creation --- Sources/SQLite/Typed/Query.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 61deaa1f..54e44fa5 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -663,12 +663,14 @@ extension QueryType { } fileprivate func insertMany(_ or: OnConflict?, _ values: [[Setter]]) -> Insert { - guard values.count > 0 else { + guard let firstInsert = values.first else { + // must be at least 1 object or else we don't know columns. Default to default inserts. return insert() } - let insertRows = values.map { rowValues in - rowValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in - (insert.columns + [setter.column], insert.values + [setter.value]) + let columns = firstInsert.map { $0.column } + let insertValues = values.map { rowValues in + rowValues.reduce([Expressible]()) { insert, setter in + insert + [setter.value] } } @@ -677,9 +679,9 @@ extension QueryType { or.map { Expression(literal: "OR \($0.rawValue)") }, Expression(literal: "INTO"), tableName(), - "".wrap(insertRows[0].columns) as Expression, + "".wrap(columns) as Expression, Expression(literal: "VALUES"), - ", ".join(insertRows.map(\.values).map({ "".wrap($0) as Expression })), + ", ".join(insertValues.map({ "".wrap($0) as Expression })), whereClause ] return Insert(" ".join(clauses.compactMap { $0 }).expression) From 4fde8dba065b213ae0a5a067a1d8d31680091b5a Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 14:52:52 -0700 Subject: [PATCH 074/391] try and fix travis? --- .travis.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8fc5feb..ea60b063 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c -rvm: 2.3 +rvm: 2.7.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.2 +osx_image: xcode12.2 env: global: - - IOS_SIMULATOR="iPhone XS" - - IOS_VERSION="12.2" + - IOS_SIMULATOR="iPhone 11" + - IOS_VERSION="14.2" matrix: include: - env: BUILD_SCHEME="SQLite iOS" @@ -25,7 +25,4 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: -# Workaround for Xcode 10.2/tvOS 9.1 bug -# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 - - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh From 183a43948f40637643c15e6e19a4b983d2df8325 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 15:32:43 -0700 Subject: [PATCH 075/391] Update Planning.md --- Documentation/Planning.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 5f885de8..62df1f24 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -33,6 +33,3 @@ be referred to when it comes time to add the corresponding feature._ _Features that are not actively being considered, perhaps because of no clean type-safe way to implement them with the current Swift, or bugs, or just general uncertainty._ - - * provide a mechanism for INSERT INTO multiple values, per - [#168](https://github.com/stephencelis/SQLite.swift/issues/168) From b260b0a4aa1754b609ac48c637578ef1cea0898c Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 15:35:12 -0700 Subject: [PATCH 076/391] upgrade test deps to force it working --- .travis.yml | 2 +- Tests/CocoaPods/Gemfile.lock | 46 ++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea60b063..2a0460bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ matrix: - env: CARTHAGE_PLATFORM="tvOS" - env: PACKAGE_MANAGER_COMMAND="test" before_install: - - gem update bundler + - gem install bundler - gem install xcpretty --no-document - brew update - brew outdated carthage || brew upgrade carthage diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index b5172144..e5c53ea3 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,18 +1,18 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - activesupport (4.2.11) + CFPropertyList (3.0.3) + activesupport (4.2.11.3) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) atomos (0.1.3) - claide (1.0.2) - cocoapods (1.6.0.beta.2) + claide (1.0.3) + cocoapods (1.6.2) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.0.beta.2) + cocoapods-core (= 1.6.2) cocoapods-deintegrate (>= 1.0.2, < 2.0) cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -22,56 +22,56 @@ GEM cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.2.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.3, >= 1.3.1) - xcodeproj (>= 1.7.0, < 2.0) - cocoapods-core (1.6.0.beta.2) + ruby-macho (~> 1.4) + xcodeproj (>= 1.8.1, < 2.0) + cocoapods-core (1.6.2) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.2) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.1) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.8) escape (0.0.4) - fourflusher (2.0.1) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) i18n (0.9.5) concurrent-ruby (~> 1.0) minitest (5.11.3) molinillo (0.6.6) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.3.1) + ruby-macho (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.5) + tzinfo (1.2.9) thread_safe (~> 0.1) - xcodeproj (1.7.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.0beta2) + cocoapods (~> 1.6.1) minitest BUNDLED WITH - 1.17.1 + 1.17.2 From e40e3369c14319df540bbac476c419cfea20294e Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 17:43:16 -0700 Subject: [PATCH 077/391] update deps --- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 51 +++++++++++++++++++---------- Tests/CocoaPods/integration_test.rb | 2 +- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 77d90eec..19db2b36 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.1' +gem 'cocoapods', '~> 1.10.1' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index e5c53ea3..d99ac49a 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -2,42 +2,51 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.3) - activesupport (4.2.11.3) - i18n (~> 0.7) + activesupport (5.2.5) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) atomos (0.1.3) claide (1.0.3) - cocoapods (1.6.2) - activesupport (>= 4.0.2, < 5) + cocoapods (1.10.1) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.2) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-core (= 1.10.1) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (>= 2.2.0, < 3.0) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) ruby-macho (~> 1.4) - xcodeproj (>= 1.8.1, < 2.0) - cocoapods-core (1.6.2) - activesupport (>= 4.0.2, < 6) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.1) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) cocoapods-deintegrate (1.0.4) cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) @@ -45,18 +54,26 @@ GEM colored2 (3.1.2) concurrent-ruby (1.1.8) escape (0.0.4) + ethon (0.14.0) + ffi (>= 1.15.0) + ffi (1.15.0) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - i18n (0.9.5) + httpclient (2.8.3) + i18n (1.8.10) concurrent-ruby (~> 1.0) + json (2.5.1) minitest (5.11.3) molinillo (0.6.6) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + public_suffix (4.0.6) ruby-macho (1.4.0) thread_safe (0.3.6) + typhoeus (1.4.0) + ethon (>= 0.9.0) tzinfo (1.2.9) thread_safe (~> 0.1) xcodeproj (1.19.0) @@ -70,7 +87,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.1) + cocoapods (~> 1.10.1) minitest BUNDLED WITH diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 98a539b5..2ce2997c 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -40,7 +40,7 @@ def test_pod super unless consumer.platform_name == :watchos end - def xcodebuild(action, scheme, configuration) + def xcodebuild(action, scheme, configuration, _) require 'fourflusher' command = %W(#{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) case consumer.platform_name From 0742ee04976029504502b6212b60462017519887 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 17:46:47 -0700 Subject: [PATCH 078/391] apparently carthage doesnt support xcode 12 out of box --- .travis.yml | 8 ++++---- Makefile | 2 +- Tests/Carthage/Makefile | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a0460bb..b8b2eaba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c rvm: 2.7.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode12.2 +osx_image: xcode11.6 env: global: - - IOS_SIMULATOR="iPhone 11" - - IOS_VERSION="14.2" + - IOS_SIMULATOR="iPhone XS" + - IOS_VERSION="13.6" matrix: include: - env: BUILD_SCHEME="SQLite iOS" @@ -20,7 +20,7 @@ matrix: - env: CARTHAGE_PLATFORM="tvOS" - env: PACKAGE_MANAGER_COMMAND="test" before_install: - - gem install bundler + - gem install bundler - gem install xcpretty --no-document - brew update - brew outdated carthage || brew upgrade carthage diff --git a/Makefile b/Makefile index 50d07148..b38b90e0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone XS -IOS_VERSION = 12.2 +IOS_VERSION = 13.6 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index f28eb25b..7f068985 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -3,7 +3,7 @@ CARTHAGE_PLATFORM := iOS CARTHAGE_CONFIGURATION := Release CARTHAGE_DIR := Carthage CARTHAGE_ARGS := --no-use-binaries -CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.Swift_3_0 +CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.XcodeDefault CARTHAGE_CMDLINE := --configuration $(CARTHAGE_CONFIGURATION) --platform $(CARTHAGE_PLATFORM) --toolchain $(CARTHAGE_TOOLCHAIN) $(CARTHAGE_ARGS) test: $(CARTHAGE) Cartfile From a78d8c4ae303c7d4875a29a01bc307e48095f7d6 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 18:04:29 -0700 Subject: [PATCH 079/391] try 2.6 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b8b2eaba..e9d5ec2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -rvm: 2.7.3 +rvm: 2.6 # https://docs.travis-ci.com/user/reference/osx osx_image: xcode11.6 env: From 5052cbbd6d881396c87a9ede9de2e30a8f86a706 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 18:13:28 -0700 Subject: [PATCH 080/391] use iphone 11 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9d5ec2a..7bff9ce2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rvm: 2.6 osx_image: xcode11.6 env: global: - - IOS_SIMULATOR="iPhone XS" + - IOS_SIMULATOR="iPhone 11" - IOS_VERSION="13.6" matrix: include: From f961db5344375ea667f3f5d44c8df202a5cfc5aa Mon Sep 17 00:00:00 2001 From: Bennett Smith Date: Mon, 17 May 2021 18:23:32 -0700 Subject: [PATCH 081/391] Updates for Xcode 12.5 * Updates ruby version used by Travis-CI. * Updates Xcode image to 12.5 for Travis-CI. * Removes workaround in run-tests.sh for older simulators. * Updates Carthage toolchain. * Updates Package.swift tool version. * Fixes up SPM build for SQLiteObjc module. * Updated Xcode build settings. --- .travis.yml | 10 ++--- CHANGELOG.md | 8 ++++ Makefile | 2 +- Package.swift | 45 ++++++++++++++++--- SQLite.xcodeproj/project.pbxproj | 30 +++++-------- .../xcschemes/SQLite Mac.xcscheme | 24 +++++----- .../xcschemes/SQLite iOS.xcscheme | 24 +++++----- .../xcschemes/SQLite tvOS.xcscheme | 24 +++++----- .../xcschemes/SQLite watchOS.xcscheme | 6 +-- Sources/SQLiteObjc/SQLiteObjc.h | 36 +++++++++++++++ Sources/SQLiteObjc/include/README.md | 7 +++ Tests/Carthage/Makefile | 5 ++- 12 files changed, 141 insertions(+), 80 deletions(-) create mode 100644 Sources/SQLiteObjc/SQLiteObjc.h create mode 100644 Sources/SQLiteObjc/include/README.md diff --git a/.travis.yml b/.travis.yml index c8fc5feb..98987db1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c -rvm: 2.3 +rvm: 2.6 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.2 +osx_image: xcode12.5 env: global: - IOS_SIMULATOR="iPhone XS" - - IOS_VERSION="12.2" + - IOS_VERSION="12.4" matrix: include: - env: BUILD_SCHEME="SQLite iOS" @@ -25,7 +25,7 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: -# Workaround for Xcode 10.2/tvOS 9.1 bug +# Workaround for Xcode 10.2/tvOS 9.1 bug (This is an outdated workaround; commenting out.) # See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 - - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift +# - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 027611ee..1f29afeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +0.12.3 (18-05-2021), [diff][diff-0.12.2] +======================================== + +* Swift 5.3 support. +* Xcode 12.5 support. +* Bumps minimum deployment versions. +* Fixes up Package.swift to build SQLiteObjc module. + 0.11.6 (xxx), [diff][diff-0.11.6] ======================================== diff --git a/Makefile b/Makefile index 50d07148..be426dc1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone XS -IOS_VERSION = 12.2 +IOS_VERSION = 12.4 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/Package.swift b/Package.swift index 430ae6f2..73339b7b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,15 +1,46 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.3 import PackageDescription let package = Package( name: "SQLite.swift", - products: [.library(name: "SQLite", targets: ["SQLite"])], + products: [ + .library( + name: "SQLite", + targets: ["SQLite"] + ) + ], targets: [ - .target(name: "SQLite", dependencies: ["SQLiteObjc"]), - .target(name: "SQLiteObjc"), - .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") - ], - swiftLanguageVersions: [4, 5] + .target( + name: "SQLite", + dependencies: ["SQLiteObjc"], + exclude: [ + "Info.plist" + ] + ), + .target( + name: "SQLiteObjc", + dependencies: [], + exclude: [ + "SQLiteObjc.h", + "fts3_tokenizer.h", + "include/README.md" + ] + ), + .testTarget( + name: "SQLiteTests", + dependencies: [ + "SQLite" + ], + path: "Tests/SQLiteTests", + exclude: [ + "Info.plist" + ], + resources: [ + .copy("fixtures/encrypted-3.x.sqlite"), + .copy("fixtures/encrypted-4.x.sqlite") + ] + ) + ] ) #if os(Linux) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 30a40214..2e8807ed 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -277,7 +277,7 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; + EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/SQLiteObjc.h; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -680,7 +680,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1250; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -1033,7 +1033,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1053,7 +1053,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1065,7 +1065,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1077,7 +1077,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1148,6 +1148,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1173,8 +1174,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1210,6 +1211,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1229,8 +1231,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; @@ -1255,7 +1257,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1276,7 +1277,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1288,7 +1288,6 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1299,7 +1298,6 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1320,7 +1318,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"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1343,7 +1340,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"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1359,7 +1355,6 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1373,7 +1368,6 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index 4e94e80a..a0db21a2 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ + + + + @@ -39,17 +48,6 @@ - - - - - - - - + + + + @@ -39,17 +48,6 @@ - - - - - - - - + + + + @@ -39,17 +48,6 @@ - - - - - - - - - - - - Date: Thu, 24 Jun 2021 01:24:18 +0200 Subject: [PATCH 082/391] - Set iOS deployment target to 9.0 - Bump watchOS deployment version to 3.0 #971 - Fix SQLCipher compilation #1024 --- .travis.yml | 3 - SQLite.swift.podspec | 14 ++--- SQLite.xcodeproj/project.pbxproj | 8 +-- Sources/SQLiteObjc/SQLiteObjc.h | 2 + Sources/SQLiteObjc/include/README.md | 7 --- Sources/SQLiteObjc/include/SQLiteObjc.h | 36 ----------- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 83 +++++++++++++++---------- Tests/CocoaPods/integration_test.rb | 37 +---------- Tests/SQLiteTests/ConnectionTests.swift | 6 +- 10 files changed, 69 insertions(+), 129 deletions(-) delete mode 100644 Sources/SQLiteObjc/include/README.md delete mode 100644 Sources/SQLiteObjc/include/SQLiteObjc.h diff --git a/.travis.yml b/.travis.yml index 98987db1..90ff205c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,4 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: -# Workaround for Xcode 10.2/tvOS 9.1 bug (This is an outdated workaround; commenting out.) -# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 -# - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 712ddc36..bcefa5fe 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -19,10 +19,10 @@ Pod::Spec.new do |s| s.swift_versions = ['4.2', '5'] - ios_deployment_target = '8.0' + ios_deployment_target = '9.0' tvos_deployment_target = '9.1' osx_deployment_target = '10.10' - watchos_deployment_target = '2.2' + watchos_deployment_target = '3.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target @@ -32,7 +32,7 @@ Pod::Spec.new do |s| s.subspec 'standard' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.library = 'sqlite3' ss.test_spec 'tests' do |test_spec| @@ -47,7 +47,7 @@ Pod::Spec.new do |s| s.subspec 'standalone' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', @@ -66,12 +66,12 @@ Pod::Spec.new do |s| s.subspec 'SQLCipher' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' - ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' } - ss.dependency 'SQLCipher', '>= 3.4.0' + ss.dependency 'SQLCipher', '>= 4.0.0' ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2e8807ed..92795299 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1099,7 +1099,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.2; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Debug; }; @@ -1121,7 +1121,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.2; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Release; }; @@ -1174,7 +1174,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1231,7 +1231,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; diff --git a/Sources/SQLiteObjc/SQLiteObjc.h b/Sources/SQLiteObjc/SQLiteObjc.h index e8ba9a7d..610cdf10 100644 --- a/Sources/SQLiteObjc/SQLiteObjc.h +++ b/Sources/SQLiteObjc/SQLiteObjc.h @@ -25,6 +25,8 @@ @import Foundation; #if defined(SQLITE_SWIFT_STANDALONE) @import sqlite3; +#elif defined(SQLITE_SWIFT_SQLCIPHER) +@import SQLCipher; #else @import SQLite3; #endif diff --git a/Sources/SQLiteObjc/include/README.md b/Sources/SQLiteObjc/include/README.md deleted file mode 100644 index 54b375e5..00000000 --- a/Sources/SQLiteObjc/include/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# README - -This folder contains the public interface for the SQLiteObjc module. It is a duplicate -copy of the header file found in the Sources/SQLiteObjc folder. If you change one, change -the other too! - - diff --git a/Sources/SQLiteObjc/include/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h deleted file mode 100644 index e8ba9a7d..00000000 --- a/Sources/SQLiteObjc/include/SQLiteObjc.h +++ /dev/null @@ -1,36 +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; -#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/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 77d90eec..19db2b36 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.1' +gem 'cocoapods', '~> 1.10.1' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index b5172144..0cf77eda 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,77 +1,94 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - activesupport (4.2.11) - i18n (~> 0.7) + CFPropertyList (3.0.3) + activesupport (5.2.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) atomos (0.1.3) - claide (1.0.2) - cocoapods (1.6.0.beta.2) - activesupport (>= 4.0.2, < 5) + claide (1.0.3) + cocoapods (1.10.1) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.0.beta.2) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-core (= 1.10.1) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.3, >= 1.3.1) - xcodeproj (>= 1.7.0, < 2.0) - cocoapods-core (1.6.0.beta.2) - activesupport (>= 4.0.2, < 6) + ruby-macho (~> 1.4) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.1) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.2) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.1) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.9) escape (0.0.4) - fourflusher (2.0.1) + ethon (0.14.0) + ffi (>= 1.15.0) + ffi (1.15.3) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - i18n (0.9.5) + httpclient (2.8.3) + i18n (1.8.10) concurrent-ruby (~> 1.0) - minitest (5.11.3) + json (2.5.1) + minitest (5.14.4) molinillo (0.6.6) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.3.1) + public_suffix (4.0.6) + ruby-macho (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.5) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.9) thread_safe (~> 0.1) - xcodeproj (1.7.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.0beta2) + cocoapods (~> 1.10.1) minitest BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 98a539b5..67b13385 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -13,7 +13,7 @@ def test_validate_project private def validator - @validator ||= CustomValidator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| + @validator ||= Pod::Validator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| validator.config.verbose = true validator.no_clean = true validator.use_frameworks = true @@ -32,39 +32,4 @@ def validator def podspec File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec') end - - - class CustomValidator < Pod::Validator - def test_pod - # https://github.com/CocoaPods/CocoaPods/issues/7009 - super unless consumer.platform_name == :watchos - end - - def xcodebuild(action, scheme, configuration) - require 'fourflusher' - command = %W(#{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) - case consumer.platform_name - when :osx, :macos - command += %w(CODE_SIGN_IDENTITY=) - when :ios - command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) - command += Fourflusher::SimControl.new.destination(nil, 'iOS', deployment_target) - when :watchos - command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator) - command += Fourflusher::SimControl.new.destination(:oldest, 'watchOS', deployment_target) - when :tvos - command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) - command += Fourflusher::SimControl.new.destination(:oldest, 'tvOS', deployment_target) - end - - begin - _xcodebuild(command, true) - rescue => e - message = 'Returned an unsuccessful exit code.' - message += ' You can use `--verbose` for more information.' unless config.verbose? - error('xcodebuild', message) - e.message - end - end - end end diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index eab3cf00..fdfc9637 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -38,12 +38,14 @@ class ConnectionTests : SQLiteTestCase { func test_init_withURI_returnsURIConnection() { let db = try! Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + 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") - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + let url = URL(fileURLWithPath: db.description) + XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } func test_readonly_returnsFalseOnReadWriteConnections() { From 3976805368faf2d5ba9fbf00a397ec8b3a4d81c8 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 17:39:36 +0200 Subject: [PATCH 083/391] Moving to GitHub Actions --- .../template.md} | 0 .github/workflows/build.yml | 60 +++++++++++++++++++ .travis.yml | 31 ---------- 3 files changed, 60 insertions(+), 31 deletions(-) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/template.md} (100%) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/template.md similarity index 100% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/template.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..462233d4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,60 @@ +name: Build and test +on: [push, pull_request] +env: + IOS_SIMULATOR: iPhone XS + IOS_VERSION: 12.4 +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Install + run: | + gem update bundler + gem install xcpretty --no-document + brew update + brew outdated carthage || brew upgrade carthage + - name: "Run tests (BUILD_SCHEME: SQLite iOS)" + env: + BUILD_SCHEME: SQLite iOS + run: ./run-tests.sh + - name: "Run tests (BUILD_SCHEME: SQLite Mac)" + env: + BUILD_SCHEME: SQLite Mac + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: none)" + env: + VALIDATOR_SUBSPEC: none + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: standard)" + env: + VALIDATOR_SUBSPEC: standard + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: standalone)" + env: + VALIDATOR_SUBSPEC: standalone + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: SQLCipher)" + env: + VALIDATOR_SUBSPEC: SQLCipher + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: iOS)" + env: + CARTHAGE_PLATFORM: iOS + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: Mac)" + env: + CARTHAGE_PLATFORM: Mac + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: watchOS)" + env: + CARTHAGE_PLATFORM: watchOS + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: tvOS)" + env: + CARTHAGE_PLATFORM: tvOS + run: ./run-tests.sh + - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" + env: + PACKAGE_MANAGER_COMMAND: test + run: ./run-tests.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 98987db1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: objective-c -rvm: 2.6 -# https://docs.travis-ci.com/user/reference/osx -osx_image: xcode12.5 -env: - global: - - IOS_SIMULATOR="iPhone XS" - - IOS_VERSION="12.4" -matrix: - include: - - env: BUILD_SCHEME="SQLite iOS" - - env: BUILD_SCHEME="SQLite Mac" - - env: VALIDATOR_SUBSPEC="none" - - env: VALIDATOR_SUBSPEC="standard" - - env: VALIDATOR_SUBSPEC="standalone" - - env: VALIDATOR_SUBSPEC="SQLCipher" - - env: CARTHAGE_PLATFORM="iOS" - - env: CARTHAGE_PLATFORM="Mac" - - env: CARTHAGE_PLATFORM="watchOS" - - env: CARTHAGE_PLATFORM="tvOS" - - env: PACKAGE_MANAGER_COMMAND="test" -before_install: - - gem update bundler - - gem install xcpretty --no-document - - brew update - - brew outdated carthage || brew upgrade carthage -script: -# Workaround for Xcode 10.2/tvOS 9.1 bug (This is an outdated workaround; commenting out.) -# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 -# - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - - ./run-tests.sh From 4b91f07fe16d6d4ca42f81d845aa37181913b2d8 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 17:46:22 +0200 Subject: [PATCH 084/391] Updating iOS test device --- .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 462233d4..1c47e3a6 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 XS - IOS_VERSION: 12.4 + IOS_SIMULATOR: iPhone 12 + IOS_VERSION: 14.4 jobs: build: runs-on: macos-latest From de2059b37a4da218367685368fa01093a7817d7a Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 18:03:26 +0200 Subject: [PATCH 085/391] Update Cocoapods version --- Tests/CocoaPods/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 77d90eec..da52ec89 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.1' +gem 'cocoapods', '~> 1.10.2' gem 'minitest' From 1e4a1f8092980b93cef68f64edba92dc2b65c573 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 18:16:27 +0200 Subject: [PATCH 086/391] Updating Cocoapods gemfile lock --- Tests/CocoaPods/Gemfile.lock | 85 ++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index b5172144..e0383a67 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,77 +1,96 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - activesupport (4.2.11) - i18n (~> 0.7) + CFPropertyList (3.0.3) + activesupport (5.2.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) atomos (0.1.3) - claide (1.0.2) - cocoapods (1.6.0.beta.2) - activesupport (>= 4.0.2, < 5) + claide (1.0.3) + cocoapods (1.10.2) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.0.beta.2) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-core (= 1.10.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.3, >= 1.3.1) - xcodeproj (>= 1.7.0, < 2.0) - cocoapods-core (1.6.0.beta.2) - activesupport (>= 4.0.2, < 6) + ruby-macho (~> 1.4) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.2) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.2) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap - cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.1) + cocoapods-search (1.0.1) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.9) escape (0.0.4) - fourflusher (2.0.1) + ethon (0.14.0) + ffi (>= 1.15.0) + ffi (1.15.3) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - i18n (0.9.5) + httpclient (2.8.3) + i18n (1.8.10) concurrent-ruby (~> 1.0) + json (2.5.1) minitest (5.11.3) molinillo (0.6.6) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.3.1) + public_suffix (4.0.6) + rexml (3.2.5) + ruby-macho (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.5) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.9) thread_safe (~> 0.1) - xcodeproj (1.7.0) + xcodeproj (1.21.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.0beta2) + cocoapods (~> 1.10.2) minitest BUNDLED WITH - 1.17.1 + 2.2.22 From 9016160ae16bc0376f00dd0d50132319b996c750 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 18:41:08 +0200 Subject: [PATCH 087/391] Fixed Carthage Makefil for GitHub Actions --- Tests/Carthage/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index f5185854..b44d85b9 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -11,7 +11,7 @@ test: $(CARTHAGE) Cartfile $< bootstrap $(CARTHAGE_CMDLINE) Cartfile: - echo 'git "$(TRAVIS_BUILD_DIR)" "HEAD"' > $@ + echo 'git "$(GITHUB_WORKSPACE)" "HEAD"' > $@ clean: @rm -f Cartfile Cartfile.resolved From 6ccf8a8e7f03d1909bb0452b344151091d664bf5 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 20:01:29 +0200 Subject: [PATCH 088/391] Totally removing Travis from repository --- README.md | 5 ++--- SQLite.xcodeproj/project.pbxproj | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8b0972f6..c3c89ce5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +![Build Status][GitHubActionBadge] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -283,8 +283,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [SQLite3]: http://www.sqlite.org [SQLite.swift]: https://github.com/stephencelis/SQLite.swift -[TravisBadge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat -[TravisLink]: https://travis-ci.org/stephencelis/SQLite.swift +[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 [CocoaPodsVersionLink]: http://cocoadocs.org/docsets/SQLite.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 92795299..1db9c2b7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -271,7 +271,6 @@ 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 = ""; }; EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; - EE247B8C1C3F821200AE3E12 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; }; EE247B8D1C3F821200AE3E12 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; EE247B8F1C3F822500AE3E12 /* Index.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Index.md; sourceTree = ""; }; EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; @@ -473,7 +472,6 @@ EE247B771C3F40D700AE3E12 /* README.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, - EE247B8C1C3F821200AE3E12 /* .travis.yml */, EE247B8D1C3F821200AE3E12 /* Makefile */, EE9180931C46EA210038162A /* libsqlite3.tbd */, EE9180911C46E9D30038162A /* libsqlite3.tbd */, From 65b0989dbefdb1163ac419d7121a176335a90a85 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Wed, 18 Aug 2021 14:56:18 -0400 Subject: [PATCH 089/391] Updated documentation for `===` and `!==` operators. --- Documentation/Index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index c6c54437..76a69206 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -955,8 +955,10 @@ equate or compare different types will prevent compilation. | `~=` | `(Interval, Comparable) -> Bool` | `BETWEEN` | | `&&` | `Bool -> Bool` | `AND` | | `\|\|`| `Bool -> Bool` | `OR` | +| `===` | `Equatable -> Bool` | `IS` | +| `!==` | `Equatable -> Bool` | `IS NOT` | -> *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` +> * When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` > accordingly. From b7302f6ed19b1994ef9d68882d1cb447be8fd238 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 23:21:01 +0200 Subject: [PATCH 090/391] SPM as first installation method --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 009167b0..81fb4b5d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,27 @@ and the [companion repository][SQLiteDataAccessLayer2]. > _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 +Swift code. + +1. Add the following to your `Package.swift` file: + + ```swift + dependencies: [ + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + ] + ``` + +2. Build your project: + + ```sh + $ swift build + ``` + +[Swift Package Manager]: https://swift.org/package-manager + ### Carthage [Carthage][] is a simple, decentralized dependency manager for Cocoa. To @@ -177,27 +198,6 @@ SQLite.swift with CocoaPods: [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started -### Swift Package Manager - -The [Swift Package Manager][] is a tool for managing the distribution of -Swift code. - -1. Add the following to your `Package.swift` file: - - ```swift - dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") - ] - ``` - -2. Build your project: - - ```sh - $ swift build - ``` - -[Swift Package Manager]: https://swift.org/package-manager - ### Manual To install SQLite.swift as an Xcode sub-project: From c5b7258ac175751b966a25bc6ba06f1be4ae2caa Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 19 Aug 2021 00:03:18 +0200 Subject: [PATCH 091/391] Fixing missing date in upsert test --- Tests/SQLiteTests/QueryTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 9a19945f..fc47ab27 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -280,10 +280,10 @@ class QueryTests : XCTestCase { func test_upsert_encodable() throws { let emails = Table("emails") let string = Expression("string") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0) ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", + "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\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", insert ) } From 5c49d0ed90279d5f90a22997c95ca4c6bfb5bb9f Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 19 Aug 2021 00:09:34 +0200 Subject: [PATCH 092/391] Oups --- Tests/SQLiteTests/QueryTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index fc47ab27..9b845a97 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -283,7 +283,7 @@ class QueryTests : XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", + "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\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\"", insert ) } From b20d6baca11636ac28de51aa797312811d50c2b5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 19 Aug 2021 00:11:54 +0200 Subject: [PATCH 093/391] Fix spm package structure --- Package.swift | 78 +++++++++---------- SQLite.xcodeproj/project.pbxproj | 20 ++--- Sources/SQLiteObjc/{ => include}/SQLiteObjc.h | 0 3 files changed, 48 insertions(+), 50 deletions(-) rename Sources/SQLiteObjc/{ => include}/SQLiteObjc.h (100%) diff --git a/Package.swift b/Package.swift index 73339b7b..7522fa45 100644 --- a/Package.swift +++ b/Package.swift @@ -4,52 +4,50 @@ import PackageDescription let package = Package( name: "SQLite.swift", products: [ - .library( - name: "SQLite", - targets: ["SQLite"] - ) - ], + .library( + name: "SQLite", + targets: ["SQLite"] + ) + ], targets: [ .target( - name: "SQLite", - dependencies: ["SQLiteObjc"], - exclude: [ - "Info.plist" - ] - ), + name: "SQLite", + dependencies: ["SQLiteObjc"], + exclude: [ + "Info.plist" + ] + ), .target( - name: "SQLiteObjc", - dependencies: [], - exclude: [ - "SQLiteObjc.h", - "fts3_tokenizer.h", - "include/README.md" - ] - ), + name: "SQLiteObjc", + dependencies: [], + exclude: [ + "fts3_tokenizer.h" + ] + ), .testTarget( - name: "SQLiteTests", - dependencies: [ - "SQLite" - ], - path: "Tests/SQLiteTests", - exclude: [ - "Info.plist" - ], - resources: [ - .copy("fixtures/encrypted-3.x.sqlite"), - .copy("fixtures/encrypted-4.x.sqlite") - ] - ) + name: "SQLiteTests", + dependencies: [ + "SQLite" + ], + path: "Tests/SQLiteTests", + exclude: [ + "Info.plist" + ], + resources: [ + .copy("fixtures/encrypted-3.x.sqlite"), + .copy("fixtures/encrypted-4.x.sqlite") + ] + ) ] ) #if os(Linux) - package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] - package.targets = [ - .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), - .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ - "FTS4Tests.swift", - "FTS5Tests.swift" - ]) - ] +package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] +package.targets = [ + .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), + .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ + "FTS4Tests.swift", + "FTS5Tests.swift" + ]) +] #endif diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 1b5bb0f4..a730284d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; - 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; @@ -101,9 +100,12 @@ 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 */; }; - 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; + 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, ); }; }; 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 */; }; @@ -183,8 +185,6 @@ EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180911C46E9D30038162A /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ @@ -229,6 +229,7 @@ 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 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 = ""; }; 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 = ""; }; @@ -280,7 +281,6 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/SQLiteObjc.h; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -384,6 +384,7 @@ isa = PBXGroup; children = ( EE247AD61C3F04ED00AE3E12 /* SQLite.h */, + 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, @@ -430,7 +431,6 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - EE91808D1C46E5230038162A /* SQLiteObjc.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, @@ -512,7 +512,7 @@ buildActionMask = 2147483647; files = ( 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, - 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */, + 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -521,8 +521,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */, 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, + 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */, 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -531,8 +531,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */, EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, + 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -542,8 +542,8 @@ buildActionMask = 2147483647; files = ( EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, + 3DDC112F26CDBA0200CE369F /* SQLiteObjc.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, - EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLiteObjc/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h similarity index 100% rename from Sources/SQLiteObjc/SQLiteObjc.h rename to Sources/SQLiteObjc/include/SQLiteObjc.h From 4ddcb1cc6fdec3e4dd24d89e35a1f31630d81632 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 19 Aug 2021 00:12:59 +0200 Subject: [PATCH 094/391] Fix query tests --- Tests/SQLiteTests/QueryTests.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 9b845a97..29cbd7e4 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -397,7 +397,7 @@ class QueryIntegrationTests : SQLiteTestCase { let id = Expression("id") let email = Expression("email") let age = Expression("age") - + override func setUp() { super.setUp() @@ -509,16 +509,16 @@ class QueryIntegrationTests : SQLiteTestCase { let fetchAge = { () throws -> Int? in return try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } } - + let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) XCTAssertEqual(1, id) XCTAssertEqual(30, try fetchAge()) - + let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) XCTAssertEqual(1, nextId) XCTAssertEqual(42, try fetchAge()) } - + func test_update() { let changes = try! db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) @@ -528,24 +528,24 @@ class QueryIntegrationTests : SQLiteTestCase { let changes = try! db.run(users.delete()) XCTAssertEqual(0, changes) } - + func test_union() throws { let expectedIDs = [ try db.run(users.insert(email <- "alice@example.com")), try db.run(users.insert(email <- "sally@example.com")) ] - + let query1 = users.filter(email == "alice@example.com") let query2 = users.filter(email == "sally@example.com") - + let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } XCTAssertEqual(expectedIDs, actualIDs) - + let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") - + print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) - + let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) } From 75a177a688ada617cef0efbc910f2fc3bdfbd1ff Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 21 Aug 2021 18:03:32 +0200 Subject: [PATCH 095/391] Fixed codable insert many --- Tests/SQLiteTests/QueryTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 9e6e9f7e..79e6871e 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -310,12 +310,12 @@ 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, optional: nil, sub: nil) - let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, optional: nil, sub: nil) - let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, optional: nil, sub: nil) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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) + let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0), (2, '3', 1, 3.0, 5.0), (3, '4', 1, 3.0, 6.0)", + "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 ) } From 449edebfd6c91c2dd376ee61359b4c4b65032032 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 21 Aug 2021 19:24:45 +0200 Subject: [PATCH 096/391] Updating to 0.13.0 --- .swift-version | 2 +- SQLite.swift.podspec | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.swift-version b/.swift-version index bf77d549..819e07a2 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.2 +5.0 diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index bcefa5fe..499012c1 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.12.2" + s.version = "0.13.0" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC @@ -16,12 +16,12 @@ Pod::Spec.new do |s| s.module_name = 'SQLite' s.default_subspec = 'standard' - s.swift_versions = ['4.2', '5'] + s.swift_versions = ['5'] ios_deployment_target = '9.0' tvos_deployment_target = '9.1' - osx_deployment_target = '10.10' + osx_deployment_target = '10.15' watchos_deployment_target = '3.0' s.ios.deployment_target = ios_deployment_target From 8096e9b9c5cc694f373d2176f96889d3e4afe053 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 21 Aug 2021 20:22:10 +0200 Subject: [PATCH 097/391] Deleted .swift-version --- .swift-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .swift-version diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 819e07a2..00000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -5.0 From 31f667f71f5e6b401a4ec2391d8e44d835e5d7fa Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 03:13:48 +0200 Subject: [PATCH 098/391] Pod lint fixes --- Sources/SQLite/Core/Connection.swift | 82 +++++++++++++--------------- Tests/SQLiteTests/QueryTests.swift | 15 ++++- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1bbf7f73..f4a9e8cb 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -415,17 +415,17 @@ public final class Connection { /// /// db.trace { SQL in print(SQL) } public func trace(_ callback: ((String) -> Void)?) { - #if SQLITE_SWIFT_SQLCIPHER || os(Linux) + if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { + trace_v2(callback) + } else { trace_v1(callback) - #else - if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { - trace_v2(callback) - } else { - trace_v1(callback) - } - #endif + } } + @available(OSX, deprecated: 10.2) + @available(iOS, deprecated: 10.0) + @available(watchOS, deprecated: 3.0) + @available(tvOS, deprecated: 10.0) fileprivate func trace_v1(_ callback: ((String) -> Void)?) { guard let callback = callback else { sqlite3_trace(handle, nil /* xCallback */, nil /* pCtx */) @@ -447,8 +447,36 @@ public final class Connection { trace = box } + @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 { + // 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 + return + } - + let box: Trace = { (pointer: UnsafeRawPointer) in + callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) + } + sqlite3_trace_v2(handle, UInt32(SQLITE_TRACE_STMT) /* mask */, + { + // A trace callback is invoked with four arguments: callback(T,C,P,X). + // The T argument is one of the SQLITE_TRACE constants to indicate why the + // 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. + (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in + if let P = P, + let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { + unsafeBitCast(C, to: Trace.self)(expandedSQL) + sqlite3_free(expandedSQL) + } + return Int32(0) // currently ignored + }, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */ + ) + trace = box + } fileprivate typealias Trace = @convention(block) (UnsafeRawPointer) -> Void fileprivate var trace: Trace? @@ -712,39 +740,3 @@ extension Result : CustomStringConvertible { } } } - -#if !SQLITE_SWIFT_SQLCIPHER && !os(Linux) -@available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) -extension Connection { - fileprivate func trace_v2(_ callback: ((String) -> Void)?) { - guard let callback = 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 - return - } - - let box: Trace = { (pointer: UnsafeRawPointer) in - callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) - } - sqlite3_trace_v2(handle, - UInt32(SQLITE_TRACE_STMT) /* mask */, - { - // A trace callback is invoked with four arguments: callback(T,C,P,X). - // The T argument is one of the SQLITE_TRACE constants to indicate why the - // 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. - (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in - if let P = P, - let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { - unsafeBitCast(C, to: Trace.self)(expandedSQL) - sqlite3_free(expandedSQL) - } - return Int32(0) // currently ignored - }, - unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */ - ) - trace = box - } -} -#endif diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 79e6871e..041b7d0c 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -541,10 +541,11 @@ class QueryIntegrationTests : SQLiteTestCase { let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) XCTAssertEqual(2, id) } - + func test_upsert() throws { + guard db.satisfiesMinimumVersion(minor: 24) else { return } let fetchAge = { () throws -> Int? in - return try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } + try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } } let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) @@ -613,3 +614,13 @@ class QueryIntegrationTests : SQLiteTestCase { } } } + +private extension Connection { + func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { + guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } + let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } + guard components.count == 3 else { return false } + + return components[1] >= minor && components[2] >= patch + } +} From af4801483adf9fa26fa4e80df120b1913b380652 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 22 Aug 2021 17:11:01 +0200 Subject: [PATCH 099/391] Adding a `vacuum()` fonction to `Connection` --- Sources/SQLite/Core/Connection.swift | 11 +++++++++++ Tests/SQLiteTests/ConnectionTests.swift | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f4a9e8cb..8fd07cc1 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -252,6 +252,17 @@ public final class Connection { @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } + + // MARK: - VACUUM + + /// Run a vacuum on the database + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + @discardableResult public func vacuum() throws -> Statement { + return try run("VACUUM") + } // MARK: - Scalar diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index fdfc9637..a05cceb5 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -111,6 +111,10 @@ class ConnectionTests : SQLiteTestCase { 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_scalar_preparesRunsAndReturnsScalarValues() { XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) From e48d1469f7fda8175de4abdc45638a25e3409f6b Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 22 Aug 2021 17:28:37 +0200 Subject: [PATCH 100/391] SPM as first installation method --- Documentation/Index.md | 51 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bc8bb52e..14b29b37 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1,9 +1,9 @@ # SQLite.swift Documentation - [Installation](#installation) + - [Swift Package Manager](#swift-package-manager) - [Carthage](#carthage) - [CocoaPods](#cocoapods) - - [Swift Package Manager](#swift-package-manager) - [Manual](#manual) - [Getting Started](#getting-started) - [Connecting to a Database](#connecting-to-a-database) @@ -71,6 +71,30 @@ > _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 +Swift code. It’s integrated with the Swift build system to automate the +process of downloading, compiling, and linking dependencies. + +It is the recommended approach for using SQLite.swift in OSX CLI +applications. + + 1. Add the following to your `Package.swift` file: + + ```swift + dependencies: [ + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + ] + ``` + + 2. Build your project: + + ```sh + $ swift build + ``` + +[Swift Package Manager]: https://swift.org/package-manager ### Carthage @@ -169,31 +193,6 @@ try db.rekey("another secret") [sqlite3pod]: https://github.com/clemensg/sqlite3pod [SQLCipher]: https://www.zetetic.net/sqlcipher/ -### Swift Package Manager - -The [Swift Package Manager][] is a tool for managing the distribution of -Swift code. It’s integrated with the Swift build system to automate the -process of downloading, compiling, and linking dependencies. - -It is the recommended approach for using SQLite.swift in OSX CLI -applications. - - 1. Add the following to your `Package.swift` file: - - ```swift - dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") - ] - ``` - - 2. Build your project: - - ```sh - $ swift build - ``` - -[Swift Package Manager]: https://swift.org/package-manager - ### Manual To install SQLite.swift as an Xcode sub-project: From c648408e467e84d9d2e191fe354080fde23b980c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 21:14:27 +0200 Subject: [PATCH 101/391] Ensure file can be cleaned up --- Tests/SQLiteTests/CipherTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index abecffba..a27ac17d 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -88,6 +88,11 @@ class CipherTests: XCTestCase { 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) + } + let conn = try! Connection(encryptedFile) try! conn.key("sqlcipher-test") XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64) From 368c873e75cc3de9e39464ff82a09fc65649607e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 21:42:01 +0200 Subject: [PATCH 102/391] Update CHANGELOG --- CHANGELOG.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f29afeb..465438bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,22 @@ -0.12.3 (18-05-2021), [diff][diff-0.12.2] +0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== -* Swift 5.3 support. -* Xcode 12.5 support. -* Bumps minimum deployment versions. -* Fixes up Package.swift to build SQLiteObjc module. +* Swift 5.3 support +* Xcode 12.5 support +* Bumps minimum deployment versions +* Fixes up Package.swift to build SQLiteObjc module -0.11.6 (xxx), [diff][diff-0.11.6] +0.12.1, 0.12.2 (21-06-2019) [diff][diff-0.12.2] +======================================== + +* CocoaPods modular headers support + +0.12.0 (24-04-2019) [diff][diff-0.12.0] +======================================== + +* Version with Swift 5 Support + +0.11.6 (19-04-2019), [diff][diff-0.11.6] ======================================== * Swift 4.2, SQLCipher 4.x ([#866][]) @@ -71,6 +81,9 @@ [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 [diff-0.11.5]: https://github.com/stephencelis/SQLite.swift/compare/0.11.4...0.11.5 [diff-0.11.6]: https://github.com/stephencelis/SQLite.swift/compare/0.11.5...0.11.6 +[diff-0.12.0]: https://github.com/stephencelis/SQLite.swift/compare/0.11.6...0.12.0 +[diff-0.12.2]: https://github.com/stephencelis/SQLite.swift/compare/0.12.0...0.12.2 +[diff-0.13.0]: https://github.com/stephencelis/SQLite.swift/compare/0.12.2...0.13.0 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 From d27a078f24164c58827f3adba612415b10016190 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 21:53:16 +0200 Subject: [PATCH 103/391] Update version --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 81fb4b5d..98a625c4 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") ] ``` @@ -157,7 +157,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12.0 + github "stephencelis/SQLite.swift" ~> 0.13.0 ``` 3. Run `carthage update` and @@ -189,7 +189,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12.0' + pod 'SQLite.swift', '~> 0.13.0' end ``` From 9af51e2edf491c0ea632e369a6566e09b65aa333 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 22:09:47 +0200 Subject: [PATCH 104/391] Bump version --- Documentation/Index.md | 12 ++++++------ SQLite.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 14b29b37..21b1c995 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -84,7 +84,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") ] ``` @@ -105,7 +105,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12.0 + github "stephencelis/SQLite.swift" ~> 0.13.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -135,7 +135,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12.0' + pod 'SQLite.swift', '~> 0.13.0' end ``` @@ -149,7 +149,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12.0' + pod 'SQLite.swift/standalone', '~> 0.13.0' end ``` @@ -159,7 +159,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12.0' + pod 'SQLite.swift/standalone', '~> 0.13.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -173,7 +173,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.12.0' + pod 'SQLite.swift/SQLCipher', '~> 0.13.0' end ``` diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a730284d..3056bd74 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1265,7 +1265,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.12.3; + MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1287,7 +1287,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.12.3; + MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; From 474f966e1159fb7b14e9ca20e891f980a4f9e84c Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Mon, 23 Aug 2021 11:05:13 +0200 Subject: [PATCH 105/391] Start GitHub Actions workflow From 1e5ab952f37e3fb8fb6759dc14c151c618395c23 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 01:18:03 +0200 Subject: [PATCH 106/391] Add linting --- .github/workflows/build.yml | 3 + .swiftlint.yml | 32 ++ Makefile | 3 + Sources/SQLite/Core/Blob.swift | 4 +- Sources/SQLite/Core/Connection.swift | 42 +- Sources/SQLite/Core/Statement.swift | 17 +- Sources/SQLite/Core/Value.swift | 18 +- Sources/SQLite/Extensions/Cipher.swift | 4 +- Sources/SQLite/Extensions/FTS4.swift | 19 +- Sources/SQLite/Extensions/FTS5.swift | 4 +- Sources/SQLite/Extensions/RTree.swift | 6 +- Sources/SQLite/Foundation.swift | 6 +- Sources/SQLite/Helpers.swift | 9 +- Sources/SQLite/Typed/AggregateFunctions.swift | 14 +- Sources/SQLite/Typed/Coding.swift | 119 ++-- Sources/SQLite/Typed/Collation.swift | 6 +- Sources/SQLite/Typed/CoreFunctions.swift | 28 +- Sources/SQLite/Typed/CustomFunctions.swift | 58 +- Sources/SQLite/Typed/Expression.swift | 12 +- Sources/SQLite/Typed/Operators.swift | 328 +++++------ Sources/SQLite/Typed/Query.swift | 84 ++- Sources/SQLite/Typed/Schema.swift | 172 ++++-- Sources/SQLite/Typed/Setter.swift | 130 ++--- Tests/.swiftlint.yml | 20 + .../SQLiteTests/AggregateFunctionsTests.swift | 78 +-- Tests/SQLiteTests/BlobTests.swift | 2 +- Tests/SQLiteTests/CipherTests.swift | 6 +- Tests/SQLiteTests/ConnectionTests.swift | 113 ++-- Tests/SQLiteTests/CoreFunctionsTests.swift | 146 ++--- Tests/SQLiteTests/CustomFunctionsTests.swift | 16 +- .../DateAndTimeFunctionTests.swift | 42 +- Tests/SQLiteTests/ExpressionTests.swift | 3 +- Tests/SQLiteTests/FTS4Tests.swift | 51 +- Tests/SQLiteTests/FTS5Tests.swift | 5 +- Tests/SQLiteTests/FoundationTests.swift | 2 +- Tests/SQLiteTests/OperatorsTests.swift | 530 +++++++++--------- Tests/SQLiteTests/QueryTests.swift | 263 +++++---- Tests/SQLiteTests/RTreeTests.swift | 4 +- Tests/SQLiteTests/RowTests.swift | 2 +- Tests/SQLiteTests/SchemaTests.swift | 98 +++- Tests/SQLiteTests/SelectTests.swift | 21 +- Tests/SQLiteTests/SetterTests.swift | 180 +++--- Tests/SQLiteTests/StatementTests.swift | 6 +- Tests/SQLiteTests/TestHelpers.swift | 29 +- Tests/SQLiteTests/ValueTests.swift | 2 +- 45 files changed, 1509 insertions(+), 1228 deletions(-) create mode 100644 .swiftlint.yml create mode 100644 Tests/.swiftlint.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c47e3a6..0b9d0cf1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,9 @@ jobs: gem install xcpretty --no-document brew update brew outdated carthage || brew upgrade carthage + brew install swiftlint + - name: "Lint" + run: make lint - name: "Run tests (BUILD_SCHEME: SQLite iOS)" env: BUILD_SCHEME: SQLite iOS diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..e18c09a1 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,32 @@ +disabled_rules: # rule identifiers to exclude from running + - todo + - operator_whitespace + - large_tuple + - closure_parameter_position +included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`. + - Sources + - Tests +excluded: # paths to ignore during linting. overridden by `included`. + +identifier_name: + excluded: + - db + - to + - by + - or + - eq + - gt + - lt + - fn + - a + - b + +line_length: + warning: 150 + error: 150 + ignores_comments: true + + +file_length: + warning: 700 + error: 700 diff --git a/Makefile b/Makefile index f3f355ab..5e4ff388 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,9 @@ default: test build: $(BUILD_TOOL) $(BUILD_ARGUMENTS) +lint: + swiftlint + test: ifdef XCPRETTY @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) | $(XCPRETTY) -c diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index 2f5d2a14..a49ef23b 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -43,7 +43,7 @@ public struct Blob { } -extension Blob : CustomStringConvertible { +extension Blob: CustomStringConvertible { public var description: String { return "x'\(toHex())'" @@ -51,7 +51,7 @@ extension Blob : CustomStringConvertible { } -extension Blob : Equatable { +extension Blob: Equatable { } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 9273b36a..a4dad4a6 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -70,7 +70,7 @@ public final class Connection { /// A DELETE operation. case delete - fileprivate init(rawValue:Int32) { + fileprivate init(rawValue: Int32) { switch rawValue { case SQLITE_INSERT: self = .insert @@ -86,7 +86,7 @@ public final class Connection { public var handle: OpaquePointer { return _handle! } - fileprivate var _handle: OpaquePointer? = nil + fileprivate var _handle: OpaquePointer? /// Initializes a new SQLite connection. /// @@ -252,9 +252,9 @@ public final class Connection { @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } - + // MARK: - VACUUM - + /// Run a vacuum on the database /// /// - Throws: `Result.Error` if query execution fails. @@ -311,7 +311,7 @@ public final class Connection { // MARK: - Transactions /// The mode in which a transaction acquires a lock. - public enum TransactionMode : String { + public enum TransactionMode: String { /// Defers locking the database till the first read/write executes. case deferred = "DEFERRED" @@ -446,11 +446,9 @@ public final class Connection { let box: Trace = { (pointer: UnsafeRawPointer) in callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) } - sqlite3_trace(handle, - { - (C: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in - if let C = C, let SQL = SQL { - unsafeBitCast(C, to: Trace.self)(SQL) + sqlite3_trace(handle, { (context: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in + if let context = context, let SQL = SQL { + unsafeBitCast(context, to: Trace.self)(SQL) } }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self) @@ -470,13 +468,12 @@ public final class Connection { let box: Trace = { (pointer: UnsafeRawPointer) in callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) } - sqlite3_trace_v2(handle, UInt32(SQLITE_TRACE_STMT) /* mask */, - { + sqlite3_trace_v2(handle, UInt32(SQLITE_TRACE_STMT) /* mask */, { // A trace callback is invoked with four arguments: callback(T,C,P,X). // The T argument is one of the SQLITE_TRACE constants to indicate why the // 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. - (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in + (_: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in if let P = P, let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { unsafeBitCast(C, to: Trace.self)(expandedSQL) @@ -588,7 +585,9 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { + // swiftlint:disable:next cyclomatic_complexity + public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, + _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 let box: Function = { context, argc, argv in let arguments: [Binding?] = (0.. Statement { reset(clearBindings: false) @@ -202,7 +201,7 @@ extension Statement : Sequence { } -public protocol FailableIterator : IteratorProtocol { +public protocol FailableIterator: IteratorProtocol { func failableNext() throws -> Self.Element? } @@ -221,14 +220,14 @@ extension Array { } } -extension Statement : FailableIterator { +extension Statement: FailableIterator { public typealias Element = [Binding?] public func failableNext() throws -> [Binding?]? { return try step() ? Array(row) : nil } } -extension Statement : CustomStringConvertible { +extension Statement: CustomStringConvertible { public var description: String { return String(cString: sqlite3_sql(handle)) @@ -283,7 +282,7 @@ public struct Cursor { } /// Cursors provide direct access to a statement’s current row. -extension Cursor : Sequence { +extension Cursor: Sequence { public subscript(idx: Int) -> Binding? { switch sqlite3_column_type(handle, Int32(idx)) { @@ -306,7 +305,7 @@ extension Cursor : Sequence { var idx = 0 return AnyIterator { if idx >= self.columnCount { - return Optional.none + return Binding??.none } else { idx += 1 return self[idx - 1] diff --git a/Sources/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift index 608f0ce6..b8686eab 100644 --- a/Sources/SQLite/Core/Value.swift +++ b/Sources/SQLite/Core/Value.swift @@ -29,13 +29,13 @@ /// protocol, instead. public protocol Binding {} -public protocol Number : Binding {} +public protocol Number: Binding {} -public protocol Value : Expressible { // extensions cannot have inheritance clauses +public protocol Value: Expressible { // extensions cannot have inheritance clauses associatedtype ValueType = Self - associatedtype Datatype : Binding + associatedtype Datatype: Binding static var declaredDatatype: String { get } @@ -45,7 +45,7 @@ public protocol Value : Expressible { // extensions cannot have inheritance clau } -extension Double : Number, Value { +extension Double: Number, Value { public static let declaredDatatype = "REAL" @@ -59,7 +59,7 @@ extension Double : Number, Value { } -extension Int64 : Number, Value { +extension Int64: Number, Value { public static let declaredDatatype = "INTEGER" @@ -73,7 +73,7 @@ extension Int64 : Number, Value { } -extension String : Binding, Value { +extension String: Binding, Value { public static let declaredDatatype = "TEXT" @@ -87,7 +87,7 @@ extension String : Binding, Value { } -extension Blob : Binding, Value { +extension Blob: Binding, Value { public static let declaredDatatype = "BLOB" @@ -103,7 +103,7 @@ extension Blob : Binding, Value { // MARK: - -extension Bool : Binding, Value { +extension Bool: Binding, Value { public static var declaredDatatype = Int64.declaredDatatype @@ -117,7 +117,7 @@ extension Bool : Binding, Value { } -extension Int : Number, Value { +extension Int: Number, Value { public static var declaredDatatype = Int64.declaredDatatype diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 44919aab..9fce42f7 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -1,7 +1,6 @@ #if SQLITE_SWIFT_SQLCIPHER import SQLCipher - /// Extension methods for [SQLCipher](https://www.zetetic.net/sqlcipher/). /// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/) extension Connection { @@ -32,7 +31,6 @@ extension Connection { try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } - /// Change the key on an open database. If the current database is not encrypted, this routine /// will encrypt it. /// To change the key on an existing encrypted database, it must first be unlocked with the @@ -60,7 +58,7 @@ extension Connection { // the key provided is incorrect. To test that the database can be successfully opened with the // provided key, it is necessary to perform some operation on the database (i.e. read from it). private func cipher_key_check() throws { - let _ = try scalar("SELECT count(*) FROM sqlite_master;") + _ = try scalar("SELECT count(*) FROM sqlite_master;") } } #endif diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index f0184565..a3c253f2 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -97,7 +97,8 @@ public struct Tokenizer { public static let Porter = Tokenizer("porter") - public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set = [], separators: Set = []) -> Tokenizer { + public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set = [], + separators: Set = []) -> Tokenizer { var arguments = [String]() if let removeDiacritics = removeDiacritics { @@ -134,7 +135,7 @@ public struct Tokenizer { } -extension Tokenizer : CustomStringConvertible { +extension Tokenizer: CustomStringConvertible { public var description: String { return ([name] + arguments).joined(separator: " ") @@ -145,13 +146,13 @@ 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 + 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 + let view: String.UTF8View = string.utf8 if let from = range.lowerBound.samePosition(in: view), let to = range.upperBound.samePosition(in: view) { @@ -231,7 +232,7 @@ open class FTSConfig { if let tokenizer = tokenizer { options.append("tokenize", value: Expression(literal: tokenizer.description)) } - options.appendCommaSeparated("prefix", values:prefixes.sorted().map { String($0) }) + options.appendCommaSeparated("prefix", values: prefixes.sorted().map { String($0) }) if isContentless { options.append("content", value: "") } else if let externalContentSchema = externalContentSchema { @@ -274,9 +275,9 @@ open class FTSConfig { } /// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension. -open class FTS4Config : FTSConfig { +open class FTS4Config: FTSConfig { /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) - public enum MatchInfo : CustomStringConvertible { + public enum MatchInfo: CustomStringConvertible { case fts3 public var description: String { return "fts3" @@ -284,7 +285,7 @@ open class FTS4Config : FTSConfig { } /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) - public enum Order : CustomStringConvertible { + public enum Order: CustomStringConvertible { /// 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. diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index cf13f3d8..07f49ffc 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -32,8 +32,8 @@ 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 { +open class FTS5Config: FTSConfig { + public enum Detail: CustomStringConvertible { /// store rowid, column number, term offset case full /// store rowid, column number diff --git a/Sources/SQLite/Extensions/RTree.swift b/Sources/SQLite/Extensions/RTree.swift index 4fc1a235..5ecdf78b 100644 --- a/Sources/SQLite/Extensions/RTree.swift +++ b/Sources/SQLite/Extensions/RTree.swift @@ -23,8 +23,9 @@ // extension Module { - - public static func RTree(_ primaryKey: Expression, _ pairs: (Expression, Expression)...) -> Module where T.Datatype == Int64, U.Datatype == Double { + public static func RTree(_ primaryKey: Expression, + _ pairs: (Expression, Expression)...) + -> Module where T.Datatype == Int64, U.Datatype == Double { var arguments: [Expressible] = [primaryKey] for pair in pairs { @@ -33,5 +34,4 @@ extension Module { return Module(name: "rtree", arguments: arguments) } - } diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 9986f581..d837d8ba 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -24,7 +24,7 @@ import Foundation -extension Data : Value { +extension Data: Value { public static var declaredDatatype: String { return Blob.declaredDatatype @@ -42,7 +42,7 @@ extension Data : Value { } -extension Date : Value { +extension Date: Value { public static var declaredDatatype: String { return String.declaredDatatype @@ -69,7 +69,7 @@ public var dateFormatter: DateFormatter = { return formatter }() -extension UUID : Value { +extension UUID: Value { public static var declaredDatatype: String { return String.declaredDatatype diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 44aaec19..0e3385e3 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -44,7 +44,7 @@ public protocol _OptionalType { } -extension Optional : _OptionalType { +extension Optional: _OptionalType { public typealias WrappedType = Wrapped @@ -75,7 +75,7 @@ extension String { func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { return infix([lhs, rhs], wrap: wrap) } - + func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { let expression = Expression(" \(self) ".join(terms).expression) guard wrap else { @@ -115,10 +115,11 @@ func transcode(_ literal: Binding?) -> String { } } +//swiftlint:disable force_cast func value(_ v: Binding) -> A { - return A.fromDatatypeValue(v as! A.Datatype) as! A + A.fromDatatypeValue(v as! A.Datatype) as! A } func value(_ v: Binding?) -> A { - return value(v!) + value(v!) } diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 2ec28288..325b0277 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -29,13 +29,13 @@ private enum Function: String { case avg case sum case total - + func wrap(_ expression: Expressible) -> Expression { return self.rawValue.wrap(expression) } } -extension ExpressionType where UnderlyingType : Value { +extension ExpressionType where UnderlyingType: Value { /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. /// @@ -66,7 +66,7 @@ extension ExpressionType where UnderlyingType : Value { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value { /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. /// @@ -97,7 +97,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr } -extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Comparable { +extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: Comparable { /// Builds a copy of the expression wrapped with the `max` aggregate /// function. @@ -127,7 +127,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Comparable { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value, UnderlyingType.WrappedType.Datatype: Comparable { /// Builds a copy of the expression wrapped with the `max` aggregate /// function. @@ -157,7 +157,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr } -extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Number { +extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: Number { /// Builds a copy of the expression wrapped with the `avg` aggregate /// function. @@ -200,7 +200,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Number { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value, UnderlyingType.WrappedType.Datatype: Number { /// Builds a copy of the expression wrapped with the `avg` aggregate /// function. diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 7044bc37..5a672883 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -39,12 +39,12 @@ extension QueryType { /// - otherSetters: Any other setters to include in the insert /// /// - Returns: An `INSERT` statement for the encodable object - public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], otherSetters: [Setter] = []) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } - + /// Creates an `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 @@ -63,7 +63,8 @@ extension QueryType { /// - otherSetters: Any other setters to include in the insert /// /// - Returns: An `INSERT` statement fort the encodable object - public func insert(or onConflict: OnConflict, encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + public func insert(or onConflict: OnConflict, encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], + otherSetters: [Setter] = []) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.insert(or: onConflict, encoder.setters + otherSetters) @@ -83,7 +84,8 @@ extension QueryType { /// - otherSetters: Any other setters to include in the inserts, per row/object. /// /// - Returns: An `INSERT` statement for the encodable objects - public func insertMany(_ encodables: [Encodable], userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + 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) try encodable.encode(to: encoder) @@ -108,7 +110,8 @@ extension QueryType { /// - onConflictOf: The column that if conflicts should trigger an update instead of insert. /// /// - Returns: An `INSERT` statement fort the encodable object - public func upsert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = [], onConflictOf conflicting: Expressible) throws -> Insert { + public func upsert(_ encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], + otherSetters: [Setter] = [], onConflictOf conflicting: Expressible) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.upsert(encoder.setters + otherSetters, onConflictOf: conflicting) @@ -128,7 +131,8 @@ extension QueryType { /// - otherSetters: Any other setters to include in the insert /// /// - Returns: An `UPDATE` statement fort the encodable object - public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { + public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], + otherSetters: [Setter] = []) throws -> Update { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.update(encoder.setters + otherSetters) @@ -154,8 +158,9 @@ extension Row { } /// Generates a list of settings for an Encodable object -fileprivate class SQLiteEncoder: Encoder { +private class SQLiteEncoder: Encoder { class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { + // swiftlint:disable nesting typealias Key = MyKey let encoder: SQLiteEncoder @@ -197,14 +202,12 @@ fileprivate class SQLiteEncoder: Encoder { self.encoder.setters.append(Expression(key.stringValue) <- value) } - func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { + func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { if let data = value as? Data { self.encoder.setters.append(Expression(key.stringValue) <- data) - } - else if let date = value as? Date { + } else if let date = value as? Date { self.encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - } - else { + } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) self.encoder.setters.append(Expression(key.stringValue) <- string) @@ -212,15 +215,18 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int8 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an Int8 is not supported")) } func encode(_ value: Int16, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int16 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an Int16 is not supported")) } func encode(_ value: Int32, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int32 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an Int32 is not supported")) } func encode(_ value: Int64, forKey key: Key) throws { @@ -228,26 +234,32 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt is not supported")) } func encode(_ value: UInt8, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt8 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt8 is not supported")) } func encode(_ value: UInt16, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt16 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt16 is not supported")) } func encode(_ value: UInt32, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt32 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt32 is not supported")) } func encode(_ value: UInt64, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt64 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt64 is not supported")) } - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) + -> KeyedEncodingContainer where NestedKey: CodingKey { fatalError("encoding a nested container is not supported") } @@ -272,13 +284,13 @@ fileprivate class SQLiteEncoder: Encoder { fatalError("not supported") } - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } } -fileprivate class SQLiteDecoder : Decoder { - class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { +private class SQLiteDecoder: Decoder { + class SQLiteKeyedDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = MyKey let codingPath: [CodingKey] = [] @@ -309,15 +321,18 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int8 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an Int8 is not supported")) } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int16 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an Int16 is not supported")) } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int32 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an Int32 is not supported")) } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { @@ -325,24 +340,29 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt is not supported")) } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt8 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt8 is not supported")) } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt16 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt16 is not supported")) } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt32 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt32 is not supported")) } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt64 is not supported")) } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { @@ -358,37 +378,45 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { + // swiftlint:disable force_cast if type == Data.self { let data = try self.row.get(Expression(key.stringValue)) return data as! T - } - else if type == Date.self { + } else if type == Date.self { let date = try self.row.get(Expression(key.stringValue)) return date as! T } + // swiftlint:enable force_cast guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "an unsupported type was found")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "an unsupported type was found")) } guard let data = JSONString.data(using: .utf8) else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "invalid utf8 data found")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "invalid utf8 data found")) } return try JSONDecoder().decode(type, from: data) } - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding nested containers is not supported")) + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws + -> KeyedDecodingContainer where NestedKey: CodingKey { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding nested containers is not supported")) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding unkeyed containers is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding unkeyed containers is not supported")) } func superDecoder() throws -> Swift.Decoder { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super encoders containers is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding super encoders containers is not supported")) } func superDecoder(forKey key: Key) throws -> Swift.Decoder { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super decoders is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding super decoders is not supported")) } } @@ -401,16 +429,17 @@ fileprivate class SQLiteDecoder : Decoder { self.userInfo = userInfo } - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an unkeyed container is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an unkeyed container is not supported")) } func singleValueContainer() throws -> SingleValueDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding a single value container is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding a single value container is not supported")) } } - diff --git a/Sources/SQLite/Typed/Collation.swift b/Sources/SQLite/Typed/Collation.swift index e2ff9d10..3b268ce0 100644 --- a/Sources/SQLite/Typed/Collation.swift +++ b/Sources/SQLite/Typed/Collation.swift @@ -43,7 +43,7 @@ public enum Collation { } -extension Collation : Expressible { +extension Collation: Expressible { public var expression: Expression { return Expression(literal: description) @@ -51,9 +51,9 @@ extension Collation : Expressible { } -extension Collation : CustomStringConvertible { +extension Collation: CustomStringConvertible { - public var description : String { + public var description: String { switch self { case .binary: return "BINARY" diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 068dcf02..053f17e5 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -45,21 +45,21 @@ private enum Function: String { case regexp = "REGEXP" case collate = "COLLATE" case ifnull - + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { return self.rawValue.infix(lhs, rhs, wrap: wrap) } - + func wrap(_ expression: Expressible) -> Expression { return self.rawValue.wrap(expression) } - + func wrap(_ expressions: [Expressible]) -> Expression { return self.rawValue.wrap(", ".join(expressions)) } } -extension ExpressionType where UnderlyingType : Number { +extension ExpressionType where UnderlyingType: Number { /// Builds a copy of the expression wrapped with the `abs` function. /// @@ -68,13 +68,13 @@ extension ExpressionType where UnderlyingType : Number { /// // abs("x") /// /// - Returns: A copy of the expression wrapped with the `abs` function. - public var absoluteValue : Expression { + public var absoluteValue: Expression { return Function.abs.wrap(self) } } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Number { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Number { /// Builds a copy of the expression wrapped with the `abs` function. /// @@ -83,7 +83,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// // abs("x") /// /// - Returns: A copy of the expression wrapped with the `abs` function. - public var absoluteValue : Expression { + public var absoluteValue: Expression { return Function.abs.wrap(self) } @@ -129,7 +129,7 @@ extension ExpressionType where UnderlyingType == Double? { } -extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype == Int64 { +extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype == Int64 { /// Builds an expression representing the `random` function. /// @@ -481,7 +481,7 @@ extension ExpressionType where UnderlyingType == String? { } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } - + /// Builds a copy of the expression appended with a `LIKE` query against the /// given pattern. /// @@ -671,7 +671,7 @@ extension ExpressionType where UnderlyingType == String? { } -extension Collection where Iterator.Element : Value { +extension Collection where Iterator.Element: Value { /// Builds a copy of the expression prepended with an `IN` check against the /// collection. @@ -708,7 +708,7 @@ extension Collection where Iterator.Element : Value { } extension String { - + /// Builds a copy of the expression appended with a `LIKE` query against the /// given pattern. /// @@ -751,7 +751,7 @@ extension String { /// /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. -public func ??(optional: Expression, defaultValue: V) -> Expression { +public func ??(optional: Expression, defaultValue: V) -> Expression { return Function.ifnull.wrap([optional, defaultValue]) } @@ -771,7 +771,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressi /// /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. -public func ??(optional: Expression, defaultValue: Expression) -> Expression { +public func ??(optional: Expression, defaultValue: Expression) -> Expression { return Function.ifnull.wrap([optional, defaultValue]) } @@ -791,6 +791,6 @@ public func ??(optional: Expression, defaultValue: Expression) /// /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. -public func ??(optional: Expression, defaultValue: Expression) -> Expression { +public func ??(optional: Expression, defaultValue: Expression) -> Expression { return Function.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 4d3e69ee..88fb5aaa 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -39,83 +39,107 @@ public extension Connection { /// The assigned types must be explicit. /// /// - Returns: A closure returning an SQL expression to call the function. - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws + -> () -> Expression { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws + -> () -> Expression { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } // MARK: - - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } // MARK: - - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) + -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B) -> Z) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B?) -> Z) throws -> + (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B?) -> Z) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B?) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B?) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } // MARK: - - fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z) throws -> (([Expressible]) -> Expression) { + fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, + _ block: @escaping ([Binding?]) -> Z) throws + -> ([Expressible]) -> Expression { createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in block(arguments).datatypeValue } @@ -124,7 +148,9 @@ public extension Connection { } } - fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z?) throws -> (([Expressible]) -> Expression) { + fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, + _ block: @escaping ([Binding?]) -> Z?) throws + -> ([Expressible]) -> Expression { createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in block(arguments)?.datatypeValue } diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 76ba04c4..39a3c62f 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 { // extensions cannot have inheritance clauses associatedtype UnderlyingType = Void @@ -43,14 +43,14 @@ extension ExpressionType { self.init(literal: identifier.quote()) } - public init(_ expression: U) { + public init(_ expression: U) { self.init(expression.template, expression.bindings) } } /// An `Expression` represents a raw SQL fragment and any associated bindings. -public struct Expression : ExpressionType { +public struct Expression: ExpressionType { public typealias UnderlyingType = Datatype @@ -79,7 +79,7 @@ extension Expressible { var idx = 0 return expressed.template.reduce("") { template, character in let transcoded: String - + if character == "?" { transcoded = transcode(expressed.bindings[idx]) idx += 1 @@ -108,7 +108,7 @@ extension ExpressionType { } -extension ExpressionType where UnderlyingType : Value { +extension ExpressionType where UnderlyingType: Value { public init(value: UnderlyingType) { self.init("?", [value.datatypeValue]) @@ -116,7 +116,7 @@ extension ExpressionType where UnderlyingType : Value { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value { public static var null: Self { return self.init(value: nil) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 954a4af4..32d39fa7 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -45,11 +45,11 @@ private enum Operator: String { case gte = ">=" case lte = "<=" case concatenate = "||" - + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { return self.rawValue.infix(lhs, rhs, wrap: wrap) } - + func wrap(_ expression: Expressible) -> Expression { return self.rawValue.wrap(expression) } @@ -83,520 +83,521 @@ public func +(lhs: String, rhs: Expression) -> Expression { // MARK: - -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { +public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.wrap(rhs) } -public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { +public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.wrap(rhs) } // MARK: - -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { +public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseXor.wrap(rhs) } -public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { +public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseXor.wrap(rhs) } // MARK: - -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = 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 { +public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } return Operator.eq.infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = 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 { +public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } return "IS".infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = 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 { +public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return Operator.neq.infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = 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 { +public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return "IS NOT".infix(lhs, rhs) } - -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } -public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + + rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + + rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } -public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } @@ -667,6 +668,7 @@ public func ||(lhs: Bool, rhs: Expression) -> Expression { public prefix func !(rhs: Expression) -> Expression { return Operator.not.wrap(rhs) } + public prefix func !(rhs: Expression) -> Expression { return Operator.not.wrap(rhs) } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 669001d3..8d3ded52 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -21,10 +21,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // - +// swiftlint:disable file_length import Foundation -public protocol QueryType : Expressible { +public protocol QueryType: Expressible { var clauses: QueryClauses { get set } @@ -32,7 +32,7 @@ public protocol QueryType : Expressible { } -public protocol SchemaType : QueryType { +public protocol SchemaType: QueryType { static var identifier: String { get } @@ -141,10 +141,10 @@ extension SchemaType { /// - Parameter all: A list of expressions to select. /// /// - Returns: A query with the given `SELECT` clause applied. - public func select(_ column: Expression) -> ScalarQuery { + public func select(_ column: Expression) -> ScalarQuery { return select(false, [column]) } - public func select(_ column: Expression) -> ScalarQuery { + public func select(_ column: Expression) -> ScalarQuery { return select(false, [column]) } @@ -160,10 +160,10 @@ extension SchemaType { /// - Parameter column: A list of expressions to select. /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. - public func select(distinct column: Expression) -> ScalarQuery { + public func select(distinct column: Expression) -> ScalarQuery { return select(true, [column]) } - public func select(distinct column: Expression) -> ScalarQuery { + public func select(distinct column: Expression) -> ScalarQuery { return select(true, [column]) } @@ -175,7 +175,7 @@ extension SchemaType { extension QueryType { - fileprivate func select(_ distinct: Bool, _ columns: [Expressible]) -> Q { + fileprivate func select(_ distinct: Bool, _ columns: [Expressible]) -> Q { var query = Q.init(clauses.from.name, database: clauses.from.database) query.clauses = clauses query.clauses.select = (distinct, columns) @@ -183,7 +183,7 @@ extension QueryType { } // MARK: UNION - + /// Adds a `UNION` clause to the query. /// /// let users = Table("users") @@ -202,7 +202,7 @@ extension QueryType { query.clauses.union.append(table) return query } - + // MARK: JOIN /// Adds a `JOIN` clause to the query. @@ -589,12 +589,12 @@ extension QueryType { Expression(literal: "OFFSET \(offset)") ]) } - + fileprivate var unionClause: Expressible? { guard !clauses.union.isEmpty else { return nil } - + return " ".join(clauses.union.map { query in " ".join([ Expression(literal: "UNION"), @@ -709,28 +709,28 @@ extension QueryType { query.expression ]).expression) } - + // MARK: UPSERT - + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible) -> Insert { return upsert(insertValues, onConflictOf: conflicting) } - + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible) -> Insert { let setValues = insertValues.filter { $0.column.asSQL() != conflicting.asSQL() } .map { Setter(excluded: $0.column) } return upsert(insertValues, onConflictOf: conflicting, set: setValues) } - + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { return upsert(insertValues, onConflictOf: conflicting, set: setValues) } - + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { let insert = insertValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in (insert.columns + [setter.column], insert.values + [setter.value]) } - + let clauses: [Expressible?] = [ Expression(literal: "INSERT"), Expression(literal: "INTO"), @@ -744,11 +744,10 @@ extension QueryType { Expression(literal: "DO UPDATE SET"), ", ".join(setValues.map { $0.expression }) ] - + return Insert(" ".join(clauses.compactMap { $0 }).expression) } - // MARK: UPDATE public func update(_ values: Setter...) -> Update { @@ -826,7 +825,7 @@ extension QueryType { // TODO: alias support func tableName(alias aliased: Bool = false) -> Expressible { - guard let alias = clauses.from.alias , aliased else { + guard let alias = clauses.from.alias, aliased else { return database(namespace: clauses.from.alias ?? clauses.from.name) } @@ -874,7 +873,7 @@ extension QueryType { /// Queries a collection of chainable helper functions and expressions to build /// executable SQL statements. -public struct Table : SchemaType { +public struct Table: SchemaType { public static let identifier = "TABLE" @@ -886,7 +885,7 @@ public struct Table : SchemaType { } -public struct View : SchemaType { +public struct View: SchemaType { public static let identifier = "VIEW" @@ -898,7 +897,7 @@ public struct View : SchemaType { } -public struct VirtualTable : SchemaType { +public struct VirtualTable: SchemaType { public static let identifier = "VIRTUAL TABLE" @@ -912,7 +911,7 @@ public struct VirtualTable : SchemaType { // TODO: make `ScalarQuery` work in `QueryType.select()`, `.filter()`, etc. -public struct ScalarQuery : QueryType { +public struct ScalarQuery: QueryType { public var clauses: QueryClauses @@ -924,7 +923,7 @@ public struct ScalarQuery : QueryType { // TODO: decide: simplify the below with a boxed type instead -public struct Select : ExpressionType { +public struct Select: ExpressionType { public var template: String public var bindings: [Binding?] @@ -936,7 +935,7 @@ public struct Select : ExpressionType { } -public struct Insert : ExpressionType { +public struct Insert: ExpressionType { public var template: String public var bindings: [Binding?] @@ -948,7 +947,7 @@ public struct Insert : ExpressionType { } -public struct Update : ExpressionType { +public struct Update: ExpressionType { public var template: String public var bindings: [Binding?] @@ -960,7 +959,7 @@ public struct Update : ExpressionType { } -public struct Delete : ExpressionType { +public struct Delete: ExpressionType { public var template: String public var bindings: [Binding?] @@ -972,7 +971,6 @@ public struct Delete : ExpressionType { } - public struct RowIterator: FailableIterator { public typealias Element = Row let statement: Statement @@ -1003,7 +1001,6 @@ extension Connection { AnyIterator { statement.next().map { Row(columnNames, $0) } } } } - public func prepareRowIterator(_ query: QueryType) throws -> RowIterator { let expression = query.expression @@ -1017,9 +1014,9 @@ extension Connection { var names = each.expression.template.split { $0 == "." }.map(String.init) let column = names.removeLast() let namespace = names.joined(separator: ".") - + func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { - return { (query: QueryType) throws -> (Void) in + return { (query: QueryType) throws -> Void in var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) q.clauses.select = query.clauses.select let e = q.expression @@ -1028,7 +1025,7 @@ extension Connection { for name in names { columnNames[name] = idx; idx += 1 } } } - + if column == "*" { var select = query select.clauses.select = (false, [Expression(literal: "*") as Expressible]) @@ -1047,30 +1044,30 @@ extension Connection { } continue } - + columnNames[each.expression.template] = idx idx += 1 } return columnNames } - public func scalar(_ query: ScalarQuery) throws -> V { + public func scalar(_ query: ScalarQuery) throws -> V { let expression = query.expression return value(try scalar(expression.template, expression.bindings)) } - public func scalar(_ query: ScalarQuery) throws -> V.ValueType? { + 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) } - public func scalar(_ query: Select) throws -> V { + public func scalar(_ query: Select) throws -> V { let expression = query.expression 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) @@ -1186,17 +1183,17 @@ public struct Row { return valueAtIndex(idx) } - public subscript(column: Expression) -> T { + public subscript(column: Expression) -> T { return try! get(column) } - public subscript(column: Expression) -> T? { + public subscript(column: Expression) -> T? { return try! get(column) } } /// Determines the join operator for a query’s `JOIN` clause. -public enum JoinType : String { +public enum JoinType: String { /// A `CROSS` join. case cross = "CROSS" @@ -1241,7 +1238,7 @@ public struct QueryClauses { var order = [Expressible]() var limit: (length: Int, offset: Int?)? - + var union = [QueryType]() fileprivate init(_ name: String, alias: String?, database: String?) { @@ -1249,4 +1246,3 @@ public struct QueryClauses { } } - diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index febfe68c..d8d45e41 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -36,7 +36,8 @@ extension Table { // MARK: - CREATE TABLE - public func create(temporary: Bool = false, ifNotExists: Bool = false, withoutRowid: Bool = false, block: (TableBuilder) -> Void) -> String { + public func create(temporary: Bool = false, ifNotExists: Bool = false, withoutRowid: Bool = false, + block: (TableBuilder) -> Void) -> String { let builder = TableBuilder() block(builder) @@ -62,51 +63,59 @@ extension Table { // MARK: - ALTER TABLE … ADD COLUMN - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V) -> String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V) -> String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil) -> String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } @@ -140,7 +149,6 @@ extension Table { // MARK: - DROP INDEX - public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { return drop("INDEX", indexName(columns), ifExists) } @@ -211,138 +219,175 @@ public final class TableBuilder { fileprivate var definitions = [Expressible]() - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil) } - public func column(_ name: Expression, primaryKey: Bool, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, primaryKey: Bool, check: Expression, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil) } - public func column(_ name: Expression, primaryKey: PrimaryKey, check: Expression? = nil) where V.Datatype == Int64 { + public func column(_ name: Expression, primaryKey: PrimaryKey, + check: Expression? = nil) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) } - public func column(_ name: Expression, primaryKey: PrimaryKey, check: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, primaryKey: PrimaryKey, + check: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + 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, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + 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, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { + // swiftlint:disable:next function_parameter_count + fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, + _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, + _ references: (QueryType, Expressible)?, _ collate: Collation?) { definitions.append(definition(name, datatype, primaryKey, null, unique, check, defaultValue, references, collate)) } // MARK: - - public func primaryKey(_ column: Expression) { + public func primaryKey(_ column: Expression) { primaryKey([column]) } - public func primaryKey(_ compositeA: Expression, _ b: Expression) { - primaryKey([compositeA, b]) + public func primaryKey(_ compositeA: Expression, + _ expr: Expression) { + primaryKey([compositeA, expr]) } - public func primaryKey(_ compositeA: Expression, _ b: Expression, _ c: Expression) { - primaryKey([compositeA, b, c]) + public func primaryKey(_ compositeA: Expression, + _ expr1: Expression, + _ expr2: Expression) { + primaryKey([compositeA, expr1, expr2]) } - public func primaryKey(_ compositeA: Expression, _ b: Expression, _ c: Expression, _ d: Expression) { - primaryKey([compositeA, b, c, d]) + public func primaryKey(_ compositeA: Expression, + _ expr1: Expression, + _ expr2: Expression, + _ expr3: Expression) { + primaryKey([compositeA, expr1, expr2, expr3]) } fileprivate func primaryKey(_ composite: [Expressible]) { @@ -379,29 +424,37 @@ public final class TableBuilder { } - public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, + update: Dependency? = nil, delete: Dependency? = nil) { foreignKey(column, (table, other), update, delete) } - public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, + update: Dependency? = nil, delete: Dependency? = nil) { foreignKey(column, (table, other), update, delete) } - public func foreignKey(_ composite: (Expression, Expression), references table: QueryType, _ other: (Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ composite: (Expression, Expression), + references table: QueryType, _ other: (Expression, Expression), + update: Dependency? = nil, delete: Dependency? = nil) { let composite = ", ".join([composite.0, composite.1]) let references = (table, ", ".join([other.0, other.1])) foreignKey(composite, references, update, delete) } - public func foreignKey(_ composite: (Expression, Expression, Expression), references table: QueryType, _ other: (Expression, Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ composite: (Expression, Expression, Expression), + references table: QueryType, + _ other: (Expression, Expression, Expression), + update: Dependency? = nil, delete: Dependency? = nil) { let composite = ", ".join([composite.0, composite.1, composite.2]) let references = (table, ", ".join([other.0, other.1, other.2])) foreignKey(composite, references, update, delete) } - fileprivate func foreignKey(_ column: Expressible, _ references: (QueryType, Expressible), _ update: Dependency?, _ delete: Dependency?) { + fileprivate func foreignKey(_ column: Expressible, _ references: (QueryType, Expressible), + _ update: Dependency?, _ delete: Dependency?) { let clauses: [Expressible?] = [ "FOREIGN KEY".prefix(column), reference(references), @@ -439,7 +492,7 @@ public struct Module { } -extension Module : Expressible { +extension Module: Expressible { public var expression: Expression { return name.wrap(arguments) @@ -484,7 +537,10 @@ private extension QueryType { } -private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { +// swiftlint:disable:next function_parameter_count +private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, + _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, + _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { let clauses: [Expressible?] = [ column, Expression(literal: datatype), @@ -508,7 +564,7 @@ private func reference(_ primary: (QueryType, Expressible)) -> Expressible { ]) } -private enum Modifier : String { +private enum Modifier: String { case unique = "UNIQUE" diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 3d3170f6..25e88342 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -35,27 +35,27 @@ public struct Setter { let column: Expressible let value: Expressible - fileprivate init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - fileprivate init(column: Expression, value: V) { + fileprivate init(column: Expression, value: V) { self.column = column self.value = value } - fileprivate init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - fileprivate init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - fileprivate init(column: Expression, value: V?) { + fileprivate init(column: Expression, value: V?) { self.column = column self.value = Expression(value: value) } @@ -67,7 +67,7 @@ public struct Setter { } } -extension Setter : Expressible { +extension Setter: Expressible { public var expression: Expression { return "=".infix(column, value, wrap: false) @@ -75,19 +75,19 @@ extension Setter : Expressible { } -public func <-(column: Expression, value: Expression) -> Setter { +public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: V) -> Setter { +public func <-(column: Expression, value: V) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: Expression) -> Setter { +public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: Expression) -> Setter { +public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: V?) -> Setter { +public func <-(column: Expression, value: V?) -> Setter { return Setter(column: column, value: value) } @@ -107,176 +107,176 @@ public func +=(column: Expression, value: String) -> Setter { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column + value } -public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column - value } -public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column * value } -public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column / value } -public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) += 1 } -public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) += 1 } -public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) -= 1 } -public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) -= 1 } diff --git a/Tests/.swiftlint.yml b/Tests/.swiftlint.yml new file mode 100644 index 00000000..3537085c --- /dev/null +++ b/Tests/.swiftlint.yml @@ -0,0 +1,20 @@ +parent_config: ../.swiftlint.yml + +disabled_rules: + - force_cast + - force_try + - identifier_name + +type_body_length: + warning: 800 + error: 800 + +function_body_length: + warning: 200 + error: 200 + +file_length: + warning: 1000 + error: 1000 + + diff --git a/Tests/SQLiteTests/AggregateFunctionsTests.swift b/Tests/SQLiteTests/AggregateFunctionsTests.swift index 6b583ccf..71ac79fb 100644 --- a/Tests/SQLiteTests/AggregateFunctionsTests.swift +++ b/Tests/SQLiteTests/AggregateFunctionsTests.swift @@ -1,68 +1,68 @@ import XCTest import SQLite -class AggregateFunctionsTests : XCTestCase { +class AggregateFunctionsTests: XCTestCase { func test_distinct_prependsExpressionsWithDistinctKeyword() { - AssertSQL("DISTINCT \"int\"", int.distinct) - AssertSQL("DISTINCT \"intOptional\"", intOptional.distinct) - AssertSQL("DISTINCT \"double\"", double.distinct) - AssertSQL("DISTINCT \"doubleOptional\"", doubleOptional.distinct) - AssertSQL("DISTINCT \"string\"", string.distinct) - AssertSQL("DISTINCT \"stringOptional\"", stringOptional.distinct) + assertSQL("DISTINCT \"int\"", int.distinct) + assertSQL("DISTINCT \"intOptional\"", intOptional.distinct) + assertSQL("DISTINCT \"double\"", double.distinct) + assertSQL("DISTINCT \"doubleOptional\"", doubleOptional.distinct) + assertSQL("DISTINCT \"string\"", string.distinct) + assertSQL("DISTINCT \"stringOptional\"", stringOptional.distinct) } func test_count_wrapsOptionalExpressionsWithCountFunction() { - AssertSQL("count(\"intOptional\")", intOptional.count) - AssertSQL("count(\"doubleOptional\")", doubleOptional.count) - AssertSQL("count(\"stringOptional\")", stringOptional.count) + assertSQL("count(\"intOptional\")", intOptional.count) + assertSQL("count(\"doubleOptional\")", doubleOptional.count) + assertSQL("count(\"stringOptional\")", stringOptional.count) } func test_max_wrapsComparableExpressionsWithMaxFunction() { - AssertSQL("max(\"int\")", int.max) - AssertSQL("max(\"intOptional\")", intOptional.max) - AssertSQL("max(\"double\")", double.max) - AssertSQL("max(\"doubleOptional\")", doubleOptional.max) - AssertSQL("max(\"string\")", string.max) - AssertSQL("max(\"stringOptional\")", stringOptional.max) - AssertSQL("max(\"date\")", date.max) - AssertSQL("max(\"dateOptional\")", dateOptional.max) + assertSQL("max(\"int\")", int.max) + assertSQL("max(\"intOptional\")", intOptional.max) + assertSQL("max(\"double\")", double.max) + assertSQL("max(\"doubleOptional\")", doubleOptional.max) + assertSQL("max(\"string\")", string.max) + assertSQL("max(\"stringOptional\")", stringOptional.max) + assertSQL("max(\"date\")", date.max) + assertSQL("max(\"dateOptional\")", dateOptional.max) } func test_min_wrapsComparableExpressionsWithMinFunction() { - AssertSQL("min(\"int\")", int.min) - AssertSQL("min(\"intOptional\")", intOptional.min) - AssertSQL("min(\"double\")", double.min) - AssertSQL("min(\"doubleOptional\")", doubleOptional.min) - AssertSQL("min(\"string\")", string.min) - AssertSQL("min(\"stringOptional\")", stringOptional.min) - AssertSQL("min(\"date\")", date.min) - AssertSQL("min(\"dateOptional\")", dateOptional.min) + assertSQL("min(\"int\")", int.min) + assertSQL("min(\"intOptional\")", intOptional.min) + assertSQL("min(\"double\")", double.min) + assertSQL("min(\"doubleOptional\")", doubleOptional.min) + assertSQL("min(\"string\")", string.min) + assertSQL("min(\"stringOptional\")", stringOptional.min) + assertSQL("min(\"date\")", date.min) + assertSQL("min(\"dateOptional\")", dateOptional.min) } func test_average_wrapsNumericExpressionsWithAvgFunction() { - AssertSQL("avg(\"int\")", int.average) - AssertSQL("avg(\"intOptional\")", intOptional.average) - AssertSQL("avg(\"double\")", double.average) - AssertSQL("avg(\"doubleOptional\")", doubleOptional.average) + assertSQL("avg(\"int\")", int.average) + assertSQL("avg(\"intOptional\")", intOptional.average) + assertSQL("avg(\"double\")", double.average) + assertSQL("avg(\"doubleOptional\")", doubleOptional.average) } func test_sum_wrapsNumericExpressionsWithSumFunction() { - AssertSQL("sum(\"int\")", int.sum) - AssertSQL("sum(\"intOptional\")", intOptional.sum) - AssertSQL("sum(\"double\")", double.sum) - AssertSQL("sum(\"doubleOptional\")", doubleOptional.sum) + assertSQL("sum(\"int\")", int.sum) + assertSQL("sum(\"intOptional\")", intOptional.sum) + assertSQL("sum(\"double\")", double.sum) + assertSQL("sum(\"doubleOptional\")", doubleOptional.sum) } func test_total_wrapsNumericExpressionsWithTotalFunction() { - AssertSQL("total(\"int\")", int.total) - AssertSQL("total(\"intOptional\")", intOptional.total) - AssertSQL("total(\"double\")", double.total) - AssertSQL("total(\"doubleOptional\")", doubleOptional.total) + assertSQL("total(\"int\")", int.total) + assertSQL("total(\"intOptional\")", intOptional.total) + assertSQL("total(\"double\")", double.total) + assertSQL("total(\"doubleOptional\")", doubleOptional.total) } func test_count_withStar_wrapsStarWithCountFunction() { - AssertSQL("count(*)", count(*)) + assertSQL("count(*)", count(*)) } } diff --git a/Tests/SQLiteTests/BlobTests.swift b/Tests/SQLiteTests/BlobTests.swift index 817205d6..87eb5709 100644 --- a/Tests/SQLiteTests/BlobTests.swift +++ b/Tests/SQLiteTests/BlobTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class BlobTests : XCTestCase { +class BlobTests: XCTestCase { func test_toHex() { let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255]) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index a27ac17d..77e8e7f0 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -77,7 +77,7 @@ class CipherTests: XCTestCase { // sqlite> pragma key = 'sqlcipher-test'; // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); - guard let cipherVersion:String = db1.cipherVersion, + guard let cipherVersion: String = db1.cipherVersion, cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") else { return } @@ -85,12 +85,12 @@ 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) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index a05cceb5..00fe4f6b 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -13,12 +13,12 @@ import CSQLite import SQLite3 #endif -class ConnectionTests : SQLiteTestCase { +class ConnectionTests: SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() + createUsersTable() } func test_init_withInMemory_returnsInMemoryConnection() { @@ -62,13 +62,13 @@ class ConnectionTests : SQLiteTestCase { } func test_lastInsertRowid_returnsLastIdAfterInserts() { - try! InsertUser("alice") + try! insertUser("alice") XCTAssertEqual(1, db.lastInsertRowid) } func test_lastInsertRowid_doesNotResetAfterError() { 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')") @@ -83,17 +83,17 @@ class ConnectionTests : SQLiteTestCase { } func test_changes_returnsNumberOfChanges() { - try! InsertUser("alice") + try! insertUser("alice") XCTAssertEqual(1, db.changes) - try! InsertUser("betsy") + try! insertUser("betsy") XCTAssertEqual(1, db.changes) } func test_totalChanges_returnsTotalNumberOfChanges() { XCTAssertEqual(0, db.totalChanges) - try! InsertUser("alice") + try! insertUser("alice") XCTAssertEqual(1, db.totalChanges) - try! InsertUser("betsy") + try! insertUser("betsy") XCTAssertEqual(2, db.totalChanges) } @@ -109,9 +109,9 @@ class ConnectionTests : SQLiteTestCase { 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) + assertSQL("SELECT * FROM users WHERE admin = 0", 4) } - + func test_vacuum() { try! db.vacuum() } @@ -121,31 +121,31 @@ class ConnectionTests : SQLiteTestCase { 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) + assertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } func test_execute_comment() { try! db.run("-- this is a comment\nSELECT 1") - AssertSQL("-- this is a comment", 0) - AssertSQL("SELECT 1", 0) + assertSQL("-- this is a comment", 0) + assertSQL("SELECT 1", 0) } func test_transaction_executesBeginDeferred() { try! db.transaction(.deferred) {} - AssertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("BEGIN DEFERRED TRANSACTION") } func test_transaction_executesBeginImmediate() { try! db.transaction(.immediate) {} - AssertSQL("BEGIN IMMEDIATE TRANSACTION") + assertSQL("BEGIN IMMEDIATE TRANSACTION") } func test_transaction_executesBeginExclusive() { try! db.transaction(.exclusive) {} - AssertSQL("BEGIN EXCLUSIVE TRANSACTION") + assertSQL("BEGIN EXCLUSIVE TRANSACTION") } func test_transaction_beginsAndCommitsTransactions() { @@ -155,10 +155,10 @@ class ConnectionTests : SQLiteTestCase { try stmt.run() } - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") - AssertSQL("COMMIT TRANSACTION") - AssertSQL("ROLLBACK TRANSACTION", 0) + assertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + assertSQL("COMMIT TRANSACTION") + assertSQL("ROLLBACK TRANSACTION", 0) } func test_transaction_rollsBackTransactionsIfCommitsFail() { @@ -186,10 +186,10 @@ class ConnectionTests : SQLiteTestCase { XCTFail("unexpected error: \(error)") } - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email, manager_id) VALUES ('alice@example.com', 100)") - AssertSQL("COMMIT TRANSACTION") - AssertSQL("ROLLBACK TRANSACTION") + assertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("INSERT INTO users (email, manager_id) VALUES ('alice@example.com', 100)") + assertSQL("COMMIT TRANSACTION") + assertSQL("ROLLBACK TRANSACTION") // Run another transaction to ensure that a subsequent transaction does not fail with an "cannot start a // transaction within a transaction" error. @@ -210,14 +210,14 @@ class ConnectionTests : SQLiteTestCase { } catch { } - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) + assertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + assertSQL("ROLLBACK TRANSACTION") + assertSQL("COMMIT TRANSACTION", 0) } func test_savepoint_beginsAndCommitsSavepoints() { - let db:Connection = self.db + let db: Connection = self.db try! db.savepoint("1") { try db.savepoint("2") { @@ -225,17 +225,17 @@ class ConnectionTests : SQLiteTestCase { } } - AssertSQL("SAVEPOINT '1'") - AssertSQL("SAVEPOINT '2'") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") - AssertSQL("RELEASE SAVEPOINT '2'") - AssertSQL("RELEASE SAVEPOINT '1'") - AssertSQL("ROLLBACK TO SAVEPOINT '2'", 0) - AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0) + assertSQL("SAVEPOINT '1'") + assertSQL("SAVEPOINT '2'") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + assertSQL("RELEASE SAVEPOINT '2'") + assertSQL("RELEASE SAVEPOINT '1'") + assertSQL("ROLLBACK TO SAVEPOINT '2'", 0) + assertSQL("ROLLBACK TO SAVEPOINT '1'", 0) } func test_savepoint_beginsAndRollsSavepointsBack() { - let db:Connection = self.db + let db: Connection = self.db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { @@ -254,13 +254,13 @@ class ConnectionTests : SQLiteTestCase { } catch { } - AssertSQL("SAVEPOINT '1'") - AssertSQL("SAVEPOINT '2'") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) - AssertSQL("ROLLBACK TO SAVEPOINT '2'") - AssertSQL("ROLLBACK TO SAVEPOINT '1'") - AssertSQL("RELEASE SAVEPOINT '2'", 0) - AssertSQL("RELEASE SAVEPOINT '1'", 0) + assertSQL("SAVEPOINT '1'") + assertSQL("SAVEPOINT '2'") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + assertSQL("ROLLBACK TO SAVEPOINT '2'") + assertSQL("ROLLBACK TO SAVEPOINT '1'") + assertSQL("RELEASE SAVEPOINT '2'", 0) + assertSQL("RELEASE SAVEPOINT '1'", 0) } func test_updateHook_setsUpdateHook_withInsert() { @@ -272,12 +272,12 @@ class ConnectionTests : SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! InsertUser("alice") + try! insertUser("alice") } } func test_updateHook_setsUpdateHook_withUpdate() { - try! InsertUser("alice") + try! insertUser("alice") async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.update, operation) @@ -291,7 +291,7 @@ class ConnectionTests : SQLiteTestCase { } func test_updateHook_setsUpdateHook_withDelete() { - try! InsertUser("alice") + try! insertUser("alice") async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.delete, operation) @@ -310,7 +310,7 @@ class ConnectionTests : SQLiteTestCase { done() } try! db.transaction { - try self.InsertUser("alice") + try self.insertUser("alice") } XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } @@ -321,8 +321,8 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.InsertUser("alice") - try self.InsertUser("alice") // throw + try self.insertUser("alice") + try self.insertUser("alice") // throw } } catch { } @@ -338,7 +338,7 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.InsertUser("alice") + try self.insertUser("alice") } } catch { } @@ -376,7 +376,7 @@ class ConnectionTests : SQLiteTestCase { func test_interrupt_interruptsLongRunningQuery() { let semaphore = DispatchSemaphore(value: 0) - db.createFunction("sleep") { args in + db.createFunction("sleep") { _ in DispatchQueue.global(qos: .background).async { self.db.interrupt() semaphore.signal() @@ -396,7 +396,7 @@ class ConnectionTests : SQLiteTestCase { func test_concurrent_access_single_connection() { // 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 } + 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);") @@ -416,8 +416,7 @@ class ConnectionTests : SQLiteTestCase { } } - -class ResultTests : XCTestCase { +class ResultTests: XCTestCase { let connection = try! Connection(.inMemory) func test_init_with_ok_code_returns_nil() { @@ -434,13 +433,13 @@ class ResultTests : XCTestCase { 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) { + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { XCTAssertEqual("not an error", message) XCTAssertEqual(SQLITE_MISUSE, code) XCTAssertNil(statement) XCTAssert(self.connection === connection) } else { - XCTFail() + XCTFail("no error") } } diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index e7402de3..e03e3769 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -1,145 +1,145 @@ import XCTest @testable import SQLite -class CoreFunctionsTests : XCTestCase { +class CoreFunctionsTests: XCTestCase { func test_round_wrapsDoubleExpressionsWithRoundFunction() { - AssertSQL("round(\"double\")", double.round()) - AssertSQL("round(\"doubleOptional\")", doubleOptional.round()) + assertSQL("round(\"double\")", double.round()) + assertSQL("round(\"doubleOptional\")", doubleOptional.round()) - AssertSQL("round(\"double\", 1)", double.round(1)) - AssertSQL("round(\"doubleOptional\", 2)", doubleOptional.round(2)) + assertSQL("round(\"double\", 1)", double.round(1)) + assertSQL("round(\"doubleOptional\", 2)", doubleOptional.round(2)) } func test_random_generatesExpressionWithRandomFunction() { - AssertSQL("random()", Expression.random()) - AssertSQL("random()", Expression.random()) + assertSQL("random()", Expression.random()) + assertSQL("random()", Expression.random()) } func test_length_wrapsStringExpressionWithLengthFunction() { - AssertSQL("length(\"string\")", string.length) - AssertSQL("length(\"stringOptional\")", stringOptional.length) + assertSQL("length(\"string\")", string.length) + assertSQL("length(\"stringOptional\")", stringOptional.length) } func test_lowercaseString_wrapsStringExpressionWithLowerFunction() { - AssertSQL("lower(\"string\")", string.lowercaseString) - AssertSQL("lower(\"stringOptional\")", stringOptional.lowercaseString) + assertSQL("lower(\"string\")", string.lowercaseString) + assertSQL("lower(\"stringOptional\")", stringOptional.lowercaseString) } func test_uppercaseString_wrapsStringExpressionWithUpperFunction() { - AssertSQL("upper(\"string\")", string.uppercaseString) - AssertSQL("upper(\"stringOptional\")", stringOptional.uppercaseString) + assertSQL("upper(\"string\")", string.uppercaseString) + assertSQL("upper(\"stringOptional\")", stringOptional.uppercaseString) } func test_like_buildsExpressionWithLikeOperator() { - AssertSQL("(\"string\" LIKE 'a%')", string.like("a%")) - AssertSQL("(\"stringOptional\" LIKE 'b%')", stringOptional.like("b%")) - - AssertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) - AssertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) - - AssertSQL("(\"string\" LIKE \"a\")", string.like(Expression("a"))) - AssertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(Expression("a"))) - - AssertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) - AssertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) - - AssertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) - AssertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) + assertSQL("(\"string\" LIKE 'a%')", string.like("a%")) + assertSQL("(\"stringOptional\" LIKE 'b%')", stringOptional.like("b%")) + + assertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) + assertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) + + assertSQL("(\"string\" LIKE \"a\")", string.like(Expression("a"))) + assertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(Expression("a"))) + + assertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) + assertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) + + assertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) + assertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { - AssertSQL("(\"string\" GLOB 'a*')", string.glob("a*")) - AssertSQL("(\"stringOptional\" GLOB 'b*')", stringOptional.glob("b*")) + assertSQL("(\"string\" GLOB 'a*')", string.glob("a*")) + assertSQL("(\"stringOptional\" GLOB 'b*')", stringOptional.glob("b*")) } func test_match_buildsExpressionWithMatchOperator() { - AssertSQL("(\"string\" MATCH 'a*')", string.match("a*")) - AssertSQL("(\"stringOptional\" MATCH 'b*')", stringOptional.match("b*")) + assertSQL("(\"string\" MATCH 'a*')", string.match("a*")) + assertSQL("(\"stringOptional\" MATCH 'b*')", stringOptional.match("b*")) } func test_regexp_buildsExpressionWithRegexpOperator() { - AssertSQL("(\"string\" REGEXP '^.+@.+\\.com$')", string.regexp("^.+@.+\\.com$")) - AssertSQL("(\"stringOptional\" REGEXP '^.+@.+\\.net$')", stringOptional.regexp("^.+@.+\\.net$")) + assertSQL("(\"string\" REGEXP '^.+@.+\\.com$')", string.regexp("^.+@.+\\.com$")) + assertSQL("(\"stringOptional\" REGEXP '^.+@.+\\.net$')", stringOptional.regexp("^.+@.+\\.net$")) } func test_collate_buildsExpressionWithCollateOperator() { - AssertSQL("(\"string\" COLLATE BINARY)", string.collate(.binary)) - AssertSQL("(\"string\" COLLATE NOCASE)", string.collate(.nocase)) - AssertSQL("(\"string\" COLLATE RTRIM)", string.collate(.rtrim)) - AssertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.custom("CUSTOM"))) + assertSQL("(\"string\" COLLATE BINARY)", string.collate(.binary)) + assertSQL("(\"string\" COLLATE NOCASE)", string.collate(.nocase)) + assertSQL("(\"string\" COLLATE RTRIM)", string.collate(.rtrim)) + assertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.custom("CUSTOM"))) - AssertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.binary)) - AssertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.nocase)) - AssertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.rtrim)) - AssertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.custom("CUSTOM"))) + assertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.binary)) + assertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.nocase)) + assertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.rtrim)) + assertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.custom("CUSTOM"))) } func test_ltrim_wrapsStringWithLtrimFunction() { - AssertSQL("ltrim(\"string\")", string.ltrim()) - AssertSQL("ltrim(\"stringOptional\")", stringOptional.ltrim()) + assertSQL("ltrim(\"string\")", string.ltrim()) + assertSQL("ltrim(\"stringOptional\")", stringOptional.ltrim()) - AssertSQL("ltrim(\"string\", ' ')", string.ltrim([" "])) - AssertSQL("ltrim(\"stringOptional\", ' ')", stringOptional.ltrim([" "])) + assertSQL("ltrim(\"string\", ' ')", string.ltrim([" "])) + assertSQL("ltrim(\"stringOptional\", ' ')", stringOptional.ltrim([" "])) } func test_ltrim_wrapsStringWithRtrimFunction() { - AssertSQL("rtrim(\"string\")", string.rtrim()) - AssertSQL("rtrim(\"stringOptional\")", stringOptional.rtrim()) + assertSQL("rtrim(\"string\")", string.rtrim()) + assertSQL("rtrim(\"stringOptional\")", stringOptional.rtrim()) - AssertSQL("rtrim(\"string\", ' ')", string.rtrim([" "])) - AssertSQL("rtrim(\"stringOptional\", ' ')", stringOptional.rtrim([" "])) + assertSQL("rtrim(\"string\", ' ')", string.rtrim([" "])) + assertSQL("rtrim(\"stringOptional\", ' ')", stringOptional.rtrim([" "])) } func test_ltrim_wrapsStringWithTrimFunction() { - AssertSQL("trim(\"string\")", string.trim()) - AssertSQL("trim(\"stringOptional\")", stringOptional.trim()) + assertSQL("trim(\"string\")", string.trim()) + assertSQL("trim(\"stringOptional\")", stringOptional.trim()) - AssertSQL("trim(\"string\", ' ')", string.trim([" "])) - AssertSQL("trim(\"stringOptional\", ' ')", stringOptional.trim([" "])) + assertSQL("trim(\"string\", ' ')", string.trim([" "])) + assertSQL("trim(\"stringOptional\", ' ')", stringOptional.trim([" "])) } func test_replace_wrapsStringWithReplaceFunction() { - AssertSQL("replace(\"string\", '@example.com', '@example.net')", string.replace("@example.com", with: "@example.net")) - AssertSQL("replace(\"stringOptional\", '@example.net', '@example.com')", stringOptional.replace("@example.net", with: "@example.com")) + assertSQL("replace(\"string\", '@example.com', '@example.net')", string.replace("@example.com", with: "@example.net")) + assertSQL("replace(\"stringOptional\", '@example.net', '@example.com')", stringOptional.replace("@example.net", with: "@example.com")) } func test_substring_wrapsStringWithSubstrFunction() { - AssertSQL("substr(\"string\", 1, 2)", string.substring(1, length: 2)) - AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional.substring(2, length: 1)) + assertSQL("substr(\"string\", 1, 2)", string.substring(1, length: 2)) + assertSQL("substr(\"stringOptional\", 2, 1)", stringOptional.substring(2, length: 1)) } func test_subscriptWithRange_wrapsStringWithSubstrFunction() { - AssertSQL("substr(\"string\", 1, 2)", string[1..<3]) - AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional[2..<3]) + assertSQL("substr(\"string\", 1, 2)", string[1..<3]) + assertSQL("substr(\"stringOptional\", 2, 1)", stringOptional[2..<3]) } func test_nilCoalescingOperator_wrapsOptionalsWithIfnullFunction() { - AssertSQL("ifnull(\"intOptional\", 1)", intOptional ?? 1) + assertSQL("ifnull(\"intOptional\", 1)", intOptional ?? 1) // AssertSQL("ifnull(\"doubleOptional\", 1.0)", doubleOptional ?? 1) // rdar://problem/21677256 XCTAssertEqual("ifnull(\"doubleOptional\", 1.0)", (doubleOptional ?? 1).asSQL()) - AssertSQL("ifnull(\"stringOptional\", 'literal')", stringOptional ?? "literal") + assertSQL("ifnull(\"stringOptional\", 'literal')", stringOptional ?? "literal") - AssertSQL("ifnull(\"intOptional\", \"int\")", intOptional ?? int) - AssertSQL("ifnull(\"doubleOptional\", \"double\")", doubleOptional ?? double) - AssertSQL("ifnull(\"stringOptional\", \"string\")", stringOptional ?? string) + assertSQL("ifnull(\"intOptional\", \"int\")", intOptional ?? int) + assertSQL("ifnull(\"doubleOptional\", \"double\")", doubleOptional ?? double) + assertSQL("ifnull(\"stringOptional\", \"string\")", stringOptional ?? string) - AssertSQL("ifnull(\"intOptional\", \"intOptional\")", intOptional ?? intOptional) - AssertSQL("ifnull(\"doubleOptional\", \"doubleOptional\")", doubleOptional ?? doubleOptional) - AssertSQL("ifnull(\"stringOptional\", \"stringOptional\")", stringOptional ?? stringOptional) + assertSQL("ifnull(\"intOptional\", \"intOptional\")", intOptional ?? intOptional) + assertSQL("ifnull(\"doubleOptional\", \"doubleOptional\")", doubleOptional ?? doubleOptional) + assertSQL("ifnull(\"stringOptional\", \"stringOptional\")", stringOptional ?? stringOptional) } func test_absoluteValue_wrapsNumberWithAbsFucntion() { - AssertSQL("abs(\"int\")", int.absoluteValue) - AssertSQL("abs(\"intOptional\")", intOptional.absoluteValue) + assertSQL("abs(\"int\")", int.absoluteValue) + assertSQL("abs(\"intOptional\")", intOptional.absoluteValue) - AssertSQL("abs(\"double\")", double.absoluteValue) - AssertSQL("abs(\"doubleOptional\")", doubleOptional.absoluteValue) + assertSQL("abs(\"double\")", double.absoluteValue) + assertSQL("abs(\"doubleOptional\")", doubleOptional.absoluteValue) } func test_contains_buildsExpressionWithInOperator() { - AssertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string)) - AssertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional)) + assertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string)) + assertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional)) } } diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 919986b6..0ebcd13c 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class CustomFunctionNoArgsTests : SQLiteTestCase { +class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> Expression typealias FunctionResultOptional = () -> Expression @@ -22,7 +22,7 @@ class CustomFunctionNoArgsTests : SQLiteTestCase { } } -class CustomFunctionWithOneArgTests : SQLiteTestCase { +class CustomFunctionWithOneArgTests: SQLiteTestCase { typealias FunctionNoOptional = (Expression) -> Expression typealias FunctionLeftOptional = (Expression) -> Expression typealias FunctionResultOptional = (Expression) -> Expression @@ -53,7 +53,7 @@ class CustomFunctionWithOneArgTests : SQLiteTestCase { } func testFunctionLeftResultOptional() { - let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a:String?) -> String? in + let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a: String?) -> String? in return "b"+a! } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String @@ -61,14 +61,14 @@ class CustomFunctionWithOneArgTests : SQLiteTestCase { } } -class CustomFunctionWithTwoArgsTests : SQLiteTestCase { - typealias FunctionNoOptional = (Expression, Expression) -> Expression +class CustomFunctionWithTwoArgsTests: SQLiteTestCase { + typealias FunctionNoOptional = (Expression, Expression) -> Expression typealias FunctionLeftOptional = (Expression, Expression) -> Expression - typealias FunctionRightOptional = (Expression, Expression) -> Expression - typealias FunctionResultOptional = (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 FunctionRightResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression func testNoOptional() { diff --git a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift index 628b5910..393e9c7c 100644 --- a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift +++ b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift @@ -1,66 +1,66 @@ import XCTest @testable import SQLite -class DateAndTimeFunctionsTests : XCTestCase { +class DateAndTimeFunctionsTests: XCTestCase { func test_date() { - AssertSQL("date('now')", DateFunctions.date("now")) - AssertSQL("date('now', 'localtime')", DateFunctions.date("now", "localtime")) + assertSQL("date('now')", DateFunctions.date("now")) + assertSQL("date('now', 'localtime')", DateFunctions.date("now", "localtime")) } func test_time() { - AssertSQL("time('now')", DateFunctions.time("now")) - AssertSQL("time('now', 'localtime')", DateFunctions.time("now", "localtime")) + assertSQL("time('now')", DateFunctions.time("now")) + assertSQL("time('now', 'localtime')", DateFunctions.time("now", "localtime")) } func test_datetime() { - AssertSQL("datetime('now')", DateFunctions.datetime("now")) - AssertSQL("datetime('now', 'localtime')", DateFunctions.datetime("now", "localtime")) + assertSQL("datetime('now')", DateFunctions.datetime("now")) + assertSQL("datetime('now', 'localtime')", DateFunctions.datetime("now", "localtime")) } func test_julianday() { - AssertSQL("julianday('now')", DateFunctions.julianday("now")) - AssertSQL("julianday('now', 'localtime')", DateFunctions.julianday("now", "localtime")) + assertSQL("julianday('now')", DateFunctions.julianday("now")) + assertSQL("julianday('now', 'localtime')", DateFunctions.julianday("now", "localtime")) } func test_strftime() { - AssertSQL("strftime('%Y-%m-%d', 'now')", DateFunctions.strftime("%Y-%m-%d", "now")) - AssertSQL("strftime('%Y-%m-%d', 'now', 'localtime')", DateFunctions.strftime("%Y-%m-%d", "now", "localtime")) + assertSQL("strftime('%Y-%m-%d', 'now')", DateFunctions.strftime("%Y-%m-%d", "now")) + assertSQL("strftime('%Y-%m-%d', 'now', 'localtime')", DateFunctions.strftime("%Y-%m-%d", "now", "localtime")) } } -class DateExtensionTests : XCTestCase { +class DateExtensionTests: XCTestCase { func test_time() { - AssertSQL("time('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).time) + assertSQL("time('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).time) } func test_date() { - AssertSQL("date('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).date) + assertSQL("date('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).date) } func test_datetime() { - AssertSQL("datetime('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).datetime) + assertSQL("datetime('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).datetime) } func test_julianday() { - AssertSQL("julianday('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).julianday) + assertSQL("julianday('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).julianday) } } -class DateExpressionTests : XCTestCase { +class DateExpressionTests: XCTestCase { func test_date() { - AssertSQL("date(\"date\")", date.date) + assertSQL("date(\"date\")", date.date) } func test_time() { - AssertSQL("time(\"date\")", date.time) + assertSQL("time(\"date\")", date.time) } func test_datetime() { - AssertSQL("datetime(\"date\")", date.datetime) + assertSQL("datetime(\"date\")", date.datetime) } func test_julianday() { - AssertSQL("julianday(\"date\")", date.julianday) + assertSQL("julianday(\"date\")", date.julianday) } } diff --git a/Tests/SQLiteTests/ExpressionTests.swift b/Tests/SQLiteTests/ExpressionTests.swift index 036e10ce..32100d4d 100644 --- a/Tests/SQLiteTests/ExpressionTests.swift +++ b/Tests/SQLiteTests/ExpressionTests.swift @@ -1,6 +1,5 @@ import XCTest import SQLite -class ExpressionTests : XCTestCase { - +class ExpressionTests: XCTestCase { } diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index 79f0a8e2..b637955b 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class FTS4Tests : XCTestCase { +class FTS4Tests: XCTestCase { func test_create_onVirtualTable_withFTS4_compilesCreateVirtualTableExpression() { XCTAssertEqual( @@ -25,26 +25,33 @@ class FTS4Tests : XCTestCase { virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: false))) ) XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", - virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"]))) + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + \"tokenchars=.\" \"separators=X\") + """.replacingOccurrences(of: "\n", with: ""), + virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, + tokenchars: ["."], separators: ["X"]))) ) } 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 Expression) + assertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression) + assertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression) } func test_match_onVirtualTableAsQueryType_compilesMatchExpression() { - AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH 'string')", virtualTable.match("string") as QueryType) - AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as QueryType) - AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as QueryType) + assertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH 'string')", + virtualTable.match("string") as QueryType) + assertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"string\")", + virtualTable.match(string) as QueryType) + assertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"stringOptional\")", + virtualTable.match(stringOptional) as QueryType) } } -class FTS4ConfigTests : XCTestCase { +class FTS4ConfigTests: XCTestCase { var config: FTS4Config! override func setUp() { @@ -108,7 +115,10 @@ class FTS4ConfigTests : XCTestCase { func test_tokenizer_unicode61_with_options() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + \"tokenchars=.\" \"separators=X\") + """.replacingOccurrences(of: "\n", with: ""), sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) } @@ -156,7 +166,11 @@ class FTS4ConfigTests : XCTestCase { func test_config_all() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"int\", \"string\", \"date\", tokenize=porter, prefix=\"2,4\", content=\"table\", notindexed=\"string\", notindexed=\"date\", languageid=\"lid\", matchinfo=\"fts3\", order=\"desc\")", + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"int\", \"string\", \"date\", + tokenize=porter, prefix=\"2,4\", content=\"table\", notindexed=\"string\", notindexed=\"date\", + languageid=\"lid\", matchinfo=\"fts3\", order=\"desc\") + """.replacingOccurrences(of: "\n", with: ""), sql(config .tokenizer(.Porter) .column(int) @@ -175,7 +189,7 @@ class FTS4ConfigTests : XCTestCase { } } -class FTS4IntegrationTests : SQLiteTestCase { +class FTS4IntegrationTests: SQLiteTestCase { #if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER func test_registerTokenizer_registersTokenizer() { let emails = VirtualTable("emails") @@ -184,9 +198,11 @@ class FTS4IntegrationTests : SQLiteTestCase { let locale = CFLocaleCopyCurrent() let tokenizerName = "tokenizer" - let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + 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))) + CFStringTokenizerSetString(tokenizer, string as CFString, + CFRangeMake(0, CFStringGetLength(string as CFString))) if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { return nil } @@ -199,7 +215,10 @@ class FTS4IntegrationTests : SQLiteTestCase { } try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") + 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)) diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index 63d8dc40..8079b28c 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -107,7 +107,10 @@ class FTS5Tests: XCTestCase { func test_fts5_config_all() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"int\", \"string\" UNINDEXED, \"date\" UNINDEXED, tokenize=porter, prefix=\"2,4\", content=\"table\")", + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"int\", \"string\" UNINDEXED, \"date\" UNINDEXED, + tokenize=porter, prefix=\"2,4\", content=\"table\") + """.replacingOccurrences(of: "\n", with: ""), sql(config .tokenizer(.Porter) .column(int) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index ba9685b7..cef485fc 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class FoundationTests : XCTestCase { +class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index c9665671..370b910b 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -1,378 +1,378 @@ import XCTest import SQLite -class OperatorsTests : XCTestCase { +class OperatorsTests: XCTestCase { func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { - AssertSQL("(\"string\" || \"string\")", string + string) - AssertSQL("(\"string\" || \"stringOptional\")", string + stringOptional) - AssertSQL("(\"stringOptional\" || \"string\")", stringOptional + string) - AssertSQL("(\"stringOptional\" || \"stringOptional\")", stringOptional + stringOptional) - AssertSQL("(\"string\" || 'literal')", string + "literal") - AssertSQL("(\"stringOptional\" || 'literal')", stringOptional + "literal") - AssertSQL("('literal' || \"string\")", "literal" + string) - AssertSQL("('literal' || \"stringOptional\")", "literal" + stringOptional) + assertSQL("(\"string\" || \"string\")", string + string) + assertSQL("(\"string\" || \"stringOptional\")", string + stringOptional) + assertSQL("(\"stringOptional\" || \"string\")", stringOptional + string) + assertSQL("(\"stringOptional\" || \"stringOptional\")", stringOptional + stringOptional) + assertSQL("(\"string\" || 'literal')", string + "literal") + assertSQL("(\"stringOptional\" || 'literal')", stringOptional + "literal") + assertSQL("('literal' || \"string\")", "literal" + string) + assertSQL("('literal' || \"stringOptional\")", "literal" + stringOptional) } func test_numberExpression_plusNumberExpression_buildsAdditiveNumberExpression() { - AssertSQL("(\"int\" + \"int\")", int + int) - AssertSQL("(\"int\" + \"intOptional\")", int + intOptional) - AssertSQL("(\"intOptional\" + \"int\")", intOptional + int) - AssertSQL("(\"intOptional\" + \"intOptional\")", intOptional + intOptional) - AssertSQL("(\"int\" + 1)", int + 1) - AssertSQL("(\"intOptional\" + 1)", intOptional + 1) - AssertSQL("(1 + \"int\")", 1 + int) - AssertSQL("(1 + \"intOptional\")", 1 + intOptional) - - AssertSQL("(\"double\" + \"double\")", double + double) - AssertSQL("(\"double\" + \"doubleOptional\")", double + doubleOptional) - AssertSQL("(\"doubleOptional\" + \"double\")", doubleOptional + double) - AssertSQL("(\"doubleOptional\" + \"doubleOptional\")", doubleOptional + doubleOptional) - AssertSQL("(\"double\" + 1.0)", double + 1) - AssertSQL("(\"doubleOptional\" + 1.0)", doubleOptional + 1) - AssertSQL("(1.0 + \"double\")", 1 + double) - AssertSQL("(1.0 + \"doubleOptional\")", 1 + doubleOptional) + assertSQL("(\"int\" + \"int\")", int + int) + assertSQL("(\"int\" + \"intOptional\")", int + intOptional) + assertSQL("(\"intOptional\" + \"int\")", intOptional + int) + assertSQL("(\"intOptional\" + \"intOptional\")", intOptional + intOptional) + assertSQL("(\"int\" + 1)", int + 1) + assertSQL("(\"intOptional\" + 1)", intOptional + 1) + assertSQL("(1 + \"int\")", 1 + int) + assertSQL("(1 + \"intOptional\")", 1 + intOptional) + + assertSQL("(\"double\" + \"double\")", double + double) + assertSQL("(\"double\" + \"doubleOptional\")", double + doubleOptional) + assertSQL("(\"doubleOptional\" + \"double\")", doubleOptional + double) + assertSQL("(\"doubleOptional\" + \"doubleOptional\")", doubleOptional + doubleOptional) + assertSQL("(\"double\" + 1.0)", double + 1) + assertSQL("(\"doubleOptional\" + 1.0)", doubleOptional + 1) + assertSQL("(1.0 + \"double\")", 1 + double) + assertSQL("(1.0 + \"doubleOptional\")", 1 + doubleOptional) } func test_numberExpression_minusNumberExpression_buildsSubtractiveNumberExpression() { - AssertSQL("(\"int\" - \"int\")", int - int) - AssertSQL("(\"int\" - \"intOptional\")", int - intOptional) - AssertSQL("(\"intOptional\" - \"int\")", intOptional - int) - AssertSQL("(\"intOptional\" - \"intOptional\")", intOptional - intOptional) - AssertSQL("(\"int\" - 1)", int - 1) - AssertSQL("(\"intOptional\" - 1)", intOptional - 1) - AssertSQL("(1 - \"int\")", 1 - int) - AssertSQL("(1 - \"intOptional\")", 1 - intOptional) - - AssertSQL("(\"double\" - \"double\")", double - double) - AssertSQL("(\"double\" - \"doubleOptional\")", double - doubleOptional) - AssertSQL("(\"doubleOptional\" - \"double\")", doubleOptional - double) - AssertSQL("(\"doubleOptional\" - \"doubleOptional\")", doubleOptional - doubleOptional) - AssertSQL("(\"double\" - 1.0)", double - 1) - AssertSQL("(\"doubleOptional\" - 1.0)", doubleOptional - 1) - AssertSQL("(1.0 - \"double\")", 1 - double) - AssertSQL("(1.0 - \"doubleOptional\")", 1 - doubleOptional) + assertSQL("(\"int\" - \"int\")", int - int) + assertSQL("(\"int\" - \"intOptional\")", int - intOptional) + assertSQL("(\"intOptional\" - \"int\")", intOptional - int) + assertSQL("(\"intOptional\" - \"intOptional\")", intOptional - intOptional) + assertSQL("(\"int\" - 1)", int - 1) + assertSQL("(\"intOptional\" - 1)", intOptional - 1) + assertSQL("(1 - \"int\")", 1 - int) + assertSQL("(1 - \"intOptional\")", 1 - intOptional) + + assertSQL("(\"double\" - \"double\")", double - double) + assertSQL("(\"double\" - \"doubleOptional\")", double - doubleOptional) + assertSQL("(\"doubleOptional\" - \"double\")", doubleOptional - double) + assertSQL("(\"doubleOptional\" - \"doubleOptional\")", doubleOptional - doubleOptional) + assertSQL("(\"double\" - 1.0)", double - 1) + assertSQL("(\"doubleOptional\" - 1.0)", doubleOptional - 1) + assertSQL("(1.0 - \"double\")", 1 - double) + assertSQL("(1.0 - \"doubleOptional\")", 1 - doubleOptional) } func test_numberExpression_timesNumberExpression_buildsMultiplicativeNumberExpression() { - AssertSQL("(\"int\" * \"int\")", int * int) - AssertSQL("(\"int\" * \"intOptional\")", int * intOptional) - AssertSQL("(\"intOptional\" * \"int\")", intOptional * int) - AssertSQL("(\"intOptional\" * \"intOptional\")", intOptional * intOptional) - AssertSQL("(\"int\" * 1)", int * 1) - AssertSQL("(\"intOptional\" * 1)", intOptional * 1) - AssertSQL("(1 * \"int\")", 1 * int) - AssertSQL("(1 * \"intOptional\")", 1 * intOptional) - - AssertSQL("(\"double\" * \"double\")", double * double) - AssertSQL("(\"double\" * \"doubleOptional\")", double * doubleOptional) - AssertSQL("(\"doubleOptional\" * \"double\")", doubleOptional * double) - AssertSQL("(\"doubleOptional\" * \"doubleOptional\")", doubleOptional * doubleOptional) - AssertSQL("(\"double\" * 1.0)", double * 1) - AssertSQL("(\"doubleOptional\" * 1.0)", doubleOptional * 1) - AssertSQL("(1.0 * \"double\")", 1 * double) - AssertSQL("(1.0 * \"doubleOptional\")", 1 * doubleOptional) + assertSQL("(\"int\" * \"int\")", int * int) + assertSQL("(\"int\" * \"intOptional\")", int * intOptional) + assertSQL("(\"intOptional\" * \"int\")", intOptional * int) + assertSQL("(\"intOptional\" * \"intOptional\")", intOptional * intOptional) + assertSQL("(\"int\" * 1)", int * 1) + assertSQL("(\"intOptional\" * 1)", intOptional * 1) + assertSQL("(1 * \"int\")", 1 * int) + assertSQL("(1 * \"intOptional\")", 1 * intOptional) + + assertSQL("(\"double\" * \"double\")", double * double) + assertSQL("(\"double\" * \"doubleOptional\")", double * doubleOptional) + assertSQL("(\"doubleOptional\" * \"double\")", doubleOptional * double) + assertSQL("(\"doubleOptional\" * \"doubleOptional\")", doubleOptional * doubleOptional) + assertSQL("(\"double\" * 1.0)", double * 1) + assertSQL("(\"doubleOptional\" * 1.0)", doubleOptional * 1) + assertSQL("(1.0 * \"double\")", 1 * double) + assertSQL("(1.0 * \"doubleOptional\")", 1 * doubleOptional) } func test_numberExpression_dividedByNumberExpression_buildsDivisiveNumberExpression() { - AssertSQL("(\"int\" / \"int\")", int / int) - AssertSQL("(\"int\" / \"intOptional\")", int / intOptional) - AssertSQL("(\"intOptional\" / \"int\")", intOptional / int) - AssertSQL("(\"intOptional\" / \"intOptional\")", intOptional / intOptional) - AssertSQL("(\"int\" / 1)", int / 1) - AssertSQL("(\"intOptional\" / 1)", intOptional / 1) - AssertSQL("(1 / \"int\")", 1 / int) - AssertSQL("(1 / \"intOptional\")", 1 / intOptional) - - AssertSQL("(\"double\" / \"double\")", double / double) - AssertSQL("(\"double\" / \"doubleOptional\")", double / doubleOptional) - AssertSQL("(\"doubleOptional\" / \"double\")", doubleOptional / double) - AssertSQL("(\"doubleOptional\" / \"doubleOptional\")", doubleOptional / doubleOptional) - AssertSQL("(\"double\" / 1.0)", double / 1) - AssertSQL("(\"doubleOptional\" / 1.0)", doubleOptional / 1) - AssertSQL("(1.0 / \"double\")", 1 / double) - AssertSQL("(1.0 / \"doubleOptional\")", 1 / doubleOptional) + assertSQL("(\"int\" / \"int\")", int / int) + assertSQL("(\"int\" / \"intOptional\")", int / intOptional) + assertSQL("(\"intOptional\" / \"int\")", intOptional / int) + assertSQL("(\"intOptional\" / \"intOptional\")", intOptional / intOptional) + assertSQL("(\"int\" / 1)", int / 1) + assertSQL("(\"intOptional\" / 1)", intOptional / 1) + assertSQL("(1 / \"int\")", 1 / int) + assertSQL("(1 / \"intOptional\")", 1 / intOptional) + + assertSQL("(\"double\" / \"double\")", double / double) + assertSQL("(\"double\" / \"doubleOptional\")", double / doubleOptional) + assertSQL("(\"doubleOptional\" / \"double\")", doubleOptional / double) + assertSQL("(\"doubleOptional\" / \"doubleOptional\")", doubleOptional / doubleOptional) + assertSQL("(\"double\" / 1.0)", double / 1) + assertSQL("(\"doubleOptional\" / 1.0)", doubleOptional / 1) + assertSQL("(1.0 / \"double\")", 1 / double) + assertSQL("(1.0 / \"doubleOptional\")", 1 / doubleOptional) } func test_numberExpression_prefixedWithMinus_buildsInvertedNumberExpression() { - AssertSQL("-(\"int\")", -int) - AssertSQL("-(\"intOptional\")", -intOptional) + assertSQL("-(\"int\")", -int) + assertSQL("-(\"intOptional\")", -intOptional) - AssertSQL("-(\"double\")", -double) - AssertSQL("-(\"doubleOptional\")", -doubleOptional) + assertSQL("-(\"double\")", -double) + assertSQL("-(\"doubleOptional\")", -doubleOptional) } func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { - AssertSQL("(\"int\" % \"int\")", int % int) - AssertSQL("(\"int\" % \"intOptional\")", int % intOptional) - AssertSQL("(\"intOptional\" % \"int\")", intOptional % int) - AssertSQL("(\"intOptional\" % \"intOptional\")", intOptional % intOptional) - AssertSQL("(\"int\" % 1)", int % 1) - AssertSQL("(\"intOptional\" % 1)", intOptional % 1) - AssertSQL("(1 % \"int\")", 1 % int) - AssertSQL("(1 % \"intOptional\")", 1 % intOptional) + assertSQL("(\"int\" % \"int\")", int % int) + assertSQL("(\"int\" % \"intOptional\")", int % intOptional) + assertSQL("(\"intOptional\" % \"int\")", intOptional % int) + assertSQL("(\"intOptional\" % \"intOptional\")", intOptional % intOptional) + assertSQL("(\"int\" % 1)", int % 1) + assertSQL("(\"intOptional\" % 1)", intOptional % 1) + assertSQL("(1 % \"int\")", 1 % int) + assertSQL("(1 % \"intOptional\")", 1 % intOptional) } func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { - AssertSQL("(\"int\" << \"int\")", int << int) - AssertSQL("(\"int\" << \"intOptional\")", int << intOptional) - AssertSQL("(\"intOptional\" << \"int\")", intOptional << int) - AssertSQL("(\"intOptional\" << \"intOptional\")", intOptional << intOptional) - AssertSQL("(\"int\" << 1)", int << 1) - AssertSQL("(\"intOptional\" << 1)", intOptional << 1) - AssertSQL("(1 << \"int\")", 1 << int) - AssertSQL("(1 << \"intOptional\")", 1 << intOptional) + assertSQL("(\"int\" << \"int\")", int << int) + assertSQL("(\"int\" << \"intOptional\")", int << intOptional) + assertSQL("(\"intOptional\" << \"int\")", intOptional << int) + assertSQL("(\"intOptional\" << \"intOptional\")", intOptional << intOptional) + assertSQL("(\"int\" << 1)", int << 1) + assertSQL("(\"intOptional\" << 1)", intOptional << 1) + assertSQL("(1 << \"int\")", 1 << int) + assertSQL("(1 << \"intOptional\")", 1 << intOptional) } func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { - AssertSQL("(\"int\" >> \"int\")", int >> int) - AssertSQL("(\"int\" >> \"intOptional\")", int >> intOptional) - AssertSQL("(\"intOptional\" >> \"int\")", intOptional >> int) - AssertSQL("(\"intOptional\" >> \"intOptional\")", intOptional >> intOptional) - AssertSQL("(\"int\" >> 1)", int >> 1) - AssertSQL("(\"intOptional\" >> 1)", intOptional >> 1) - AssertSQL("(1 >> \"int\")", 1 >> int) - AssertSQL("(1 >> \"intOptional\")", 1 >> intOptional) + assertSQL("(\"int\" >> \"int\")", int >> int) + assertSQL("(\"int\" >> \"intOptional\")", int >> intOptional) + assertSQL("(\"intOptional\" >> \"int\")", intOptional >> int) + assertSQL("(\"intOptional\" >> \"intOptional\")", intOptional >> intOptional) + assertSQL("(\"int\" >> 1)", int >> 1) + assertSQL("(\"intOptional\" >> 1)", intOptional >> 1) + assertSQL("(1 >> \"int\")", 1 >> int) + assertSQL("(1 >> \"intOptional\")", 1 >> intOptional) } func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { - AssertSQL("(\"int\" & \"int\")", int & int) - AssertSQL("(\"int\" & \"intOptional\")", int & intOptional) - AssertSQL("(\"intOptional\" & \"int\")", intOptional & int) - AssertSQL("(\"intOptional\" & \"intOptional\")", intOptional & intOptional) - AssertSQL("(\"int\" & 1)", int & 1) - AssertSQL("(\"intOptional\" & 1)", intOptional & 1) - AssertSQL("(1 & \"int\")", 1 & int) - AssertSQL("(1 & \"intOptional\")", 1 & intOptional) + assertSQL("(\"int\" & \"int\")", int & int) + assertSQL("(\"int\" & \"intOptional\")", int & intOptional) + assertSQL("(\"intOptional\" & \"int\")", intOptional & int) + assertSQL("(\"intOptional\" & \"intOptional\")", intOptional & intOptional) + assertSQL("(\"int\" & 1)", int & 1) + assertSQL("(\"intOptional\" & 1)", intOptional & 1) + assertSQL("(1 & \"int\")", 1 & int) + assertSQL("(1 & \"intOptional\")", 1 & intOptional) } func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQL("(\"int\" | \"int\")", int | int) - AssertSQL("(\"int\" | \"intOptional\")", int | intOptional) - AssertSQL("(\"intOptional\" | \"int\")", intOptional | int) - AssertSQL("(\"intOptional\" | \"intOptional\")", intOptional | intOptional) - AssertSQL("(\"int\" | 1)", int | 1) - AssertSQL("(\"intOptional\" | 1)", intOptional | 1) - AssertSQL("(1 | \"int\")", 1 | int) - AssertSQL("(1 | \"intOptional\")", 1 | intOptional) + assertSQL("(\"int\" | \"int\")", int | int) + assertSQL("(\"int\" | \"intOptional\")", int | intOptional) + assertSQL("(\"intOptional\" | \"int\")", intOptional | int) + assertSQL("(\"intOptional\" | \"intOptional\")", intOptional | intOptional) + assertSQL("(\"int\" | 1)", int | 1) + assertSQL("(\"intOptional\" | 1)", intOptional | 1) + assertSQL("(1 | \"int\")", 1 | int) + assertSQL("(1 | \"intOptional\")", 1 | intOptional) } func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQL("(~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^ int) - AssertSQL("(~((\"int\" & \"intOptional\")) & (\"int\" | \"intOptional\"))", int ^ intOptional) - AssertSQL("(~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^ int) - AssertSQL("(~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^ intOptional) - AssertSQL("(~((\"int\" & 1)) & (\"int\" | 1))", int ^ 1) - AssertSQL("(~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^ 1) - AssertSQL("(~((1 & \"int\")) & (1 | \"int\"))", 1 ^ int) - AssertSQL("(~((1 & \"intOptional\")) & (1 | \"intOptional\"))", 1 ^ intOptional) + assertSQL("(~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^ int) + assertSQL("(~((\"int\" & \"intOptional\")) & (\"int\" | \"intOptional\"))", int ^ intOptional) + assertSQL("(~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^ int) + assertSQL("(~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^ intOptional) + assertSQL("(~((\"int\" & 1)) & (\"int\" | 1))", int ^ 1) + assertSQL("(~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^ 1) + assertSQL("(~((1 & \"int\")) & (1 | \"int\"))", 1 ^ int) + assertSQL("(~((1 & \"intOptional\")) & (1 | \"intOptional\"))", 1 ^ intOptional) } func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { - AssertSQL("~(\"int\")", ~int) - AssertSQL("~(\"intOptional\")", ~intOptional) + assertSQL("~(\"int\")", ~int) + assertSQL("~(\"intOptional\")", ~intOptional) } func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" = \"bool\")", bool == bool) - AssertSQL("(\"bool\" = \"boolOptional\")", bool == boolOptional) - AssertSQL("(\"boolOptional\" = \"bool\")", boolOptional == bool) - AssertSQL("(\"boolOptional\" = \"boolOptional\")", boolOptional == boolOptional) - AssertSQL("(\"bool\" = 1)", bool == true) - AssertSQL("(\"boolOptional\" = 1)", boolOptional == true) - AssertSQL("(1 = \"bool\")", true == bool) - AssertSQL("(1 = \"boolOptional\")", true == boolOptional) + assertSQL("(\"bool\" = \"bool\")", bool == bool) + assertSQL("(\"bool\" = \"boolOptional\")", bool == boolOptional) + assertSQL("(\"boolOptional\" = \"bool\")", boolOptional == bool) + assertSQL("(\"boolOptional\" = \"boolOptional\")", boolOptional == boolOptional) + assertSQL("(\"bool\" = 1)", bool == true) + assertSQL("(\"boolOptional\" = 1)", boolOptional == true) + assertSQL("(1 = \"bool\")", true == bool) + assertSQL("(1 = \"boolOptional\")", true == boolOptional) - AssertSQL("(\"boolOptional\" IS NULL)", boolOptional == nil) - AssertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) + assertSQL("(\"boolOptional\" IS NULL)", boolOptional == nil) + assertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) } func test_isOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" IS \"bool\")", bool === bool) - AssertSQL("(\"bool\" IS \"boolOptional\")", bool === boolOptional) - AssertSQL("(\"boolOptional\" IS \"bool\")", boolOptional === bool) - AssertSQL("(\"boolOptional\" IS \"boolOptional\")", boolOptional === boolOptional) - AssertSQL("(\"bool\" IS 1)", bool === true) - AssertSQL("(\"boolOptional\" IS 1)", boolOptional === true) - AssertSQL("(1 IS \"bool\")", true === bool) - AssertSQL("(1 IS \"boolOptional\")", true === boolOptional) - - AssertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) - AssertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) - } - + assertSQL("(\"bool\" IS \"bool\")", bool === bool) + assertSQL("(\"bool\" IS \"boolOptional\")", bool === boolOptional) + assertSQL("(\"boolOptional\" IS \"bool\")", boolOptional === bool) + assertSQL("(\"boolOptional\" IS \"boolOptional\")", boolOptional === boolOptional) + assertSQL("(\"bool\" IS 1)", bool === true) + assertSQL("(\"boolOptional\" IS 1)", boolOptional === true) + assertSQL("(1 IS \"bool\")", true === bool) + assertSQL("(1 IS \"boolOptional\")", true === boolOptional) + + assertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) + assertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) + } + func test_isNotOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" IS NOT \"bool\")", bool !== bool) - AssertSQL("(\"bool\" IS NOT \"boolOptional\")", bool !== boolOptional) - AssertSQL("(\"boolOptional\" IS NOT \"bool\")", boolOptional !== bool) - AssertSQL("(\"boolOptional\" IS NOT \"boolOptional\")", boolOptional !== boolOptional) - AssertSQL("(\"bool\" IS NOT 1)", bool !== true) - AssertSQL("(\"boolOptional\" IS NOT 1)", boolOptional !== true) - AssertSQL("(1 IS NOT \"bool\")", true !== bool) - AssertSQL("(1 IS NOT \"boolOptional\")", true !== boolOptional) - - AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional !== nil) - AssertSQL("(NULL IS NOT \"boolOptional\")", nil !== boolOptional) + assertSQL("(\"bool\" IS NOT \"bool\")", bool !== bool) + assertSQL("(\"bool\" IS NOT \"boolOptional\")", bool !== boolOptional) + assertSQL("(\"boolOptional\" IS NOT \"bool\")", boolOptional !== bool) + assertSQL("(\"boolOptional\" IS NOT \"boolOptional\")", boolOptional !== boolOptional) + assertSQL("(\"bool\" IS NOT 1)", bool !== true) + assertSQL("(\"boolOptional\" IS NOT 1)", boolOptional !== true) + assertSQL("(1 IS NOT \"bool\")", true !== bool) + assertSQL("(1 IS NOT \"boolOptional\")", true !== boolOptional) + + assertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional !== nil) + assertSQL("(NULL IS NOT \"boolOptional\")", nil !== boolOptional) } func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" != \"bool\")", bool != bool) - AssertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) - AssertSQL("(\"boolOptional\" != \"bool\")", boolOptional != bool) - AssertSQL("(\"boolOptional\" != \"boolOptional\")", boolOptional != boolOptional) - AssertSQL("(\"bool\" != 1)", bool != true) - AssertSQL("(\"boolOptional\" != 1)", boolOptional != true) - AssertSQL("(1 != \"bool\")", true != bool) - AssertSQL("(1 != \"boolOptional\")", true != boolOptional) + assertSQL("(\"bool\" != \"bool\")", bool != bool) + assertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) + assertSQL("(\"boolOptional\" != \"bool\")", boolOptional != bool) + assertSQL("(\"boolOptional\" != \"boolOptional\")", boolOptional != boolOptional) + assertSQL("(\"bool\" != 1)", bool != true) + assertSQL("(\"boolOptional\" != 1)", boolOptional != true) + assertSQL("(1 != \"bool\")", true != bool) + assertSQL("(1 != \"boolOptional\")", true != boolOptional) - AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional != nil) - AssertSQL("(NULL IS NOT \"boolOptional\")", nil != boolOptional) + assertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional != nil) + assertSQL("(NULL IS NOT \"boolOptional\")", nil != boolOptional) } func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" > \"bool\")", bool > bool) - AssertSQL("(\"bool\" > \"boolOptional\")", bool > boolOptional) - AssertSQL("(\"boolOptional\" > \"bool\")", boolOptional > bool) - AssertSQL("(\"boolOptional\" > \"boolOptional\")", boolOptional > boolOptional) - AssertSQL("(\"bool\" > 1)", bool > true) - AssertSQL("(\"boolOptional\" > 1)", boolOptional > true) - AssertSQL("(1 > \"bool\")", true > bool) - AssertSQL("(1 > \"boolOptional\")", true > boolOptional) + assertSQL("(\"bool\" > \"bool\")", bool > bool) + assertSQL("(\"bool\" > \"boolOptional\")", bool > boolOptional) + assertSQL("(\"boolOptional\" > \"bool\")", boolOptional > bool) + assertSQL("(\"boolOptional\" > \"boolOptional\")", boolOptional > boolOptional) + assertSQL("(\"bool\" > 1)", bool > true) + assertSQL("(\"boolOptional\" > 1)", boolOptional > true) + assertSQL("(1 > \"bool\")", true > bool) + assertSQL("(1 > \"boolOptional\")", true > boolOptional) } func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" >= \"bool\")", bool >= bool) - AssertSQL("(\"bool\" >= \"boolOptional\")", bool >= boolOptional) - AssertSQL("(\"boolOptional\" >= \"bool\")", boolOptional >= bool) - AssertSQL("(\"boolOptional\" >= \"boolOptional\")", boolOptional >= boolOptional) - AssertSQL("(\"bool\" >= 1)", bool >= true) - AssertSQL("(\"boolOptional\" >= 1)", boolOptional >= true) - AssertSQL("(1 >= \"bool\")", true >= bool) - AssertSQL("(1 >= \"boolOptional\")", true >= boolOptional) + assertSQL("(\"bool\" >= \"bool\")", bool >= bool) + assertSQL("(\"bool\" >= \"boolOptional\")", bool >= boolOptional) + assertSQL("(\"boolOptional\" >= \"bool\")", boolOptional >= bool) + assertSQL("(\"boolOptional\" >= \"boolOptional\")", boolOptional >= boolOptional) + assertSQL("(\"bool\" >= 1)", bool >= true) + assertSQL("(\"boolOptional\" >= 1)", boolOptional >= true) + assertSQL("(1 >= \"bool\")", true >= bool) + assertSQL("(1 >= \"boolOptional\")", true >= boolOptional) } func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" < \"bool\")", bool < bool) - AssertSQL("(\"bool\" < \"boolOptional\")", bool < boolOptional) - AssertSQL("(\"boolOptional\" < \"bool\")", boolOptional < bool) - AssertSQL("(\"boolOptional\" < \"boolOptional\")", boolOptional < boolOptional) - AssertSQL("(\"bool\" < 1)", bool < true) - AssertSQL("(\"boolOptional\" < 1)", boolOptional < true) - AssertSQL("(1 < \"bool\")", true < bool) - AssertSQL("(1 < \"boolOptional\")", true < boolOptional) + assertSQL("(\"bool\" < \"bool\")", bool < bool) + assertSQL("(\"bool\" < \"boolOptional\")", bool < boolOptional) + assertSQL("(\"boolOptional\" < \"bool\")", boolOptional < bool) + assertSQL("(\"boolOptional\" < \"boolOptional\")", boolOptional < boolOptional) + assertSQL("(\"bool\" < 1)", bool < true) + assertSQL("(\"boolOptional\" < 1)", boolOptional < true) + assertSQL("(1 < \"bool\")", true < bool) + assertSQL("(1 < \"boolOptional\")", true < boolOptional) } func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" <= \"bool\")", bool <= bool) - AssertSQL("(\"bool\" <= \"boolOptional\")", bool <= boolOptional) - AssertSQL("(\"boolOptional\" <= \"bool\")", boolOptional <= bool) - AssertSQL("(\"boolOptional\" <= \"boolOptional\")", boolOptional <= boolOptional) - AssertSQL("(\"bool\" <= 1)", bool <= true) - AssertSQL("(\"boolOptional\" <= 1)", boolOptional <= true) - AssertSQL("(1 <= \"bool\")", true <= bool) - AssertSQL("(1 <= \"boolOptional\")", true <= boolOptional) + assertSQL("(\"bool\" <= \"bool\")", bool <= bool) + assertSQL("(\"bool\" <= \"boolOptional\")", bool <= boolOptional) + assertSQL("(\"boolOptional\" <= \"bool\")", boolOptional <= bool) + assertSQL("(\"boolOptional\" <= \"boolOptional\")", boolOptional <= boolOptional) + assertSQL("(\"bool\" <= 1)", bool <= true) + assertSQL("(\"boolOptional\" <= 1)", boolOptional <= true) + assertSQL("(1 <= \"bool\")", true <= bool) + assertSQL("(1 <= \"boolOptional\")", true <= boolOptional) } func test_patternMatchingOperator_withComparableCountableClosedRange_buildsBetweenBooleanExpression() { - AssertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int) - AssertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional) + assertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int) + assertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional) } func test_patternMatchingOperator_withComparableClosedRange_buildsBetweenBooleanExpression() { - AssertSQL("\"double\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= double) - AssertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) + assertSQL("\"double\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= double) + assertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparableRange_buildsBooleanExpression() { - AssertSQL("\"double\" >= 1.2 AND \"double\" < 4.5", 1.2..<4.5 ~= double) - AssertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) + assertSQL("\"double\" >= 1.2 AND \"double\" < 4.5", 1.2..<4.5 ~= double) + assertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparablePartialRangeThrough_buildsBooleanExpression() { - AssertSQL("\"double\" <= 4.5", ...4.5 ~= double) - AssertSQL("\"doubleOptional\" <= 4.5", ...4.5 ~= doubleOptional) + assertSQL("\"double\" <= 4.5", ...4.5 ~= double) + assertSQL("\"doubleOptional\" <= 4.5", ...4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparablePartialRangeUpTo_buildsBooleanExpression() { - AssertSQL("\"double\" < 4.5", ..<4.5 ~= double) - AssertSQL("\"doubleOptional\" < 4.5", ..<4.5 ~= doubleOptional) + assertSQL("\"double\" < 4.5", ..<4.5 ~= double) + assertSQL("\"doubleOptional\" < 4.5", ..<4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparablePartialRangeFrom_buildsBooleanExpression() { - AssertSQL("\"double\" >= 4.5", 4.5... ~= double) - AssertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) + assertSQL("\"double\" >= 4.5", 4.5... ~= double) + assertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) } func test_patternMatchingOperator_withComparableClosedRangeString_buildsBetweenBooleanExpression() { - AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) - AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) + assertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) + assertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) } func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" AND \"bool\")", bool && bool) - AssertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) - AssertSQL("(\"boolOptional\" AND \"bool\")", boolOptional && bool) - AssertSQL("(\"boolOptional\" AND \"boolOptional\")", boolOptional && boolOptional) - AssertSQL("(\"bool\" AND 1)", bool && true) - AssertSQL("(\"boolOptional\" AND 1)", boolOptional && true) - AssertSQL("(1 AND \"bool\")", true && bool) - AssertSQL("(1 AND \"boolOptional\")", true && boolOptional) - } - + assertSQL("(\"bool\" AND \"bool\")", bool && bool) + assertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) + assertSQL("(\"boolOptional\" AND \"bool\")", boolOptional && bool) + assertSQL("(\"boolOptional\" AND \"boolOptional\")", boolOptional && boolOptional) + assertSQL("(\"bool\" AND 1)", bool && true) + assertSQL("(\"boolOptional\" AND 1)", boolOptional && true) + assertSQL("(1 AND \"bool\")", true && bool) + assertSQL("(1 AND \"boolOptional\")", true && boolOptional) + } + func test_andFunction_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and([bool, bool, bool])) - AssertSQL("(\"bool\" AND \"bool\")", and([bool, bool])) - AssertSQL("(\"bool\")", and([bool])) - - AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and(bool, bool, bool)) - AssertSQL("(\"bool\" AND \"bool\")", and(bool, bool)) - AssertSQL("(\"bool\")", and(bool)) + assertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and([bool, bool, bool])) + assertSQL("(\"bool\" AND \"bool\")", and([bool, bool])) + assertSQL("(\"bool\")", and([bool])) + + assertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and(bool, bool, bool)) + assertSQL("(\"bool\" AND \"bool\")", and(bool, bool)) + assertSQL("(\"bool\")", and(bool)) } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" OR \"bool\")", bool || bool) - AssertSQL("(\"bool\" OR \"boolOptional\")", bool || boolOptional) - AssertSQL("(\"boolOptional\" OR \"bool\")", boolOptional || bool) - AssertSQL("(\"boolOptional\" OR \"boolOptional\")", boolOptional || boolOptional) - AssertSQL("(\"bool\" OR 1)", bool || true) - AssertSQL("(\"boolOptional\" OR 1)", boolOptional || true) - AssertSQL("(1 OR \"bool\")", true || bool) - AssertSQL("(1 OR \"boolOptional\")", true || boolOptional) - } - + assertSQL("(\"bool\" OR \"bool\")", bool || bool) + assertSQL("(\"bool\" OR \"boolOptional\")", bool || boolOptional) + assertSQL("(\"boolOptional\" OR \"bool\")", boolOptional || bool) + assertSQL("(\"boolOptional\" OR \"boolOptional\")", boolOptional || boolOptional) + assertSQL("(\"bool\" OR 1)", bool || true) + assertSQL("(\"boolOptional\" OR 1)", boolOptional || true) + assertSQL("(1 OR \"bool\")", true || bool) + assertSQL("(1 OR \"boolOptional\")", true || boolOptional) + } + func test_orFunction_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or([bool, bool, bool])) - AssertSQL("(\"bool\" OR \"bool\")", or([bool, bool])) - AssertSQL("(\"bool\")", or([bool])) - - AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or(bool, bool, bool)) - AssertSQL("(\"bool\" OR \"bool\")", or(bool, bool)) - AssertSQL("(\"bool\")", or(bool)) + assertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or([bool, bool, bool])) + assertSQL("(\"bool\" OR \"bool\")", or([bool, bool])) + assertSQL("(\"bool\")", or([bool])) + + assertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or(bool, bool, bool)) + assertSQL("(\"bool\" OR \"bool\")", or(bool, bool)) + assertSQL("(\"bool\")", or(bool)) } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { - AssertSQL("NOT (\"bool\")", !bool) - AssertSQL("NOT (\"boolOptional\")", !boolOptional) + assertSQL("NOT (\"bool\")", !bool) + assertSQL("NOT (\"boolOptional\")", !boolOptional) } func test_precedencePreserved() { let n = 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)) + 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)) } func test_dateExpressionLessGreater() { let begin = Date(timeIntervalSince1970: 0) - AssertSQL("(\"date\" < '1970-01-01T00:00:00.000')", date < begin) - AssertSQL("(\"date\" > '1970-01-01T00:00:00.000')", date > begin) - AssertSQL("(\"date\" >= '1970-01-01T00:00:00.000')", date >= begin) - AssertSQL("(\"date\" <= '1970-01-01T00:00:00.000')", date <= begin) + assertSQL("(\"date\" < '1970-01-01T00:00:00.000')", date < begin) + assertSQL("(\"date\" > '1970-01-01T00:00:00.000')", date > begin) + assertSQL("(\"date\" >= '1970-01-01T00:00:00.000')", date >= begin) + assertSQL("(\"date\" <= '1970-01-01T00:00:00.000')", date <= begin) } func test_dateExpressionRange() { let begin = Date(timeIntervalSince1970: 0) let end = Date(timeIntervalSince1970: 5000) - AssertSQL( + assertSQL( "\"date\" >= '1970-01-01T00:00:00.000' AND \"date\" < '1970-01-01T01:23:20.000'", (begin..("id") @@ -28,65 +28,65 @@ class QueryTests : XCTestCase { let tag = Expression("tag") func test_select_withExpression_compilesSelectClause() { - AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) + assertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) } func test_select_withStarExpression_compilesSelectClause() { - AssertSQL("SELECT * FROM \"users\"", users.select(*)) + assertSQL("SELECT * FROM \"users\"", users.select(*)) } func test_select_withNamespacedStarExpression_compilesSelectClause() { - AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) + assertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) } func test_select_withVariadicExpressions_compilesSelectClause() { - AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) + assertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) } func test_select_withExpressions_compilesSelectClause() { - AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select([email, count(*)])) + assertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select([email, count(*)])) } func test_selectDistinct_withExpression_compilesSelectClause() { - AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) + assertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) } func test_selectDistinct_withExpressions_compilesSelectClause() { - AssertSQL("SELECT DISTINCT \"age\", \"admin\" FROM \"users\"", users.select(distinct: [age, admin])) + assertSQL("SELECT DISTINCT \"age\", \"admin\" FROM \"users\"", users.select(distinct: [age, admin])) } func test_selectDistinct_withStar_compilesSelectClause() { - AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) + assertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) } func test_join_compilesJoinClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", users.join(posts, on: posts[userId] == users[id]) ) } func test_join_withExplicitType_compilesJoinClauseWithType() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" LEFT OUTER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", users.join(.leftOuter, posts, on: posts[userId] == users[id]) ) - AssertSQL( + assertSQL( "SELECT * FROM \"users\" CROSS JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", users.join(.cross, posts, on: posts[userId] == users[id]) ) } func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" INNER JOIN \"posts\" ON ((\"posts\".\"user_id\" = \"users\".\"id\") AND \"published\")", users.join(posts.filter(published), on: posts[userId] == users[id]) ) } func test_join_whenChained_compilesAggregateJoinClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" " + "INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\") " + "INNER JOIN \"categories\" ON (\"categories\".\"id\" = \"posts\".\"category_id\")", @@ -95,79 +95,79 @@ class QueryTests : XCTestCase { } func test_filter_compilesWhereClause() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) } func test_filter_compilesWhereClause_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(admin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(admin == false)) } func test_filter_compilesWhereClause_optional() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(optionalAdmin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(optionalAdmin == true)) } func test_filter_compilesWhereClause_optional_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(optionalAdmin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(optionalAdmin == false)) } func test_where_compilesWhereClause() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(admin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(admin == true)) } func test_where_compilesWhereClause_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(admin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(admin == false)) } func test_where_compilesWhereClause_optional() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(optionalAdmin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(optionalAdmin == true)) } func test_where_compilesWhereClause_optional_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(optionalAdmin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(optionalAdmin == false)) } func test_filter_whenChained_compilesAggregateWhereClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" WHERE ((\"age\" >= 35) AND \"admin\")", users.filter(age >= 35).filter(admin) ) } func test_group_withSingleExpressionName_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", users.group(age)) } func test_group_withVariadicExpressionNames_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) } func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING \"admin\"", users.group(age, having: admin)) - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING \"admin\"", users.group(age, having: admin)) + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) } func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING \"admin\"", users.group([age, admin], having: admin) ) - AssertSQL( + assertSQL( "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)", users.group([age, admin], having: age >= 30) ) } func test_order_withSingleExpressionName_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) } func test_order_withVariadicExpressionNames_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) } func test_order_withArrayExpressionNames_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order([age, email])) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order([age, email])) } func test_order_withExpressionAndSortDirection_compilesOrderClause() { @@ -175,7 +175,7 @@ class QueryTests : XCTestCase { } func test_order_whenChained_resetsOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) } func test_reverse_withoutOrder_ordersByRowIdDescending() { @@ -187,25 +187,25 @@ class QueryTests : XCTestCase { } func test_limit_compilesLimitClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) + assertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) } func test_limit_withOffset_compilesOffsetClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) + assertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) } func test_limit_whenChained_overridesLimit() { let query = users.limit(5) - AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + assertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) + assertSQL("SELECT * FROM \"users\"", query.limit(nil)) } func test_limit_whenChained_withOffset_overridesOffset() { let query = users.limit(5, offset: 5) - AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 20", query.limit(10, offset: 20)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + assertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 20", query.limit(10, offset: 20)) + assertSQL("SELECT * FROM \"users\"", query.limit(nil)) } func test_alias_aliasesTable() { @@ -213,7 +213,7 @@ class QueryTests : XCTestCase { let managers = users.alias("managers") - AssertSQL( + assertSQL( "SELECT * FROM \"users\" " + "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")", users.join(managers, on: managers[id] == users[managerId]) @@ -221,78 +221,99 @@ class QueryTests : XCTestCase { } func test_insert_compilesInsertExpression() { - AssertSQL( + assertSQL( "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", users.insert(email <- "alice@example.com", age <- 30) ) } func test_insert_withOnConflict_compilesInsertOrOnConflictExpression() { - AssertSQL( + assertSQL( "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", users.insert(or: .replace, email <- "alice@example.com", age <- 30) ) } func test_insert_compilesInsertExpressionWithDefaultValues() { - AssertSQL("INSERT INTO \"users\" DEFAULT VALUES", users.insert()) + assertSQL("INSERT INTO \"users\" DEFAULT VALUES", users.insert()) } func test_insert_withQuery_compilesInsertExpressionWithSelectStatement() { let emails = Table("emails") - AssertSQL( + assertSQL( "INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE \"admin\"", emails.insert(users.select(email).filter(admin)) ) } func test_insert_many_compilesInsertManyExpression() { - AssertSQL( - "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", - users.insertMany([[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + assertSQL( + """ + INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), + ('alex@example.com', 83) + """.replacingOccurrences(of: "\n", with: ""), + users.insertMany([[email <- "alice@example.com", age <- 30], + [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) ) } func test_insert_many_compilesInsertManyNoneExpression() { - AssertSQL( + assertSQL( "INSERT INTO \"users\" DEFAULT VALUES", users.insertMany([]) ) } func test_insert_many_withOnConflict_compilesInsertManyOrOnConflictExpression() { - AssertSQL( - "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", - users.insertMany(or: .replace, [[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + assertSQL( + """ + INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), + ('geoff@example.com', 32), ('alex@example.com', 83) + """.replacingOccurrences(of: "\n", with: ""), + users.insertMany(or: .replace, [[email <- "alice@example.com", age <- 30], + [email <- "geoff@example.com", age <- 32], + [email <- "alex@example.com", age <- 83]]) ) } 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) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), 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')", + assertSQL( + """ + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') + """.replacingOccurrences(of: "\n", with: ""), insert ) } func test_insert_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), 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) 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)')", + 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)') + """.replacingOccurrences(of: "\n", with: ""), insert ) } func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { - AssertSQL( - "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30) ON CONFLICT (\"email\") DO UPDATE SET \"age\" = \"excluded\".\"age\"", + assertSQL( + """ + INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30) ON CONFLICT (\"email\") + DO UPDATE SET \"age\" = \"excluded\".\"age\" + """.replacingOccurrences(of: "\n", with: ""), users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email) ) } @@ -300,35 +321,48 @@ class QueryTests : XCTestCase { func test_upsert_encodable() throws { 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) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), 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\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\"", + 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\") + DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", + \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\" + """.replacingOccurrences(of: "\n", with: ""), insert ) } 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) - let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, date: Date(timeIntervalSince1970: 0), 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) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), 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) + let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, + date: Date(timeIntervalSince1970: 0), 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')", + 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') + """.replacingOccurrences(of: "\n", with: ""), insert ) } func test_update_compilesUpdateExpression() { - AssertSQL( + assertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", users.filter(id == 1).update(age <- 30, admin <- true) ) } func test_update_compilesUpdateLimitOrderExpression() { - AssertSQL( + assertSQL( "UPDATE \"users\" SET \"age\" = 30 ORDER BY \"id\" LIMIT 1", users.order(id).limit(1).update(age <- 30) ) @@ -336,95 +370,104 @@ 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) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), 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'", + assertSQL( + """ + UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, + \"date\" = '1970-01-01T00:00:00.000' + """.replacingOccurrences(of: "\n", with: ""), update ) } func test_update_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), 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) let update = try emails.update(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)'", + assertSQL( + """ + UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, + \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)' + """.replacingOccurrences(of: "\n", with: ""), update ) } func test_delete_compilesDeleteExpression() { - AssertSQL( + assertSQL( "DELETE FROM \"users\" WHERE (\"id\" = 1)", users.filter(id == 1).delete() ) } func test_delete_compilesDeleteLimitOrderExpression() { - AssertSQL( + assertSQL( "DELETE FROM \"users\" ORDER BY \"id\" LIMIT 1", users.order(id).limit(1).delete() ) } func test_delete_compilesExistsExpression() { - AssertSQL( + assertSQL( "SELECT EXISTS (SELECT * FROM \"users\")", users.exists ) } func test_count_returnsCountExpression() { - AssertSQL("SELECT count(*) FROM \"users\"", users.count) + assertSQL("SELECT count(*) FROM \"users\"", users.count) } func test_scalar_returnsScalarExpression() { - AssertSQL("SELECT \"int\" FROM \"table\"", table.select(int) as ScalarQuery) - AssertSQL("SELECT \"intOptional\" FROM \"table\"", table.select(intOptional) as ScalarQuery) - AssertSQL("SELECT DISTINCT \"int\" FROM \"table\"", table.select(distinct: int) as ScalarQuery) - AssertSQL("SELECT DISTINCT \"intOptional\" FROM \"table\"", table.select(distinct: intOptional) as ScalarQuery) + assertSQL("SELECT \"int\" FROM \"table\"", table.select(int) as ScalarQuery) + assertSQL("SELECT \"intOptional\" FROM \"table\"", table.select(intOptional) as ScalarQuery) + assertSQL("SELECT DISTINCT \"int\" FROM \"table\"", table.select(distinct: int) as ScalarQuery) + assertSQL("SELECT DISTINCT \"intOptional\" FROM \"table\"", table.select(distinct: intOptional) as ScalarQuery) } func test_subscript_withExpression_returnsNamespacedExpression() { let query = Table("query") - AssertSQL("\"query\".\"blob\"", query[data]) - AssertSQL("\"query\".\"blobOptional\"", query[dataOptional]) + assertSQL("\"query\".\"blob\"", query[data]) + assertSQL("\"query\".\"blobOptional\"", query[dataOptional]) - AssertSQL("\"query\".\"bool\"", query[bool]) - AssertSQL("\"query\".\"boolOptional\"", query[boolOptional]) + assertSQL("\"query\".\"bool\"", query[bool]) + assertSQL("\"query\".\"boolOptional\"", query[boolOptional]) - AssertSQL("\"query\".\"date\"", query[date]) - AssertSQL("\"query\".\"dateOptional\"", query[dateOptional]) + assertSQL("\"query\".\"date\"", query[date]) + assertSQL("\"query\".\"dateOptional\"", query[dateOptional]) - AssertSQL("\"query\".\"double\"", query[double]) - AssertSQL("\"query\".\"doubleOptional\"", query[doubleOptional]) + assertSQL("\"query\".\"double\"", query[double]) + assertSQL("\"query\".\"doubleOptional\"", query[doubleOptional]) - AssertSQL("\"query\".\"int\"", query[int]) - AssertSQL("\"query\".\"intOptional\"", query[intOptional]) + assertSQL("\"query\".\"int\"", query[int]) + assertSQL("\"query\".\"intOptional\"", query[intOptional]) - AssertSQL("\"query\".\"int64\"", query[int64]) - AssertSQL("\"query\".\"int64Optional\"", query[int64Optional]) + assertSQL("\"query\".\"int64\"", query[int64]) + assertSQL("\"query\".\"int64Optional\"", query[int64Optional]) - AssertSQL("\"query\".\"string\"", query[string]) - AssertSQL("\"query\".\"stringOptional\"", query[stringOptional]) + assertSQL("\"query\".\"string\"", query[string]) + assertSQL("\"query\".\"stringOptional\"", query[stringOptional]) - AssertSQL("\"query\".*", query[*]) + assertSQL("\"query\".*", query[*]) } func test_tableNamespacedByDatabase() { let table = Table("table", database: "attached") - AssertSQL("SELECT * FROM \"attached\".\"table\"", table) + assertSQL("SELECT * FROM \"attached\".\"table\"", table) } } -class QueryIntegrationTests : SQLiteTestCase { +class QueryIntegrationTests: SQLiteTestCase { let id = Expression("id") let email = Expression("email") @@ -433,7 +476,7 @@ class QueryIntegrationTests : SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() + createUsersTable() } // MARK: - @@ -452,7 +495,7 @@ class QueryIntegrationTests : SQLiteTestCase { func test_prepareRowIterator() { let names = ["a", "b", "c"] - try! InsertUsers(names) + try! insertUsers(names) let emailColumn = Expression("email") let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } @@ -462,7 +505,7 @@ class QueryIntegrationTests : SQLiteTestCase { func test_ambiguousMap() { 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 } @@ -494,8 +537,10 @@ class QueryIntegrationTests : SQLiteTestCase { 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) - let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), 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) try db.run(table.insert(value)) @@ -523,7 +568,7 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(0, try! db.scalar(users.count)) XCTAssertEqual(false, try! db.scalar(users.exists)) - try! InsertUsers("alice") + try! insertUsers("alice") XCTAssertEqual(1, try! db.scalar(users.select(id.average))) } @@ -590,7 +635,7 @@ class QueryIntegrationTests : SQLiteTestCase { func test_no_such_column() throws { let doesNotExist = Expression("doesNotExist") - try! InsertUser("alice") + try! insertUser("alice") let row = try! db.pluck(users.filter(email == "alice@example.com"))! XCTAssertThrowsError(try row.get(doesNotExist)) { error in diff --git a/Tests/SQLiteTests/RTreeTests.swift b/Tests/SQLiteTests/RTreeTests.swift index 7147533e..5525da26 100644 --- a/Tests/SQLiteTests/RTreeTests.swift +++ b/Tests/SQLiteTests/RTreeTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class RTreeTests : XCTestCase { +class RTreeTests: XCTestCase { func test_create_onVirtualTable_withRTree_createVirtualTableExpression() { XCTAssertEqual( @@ -14,4 +14,4 @@ class RTreeTests : XCTestCase { ) } -} \ No newline at end of file +} diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift index 17873e71..36721f80 100644 --- a/Tests/SQLiteTests/RowTests.swift +++ b/Tests/SQLiteTests/RowTests.swift @@ -1,7 +1,7 @@ import XCTest @testable import SQLite -class RowTests : XCTestCase { +class RowTests: XCTestCase { public func test_get_value() { let row = Row(["\"foo\"": 0], ["value"]) diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 30646b98..495a5e51 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class SchemaTests : XCTestCase { +class SchemaTests: XCTestCase { func test_drop_compilesDropTableExpression() { XCTAssertEqual("DROP TABLE \"table\"", table.drop()) @@ -330,7 +330,10 @@ class SchemaTests : XCTestCase { table.create { t in t.column(int64, unique: true, check: int64 > 0, references: table, int64) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + """ + CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES + \"table\" (\"int64\")) + """.replacingOccurrences(of: "\n", with: ""), table.create { t in t.column(int64, unique: true, check: int64Optional > 0, references: table, int64) } ) @@ -487,48 +490,95 @@ class SchemaTests : XCTestCase { table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, + unique: true, + check: string != "", + defaultValue: stringOptional, + collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') + DEFAULT (\"string\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", + defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", + defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: string != "", + defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", + defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') + DEFAULT (\"string\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: string != "", + defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') + DEFAULT (\"string\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: stringOptional != "", + defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: string != "", + defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: stringOptional != "", + defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: string != "", + defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: stringOptional != "", + defaultValue: "string", collate: .rtrim) } ) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift index bca01092..c66d401d 100644 --- a/Tests/SQLiteTests/SelectTests.swift +++ b/Tests/SQLiteTests/SelectTests.swift @@ -2,14 +2,14 @@ import XCTest @testable import SQLite class SelectTests: SQLiteTestCase { - + override func setUp() { super.setUp() - CreateUsersTable() - CreateUsersDataTable() + createUsersTable() + createUsersDataTable() } - - func CreateUsersDataTable() { + + func createUsersDataTable() { try! db.execute(""" CREATE TABLE users_name ( id INTEGER, @@ -19,27 +19,26 @@ class SelectTests: SQLiteTestCase { """ ) } - + func test_select_columns_from_multiple_tables() { 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") - - try! InsertUser("Joey") + + 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 { XCTAssertEqual($0[name], "Joey") XCTAssertEqual($0[email], "Joey@example.com") } } - } diff --git a/Tests/SQLiteTests/SetterTests.swift b/Tests/SQLiteTests/SetterTests.swift index d4f189d7..938dd013 100644 --- a/Tests/SQLiteTests/SetterTests.swift +++ b/Tests/SQLiteTests/SetterTests.swift @@ -1,137 +1,137 @@ import XCTest import SQLite -class SetterTests : XCTestCase { +class SetterTests: XCTestCase { func test_setterAssignmentOperator_buildsSetter() { - AssertSQL("\"int\" = \"int\"", int <- int) - AssertSQL("\"int\" = 1", int <- 1) - AssertSQL("\"intOptional\" = \"int\"", intOptional <- int) - AssertSQL("\"intOptional\" = \"intOptional\"", intOptional <- intOptional) - AssertSQL("\"intOptional\" = 1", intOptional <- 1) - AssertSQL("\"intOptional\" = NULL", intOptional <- nil) + assertSQL("\"int\" = \"int\"", int <- int) + assertSQL("\"int\" = 1", int <- 1) + assertSQL("\"intOptional\" = \"int\"", intOptional <- int) + assertSQL("\"intOptional\" = \"intOptional\"", intOptional <- intOptional) + assertSQL("\"intOptional\" = 1", intOptional <- 1) + assertSQL("\"intOptional\" = NULL", intOptional <- nil) } func test_plusEquals_withStringExpression_buildsSetter() { - AssertSQL("\"string\" = (\"string\" || \"string\")", string += string) - AssertSQL("\"string\" = (\"string\" || 'literal')", string += "literal") - AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"string\")", stringOptional += string) - AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"stringOptional\")", stringOptional += stringOptional) - AssertSQL("\"stringOptional\" = (\"stringOptional\" || 'literal')", stringOptional += "literal") + assertSQL("\"string\" = (\"string\" || \"string\")", string += string) + assertSQL("\"string\" = (\"string\" || 'literal')", string += "literal") + assertSQL("\"stringOptional\" = (\"stringOptional\" || \"string\")", stringOptional += string) + assertSQL("\"stringOptional\" = (\"stringOptional\" || \"stringOptional\")", stringOptional += stringOptional) + assertSQL("\"stringOptional\" = (\"stringOptional\" || 'literal')", stringOptional += "literal") } func test_plusEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" + \"int\")", int += int) - AssertSQL("\"int\" = (\"int\" + 1)", int += 1) - AssertSQL("\"intOptional\" = (\"intOptional\" + \"int\")", intOptional += int) - AssertSQL("\"intOptional\" = (\"intOptional\" + \"intOptional\")", intOptional += intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional += 1) - - AssertSQL("\"double\" = (\"double\" + \"double\")", double += double) - AssertSQL("\"double\" = (\"double\" + 1.0)", double += 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"double\")", doubleOptional += double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"doubleOptional\")", doubleOptional += doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + 1.0)", doubleOptional += 1) + assertSQL("\"int\" = (\"int\" + \"int\")", int += int) + assertSQL("\"int\" = (\"int\" + 1)", int += 1) + assertSQL("\"intOptional\" = (\"intOptional\" + \"int\")", intOptional += int) + assertSQL("\"intOptional\" = (\"intOptional\" + \"intOptional\")", intOptional += intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional += 1) + + assertSQL("\"double\" = (\"double\" + \"double\")", double += double) + assertSQL("\"double\" = (\"double\" + 1.0)", double += 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"double\")", doubleOptional += double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"doubleOptional\")", doubleOptional += doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" + 1.0)", doubleOptional += 1) } func test_minusEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" - \"int\")", int -= int) - AssertSQL("\"int\" = (\"int\" - 1)", int -= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" - \"int\")", intOptional -= int) - AssertSQL("\"intOptional\" = (\"intOptional\" - \"intOptional\")", intOptional -= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional -= 1) - - AssertSQL("\"double\" = (\"double\" - \"double\")", double -= double) - AssertSQL("\"double\" = (\"double\" - 1.0)", double -= 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"double\")", doubleOptional -= double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"doubleOptional\")", doubleOptional -= doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - 1.0)", doubleOptional -= 1) + assertSQL("\"int\" = (\"int\" - \"int\")", int -= int) + assertSQL("\"int\" = (\"int\" - 1)", int -= 1) + assertSQL("\"intOptional\" = (\"intOptional\" - \"int\")", intOptional -= int) + assertSQL("\"intOptional\" = (\"intOptional\" - \"intOptional\")", intOptional -= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional -= 1) + + assertSQL("\"double\" = (\"double\" - \"double\")", double -= double) + assertSQL("\"double\" = (\"double\" - 1.0)", double -= 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"double\")", doubleOptional -= double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"doubleOptional\")", doubleOptional -= doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" - 1.0)", doubleOptional -= 1) } func test_timesEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" * \"int\")", int *= int) - AssertSQL("\"int\" = (\"int\" * 1)", int *= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" * \"int\")", intOptional *= int) - AssertSQL("\"intOptional\" = (\"intOptional\" * \"intOptional\")", intOptional *= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" * 1)", intOptional *= 1) - - AssertSQL("\"double\" = (\"double\" * \"double\")", double *= double) - AssertSQL("\"double\" = (\"double\" * 1.0)", double *= 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"double\")", doubleOptional *= double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"doubleOptional\")", doubleOptional *= doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * 1.0)", doubleOptional *= 1) + assertSQL("\"int\" = (\"int\" * \"int\")", int *= int) + assertSQL("\"int\" = (\"int\" * 1)", int *= 1) + assertSQL("\"intOptional\" = (\"intOptional\" * \"int\")", intOptional *= int) + assertSQL("\"intOptional\" = (\"intOptional\" * \"intOptional\")", intOptional *= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" * 1)", intOptional *= 1) + + assertSQL("\"double\" = (\"double\" * \"double\")", double *= double) + assertSQL("\"double\" = (\"double\" * 1.0)", double *= 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"double\")", doubleOptional *= double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"doubleOptional\")", doubleOptional *= doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" * 1.0)", doubleOptional *= 1) } func test_dividedByEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" / \"int\")", int /= int) - AssertSQL("\"int\" = (\"int\" / 1)", int /= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" / \"int\")", intOptional /= int) - AssertSQL("\"intOptional\" = (\"intOptional\" / \"intOptional\")", intOptional /= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" / 1)", intOptional /= 1) - - AssertSQL("\"double\" = (\"double\" / \"double\")", double /= double) - AssertSQL("\"double\" = (\"double\" / 1.0)", double /= 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"double\")", doubleOptional /= double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"doubleOptional\")", doubleOptional /= doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / 1.0)", doubleOptional /= 1) + assertSQL("\"int\" = (\"int\" / \"int\")", int /= int) + assertSQL("\"int\" = (\"int\" / 1)", int /= 1) + assertSQL("\"intOptional\" = (\"intOptional\" / \"int\")", intOptional /= int) + assertSQL("\"intOptional\" = (\"intOptional\" / \"intOptional\")", intOptional /= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" / 1)", intOptional /= 1) + + assertSQL("\"double\" = (\"double\" / \"double\")", double /= double) + assertSQL("\"double\" = (\"double\" / 1.0)", double /= 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"double\")", doubleOptional /= double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"doubleOptional\")", doubleOptional /= doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" / 1.0)", doubleOptional /= 1) } func test_moduloEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" % \"int\")", int %= int) - AssertSQL("\"int\" = (\"int\" % 1)", int %= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" % \"int\")", intOptional %= int) - AssertSQL("\"intOptional\" = (\"intOptional\" % \"intOptional\")", intOptional %= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" % 1)", intOptional %= 1) + assertSQL("\"int\" = (\"int\" % \"int\")", int %= int) + assertSQL("\"int\" = (\"int\" % 1)", int %= 1) + assertSQL("\"intOptional\" = (\"intOptional\" % \"int\")", intOptional %= int) + assertSQL("\"intOptional\" = (\"intOptional\" % \"intOptional\")", intOptional %= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" % 1)", intOptional %= 1) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" << \"int\")", int <<= int) - AssertSQL("\"int\" = (\"int\" << 1)", int <<= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" << \"int\")", intOptional <<= int) - AssertSQL("\"intOptional\" = (\"intOptional\" << \"intOptional\")", intOptional <<= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" << 1)", intOptional <<= 1) + assertSQL("\"int\" = (\"int\" << \"int\")", int <<= int) + assertSQL("\"int\" = (\"int\" << 1)", int <<= 1) + assertSQL("\"intOptional\" = (\"intOptional\" << \"int\")", intOptional <<= int) + assertSQL("\"intOptional\" = (\"intOptional\" << \"intOptional\")", intOptional <<= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" << 1)", intOptional <<= 1) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" >> \"int\")", int >>= int) - AssertSQL("\"int\" = (\"int\" >> 1)", int >>= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" >> \"int\")", intOptional >>= int) - AssertSQL("\"intOptional\" = (\"intOptional\" >> \"intOptional\")", intOptional >>= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" >> 1)", intOptional >>= 1) + assertSQL("\"int\" = (\"int\" >> \"int\")", int >>= int) + assertSQL("\"int\" = (\"int\" >> 1)", int >>= 1) + assertSQL("\"intOptional\" = (\"intOptional\" >> \"int\")", intOptional >>= int) + assertSQL("\"intOptional\" = (\"intOptional\" >> \"intOptional\")", intOptional >>= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" >> 1)", intOptional >>= 1) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" & \"int\")", int &= int) - AssertSQL("\"int\" = (\"int\" & 1)", int &= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" & \"int\")", intOptional &= int) - AssertSQL("\"intOptional\" = (\"intOptional\" & \"intOptional\")", intOptional &= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" & 1)", intOptional &= 1) + assertSQL("\"int\" = (\"int\" & \"int\")", int &= int) + assertSQL("\"int\" = (\"int\" & 1)", int &= 1) + assertSQL("\"intOptional\" = (\"intOptional\" & \"int\")", intOptional &= int) + assertSQL("\"intOptional\" = (\"intOptional\" & \"intOptional\")", intOptional &= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" & 1)", intOptional &= 1) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" | \"int\")", int |= int) - AssertSQL("\"int\" = (\"int\" | 1)", int |= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" | \"int\")", intOptional |= int) - AssertSQL("\"intOptional\" = (\"intOptional\" | \"intOptional\")", intOptional |= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" | 1)", intOptional |= 1) + assertSQL("\"int\" = (\"int\" | \"int\")", int |= int) + assertSQL("\"int\" = (\"int\" | 1)", int |= 1) + assertSQL("\"intOptional\" = (\"intOptional\" | \"int\")", intOptional |= int) + assertSQL("\"intOptional\" = (\"intOptional\" | \"intOptional\")", intOptional |= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" | 1)", intOptional |= 1) } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^= int) - AssertSQL("\"int\" = (~((\"int\" & 1)) & (\"int\" | 1))", int ^= 1) - AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^= int) - AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^= intOptional) - AssertSQL("\"intOptional\" = (~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^= 1) + assertSQL("\"int\" = (~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^= int) + assertSQL("\"int\" = (~((\"int\" & 1)) & (\"int\" | 1))", int ^= 1) + assertSQL("\"intOptional\" = (~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^= int) + assertSQL("\"intOptional\" = (~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^= intOptional) + assertSQL("\"intOptional\" = (~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^= 1) } func test_postfixPlus_withIntegerValue_buildsSetter() { - AssertSQL("\"int\" = (\"int\" + 1)", int++) - AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional++) + assertSQL("\"int\" = (\"int\" + 1)", int++) + assertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional++) } func test_postfixMinus_withIntegerValue_buildsSetter() { - AssertSQL("\"int\" = (\"int\" - 1)", int--) - AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) + assertSQL("\"int\" = (\"int\" - 1)", int--) + assertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) } } diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 326259b2..5a05675d 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -1,14 +1,14 @@ import XCTest import SQLite -class StatementTests : SQLiteTestCase { +class StatementTests: SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() + createUsersTable() } func test_cursor_to_blob() { - try! InsertUsers("alice") + try! insertUsers("alice") let statement = try! db.prepare("SELECT email FROM users") XCTAssert(try! statement.step()) let blob = statement.row[0] as Blob diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 247013b4..0994dd97 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -1,15 +1,15 @@ import XCTest @testable import SQLite -class SQLiteTestCase : XCTestCase { - private var trace:[String: Int]! - var db:Connection! +class SQLiteTestCase: XCTestCase { + private var trace: [String: Int]! + var db: Connection! let users = Table("users") override func setUp() { super.setUp() db = try! Connection() - trace = [String:Int]() + trace = [String: Int]() db.trace { SQL in print(SQL) @@ -17,7 +17,7 @@ class SQLiteTestCase : XCTestCase { } } - func CreateUsersTable() { + func createUsersTable() { try! db.execute(""" CREATE TABLE users ( id INTEGER PRIMARY KEY, @@ -33,22 +33,22 @@ class SQLiteTestCase : XCTestCase { ) } - func InsertUsers(_ names: String...) throws { - try InsertUsers(names) + func insertUsers(_ names: String...) throws { + try insertUsers(names) } - func InsertUsers(_ names: [String]) throws { - for name in names { try InsertUser(name) } + func insertUsers(_ names: [String]) throws { + for name in names { try insertUser(name) } } - @discardableResult func InsertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { + @discardableResult func insertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { return try db.run( "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", "\(name)@example.com", age?.datatypeValue, admin.datatypeValue ) } - func AssertSQL(_ SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { + func assertSQL(_ SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual( executions, trace[SQL] ?? 0, message ?? SQL, @@ -56,9 +56,9 @@ class SQLiteTestCase : XCTestCase { ) } - func AssertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { + func assertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { try! statement.run() - AssertSQL(SQL, 1, message, file: file, line: line) + assertSQL(SQL, 1, message, file: file, line: line) if let count = trace[SQL] { trace[SQL] = count - 1 } } @@ -97,7 +97,8 @@ let int64Optional = Expression("int64Optional") let string = Expression("string") let stringOptional = Expression("stringOptional") -func AssertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, file: StaticString = #file, line: UInt = #line) { +func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, + file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } diff --git a/Tests/SQLiteTests/ValueTests.swift b/Tests/SQLiteTests/ValueTests.swift index bda2b4b3..f880cb34 100644 --- a/Tests/SQLiteTests/ValueTests.swift +++ b/Tests/SQLiteTests/ValueTests.swift @@ -1,6 +1,6 @@ import XCTest import SQLite -class ValueTests : XCTestCase { +class ValueTests: XCTestCase { } From ae42398825c0e391dbbb8393dc8380979e3a9004 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 01:20:49 +0200 Subject: [PATCH 107/391] brew --- .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 0b9d0cf1..1119284a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: gem install xcpretty --no-document brew update brew outdated carthage || brew upgrade carthage - brew install swiftlint + brew outdated swiftlint || brew upgrade swiftlint - name: "Lint" run: make lint - name: "Run tests (BUILD_SCHEME: SQLite iOS)" From a8f5079a2e0676b74baffe49a3226c65cb7af6e4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 01:38:03 +0200 Subject: [PATCH 108/391] Fix warnings --- .swiftlint.yml | 12 +++++++++--- Makefile | 2 +- Sources/SQLite/Core/Connection.swift | 10 +++++----- Sources/SQLite/Extensions/FTS4.swift | 1 + Sources/SQLite/Helpers.swift | 10 +++++----- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- Sources/SQLite/Typed/Query.swift | 20 +++++++++++--------- 7 files changed, 33 insertions(+), 24 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index e18c09a1..3bfc5fa5 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -11,6 +11,7 @@ excluded: # paths to ignore during linting. overridden by `included`. identifier_name: excluded: - db + - in - to - by - or @@ -20,13 +21,18 @@ identifier_name: - fn - a - b + - q + - SQLITE_TRANSIENT + +type_body_length: + warning: 260 + error: 260 line_length: warning: 150 error: 150 ignores_comments: true - file_length: - warning: 700 - error: 700 + warning: 760 + error: 760 diff --git a/Makefile b/Makefile index 5e4ff388..a9d29d58 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ build: $(BUILD_TOOL) $(BUILD_ARGUMENTS) lint: - swiftlint + swiftlint --strict test: ifdef XCPRETTY diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index a4dad4a6..61c5f94a 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -473,10 +473,10 @@ public final class Connection { // The T argument is one of the SQLITE_TRACE constants to indicate why the // 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, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in - if let P = P, - let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { - unsafeBitCast(C, to: Trace.self)(expandedSQL) + (_: UInt32, context: UnsafeMutableRawPointer?, pointer: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in + if let pointer = pointer, + let expandedSQL = sqlite3_expanded_sql(OpaquePointer(pointer)) { + unsafeBitCast(context, to: Trace.self)(expandedSQL) sqlite3_free(expandedSQL) } return Int32(0) // currently ignored @@ -585,7 +585,7 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index a3c253f2..bdb89e0e 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -91,6 +91,7 @@ extension VirtualTable { } +// swiftlint:disable identifier_name public struct Tokenizer { public static let Simple = Tokenizer("simple") diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 0e3385e3..f5c67e36 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -115,11 +115,11 @@ func transcode(_ literal: Binding?) -> String { } } -//swiftlint:disable force_cast -func value(_ v: Binding) -> A { - A.fromDatatypeValue(v as! A.Datatype) as! A +// swiftlint:disable force_cast +func value(_ binding: Binding) -> A { + A.fromDatatypeValue(binding as! A.Datatype) as! A } -func value(_ v: Binding?) -> A { - value(v!) +func value(_ binding: Binding?) -> A { + value(binding!) } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 053f17e5..42689578 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -21,7 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // - +// swiftlint:disable file_length import Foundation private enum Function: String { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 8d3ded52..6b1f6b8e 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1015,13 +1015,13 @@ extension Connection { let column = names.removeLast() let namespace = names.joined(separator: ".") - func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { - return { (query: QueryType) throws -> Void in - var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) - q.clauses.select = query.clauses.select - let e = q.expression - var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } - if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } + func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { + return { (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 + 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)" } } for name in names { columnNames[name] = idx; idx += 1 } } } @@ -1184,11 +1184,13 @@ public struct Row { } public subscript(column: Expression) -> T { - return try! get(column) + // swiftlint:disable:next force_try + try! get(column) } public subscript(column: Expression) -> T? { - return try! get(column) + // swiftlint:disable:next force_try + try! get(column) } } From 7a8269be26e128fe4731cc39a624470edcc11764 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 10:41:51 +0200 Subject: [PATCH 109/391] Remove unnecessary self --- Sources/SQLite/Core/Connection.swift | 4 +- Sources/SQLite/Core/Statement.swift | 10 +-- Sources/SQLite/Extensions/FTS4.swift | 14 ++-- Sources/SQLite/Extensions/FTS5.swift | 4 +- Sources/SQLite/Foundation.swift | 2 +- Sources/SQLite/Typed/Coding.swift | 96 ++++++++++++------------- Sources/SQLite/Typed/Query.swift | 8 +-- Sources/SQLite/Typed/Setter.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 14 ++-- 9 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 61c5f94a..2ba51d80 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -161,7 +161,7 @@ public final class Connection { /// /// - Throws: `Result.Error` if query execution fails. public func execute(_ SQL: String) throws { - _ = try sync { try self.check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) } + _ = try sync { try check(sqlite3_exec(handle, SQL, nil, nil, nil)) } } // MARK: - Prepare @@ -633,7 +633,7 @@ public final class Connection { let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self) function(context, argc, value) }, nil, nil, nil) - if functions[function] == nil { self.functions[function] = [:] } + if functions[function] == nil { functions[function] = [:] } functions[function]?[argc] = box } diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index dfb67d22..4332cb49 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -48,10 +48,10 @@ public final class Statement { sqlite3_finalize(handle) } - public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) + public lazy var columnCount: Int = Int(sqlite3_column_count(handle)) - public lazy var columnNames: [String] = (0.. Bool { - return try connection.sync { try self.connection.check(sqlite3_step(self.handle)) == SQLITE_ROW } + return try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } } fileprivate func reset(clearBindings shouldClear: Bool = true) { @@ -304,7 +304,7 @@ extension Cursor: Sequence { public func makeIterator() -> AnyIterator { var idx = 0 return AnyIterator { - if idx >= self.columnCount { + if idx >= columnCount { return Binding??.none } else { idx += 1 diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index bdb89e0e..1de03ff2 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -184,7 +184,7 @@ open class FTSConfig { /// Adds a column definition @discardableResult open func column(_ column: Expressible, _ options: [ColumnOption] = []) -> Self { - self.columnDefinitions.append((column, options)) + columnDefinitions.append((column, options)) return self } @@ -203,19 +203,19 @@ open class FTSConfig { /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6) @discardableResult open func prefix(_ prefix: [Int]) -> Self { - self.prefixes += prefix + prefixes += prefix return self } /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2) @discardableResult open func externalContent(_ schema: SchemaType) -> Self { - self.externalContentSchema = schema + externalContentSchema = schema return self } /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1) @discardableResult open func contentless() -> Self { - self.isContentless = true + isContentless = true return self } @@ -311,19 +311,19 @@ open class FTS4Config: FTSConfig { /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) @discardableResult open func compress(_ functionName: String) -> Self { - self.compressFunction = functionName + compressFunction = functionName return self } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) @discardableResult open func uncompress(_ functionName: String) -> Self { - self.uncompressFunction = functionName + uncompressFunction = functionName return self } /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3) @discardableResult open func languageId(_ columnName: String) -> Self { - self.languageId = columnName + languageId = columnName return self } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index 07f49ffc..a5d04dbf 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -59,13 +59,13 @@ open class FTS5Config: FTSConfig { /// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2) @discardableResult open func contentRowId(_ column: Expressible) -> Self { - self.contentRowId = column + contentRowId = column return self } /// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5) @discardableResult open func columnSize(_ size: Int) -> Self { - self.columnSize = size + columnSize = size return self } diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index d837d8ba..81e89d48 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -80,7 +80,7 @@ extension UUID: Value { } public var datatypeValue: String { - return self.uuidString + return uuidString } } diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 5a672883..d2df2fe6 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -149,7 +149,7 @@ extension Row { /// /// - Returns: a decoded object from this row public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { - return try V(from: self.decoder(userInfo: userInfo)) + return try V(from: decoder(userInfo: userInfo)) } public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { @@ -179,82 +179,82 @@ private class SQLiteEncoder: Encoder { } func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- nil) + encoder.setters.append(Expression(key.stringValue) <- nil) } func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: Bool, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: Float, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) + encoder.setters.append(Expression(key.stringValue) <- Double(value)) } func encode(_ value: Double, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: String, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { if let data = value as? Data { - self.encoder.setters.append(Expression(key.stringValue) <- data) + encoder.setters.append(Expression(key.stringValue) <- data) } else if let date = value as? Date { - self.encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) - self.encoder.setters.append(Expression(key.stringValue) <- string) + encoder.setters.append(Expression(key.stringValue) <- string) } } func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int32 is not supported")) } func encode(_ value: Int64, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt64 is not supported")) } @@ -301,98 +301,98 @@ private class SQLiteDecoder: Decoder { } var allKeys: [Key] { - return self.row.columnNames.keys.compactMap({Key(stringValue: $0)}) + return row.columnNames.keys.compactMap({Key(stringValue: $0)}) } func contains(_ key: Key) -> Bool { - return self.row.hasValue(for: key.stringValue) + return row.hasValue(for: key.stringValue) } func decodeNil(forKey key: Key) throws -> Bool { - return !self.contains(key) + return !contains(key) } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + 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 { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + 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 { - return Float(try self.row.get(Expression(key.stringValue))) + return Float(try row.get(Expression(key.stringValue))) } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: String.Type, forKey key: Key) throws -> String { - return try self.row.get(Expression(key.stringValue)) + return 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 { - let data = try self.row.get(Expression(key.stringValue)) + let data = try row.get(Expression(key.stringValue)) return data as! T } else if type == Date.self { - let date = try self.row.get(Expression(key.stringValue)) + let date = try row.get(Expression(key.stringValue)) return date as! T } // swiftlint:enable force_cast - guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + 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: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "invalid utf8 data found")) } return try JSONDecoder().decode(type, from: data) @@ -400,22 +400,22 @@ private class SQLiteDecoder: Decoder { func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + 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: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super decoders is not supported")) } } @@ -430,16 +430,16 @@ private class SQLiteDecoder: Decoder { } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { - return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) + return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + 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: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding a single value container is not supported")) } } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 6b1f6b8e..22742809 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1093,7 +1093,7 @@ extension Connection { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) - return self.lastInsertRowid + return lastInsertRowid } } @@ -1109,7 +1109,7 @@ extension Connection { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) - return self.changes + return changes } } @@ -1124,7 +1124,7 @@ extension Connection { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) - return self.changes + return changes } } @@ -1244,7 +1244,7 @@ public struct QueryClauses { var union = [QueryType]() fileprivate init(_ name: String, alias: String?, database: String?) { - self.from = (name, alias, database) + from = (name, alias, database) } } diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 25e88342..08fb1748 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -63,7 +63,7 @@ public struct Setter { init(excluded column: Expressible) { let excluded = Expression("excluded") self.column = column - self.value = ".".join([excluded, column.expression]) + value = ".".join([excluded, column.expression]) } } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 00fe4f6b..44c2c302 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -217,7 +217,7 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndCommitsSavepoints() { - let db: Connection = self.db + let db: Connection = db try! db.savepoint("1") { try db.savepoint("2") { @@ -235,7 +235,7 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndRollsSavepointsBack() { - let db: Connection = self.db + let db: Connection = db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { @@ -310,7 +310,7 @@ class ConnectionTests: SQLiteTestCase { done() } try! db.transaction { - try self.insertUser("alice") + try insertUser("alice") } XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } @@ -321,8 +321,8 @@ class ConnectionTests: SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.insertUser("alice") - try self.insertUser("alice") // throw + try insertUser("alice") + try insertUser("alice") // throw } } catch { } @@ -338,7 +338,7 @@ class ConnectionTests: SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.insertUser("alice") + try insertUser("alice") } } catch { } @@ -437,7 +437,7 @@ class ResultTests: XCTestCase { XCTAssertEqual("not an error", message) XCTAssertEqual(SQLITE_MISUSE, code) XCTAssertNil(statement) - XCTAssert(self.connection === connection) + XCTAssert(connection === connection) } else { XCTFail("no error") } From e2afa8b6bf00728ab275af6be155a4f7b3b16a6e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 10:48:13 +0200 Subject: [PATCH 110/391] Simplify --- Sources/SQLite/Core/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 4332cb49..1f9afbc6 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -305,7 +305,7 @@ extension Cursor: Sequence { var idx = 0 return AnyIterator { if idx >= columnCount { - return Binding??.none + return .none } else { idx += 1 return self[idx - 1] From aebb9c590a469f714b334a9fd2ce84c45475f464 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:01:21 +0200 Subject: [PATCH 111/391] Remove unnecessary returns --- Sources/SQLite/Core/Blob.swift | 6 +- Sources/SQLite/Core/Connection.swift | 30 +- Sources/SQLite/Core/Statement.swift | 28 +- Sources/SQLite/Core/Value.swift | 24 +- Sources/SQLite/Extensions/Cipher.swift | 2 +- Sources/SQLite/Extensions/FTS4.swift | 32 +- Sources/SQLite/Extensions/FTS5.swift | 4 +- Sources/SQLite/Foundation.swift | 20 +- Sources/SQLite/Helpers.swift | 12 +- Sources/SQLite/Typed/AggregateFunctions.swift | 34 +- Sources/SQLite/Typed/Coding.swift | 26 +- Sources/SQLite/Typed/Collation.swift | 2 +- Sources/SQLite/Typed/CoreFunctions.swift | 62 +-- .../SQLite/Typed/DateAndTimeFunctions.swift | 24 +- Sources/SQLite/Typed/Expression.swift | 14 +- Sources/SQLite/Typed/Operators.swift | 368 +++++++++--------- Sources/SQLite/Typed/Query.swift | 110 +++--- Sources/SQLite/Typed/Schema.swift | 42 +- Sources/SQLite/Typed/Setter.swift | 130 +++---- Tests/SQLiteTests/ConnectionTests.swift | 4 +- Tests/SQLiteTests/CustomFunctionsTests.swift | 28 +- Tests/SQLiteTests/FTS4Tests.swift | 2 +- Tests/SQLiteTests/FTS5Tests.swift | 2 +- Tests/SQLiteTests/TestHelpers.swift | 6 +- 24 files changed, 506 insertions(+), 506 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index a49ef23b..cd31483b 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -36,7 +36,7 @@ public struct Blob { } public func toHex() -> String { - return bytes.map { + bytes.map { ($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false) }.joined(separator: "") } @@ -46,7 +46,7 @@ public struct Blob { extension Blob: CustomStringConvertible { public var description: String { - return "x'\(toHex())'" + "x'\(toHex())'" } } @@ -56,5 +56,5 @@ extension Blob: Equatable { } public func ==(lhs: Blob, rhs: Blob) -> Bool { - return lhs.bytes == rhs.bytes + lhs.bytes == rhs.bytes } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 2ba51d80..07c85373 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -84,7 +84,7 @@ public final class Connection { } } - public var handle: OpaquePointer { return _handle! } + public var handle: OpaquePointer { _handle! } fileprivate var _handle: OpaquePointer? @@ -133,23 +133,23 @@ public final class Connection { // MARK: - /// Whether or not the database was opened in a read-only state. - public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } + public var readonly: Bool { sqlite3_db_readonly(handle, nil) == 1 } /// The last rowid inserted into the database via this connection. public var lastInsertRowid: Int64 { - return sqlite3_last_insert_rowid(handle) + sqlite3_last_insert_rowid(handle) } /// The last number of changes (inserts, updates, or deletes) made to the /// database via this connection. public var changes: Int { - return Int(sqlite3_changes(handle)) + Int(sqlite3_changes(handle)) } /// The total number of changes (inserts, updates, or deletes) made to the /// database via this connection. public var totalChanges: Int { - return Int(sqlite3_total_changes(handle)) + Int(sqlite3_total_changes(handle)) } // MARK: - Execute @@ -190,7 +190,7 @@ public final class Connection { /// /// - Returns: A prepared statement. public func prepare(_ statement: String, _ bindings: [Binding?]) throws -> Statement { - return try prepare(statement).bind(bindings) + try prepare(statement).bind(bindings) } /// Prepares a single SQL statement and binds parameters to it. @@ -203,7 +203,7 @@ public final class Connection { /// /// - Returns: A prepared statement. public func prepare(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { - return try prepare(statement).bind(bindings) + try prepare(statement).bind(bindings) } // MARK: - Run @@ -220,7 +220,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement { - return try run(statement, bindings) + try run(statement, bindings) } /// Prepares, binds, and runs a single SQL statement. @@ -235,7 +235,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement { - return try prepare(statement).run(bindings) + try prepare(statement).run(bindings) } /// Prepares, binds, and runs a single SQL statement. @@ -250,7 +250,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { - return try prepare(statement).run(bindings) + try prepare(statement).run(bindings) } // MARK: - VACUUM @@ -261,7 +261,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func vacuum() throws -> Statement { - return try run("VACUUM") + try run("VACUUM") } // MARK: - Scalar @@ -277,7 +277,7 @@ public final class Connection { /// /// - Returns: The first value of the first row returned. public func scalar(_ statement: String, _ bindings: Binding?...) throws -> Binding? { - return try scalar(statement, bindings) + try scalar(statement, bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -291,7 +291,7 @@ public final class Connection { /// /// - Returns: The first value of the first row returned. public func scalar(_ statement: String, _ bindings: [Binding?]) throws -> Binding? { - return try prepare(statement).scalar(bindings) + try prepare(statement).scalar(bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -305,7 +305,7 @@ public final class Connection { /// /// - Returns: The first value of the first row returned. public func scalar(_ statement: String, _ bindings: [String: Binding?]) throws -> Binding? { - return try prepare(statement).scalar(bindings) + try prepare(statement).scalar(bindings) } // MARK: - Transactions @@ -810,7 +810,7 @@ public final class Connection { extension Connection: CustomStringConvertible { public var description: String { - return String(cString: sqlite3_db_filename(handle, nil)) + String(cString: sqlite3_db_filename(handle, nil)) } } diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 1f9afbc6..48347c31 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -63,7 +63,7 @@ public final class Statement { /// /// - Returns: The statement object (useful for chaining). public func bind(_ values: Binding?...) -> Statement { - return bind(values) + bind(values) } /// Binds a list of parameters to a statement. @@ -140,7 +140,7 @@ public final class Statement { /// /// - Returns: The statement object (useful for chaining). @discardableResult public func run(_ bindings: [Binding?]) throws -> Statement { - return try bind(bindings).run() + try bind(bindings).run() } /// - Parameter bindings: A dictionary of named parameters to bind to the @@ -150,7 +150,7 @@ public final class Statement { /// /// - Returns: The statement object (useful for chaining). @discardableResult public func run(_ bindings: [String: Binding?]) throws -> Statement { - return try bind(bindings).run() + try bind(bindings).run() } /// - Parameter bindings: A list of parameters to bind to the statement. @@ -170,7 +170,7 @@ public final class Statement { /// /// - Returns: The first value of the first row returned. public func scalar(_ bindings: [Binding?]) throws -> Binding? { - return try bind(bindings).scalar() + try bind(bindings).scalar() } /// - Parameter bindings: A dictionary of named parameters to bind to the @@ -178,11 +178,11 @@ public final class Statement { /// /// - Returns: The first value of the first row returned. public func scalar(_ bindings: [String: Binding?]) throws -> Binding? { - return try bind(bindings).scalar() + try bind(bindings).scalar() } public func step() throws -> Bool { - return try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } + try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } } fileprivate func reset(clearBindings shouldClear: Bool = true) { @@ -207,7 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { - return try? failableNext() + try? failableNext() } } @@ -223,14 +223,14 @@ extension Array { extension Statement: FailableIterator { public typealias Element = [Binding?] public func failableNext() throws -> [Binding?]? { - return try step() ? Array(row) : nil + try step() ? Array(row) : nil } } extension Statement: CustomStringConvertible { public var description: String { - return String(cString: sqlite3_sql(handle)) + String(cString: sqlite3_sql(handle)) } } @@ -247,15 +247,15 @@ public struct Cursor { } public subscript(idx: Int) -> Double { - return sqlite3_column_double(handle, Int32(idx)) + sqlite3_column_double(handle, Int32(idx)) } public subscript(idx: Int) -> Int64 { - return sqlite3_column_int64(handle, Int32(idx)) + sqlite3_column_int64(handle, Int32(idx)) } public subscript(idx: Int) -> String { - return String(cString: UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) + String(cString: UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) } public subscript(idx: Int) -> Blob { @@ -272,11 +272,11 @@ public struct Cursor { // MARK: - public subscript(idx: Int) -> Bool { - return Bool.fromDatatypeValue(self[idx]) + Bool.fromDatatypeValue(self[idx]) } public subscript(idx: Int) -> Int { - return Int.fromDatatypeValue(self[idx]) + Int.fromDatatypeValue(self[idx]) } } diff --git a/Sources/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift index b8686eab..9c463f0c 100644 --- a/Sources/SQLite/Core/Value.swift +++ b/Sources/SQLite/Core/Value.swift @@ -50,11 +50,11 @@ extension Double: Number, Value { public static let declaredDatatype = "REAL" public static func fromDatatypeValue(_ datatypeValue: Double) -> Double { - return datatypeValue + datatypeValue } public var datatypeValue: Double { - return self + self } } @@ -64,11 +64,11 @@ extension Int64: Number, Value { public static let declaredDatatype = "INTEGER" public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int64 { - return datatypeValue + datatypeValue } public var datatypeValue: Int64 { - return self + self } } @@ -78,11 +78,11 @@ extension String: Binding, Value { public static let declaredDatatype = "TEXT" public static func fromDatatypeValue(_ datatypeValue: String) -> String { - return datatypeValue + datatypeValue } public var datatypeValue: String { - return self + self } } @@ -92,11 +92,11 @@ extension Blob: Binding, Value { public static let declaredDatatype = "BLOB" public static func fromDatatypeValue(_ datatypeValue: Blob) -> Blob { - return datatypeValue + datatypeValue } public var datatypeValue: Blob { - return self + self } } @@ -108,11 +108,11 @@ extension Bool: Binding, Value { public static var declaredDatatype = Int64.declaredDatatype public static func fromDatatypeValue(_ datatypeValue: Int64) -> Bool { - return datatypeValue != 0 + datatypeValue != 0 } public var datatypeValue: Int64 { - return self ? 1 : 0 + self ? 1 : 0 } } @@ -122,11 +122,11 @@ extension Int: Number, Value { public static var declaredDatatype = Int64.declaredDatatype public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int { - return Int(datatypeValue) + Int(datatypeValue) } public var datatypeValue: Int64 { - return Int64(self) + Int64(self) } } diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 9fce42f7..25d20158 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -7,7 +7,7 @@ extension Connection { /// - Returns: the SQLCipher version public var cipherVersion: String? { - return (try? scalar("PRAGMA cipher_version")) as? String + (try? scalar("PRAGMA cipher_version")) as? String } /// Specify the key for an encrypted database. This routine should be diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 1de03ff2..115b146a 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -29,15 +29,15 @@ import SQLiteObjc extension Module { public static func FTS4(_ column: Expressible, _ more: Expressible...) -> Module { - return FTS4([column] + more) + FTS4([column] + more) } public static func FTS4(_ columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { - return FTS4(FTS4Config().columns(columns).tokenizer(tokenizer)) + FTS4(FTS4Config().columns(columns).tokenizer(tokenizer)) } public static func FTS4(_ config: FTS4Config) -> Module { - return Module(name: "fts4", arguments: config.arguments()) + Module(name: "fts4", arguments: config.arguments()) } } @@ -56,15 +56,15 @@ extension VirtualTable { /// - Returns: An expression appended with a `MATCH` query against the given /// pattern. public func match(_ pattern: String) -> Expression { - return "MATCH".infix(tableName(), pattern) + "MATCH".infix(tableName(), pattern) } public func match(_ pattern: Expression) -> Expression { - return "MATCH".infix(tableName(), pattern) + "MATCH".infix(tableName(), pattern) } public func match(_ pattern: Expression) -> Expression { - return "MATCH".infix(tableName(), pattern) + "MATCH".infix(tableName(), pattern) } /// Builds a copy of the query with a `WHERE … MATCH` clause. @@ -78,15 +78,15 @@ extension VirtualTable { /// /// - Returns: A query with the given `WHERE … MATCH` clause applied. public func match(_ pattern: String) -> QueryType { - return filter(match(pattern)) + filter(match(pattern)) } public func match(_ pattern: Expression) -> QueryType { - return filter(match(pattern)) + filter(match(pattern)) } public func match(_ pattern: Expression) -> QueryType { - return filter(match(pattern)) + filter(match(pattern)) } } @@ -120,7 +120,7 @@ public struct Tokenizer { } public static func Custom(_ name: String) -> Tokenizer { - return Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) + Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) } public let name: String @@ -139,7 +139,7 @@ public struct Tokenizer { extension Tokenizer: CustomStringConvertible { public var description: String { - return ([name] + arguments).joined(separator: " ") + ([name] + arguments).joined(separator: " ") } } @@ -220,11 +220,11 @@ open class FTSConfig { } func formatColumnDefinitions() -> [Expressible] { - return columnDefinitions.map { $0.0 } + columnDefinitions.map { $0.0 } } func arguments() -> [Expressible] { - return options().arguments + options().arguments } func options() -> Options { @@ -259,11 +259,11 @@ open class FTSConfig { } @discardableResult mutating func append(_ key: String, value: CustomStringConvertible?) -> Options { - return append(key, value: value?.description) + append(key, value: value?.description) } @discardableResult mutating func append(_ key: String, value: String?) -> Options { - return append(key, value: value.map { Expression($0) }) + append(key, value: value.map { Expression($0) }) } @discardableResult mutating func append(_ key: String, value: Expressible?) -> Options { @@ -281,7 +281,7 @@ open class FTS4Config: FTSConfig { public enum MatchInfo: CustomStringConvertible { case fts3 public var description: String { - return "fts3" + "fts3" } } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index a5d04dbf..f108bbec 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -24,7 +24,7 @@ extension Module { public static func FTS5(_ config: FTS5Config) -> Module { - return Module(name: "fts5", arguments: config.arguments()) + Module(name: "fts5", arguments: config.arguments()) } } @@ -86,7 +86,7 @@ open class FTS5Config: FTSConfig { } override func formatColumnDefinitions() -> [Expressible] { - return columnDefinitions.map { definition in + columnDefinitions.map { definition in if definition.options.contains(.unindexed) { return " ".join([definition.0, Expression(literal: "UNINDEXED")]) } else { diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 81e89d48..2acbc00e 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -27,16 +27,16 @@ import Foundation extension Data: Value { public static var declaredDatatype: String { - return Blob.declaredDatatype + Blob.declaredDatatype } public static func fromDatatypeValue(_ dataValue: Blob) -> Data { - return Data(dataValue.bytes) + Data(dataValue.bytes) } public var datatypeValue: Blob { - return withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in - return Blob(bytes: pointer.baseAddress!, length: count) + withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in + Blob(bytes: pointer.baseAddress!, length: count) } } @@ -45,15 +45,15 @@ extension Data: Value { extension Date: Value { public static var declaredDatatype: String { - return String.declaredDatatype + String.declaredDatatype } public static func fromDatatypeValue(_ stringValue: String) -> Date { - return dateFormatter.date(from: stringValue)! + dateFormatter.date(from: stringValue)! } public var datatypeValue: String { - return dateFormatter.string(from: self) + dateFormatter.string(from: self) } } @@ -72,15 +72,15 @@ public var dateFormatter: DateFormatter = { extension UUID: Value { public static var declaredDatatype: String { - return String.declaredDatatype + String.declaredDatatype } public static func fromDatatypeValue(_ stringValue: String) -> UUID { - return UUID(uuidString: stringValue)! + UUID(uuidString: stringValue)! } public var datatypeValue: String { - return uuidString + uuidString } } diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index f5c67e36..d4c6828e 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -35,7 +35,7 @@ import SQLite3 public typealias Star = (Expression?, Expression?) -> Expression public func *(_: Expression?, _: Expression?) -> Expression { - return Expression(literal: "*") + Expression(literal: "*") } public protocol _OptionalType { @@ -73,7 +73,7 @@ extension String { } func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return infix([lhs, rhs], wrap: wrap) + infix([lhs, rhs], wrap: wrap) } func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { @@ -85,19 +85,19 @@ extension String { } func prefix(_ expressions: Expressible) -> Expressible { - return "\(self) ".wrap(expressions) as Expression + "\(self) ".wrap(expressions) as Expression } func prefix(_ expressions: [Expressible]) -> Expressible { - return "\(self) ".wrap(expressions) as Expression + "\(self) ".wrap(expressions) as Expression } func wrap(_ expression: Expressible) -> Expression { - return Expression("\(self)(\(expression.expression.template))", expression.expression.bindings) + Expression("\(self)(\(expression.expression.template))", expression.expression.bindings) } func wrap(_ expressions: [Expressible]) -> Expression { - return wrap(", ".join(expressions)) + wrap(", ".join(expressions)) } } diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 325b0277..bf4fb8fc 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -31,7 +31,7 @@ private enum Function: String { case total func wrap(_ expression: Expressible) -> Expression { - return self.rawValue.wrap(expression) + self.rawValue.wrap(expression) } } @@ -46,7 +46,7 @@ extension ExpressionType where UnderlyingType: Value { /// - Returns: A copy of the expression prefixed with the `DISTINCT` /// keyword. public var distinct: Expression { - return Expression("DISTINCT \(template)", bindings) + Expression("DISTINCT \(template)", bindings) } /// Builds a copy of the expression wrapped with the `count` aggregate @@ -61,7 +61,7 @@ extension ExpressionType where UnderlyingType: Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Function.count.wrap(self) + Function.count.wrap(self) } } @@ -77,7 +77,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression prefixed with the `DISTINCT` /// keyword. public var distinct: Expression { - return Expression("DISTINCT \(template)", bindings) + Expression("DISTINCT \(template)", bindings) } /// Builds a copy of the expression wrapped with the `count` aggregate @@ -92,7 +92,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Function.count.wrap(self) + Function.count.wrap(self) } } @@ -109,7 +109,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: C /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Function.max.wrap(self) + Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -122,7 +122,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: C /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Function.min.wrap(self) + Function.min.wrap(self) } } @@ -139,7 +139,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Function.max.wrap(self) + Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -152,7 +152,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Function.min.wrap(self) + Function.min.wrap(self) } } @@ -169,7 +169,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Function.avg.wrap(self) + Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -182,7 +182,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Function.sum.wrap(self) + Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -195,7 +195,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Function.total.wrap(self) + Function.total.wrap(self) } } @@ -212,7 +212,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Function.avg.wrap(self) + Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -225,7 +225,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Function.sum.wrap(self) + Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -238,7 +238,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Function.total.wrap(self) + Function.total.wrap(self) } } @@ -246,7 +246,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return Function.count.wrap(star(nil, nil)) + Function.count.wrap(star(nil, nil)) } } @@ -260,5 +260,5 @@ extension ExpressionType where UnderlyingType == Int { /// - Returns: An expression returning `count(*)` (when called with the `*` /// function literal). public func count(_ star: Star) -> Expression { - return Expression.count(star) + Expression.count(star) } diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index d2df2fe6..3dc1e6cf 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -149,11 +149,11 @@ extension Row { /// /// - Returns: a decoded object from this row public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { - return try V(from: decoder(userInfo: userInfo)) + try V(from: decoder(userInfo: userInfo)) } public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { - return SQLiteDecoder(row: self, userInfo: userInfo) + SQLiteDecoder(row: self, userInfo: userInfo) } } @@ -285,7 +285,7 @@ private class SQLiteEncoder: Encoder { } func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { - return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } } @@ -301,23 +301,23 @@ private class SQLiteDecoder: Decoder { } var allKeys: [Key] { - return row.columnNames.keys.compactMap({Key(stringValue: $0)}) + row.columnNames.keys.compactMap({ Key(stringValue: $0) }) } func contains(_ key: Key) -> Bool { - return row.hasValue(for: key.stringValue) + row.hasValue(for: key.stringValue) } func decodeNil(forKey key: Key) throws -> Bool { - return !contains(key) + !contains(key) } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { @@ -336,7 +336,7 @@ private class SQLiteDecoder: Decoder { } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { @@ -366,15 +366,15 @@ private class SQLiteDecoder: Decoder { } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - return Float(try row.get(Expression(key.stringValue))) + Float(try row.get(Expression(key.stringValue))) } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: String.Type, forKey key: Key) throws -> String { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { @@ -430,7 +430,7 @@ private class SQLiteDecoder: Decoder { } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { - return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) + KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { diff --git a/Sources/SQLite/Typed/Collation.swift b/Sources/SQLite/Typed/Collation.swift index 3b268ce0..fec66129 100644 --- a/Sources/SQLite/Typed/Collation.swift +++ b/Sources/SQLite/Typed/Collation.swift @@ -46,7 +46,7 @@ public enum Collation { extension Collation: Expressible { public var expression: Expression { - return Expression(literal: description) + Expression(literal: description) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 42689578..be38d97e 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -47,15 +47,15 @@ private enum Function: String { case ifnull func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return self.rawValue.infix(lhs, rhs, wrap: wrap) + self.rawValue.infix(lhs, rhs, wrap: wrap) } func wrap(_ expression: Expressible) -> Expression { - return self.rawValue.wrap(expression) + self.rawValue.wrap(expression) } func wrap(_ expressions: [Expressible]) -> Expression { - return self.rawValue.wrap(", ".join(expressions)) + self.rawValue.wrap(", ".join(expressions)) } } @@ -69,7 +69,7 @@ extension ExpressionType where UnderlyingType: Number { /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue: Expression { - return Function.abs.wrap(self) + Function.abs.wrap(self) } } @@ -84,7 +84,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue: Expression { - return Function.abs.wrap(self) + Function.abs.wrap(self) } } @@ -138,7 +138,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype == /// /// - Returns: An expression calling the `random` function. public static func random() -> Expression { - return Function.random.wrap([]) + Function.random.wrap([]) } } @@ -154,7 +154,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `randomblob` function. public static func random(_ length: Int) -> Expression { - return Function.randomblob.wrap([]) + Function.randomblob.wrap([]) } /// Builds an expression representing the `zeroblob` function. @@ -166,7 +166,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `zeroblob` function. public static func allZeros(_ length: Int) -> Expression { - return Function.zeroblob.wrap([]) + Function.zeroblob.wrap([]) } /// Builds a copy of the expression wrapped with the `length` function. @@ -177,7 +177,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } } @@ -192,7 +192,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } } @@ -207,7 +207,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -218,7 +218,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Function.lower.wrap(self) + Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -229,7 +229,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Function.upper.wrap(self) + Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -294,7 +294,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Function.glob.infix(self, pattern) + Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -309,7 +309,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Function.match.infix(self, pattern) + Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -320,7 +320,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Function.regexp.infix(self, pattern) + Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -335,7 +335,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Function.collate.infix(self, collation) + Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -406,7 +406,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Function.replace.wrap([self, pattern, replacement]) + Function.replace.wrap([self, pattern, replacement]) } public func substring(_ location: Int, length: Int? = nil) -> Expression { @@ -417,7 +417,7 @@ extension ExpressionType where UnderlyingType == String { } public subscript(range: Range) -> Expression { - return substring(range.lowerBound, length: range.upperBound - range.lowerBound) + substring(range.lowerBound, length: range.upperBound - range.lowerBound) } } @@ -432,7 +432,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -443,7 +443,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Function.lower.wrap(self) + Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -454,7 +454,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Function.upper.wrap(self) + Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -519,7 +519,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Function.glob.infix(self, pattern) + Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -534,7 +534,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Function.match.infix(self, pattern) + Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -545,7 +545,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Function.regexp.infix(self, pattern) + Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -560,7 +560,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Function.collate.infix(self, collation) + Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -631,7 +631,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Function.replace.wrap([self, pattern, replacement]) + Function.replace.wrap([self, pattern, replacement]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -666,7 +666,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `substr` function. public subscript(range: Range) -> Expression { - return substring(range.lowerBound, length: range.upperBound - range.lowerBound) + substring(range.lowerBound, length: range.upperBound - range.lowerBound) } } @@ -752,7 +752,7 @@ extension String { /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: V) -> Expression { - return Function.ifnull.wrap([optional, defaultValue]) + Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -772,7 +772,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressio /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Function.ifnull.wrap([optional, defaultValue]) + Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -792,5 +792,5 @@ public func ??(optional: Expression, defaultValue: Expression) /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Function.ifnull.wrap([optional, defaultValue]) + Function.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/DateAndTimeFunctions.swift b/Sources/SQLite/Typed/DateAndTimeFunctions.swift index 0b9a497f..b4382194 100644 --- a/Sources/SQLite/Typed/DateAndTimeFunctions.swift +++ b/Sources/SQLite/Typed/DateAndTimeFunctions.swift @@ -32,23 +32,23 @@ import Foundation public class DateFunctions { /// The date() function returns the date in this format: YYYY-MM-DD. public static func date(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("date", timestring: timestring, modifiers: modifiers) + timefunction("date", timestring: timestring, modifiers: modifiers) } /// The time() function returns the time as HH:MM:SS. public static func time(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("time", timestring: timestring, modifiers: modifiers) + timefunction("time", timestring: timestring, modifiers: modifiers) } /// The datetime() function returns "YYYY-MM-DD HH:MM:SS". public static func datetime(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("datetime", timestring: timestring, modifiers: modifiers) + timefunction("datetime", timestring: timestring, modifiers: modifiers) } /// The julianday() function returns the Julian day - /// the number of days since noon in Greenwich on November 24, 4714 B.C. public static func julianday(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("julianday", timestring: timestring, modifiers: modifiers) + timefunction("julianday", timestring: timestring, modifiers: modifiers) } /// The strftime() routine returns the date formatted according to the format string specified as the first argument. @@ -71,36 +71,36 @@ public class DateFunctions { extension Date { public var date: Expression { - return DateFunctions.date(dateFormatter.string(from: self)) + DateFunctions.date(dateFormatter.string(from: self)) } public var time: Expression { - return DateFunctions.time(dateFormatter.string(from: self)) + DateFunctions.time(dateFormatter.string(from: self)) } public var datetime: Expression { - return DateFunctions.datetime(dateFormatter.string(from: self)) + DateFunctions.datetime(dateFormatter.string(from: self)) } public var julianday: Expression { - return DateFunctions.julianday(dateFormatter.string(from: self)) + DateFunctions.julianday(dateFormatter.string(from: self)) } } extension Expression where UnderlyingType == Date { public var date: Expression { - return Expression("date(\(template))", bindings) + Expression("date(\(template))", bindings) } public var time: Expression { - return Expression("time(\(template))", bindings) + Expression("time(\(template))", bindings) } public var datetime: Expression { - return Expression("datetime(\(template))", bindings) + Expression("datetime(\(template))", bindings) } public var julianday: Expression { - return Expression("julianday(\(template))", bindings) + Expression("julianday(\(template))", bindings) } } diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 39a3c62f..95cdd3b9 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -95,15 +95,15 @@ extension Expressible { extension ExpressionType { public var expression: Expression { - return Expression(template, bindings) + Expression(template, bindings) } public var asc: Expressible { - return " ".join([self, Expression(literal: "ASC")]) + " ".join([self, Expression(literal: "ASC")]) } public var desc: Expressible { - return " ".join([self, Expression(literal: "DESC")]) + " ".join([self, Expression(literal: "DESC")]) } } @@ -119,7 +119,7 @@ extension ExpressionType where UnderlyingType: Value { extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value { public static var null: Self { - return self.init(value: nil) + self.init(value: nil) } public init(value: UnderlyingType.WrappedType?) { @@ -131,7 +131,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra extension Value { public var expression: Expression { - return Expression(value: self).expression + Expression(value: self).expression } } @@ -139,9 +139,9 @@ extension Value { public let rowid = Expression("ROWID") public func cast(_ expression: Expression) -> Expression { - return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) + Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) } public func cast(_ expression: Expression) -> Expression { - return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) + Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 32d39fa7..5ffbbceb 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -47,331 +47,331 @@ private enum Operator: String { case concatenate = "||" func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return self.rawValue.infix(lhs, rhs, wrap: wrap) + self.rawValue.infix(lhs, rhs, wrap: wrap) } func wrap(_ expression: Expressible) -> Expression { - return self.rawValue.wrap(expression) + self.rawValue.wrap(expression) } } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.wrap(rhs) + Operator.minus.wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.wrap(rhs) + Operator.minus.wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseXor.wrap(rhs) + Operator.bitwiseXor.wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseXor.wrap(rhs) + Operator.bitwiseXor.wrap(rhs) } // MARK: - public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + 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)) } return Operator.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + 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) } @@ -379,26 +379,26 @@ public func ==(lhs: V?, rhs: Expression) -> Expression wher } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "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)) } return "IS".infix(lhs, rhs) } public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "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) } @@ -406,26 +406,26 @@ public func ===(lhs: V?, rhs: Expression) -> Expression whe } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + 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)) } return Operator.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + 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) } @@ -433,26 +433,26 @@ public func !=(lhs: V?, rhs: Expression) -> Expression wher } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "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)) } return "IS NOT".infix(lhs, rhs) } public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "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) } @@ -460,215 +460,215 @@ public func !==(lhs: V?, rhs: Expression) -> Expression whe } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + - rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", + rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + - rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", + rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) + Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) + Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } // MARK: - public func and(_ terms: Expression...) -> Expression { - return "AND".infix(terms) + "AND".infix(terms) } public func and(_ terms: [Expression]) -> Expression { - return "AND".infix(terms) + "AND".infix(terms) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func or(_ terms: Expression...) -> Expression { - return "OR".infix(terms) + "OR".infix(terms) } public func or(_ terms: [Expression]) -> Expression { - return "OR".infix(terms) + "OR".infix(terms) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operator.not.wrap(rhs) + Operator.not.wrap(rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operator.not.wrap(rhs) + Operator.not.wrap(rhs) } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 22742809..59b24a8d 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -53,7 +53,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT` clause applied. public func select(_ column1: Expressible, _ more: Expressible...) -> Self { - return select(false, [column1] + more) + select(false, [column1] + more) } /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. @@ -68,7 +68,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. public func select(distinct column1: Expressible, _ more: Expressible...) -> Self { - return select(true, [column1] + more) + select(true, [column1] + more) } /// Builds a copy of the query with the `SELECT` clause applied. @@ -84,7 +84,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT` clause applied. public func select(_ all: [Expressible]) -> Self { - return select(false, all) + select(false, all) } /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. @@ -99,7 +99,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. public func select(distinct columns: [Expressible]) -> Self { - return select(true, columns) + select(true, columns) } /// Builds a copy of the query with the `SELECT *` clause applied. @@ -113,7 +113,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT *` clause applied. public func select(_ star: Star) -> Self { - return select([star(nil, nil)]) + select([star(nil, nil)]) } /// Builds a copy of the query with the `SELECT DISTINCT *` clause applied. @@ -127,7 +127,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT *` clause applied. public func select(distinct star: Star) -> Self { - return select(distinct: [star(nil, nil)]) + select(distinct: [star(nil, nil)]) } /// Builds a scalar copy of the query with the `SELECT` clause applied. @@ -142,10 +142,10 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT` clause applied. public func select(_ column: Expression) -> ScalarQuery { - return select(false, [column]) + select(false, [column]) } public func select(_ column: Expression) -> ScalarQuery { - return select(false, [column]) + select(false, [column]) } /// Builds a scalar copy of the query with the `SELECT DISTINCT` clause @@ -161,14 +161,14 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. public func select(distinct column: Expression) -> ScalarQuery { - return select(true, [column]) + select(true, [column]) } public func select(distinct column: Expression) -> ScalarQuery { - return select(true, [column]) + select(true, [column]) } public var count: ScalarQuery { - return select(Expression.count(*)) + select(Expression.count(*)) } } @@ -223,7 +223,7 @@ extension QueryType { /// /// - Returns: A query with the given `JOIN` clause applied. public func join(_ table: QueryType, on condition: Expression) -> Self { - return join(table, on: Expression(condition)) + join(table, on: Expression(condition)) } /// Adds a `JOIN` clause to the query. @@ -244,7 +244,7 @@ extension QueryType { /// /// - Returns: A query with the given `JOIN` clause applied. public func join(_ table: QueryType, on condition: Expression) -> Self { - return join(.inner, table, on: condition) + join(.inner, table, on: condition) } /// Adds a `JOIN` clause to the query. @@ -267,7 +267,7 @@ extension QueryType { /// /// - Returns: A query with the given `JOIN` clause applied. public func join(_ type: JoinType, _ table: QueryType, on condition: Expression) -> Self { - return join(type, table, on: Expression(condition)) + join(type, table, on: Expression(condition)) } /// Adds a `JOIN` clause to the query. @@ -291,7 +291,8 @@ extension QueryType { /// - Returns: A query with the given `JOIN` clause applied. public func join(_ type: JoinType, _ table: QueryType, on condition: Expression) -> Self { var query = self - query.clauses.join.append((type: type, query: table, condition: table.clauses.filters.map { condition && $0 } ?? condition as Expressible)) + query.clauses.join.append((type: type, query: table, + condition: table.clauses.filters.map { condition && $0 } ?? condition as Expressible)) return query } @@ -309,7 +310,7 @@ extension QueryType { /// /// - Returns: A query with the given `WHERE` clause applied. public func filter(_ predicate: Expression) -> Self { - return filter(Expression(predicate)) + filter(Expression(predicate)) } /// Adds a condition to the query’s `WHERE` clause. @@ -332,13 +333,13 @@ extension QueryType { /// Adds a condition to the query’s `WHERE` clause. /// This is an alias for `filter(predicate)` public func `where`(_ predicate: Expression) -> Self { - return `where`(Expression(predicate)) + `where`(Expression(predicate)) } /// Adds a condition to the query’s `WHERE` clause. /// This is an alias for `filter(predicate)` public func `where`(_ predicate: Expression) -> Self { - return filter(predicate) + filter(predicate) } // MARK: GROUP BY @@ -349,7 +350,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY` clause applied. public func group(_ by: Expressible...) -> Self { - return group(by) + group(by) } /// Sets a `GROUP BY` clause on the query. @@ -358,7 +359,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY` clause applied. public func group(_ by: [Expressible]) -> Self { - return group(by, nil) + group(by, nil) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -371,7 +372,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: Expressible, having: Expression) -> Self { - return group([by], having: having) + group([by], having: having) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -384,7 +385,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: Expressible, having: Expression) -> Self { - return group([by], having: having) + group([by], having: having) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -397,7 +398,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: [Expressible], having: Expression) -> Self { - return group(by, Expression(having)) + group(by, Expression(having)) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -410,7 +411,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: [Expressible], having: Expression) -> Self { - return group(by, having) + group(by, having) } fileprivate func group(_ by: [Expressible], _ having: Expression?) -> Self { @@ -434,7 +435,7 @@ extension QueryType { /// /// - Returns: A query with the given `ORDER BY` clause applied. public func order(_ by: Expressible...) -> Self { - return order(by) + order(by) } /// Sets an `ORDER BY` clause on the query. @@ -469,7 +470,7 @@ extension QueryType { /// /// - Returns: A query with the given LIMIT clause applied. public func limit(_ length: Int?) -> Self { - return limit(length, nil) + limit(length, nil) } /// Sets LIMIT and OFFSET clauses on the query. @@ -487,7 +488,7 @@ extension QueryType { /// /// - Returns: A query with the given LIMIT and OFFSET clauses applied. public func limit(_ length: Int, offset: Int) -> Self { - return limit(length, offset) + limit(length, offset) } // prevents limit(nil, offset: 5) @@ -504,12 +505,13 @@ extension QueryType { // MARK: - fileprivate var selectClause: Expressible { - return " ".join([ - Expression(literal: clauses.select.distinct ? "SELECT DISTINCT" : "SELECT"), - ", ".join(clauses.select.columns), - Expression(literal: "FROM"), - tableName(alias: true) - ]) + " ".join([ + Expression(literal: + clauses.select.distinct ? "SELECT DISTINCT" : "SELECT"), + ", ".join(clauses.select.columns), + Expression(literal: "FROM"), + tableName(alias: true) + ]) } fileprivate var joinClause: Expressible? { @@ -616,31 +618,31 @@ extension QueryType { // MARK: INSERT public func insert(_ value: Setter, _ more: Setter...) -> Insert { - return insert([value] + more) + insert([value] + more) } public func insert(_ values: [Setter]) -> Insert { - return insert(nil, values) + insert(nil, values) } public func insert(or onConflict: OnConflict, _ values: Setter...) -> Insert { - return insert(or: onConflict, values) + insert(or: onConflict, values) } public func insert(or onConflict: OnConflict, _ values: [Setter]) -> Insert { - return insert(onConflict, values) + insert(onConflict, values) } public func insertMany( _ values: [[Setter]]) -> Insert { - return insertMany(nil, values) + insertMany(nil, values) } public func insertMany(or onConflict: OnConflict, _ values: [[Setter]]) -> Insert { - return insertMany(onConflict, values) + insertMany(onConflict, values) } public func insertMany(or onConflict: OnConflict, _ values: [Setter]...) -> Insert { - return insertMany(onConflict, values) + insertMany(onConflict, values) } fileprivate func insert(_ or: OnConflict?, _ values: [Setter]) -> Insert { @@ -689,7 +691,7 @@ extension QueryType { /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. public func insert() -> Insert { - return Insert(" ".join([ + Insert(" ".join([ Expression(literal: "INSERT INTO"), tableName(), Expression(literal: "DEFAULT VALUES") @@ -703,17 +705,17 @@ extension QueryType { /// /// - Returns: The number of updated rows and statement. public func insert(_ query: QueryType) -> Update { - return Update(" ".join([ + Update(" ".join([ Expression(literal: "INSERT INTO"), tableName(), query.expression - ]).expression) + ]).expression) } // MARK: UPSERT public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible) -> Insert { - return upsert(insertValues, onConflictOf: conflicting) + upsert(insertValues, onConflictOf: conflicting) } public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible) -> Insert { @@ -723,7 +725,7 @@ extension QueryType { } public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { - return upsert(insertValues, onConflictOf: conflicting, set: setValues) + upsert(insertValues, onConflictOf: conflicting, set: setValues) } public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { @@ -751,7 +753,7 @@ extension QueryType { // MARK: UPDATE public func update(_ values: Setter...) -> Update { - return update(values) + update(values) } public func update(_ values: [Setter]) -> Update { @@ -785,7 +787,7 @@ extension QueryType { // MARK: EXISTS public var exists: Select { - return Select(" ".join([ + Select(" ".join([ Expression(literal: "SELECT EXISTS"), "".wrap(expression) as Expression ]).expression) @@ -800,15 +802,15 @@ extension QueryType { /// - Returns: A column expression namespaced with the query’s table name or /// alias. public func namespace(_ column: Expression) -> Expression { - return Expression(".".join([tableName(), column]).expression) + Expression(".".join([tableName(), column]).expression) } public subscript(column: Expression) -> Expression { - return namespace(column) + namespace(column) } public subscript(column: Expression) -> Expression { - return namespace(column) + namespace(column) } /// Prefixes a star with the query’s table name or alias. @@ -818,7 +820,7 @@ extension QueryType { /// - Returns: A `*` expression namespaced with the query’s table name or /// alias. public subscript(star: Star) -> Expression { - return namespace(star(nil, nil)) + namespace(star(nil, nil)) } // MARK: - @@ -977,7 +979,7 @@ public struct RowIterator: FailableIterator { let columnNames: [String: Int] public func failableNext() throws -> Row? { - return try statement.failableNext().flatMap { Row(columnNames, $0) } + try statement.failableNext().flatMap { Row(columnNames, $0) } } public func map(_ transform: (Element) throws -> T) throws -> [T] { @@ -1016,7 +1018,7 @@ extension Connection { let namespace = names.joined(separator: ".") func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { - return { (queryType: QueryType) throws -> Void in + { (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 let expression = query.expression @@ -1074,7 +1076,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepareRowIterator(query.limit(1, query.clauses.limit?.offset)).failableNext() + try prepareRowIterator(query.limit(1, query.clauses.limit?.offset)).failableNext() } /// Runs an `Insert` query. diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index d8d45e41..726f3e27 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -27,7 +27,7 @@ extension SchemaType { // MARK: - DROP TABLE / VIEW / VIRTUAL TABLE public func drop(ifExists: Bool = false) -> String { - return drop("TABLE", tableName(), ifExists) + drop("TABLE", tableName(), ifExists) } } @@ -64,63 +64,63 @@ extension Table { // MARK: - ALTER TABLE … ADD COLUMN public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } fileprivate func addColumn(_ expression: Expressible) -> String { - return " ".join([ + " ".join([ Expression(literal: "ALTER TABLE"), tableName(), Expression(literal: "ADD COLUMN"), @@ -131,7 +131,7 @@ extension Table { // MARK: - ALTER TABLE … RENAME TO public func rename(_ to: Table) -> String { - return rename(to: to) + rename(to: to) } // MARK: - CREATE INDEX @@ -150,7 +150,7 @@ extension Table { // MARK: - DROP INDEX public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { - return drop("INDEX", indexName(columns), ifExists) + drop("INDEX", indexName(columns), ifExists) } fileprivate func indexName(_ columns: [Expressible]) -> Expressible { @@ -188,7 +188,7 @@ extension View { // MARK: - DROP VIEW public func drop(ifExists: Bool = false) -> String { - return drop("VIEW", tableName(), ifExists) + drop("VIEW", tableName(), ifExists) } } @@ -210,7 +210,7 @@ extension VirtualTable { // MARK: - ALTER TABLE … RENAME TO public func rename(_ to: VirtualTable) -> String { - return rename(to: to) + rename(to: to) } } @@ -495,7 +495,7 @@ public struct Module { extension Module: Expressible { public var expression: Expression { - return name.wrap(arguments) + name.wrap(arguments) } } @@ -517,7 +517,7 @@ private extension QueryType { } func rename(to: Self) -> String { - return " ".join([ + " ".join([ Expression(literal: "ALTER TABLE"), tableName(), Expression(literal: "RENAME TO"), @@ -557,7 +557,7 @@ private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: } private func reference(_ primary: (QueryType, Expressible)) -> Expressible { - return " ".join([ + " ".join([ Expression(literal: "REFERENCES"), primary.0.tableName(qualified: false), "".wrap(primary.1) as Expression diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 08fb1748..7910cab8 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -70,213 +70,213 @@ public struct Setter { extension Setter: Expressible { public var expression: Expression { - return "=".infix(column, value, wrap: false) + "=".infix(column, value, wrap: false) } } public func <-(column: Expression, value: Expression) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: V) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: Expression) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: Expression) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: V?) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func +=(column: Expression, value: Expression) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: String) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: String) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) += 1 + Expression(column) += 1 } public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) += 1 + Expression(column) += 1 } public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) -= 1 + Expression(column) -= 1 } public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) -= 1 + Expression(column) -= 1 } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 44c2c302..045a8274 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -362,14 +362,14 @@ class ConnectionTests: SQLiteTestCase { func test_createCollation_createsCollation() { try! db.createCollation("NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .diacriticInsensitive) + lhs.compare(rhs, options: .diacriticInsensitive) } XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } func test_createCollation_createsQuotableCollation() { try! db.createCollation("NO DIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .diacriticInsensitive) + lhs.compare(rhs, options: .diacriticInsensitive) } XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 0ebcd13c..af1ef110 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -7,7 +7,7 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { func testFunctionNoOptional() { let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { - return "a" + "a" } let result = try! db.prepare("SELECT test()").scalar() as! String XCTAssertEqual("a", result) @@ -15,7 +15,7 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { func testFunctionResultOptional() { let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { - return "a" + "a" } let result = try! db.prepare("SELECT test()").scalar() as! String? XCTAssertEqual("a", result) @@ -30,7 +30,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionNoOptional() { let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a in - return "b"+a + "b" + a } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -38,7 +38,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionLeftOptional() { let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a in - return "b"+a! + "b" + a! } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -46,7 +46,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionResultOptional() { let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a in - return "b"+a + "b" + a } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -54,7 +54,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionLeftResultOptional() { let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a: String?) -> String? in - return "b"+a! + "b" + a! } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -73,7 +73,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testNoOptional() { let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b + a + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -81,7 +81,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testLeftOptional() { let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b + a! + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -89,7 +89,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testRightOptional() { let _: FunctionRightOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b! + a + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -97,7 +97,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testResultOptional() { let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b + a + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) @@ -105,7 +105,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionLeftRightOptional() { let _: FunctionLeftRightOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b! + a! + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -113,7 +113,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionLeftResultOptional() { let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b + a! + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) @@ -121,7 +121,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionRightResultOptional() { let _: FunctionRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b! + a + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) @@ -129,7 +129,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionLeftRightResultOptional() { let _: FunctionLeftRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b! + a! + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index b637955b..33be422c 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -185,7 +185,7 @@ class FTS4ConfigTests: XCTestCase { } func sql(_ config: FTS4Config) -> String { - return virtualTable.create(.FTS4(config)) + virtualTable.create(.FTS4(config)) } } diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index 8079b28c..4cce4523 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -122,6 +122,6 @@ class FTS5Tests: XCTestCase { } func sql(_ config: FTS5Config) -> String { - return virtualTable.create(.FTS5(config)) + virtualTable.create(.FTS5(config)) } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 0994dd97..0aa918f0 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -42,10 +42,8 @@ class SQLiteTestCase: XCTestCase { } @discardableResult func insertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { - return try db.run( - "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - "\(name)@example.com", age?.datatypeValue, admin.datatypeValue - ) + try db.run("INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", + "\(name)@example.com", age?.datatypeValue, admin.datatypeValue) } func assertSQL(_ SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { From 9b3781c66197ed1d30894fbc831112b54786f21e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:06:05 +0200 Subject: [PATCH 112/391] Unneeded --- Tests/SQLiteTests/ConnectionTests.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 045a8274..37b765ab 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -217,8 +217,6 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndCommitsSavepoints() { - let db: Connection = db - try! db.savepoint("1") { try db.savepoint("2") { try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") @@ -235,7 +233,6 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndRollsSavepointsBack() { - let db: Connection = db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { From 6a1b8f355a808255a850c58d65ca56b1b68df9bf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:32:10 +0200 Subject: [PATCH 113/391] Add a release checklist --- .cocoadocs.yml | 2 -- Documentation/Release.md | 10 ++++++ SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Typed/CustomFunctions.swift | 24 ++++++------- .../SQLiteTests/CustomAggregationTests.swift | 34 +++++++++---------- 5 files changed, 41 insertions(+), 31 deletions(-) delete mode 100644 .cocoadocs.yml create mode 100644 Documentation/Release.md diff --git a/.cocoadocs.yml b/.cocoadocs.yml deleted file mode 100644 index 27840032..00000000 --- a/.cocoadocs.yml +++ /dev/null @@ -1,2 +0,0 @@ -additional_guides: - - Documentation/Index.md diff --git a/Documentation/Release.md b/Documentation/Release.md new file mode 100644 index 00000000..ef174683 --- /dev/null +++ b/Documentation/Release.md @@ -0,0 +1,10 @@ +# SQLite.swift Release checklist + +* [ ] Make sure current master branch has a green build +* [ ] Make sure `CHANGELOG.md` is up-to-date +* [ ] Run `pod lib lint` locally +* [ ] Update the version numbers mentioned in `README.md`, `Documentation/Index.md` +* [ ] Update `MARKETING_VERSION` in `SQLite.xcodeproj/project.pbxproj` +* [ ] Create a tag with the version number (`x.y.z`) +* [ ] Publish to CocoaPods: `pod trunk push` +* [ ] Update the release information on GitHub diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 03d8b326..3b5428d0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -232,6 +232,7 @@ 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; + 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 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 = ""; }; @@ -496,6 +497,7 @@ children = ( EE247B8F1C3F822500AE3E12 /* Index.md */, EE247B901C3F822500AE3E12 /* Resources */, + 19A17EA3A313F129011B3FA0 /* Release.md */, ); path = Documentation; sourceTree = ""; diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 88fb5aaa..f2d90b0f 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -160,8 +160,8 @@ public extension Connection { } // MARK: - - - public func createAggregation( + + func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, @@ -169,14 +169,14 @@ public extension Connection { reduce: @escaping (T, [Binding?]) -> T, result: @escaping (T) -> Binding? ) { - + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, ptr) in let p = ptr.pointee.assumingMemoryBound(to: T.self) let current = Unmanaged.fromOpaque(p).takeRetainedValue() let next = reduce(current, bindings) ptr.pointee = Unmanaged.passRetained(next).toOpaque() } - + let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in let p = ptr.pointee.assumingMemoryBound(to: T.self) let obj = Unmanaged.fromOpaque(p).takeRetainedValue() @@ -184,17 +184,17 @@ public extension Connection { ptr.deallocate() return value } - + let state: () -> UnsafeMutablePointer = { let p = UnsafeMutablePointer.allocate(capacity: 1) p.pointee = Unmanaged.passRetained(initialValue).toOpaque() return p } - + createAggregation(aggregate, step: step, final: final, state: state) } - - public func createAggregation( + + func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, @@ -202,25 +202,25 @@ public extension Connection { reduce: @escaping (T, [Binding?]) -> T, result: @escaping (T) -> Binding? ) { - + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, p) in let current = p.pointee let next = reduce(current, bindings) p.pointee = next } - + let final: (UnsafeMutablePointer) -> Binding? = { (p) in let v = result(p.pointee) p.deallocate() return v } - + let state: () -> UnsafeMutablePointer = { let p = UnsafeMutablePointer.allocate(capacity: 1) p.pointee = initialValue return p } - + createAggregation(aggregate, step: step, final: final, state: state) } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index f8efc7e4..05ed619a 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -16,19 +16,19 @@ import SQLite3 class CustomAggregationTests : SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() - try! InsertUser("Alice", age: 30, admin: true) - try! InsertUser("Bob", age: 25, admin: true) - try! InsertUser("Eve", age: 28, admin: false) + createUsersTable() + try! insertUser("Alice", age: 30, admin: true) + try! insertUser("Bob", age: 25, admin: true) + try! insertUser("Eve", age: 28, admin: false) } - + func testUnsafeCustomSum() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v } } - + let final = { (state: UnsafeMutablePointer) -> Binding? in let v = state.pointee let p = UnsafeMutableBufferPointer(start: state, count: 1) @@ -41,13 +41,13 @@ class CustomAggregationTests : SQLiteTestCase { return v.baseAddress! } let result = try! db.prepare("SELECT mySUM1(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 XCTAssertEqual(83, value) } } - + func testUnsafeCustomSumGrouping() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { @@ -66,11 +66,11 @@ class CustomAggregationTests : SQLiteTestCase { return v.baseAddress! } let result = try! db.prepare("SELECT mySUM2(age) AS s FROM users GROUP BY admin ORDER BY s") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([28, 55])) } - + func testCustomSum() { let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 @@ -78,7 +78,7 @@ class CustomAggregationTests : SQLiteTestCase { } let _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 XCTAssertEqual(2083, value) @@ -92,11 +92,11 @@ class CustomAggregationTests : SQLiteTestCase { } let _ = 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 i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([3028, 3055])) } - + func testCustomStringAgg() { let initial = String(repeating: " ", count: 64) let reduce : (String, [Binding?]) -> String = { (last, bindings) in @@ -105,13 +105,13 @@ class CustomAggregationTests : SQLiteTestCase { } let _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? String XCTAssertEqual("\(initial)Alice@example.comBob@example.comEve@example.com", value) } } - + func testCustomObjectSum() { { let initial = TestObject(value: 1000) @@ -126,7 +126,7 @@ class CustomAggregationTests : SQLiteTestCase { { XCTAssertEqual(TestObject.inits, 1) let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 XCTAssertEqual(1083, value) @@ -143,7 +143,7 @@ class CustomAggregationTests : SQLiteTestCase { class TestObject { static var inits = 0 static var deinits = 0 - + var value: Int64 init(value: Int64) { self.value = value From 75ef800901784da018a4d9eed1942e49e0a82ae1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:37:02 +0200 Subject: [PATCH 114/391] Need to update podspec --- Documentation/Release.md | 1 + SQLite.swift.podspec | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/Release.md b/Documentation/Release.md index ef174683..0177fc8f 100644 --- a/Documentation/Release.md +++ b/Documentation/Release.md @@ -2,6 +2,7 @@ * [ ] Make sure current master branch has a green build * [ ] Make sure `CHANGELOG.md` is up-to-date +* [ ] Update the version number in `SQLite.swift.podspec` * [ ] Run `pod lib lint` locally * [ ] Update the version numbers mentioned in `README.md`, `Documentation/Index.md` * [ ] Update `MARKETING_VERSION` in `SQLite.xcodeproj/project.pbxproj` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 499012c1..f273ffd8 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" s.version = "0.13.0" - s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." + s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC SQLite.swift provides compile-time confidence in SQL statement syntax and @@ -17,13 +17,12 @@ Pod::Spec.new do |s| s.module_name = 'SQLite' s.default_subspec = 'standard' s.swift_versions = ['5'] - - + ios_deployment_target = '9.0' tvos_deployment_target = '9.1' osx_deployment_target = '10.15' watchos_deployment_target = '3.0' - + s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target s.osx.deployment_target = osx_deployment_target From 8ba6c0e6ea34e3b85f71a47511420fe2ad34bddd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:45:50 +0200 Subject: [PATCH 115/391] https links, cocoadocs is dead --- CONTRIBUTING.md | 2 +- README.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c367b95..6969a705 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ addresses everything. If it doesn’t, continue the conversation there. If your searches return empty, see the [bug](#bugs) or [feature request](#feature-requests) guidelines below. -[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift +[Ask on Stack Overflow]: https://stackoverflow.com/questions/tagged/sqlite.swift [Search]: https://github.com/stephencelis/SQLite.swift/search?type=Issues diff --git a/README.md b/README.md index 98a625c4..ac044dc0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ syntax _and_ intent. - Extensively tested - [SQLCipher][] support via CocoaPods - Active support at - [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), + [StackOverflow](https://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) @@ -119,7 +119,7 @@ For a more comprehensive example, see and the [companion repository][SQLiteDataAccessLayer2]. -[Create a Data Access Layer with SQLite.swift and Swift 2]: http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html +[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 @@ -226,7 +226,7 @@ device: [Xcode]: https://developer.apple.com/xcode/downloads/ -[Submodule]: http://git-scm.com/book/en/Git-Tools-Submodules +[Submodule]: https://git-scm.com/book/en/Git-Tools-Submodules [download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip @@ -243,7 +243,7 @@ device: [See the planning document]: /Documentation/Planning.md [Read the contributing guidelines]: ./CONTRIBUTING.md#contributing -[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift +[Ask on Stack Overflow]: https://stackoverflow.com/questions/tagged/sqlite.swift [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new [Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork @@ -280,16 +280,16 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) [Swift]: https://swift.org/ -[SQLite3]: http://www.sqlite.org +[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 [CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png -[CocoaPodsVersionLink]: http://cocoadocs.org/docsets/SQLite.swift +[CocoaPodsVersionLink]: https://cocoapods.org/pods/SQLite.swift [PlatformBadge]: https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png -[PlatformLink]: http://cocoadocs.org/docsets/SQLite.swift +[PlatformLink]: https://cocoapods.org/pods/SQLite.swift [CartagheBadge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat [CarthageLink]: https://github.com/Carthage/Carthage From 66e01e2191707158dff6127ce1055fb084f8f2b5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 12:03:19 +0200 Subject: [PATCH 116/391] Fix lint post merge --- .swiftlint.yml | 8 +++-- Sources/SQLite/Core/Connection.swift | 32 ++++++++--------- Sources/SQLite/Typed/CoreFunctions.swift | 1 - Sources/SQLite/Typed/CustomFunctions.swift | 36 +++++++++---------- .../SQLiteTests/CustomAggregationTests.swift | 24 +++++++------ 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 3bfc5fa5..b5e53d56 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -28,11 +28,15 @@ type_body_length: warning: 260 error: 260 +function_body_length: + warning: 60 + error: 60 + line_length: warning: 150 error: 150 ignores_comments: true file_length: - warning: 760 - error: 760 + warning: 900 + error: 900 diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 07c85373..2bea05c8 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -35,6 +35,7 @@ import SQLite3 #endif /// A connection to SQLite. +// swiftlint:disable:next type_body_length public final class Connection { /// The location of a SQLite database. @@ -585,7 +586,7 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - // swiftlint:disable:next cyclomatic_complexity function_body_length + // swiftlint:disable:next cyclomatic_complexity public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 @@ -636,7 +637,7 @@ public final class Connection { if functions[function] == nil { functions[function] = [:] } functions[function]?[argc] = box } - + /// Creates or redefines a custom SQL aggregate. /// /// - Parameters: @@ -665,19 +666,19 @@ public final class Connection { /// - state: A block of code to run to produce a fresh state variable for /// each aggregation group. The block should return an /// UnsafeMutablePointer to the fresh state variable. + // swiftlint:disable:next cyclomatic_complexity public func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, - step: @escaping ([Binding?], UnsafeMutablePointer) -> (), + step: @escaping ([Binding?], UnsafeMutablePointer) -> Void, final: @escaping (UnsafeMutablePointer) -> Binding?, state: @escaping () -> UnsafeMutablePointer) { - - + let argc = argumentCount.map { Int($0) } ?? -1 - let box : Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? - let p = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) + let mutablePointer = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) if stepFlag > 0 { let arguments: [Binding?] = (0.. Binding? ) { - let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, ptr) in - let p = ptr.pointee.assumingMemoryBound(to: T.self) - let current = Unmanaged.fromOpaque(p).takeRetainedValue() + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, ptr) in + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let current = Unmanaged.fromOpaque(pointer).takeRetainedValue() let next = reduce(current, bindings) ptr.pointee = Unmanaged.passRetained(next).toOpaque() } let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in - let p = ptr.pointee.assumingMemoryBound(to: T.self) - let obj = Unmanaged.fromOpaque(p).takeRetainedValue() + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let obj = Unmanaged.fromOpaque(pointer).takeRetainedValue() let value = result(obj) ptr.deallocate() return value } let state: () -> UnsafeMutablePointer = { - let p = UnsafeMutablePointer.allocate(capacity: 1) - p.pointee = Unmanaged.passRetained(initialValue).toOpaque() - return p + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = Unmanaged.passRetained(initialValue).toOpaque() + return pointer } createAggregation(aggregate, step: step, final: final, state: state) @@ -203,22 +203,22 @@ public extension Connection { result: @escaping (T) -> Binding? ) { - let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, p) in - let current = p.pointee + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, pointer) in + let current = pointer.pointee let next = reduce(current, bindings) - p.pointee = next + pointer.pointee = next } - let final: (UnsafeMutablePointer) -> Binding? = { (p) in - let v = result(p.pointee) - p.deallocate() - return v + let final: (UnsafeMutablePointer) -> Binding? = { pointer in + let value = result(pointer.pointee) + pointer.deallocate() + return value } let state: () -> UnsafeMutablePointer = { - let p = UnsafeMutablePointer.allocate(capacity: 1) - p.pointee = initialValue - return p + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = initialValue + return pointer } createAggregation(aggregate, step: step, final: final, state: state) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 05ed619a..99cbb00d 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -13,7 +13,7 @@ import CSQLite import SQLite3 #endif -class CustomAggregationTests : SQLiteTestCase { +class CustomAggregationTests: SQLiteTestCase { override func setUp() { super.setUp() createUsersTable() @@ -35,7 +35,7 @@ class CustomAggregationTests : SQLiteTestCase { p.deallocate() return v } - let _ = db.createAggregation("mySUM1", step: step, final: final) { + _ = db.createAggregation("mySUM1", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -60,7 +60,7 @@ class CustomAggregationTests : SQLiteTestCase { p.deallocate() return v } - let _ = db.createAggregation("mySUM2", step: step, final: final) { + _ = db.createAggregation("mySUM2", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -72,11 +72,11 @@ class CustomAggregationTests : SQLiteTestCase { } func testCustomSum() { - let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } - let _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) + _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -86,11 +86,11 @@ class CustomAggregationTests : SQLiteTestCase { } func testCustomSumGrouping() { - let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } - let _ = db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) + _ = 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 i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } @@ -99,11 +99,11 @@ class CustomAggregationTests : SQLiteTestCase { func testCustomStringAgg() { let initial = String(repeating: " ", count: 64) - let reduce : (String, [Binding?]) -> String = { (last, bindings) in + let reduce: (String, [Binding?]) -> String = { (last, bindings) in let v = (bindings[0] as? String) ?? "" return last + v } - let _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) + _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -115,14 +115,16 @@ class CustomAggregationTests : SQLiteTestCase { func testCustomObjectSum() { { let initial = TestObject(value: 1000) - let reduce : (TestObject, [Binding?]) -> TestObject = { (last, bindings) in + let reduce: (TestObject, [Binding?]) -> TestObject = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return TestObject(value: last.value + v) } - let _ = db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) + db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) // end this scope to ensure that the initial value is retained // by the createAggregation call. + // swiftlint:disable:next trailing_semicolon }(); + { XCTAssertEqual(TestObject.inits, 1) let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") From 985b2e4d8c21f94879a084c5d5becafee4821dfe Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 12:04:41 +0200 Subject: [PATCH 117/391] Fix warnings --- Tests/SQLiteTests/CustomAggregationTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 99cbb00d..337a5aa8 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -35,7 +35,7 @@ class CustomAggregationTests: SQLiteTestCase { p.deallocate() return v } - _ = db.createAggregation("mySUM1", step: step, final: final) { + db.createAggregation("mySUM1", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -60,7 +60,7 @@ class CustomAggregationTests: SQLiteTestCase { p.deallocate() return v } - _ = db.createAggregation("mySUM2", step: step, final: final) { + db.createAggregation("mySUM2", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -76,7 +76,7 @@ class CustomAggregationTests: SQLiteTestCase { let v = (bindings[0] as? Int64) ?? 0 return last + v } - _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) + db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -90,7 +90,7 @@ class CustomAggregationTests: SQLiteTestCase { let v = (bindings[0] as? Int64) ?? 0 return last + v } - _ = db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) + 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 i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } @@ -103,7 +103,7 @@ class CustomAggregationTests: SQLiteTestCase { let v = (bindings[0] as? String) ?? "" return last + v } - _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) + db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { From 896d2978548a336bcade3c8e1b3076f7b2205c55 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 13:16:20 +0200 Subject: [PATCH 118/391] Fail build if there are compiler warnings Closes #1065 --- .github/workflows/build.yml | 8 ++++---- Sources/SQLite/Core/Connection.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1119284a..af6666ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,10 @@ jobs: brew outdated swiftlint || brew upgrade swiftlint - name: "Lint" run: make lint + - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" + env: + PACKAGE_MANAGER_COMMAND: test -Xswiftc -warnings-as-errors + run: ./run-tests.sh - name: "Run tests (BUILD_SCHEME: SQLite iOS)" env: BUILD_SCHEME: SQLite iOS @@ -57,7 +61,3 @@ jobs: env: CARTHAGE_PLATFORM: tvOS run: ./run-tests.sh - - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" - env: - PACKAGE_MANAGER_COMMAND: test - run: ./run-tests.sh diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 2bea05c8..fa086181 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -434,7 +434,7 @@ public final class Connection { } } - @available(OSX, deprecated: 10.2) + @available(OSX, deprecated: 10.12) @available(iOS, deprecated: 10.0) @available(watchOS, deprecated: 3.0) @available(tvOS, deprecated: 10.0) From 559b796187ab1b71148ac68f37c4b2e23db3b49a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 13:42:34 +0200 Subject: [PATCH 119/391] Simplify CocoaPods linting Closes #1066 --- Tests/CocoaPods/.gitignore | 1 - Tests/CocoaPods/Gemfile | 4 -- Tests/CocoaPods/Gemfile.lock | 96 ----------------------------- Tests/CocoaPods/Makefile | 15 ----- Tests/CocoaPods/integration_test.rb | 35 ----------- run-tests.sh | 8 ++- 6 files changed, 6 insertions(+), 153 deletions(-) delete mode 100644 Tests/CocoaPods/.gitignore delete mode 100644 Tests/CocoaPods/Gemfile delete mode 100644 Tests/CocoaPods/Gemfile.lock delete mode 100644 Tests/CocoaPods/Makefile delete mode 100755 Tests/CocoaPods/integration_test.rb diff --git a/Tests/CocoaPods/.gitignore b/Tests/CocoaPods/.gitignore deleted file mode 100644 index 4cf24de2..00000000 --- a/Tests/CocoaPods/.gitignore +++ /dev/null @@ -1 +0,0 @@ -gems/ diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile deleted file mode 100644 index da52ec89..00000000 --- a/Tests/CocoaPods/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source 'https://rubygems.org' - -gem 'cocoapods', '~> 1.10.2' -gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock deleted file mode 100644 index e0383a67..00000000 --- a/Tests/CocoaPods/Gemfile.lock +++ /dev/null @@ -1,96 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.3) - activesupport (5.2.6) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - claide (1.0.3) - cocoapods (1.10.2) - addressable (~> 2.6) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.10.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.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.6.6) - nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.19.0, < 2.0) - cocoapods-core (1.10.2) - activesupport (> 5.0, < 6) - addressable (~> 2.6) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.4.0) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.5.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored2 (3.1.2) - concurrent-ruby (1.1.9) - escape (0.0.4) - ethon (0.14.0) - ffi (>= 1.15.0) - ffi (1.15.3) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.8.10) - concurrent-ruby (~> 1.0) - json (2.5.1) - minitest (5.11.3) - molinillo (0.6.6) - nanaimo (0.3.0) - nap (1.1.0) - netrc (0.11.0) - public_suffix (4.0.6) - rexml (3.2.5) - ruby-macho (1.4.0) - thread_safe (0.3.6) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (1.2.9) - thread_safe (~> 0.1) - xcodeproj (1.21.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 - ruby - -DEPENDENCIES - cocoapods (~> 1.10.2) - minitest - -BUNDLED WITH - 2.2.22 diff --git a/Tests/CocoaPods/Makefile b/Tests/CocoaPods/Makefile deleted file mode 100644 index 532dcbff..00000000 --- a/Tests/CocoaPods/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -XCPRETTY := $(shell command -v xcpretty) - -test: install repo_update - @set -e; \ - for test in *_test.rb; do \ - bundle exec ./$$test | $(XCPRETTY) -c; \ - done - -repo_update: - @bundle exec pod repo update --silent - -install: - @bundle install --path gems - -.PHONY: test install diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb deleted file mode 100755 index 67b13385..00000000 --- a/Tests/CocoaPods/integration_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env ruby - -require 'cocoapods' -require 'cocoapods/validator' -require 'minitest/autorun' - -class IntegrationTest < Minitest::Test - - def test_validate_project - assert validator.validate, "validation failed: #{validator.failure_reason}" - end - - private - - def validator - @validator ||= Pod::Validator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| - validator.config.verbose = true - validator.no_clean = true - validator.use_frameworks = true - validator.fail_fast = true - validator.local = true - validator.allow_warnings = true - subspec = ENV['VALIDATOR_SUBSPEC'] - if subspec == 'none' - validator.no_subspecs = true - else - validator.only_subspec = subspec - end - end - end - - def podspec - File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec') - end -end diff --git a/run-tests.sh b/run-tests.sh index 0a105c41..edc60987 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,9 +7,13 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then - cd Tests/CocoaPods && make test + if [ "$VALIDATOR_SUBSPEC" == "none" ]; then + pod lib lint --no-subspecs + else + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" + fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift ${PACKAGE_MANAGER_COMMAND} + swift "${PACKAGE_MANAGER_COMMAND}" fi From 00b7d3f2eea7a5d98941f88f05c4f9c38eb04654 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 13:51:00 +0200 Subject: [PATCH 120/391] rm obsolete swiftcov --- Makefile | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index a9d29d58..0b9a1606 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,6 @@ else endif XCPRETTY := $(shell command -v xcpretty) -SWIFTCOV := $(shell command -v swiftcov) -GCOVR := $(shell command -v gcovr) TEST_ACTIONS := clean build build-for-testing test-without-building default: test @@ -28,36 +26,14 @@ else $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) endif -coverage: -ifdef SWIFTCOV - $(SWIFTCOV) generate --output coverage \ - $(BUILD_TOOL) $(BUILD_ARGUMENTS) -configuration Release test \ - -- ./SQLite/*.swift -ifdef GCOVR - $(GCOVR) \ - --root . \ - --use-gcov-files \ - --html \ - --html-details \ - --output coverage/index.html \ - --keep -else - @echo gcovr must be installed for HTML output: https://github.com/gcovr/gcovr -endif -else - @echo swiftcov must be installed for coverage: https://github.com/realm/SwiftCov - @exit 1 -endif - clean: $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean - rm -r coverage repl: @$(BUILD_TOOL) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \ swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' sloc: - @zsh -c "grep -vE '^ *//|^$$' SQLite/*/*.{swift,h,m} | wc -l" + @zsh -c "grep -vE '^ *//|^$$' Sources/**/*.{swift,h,m} | wc -l" -.PHONY: test coverage clean repl sloc +.PHONY: test clean repl sloc From 325cfd9480f59a280f4bfe64eb05b1b9288a6402 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:00:42 +0200 Subject: [PATCH 121/391] Unquote --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index edc60987..1ae7a5ca 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -15,5 +15,5 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift "${PACKAGE_MANAGER_COMMAND}" + swift ${PACKAGE_MANAGER_COMMAND} fi From 05c84149a49ff2c41b3f15c1f971b736985c9553 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:12:21 +0200 Subject: [PATCH 122/391] Treat warnings as errors --- SQLite.xcodeproj/project.pbxproj | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3b5428d0..ea7ab871 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -231,8 +231,8 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; - 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.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 = ""; }; 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 = ""; }; @@ -1042,6 +1042,7 @@ 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"; @@ -1049,6 +1050,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; @@ -1062,6 +1064,7 @@ 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"; @@ -1069,6 +1072,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; @@ -1076,11 +1080,13 @@ 03A65E6D1C6BB0F60062603F /* 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 = appletvos; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; @@ -1088,11 +1094,13 @@ 03A65E6E1C6BB0F60062603F /* 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 = appletvos; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; @@ -1107,6 +1115,7 @@ 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"; @@ -1114,6 +1123,7 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 3.0; }; @@ -1129,6 +1139,7 @@ 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"; @@ -1136,6 +1147,7 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 3.0; }; @@ -1271,6 +1283,7 @@ 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"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -1280,6 +1293,7 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; @@ -1293,6 +1307,7 @@ 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"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -1301,26 +1316,31 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; EE247AEB1C3F04ED00AE3E12 /* 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)"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; EE247AEC1C3F04ED00AE3E12 /* 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)"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; @@ -1335,6 +1355,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; + 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"; @@ -1343,6 +1364,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; @@ -1357,6 +1379,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; + 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"; @@ -1365,6 +1388,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; @@ -1373,11 +1397,13 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; + 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 = macosx; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; @@ -1386,11 +1412,13 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; + 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 = macosx; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; From fdac6551b06350181904eab08a0be0a4be4a2160 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:13:58 +0200 Subject: [PATCH 123/391] fail fast --- run-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 1ae7a5ca..32465388 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 + pod lib lint --no-subspecs --fail-fast else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From 6d5bb0ca1d561589cbcf5ffbacd154e5c05659cf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:52:47 +0200 Subject: [PATCH 124/391] Initialize pointer --- Sources/SQLite/Typed/CustomFunctions.swift | 2 +- Tests/SQLiteTests/CustomAggregationTests.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 1d66d075..c38113d1 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -217,7 +217,7 @@ public extension Connection { let state: () -> UnsafeMutablePointer = { let pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.pointee = initialValue + pointer.initialize(to: initialValue) return pointer } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 337a5aa8..d4569eef 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -105,6 +105,7 @@ class CustomAggregationTests: SQLiteTestCase { } db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? String From 9bfc767ce660df89eaacd35e2ff23c8465c3ac3b Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 24 Aug 2021 10:36:16 +0200 Subject: [PATCH 125/391] Triggering GitHub Actions workflow From 7ec7a266bf3cc071db3a6dbdb2c1628ebf3e212c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 24 Aug 2021 17:49:52 +0200 Subject: [PATCH 126/391] Fix truncated strings in function Closes #468 --- Sources/SQLite/Core/Connection.swift | 2 +- Tests/SQLiteTests/CustomFunctionsTests.swift | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index fa086181..b2bc2cbd 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -616,7 +616,7 @@ public final class Connection { } else if let result = result as? Int64 { sqlite3_result_int64(context, result) } else if let result = result as? String { - sqlite3_result_text(context, result, Int32(result.count), SQLITE_TRANSIENT) + sqlite3_result_text(context, result, Int32(result.lengthOfBytes(using: .utf8)), SQLITE_TRANSIENT) } else if result == nil { sqlite3_result_null(context) } else { diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index af1ef110..8e702e9c 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -135,3 +135,12 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { 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 + XCTAssertEqual("töl-aa 12", result) + } +} From 4b4dc65997c1fce4735178c7563285b988133c6a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 10:53:04 +0200 Subject: [PATCH 127/391] Make reference to libsqlite3.tbd non-version specific on macOS https://github.com/stephencelis/SQLite.swift/pull/846 --- SQLite.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ea7ab871..fcdb521c 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -286,7 +286,7 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ From be6bed351703b1e4c054e7ef403a50ca8de53e91 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 11:06:07 +0200 Subject: [PATCH 128/391] Renamed --- Documentation/Index.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 5cc678a6..fce068a2 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -262,10 +262,8 @@ If you have bundled it in your application, you can use FileManager to copy it t ```swift func copyDatabaseIfNeeded(sourcePath: String) -> Bool { - let path = NSSearchPathForDirectoriesInDomains( - .documentDirectory, .userDomainMask, true - ).first! - let destinationPath = destinationPath + "/db.sqlite3" + let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + let destinationPath = documents + "/db.sqlite3" let exists = FileManager.default.fileExists(atPath: destinationPath) guard !exists else { return false } do { From 03823fb0ccf57628db29d8644b1e09d0bb6cc2f8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 12:44:22 +0200 Subject: [PATCH 129/391] Add Linux tests Closes #1064 --- .github/workflows/build.yml | 10 ++++++++ Documentation/Linux.md | 24 +++++++++++++++++++ SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Core/Connection.swift | 16 ++++++++++--- Tests/LinuxMain.swift | 6 ----- Tests/SQLiteTests/ConnectionTests.swift | 3 +++ .../SQLiteTests/CustomAggregationTests.swift | 4 ++++ Tests/SQLiteTests/CustomFunctionsTests.swift | 5 ++++ Tests/SQLiteTests/QueryTests.swift | 2 ++ 9 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 Documentation/Linux.md delete mode 100644 Tests/LinuxMain.swift diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af6666ba..51654fba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,3 +61,13 @@ jobs: env: CARTHAGE_PLATFORM: tvOS run: ./run-tests.sh + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install + run: | + sudo apt-get update -qq + sudo apt-get install -y libsqlite3-dev + - name: Test + run: swift test diff --git a/Documentation/Linux.md b/Documentation/Linux.md new file mode 100644 index 00000000..19189c53 --- /dev/null +++ b/Documentation/Linux.md @@ -0,0 +1,24 @@ +# Linux + +## Debugging + +### Create and launch docker container + +```shell +$ docker container create swift:focal +$ docker run --cap-add=SYS_PTRACE \ + --security-opt seccomp=unconfined \ + --security-opt apparmor=unconfined \ + -i -t swift:focal bash +``` + +### Compile and run tests in debugger + +```shell +$ apt-get update && apt-get install libsqlite3-dev +$ git clone https://github.com/stephencelis/SQLite.swift.git +$ swift test +$ lldb .build/x86_64-unknown-linux-gnu/debug/SQLite.swiftPackageTests.xctest +(lldb) target create ".build/x86_64-unknown-linux-gnu/debug/SQLite.swiftPackageTests.xctest" +(lldb) run +``` diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fcdb521c..6aff8045 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -227,6 +227,7 @@ 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; + 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 = ""; }; @@ -498,6 +499,7 @@ EE247B8F1C3F822500AE3E12 /* Index.md */, EE247B901C3F822500AE3E12 /* Resources */, 19A17EA3A313F129011B3FA0 /* Release.md */, + 19A1794B7972D14330A65BBD /* Linux.md */, ); path = Documentation; sourceTree = ""; diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index b2bc2cbd..39f19a5c 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -629,12 +629,22 @@ public final class Connection { flags |= SQLITE_DETERMINISTIC } #endif - sqlite3_create_function_v2(handle, function, Int32(argc), flags, - unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in + let resultCode = sqlite3_create_function_v2(handle, + function, + Int32(argc), + flags, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self) function(context, argc, value) }, nil, nil, nil) - if functions[function] == nil { functions[function] = [:] } + + if let result = Result(errorCode: resultCode, connection: self, statement: nil) { + fatalError("Error creating function: \(result)") + } + + if functions[function] == nil { + functions[function] = [:] // fails on Linux, https://bugs.swift.org/browse/SR-4429 + } functions[function]?[argc] = box } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 59796fde..00000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,6 +0,0 @@ -import XCTest -@testable import SQLiteTests - -XCTMain([ -testCase([ -])]) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 37b765ab..6c4d170a 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -343,6 +343,8 @@ class ConnectionTests: SQLiteTestCase { } } + // https://bugs.swift.org/browse/SR-4429 + #if !os(Linux) func test_createFunction_withArrayArguments() { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } @@ -390,6 +392,7 @@ class ConnectionTests: SQLiteTestCase { } } } + #endif func test_concurrent_access_single_connection() { // test can fail on iOS/tvOS 9.x: SQLite compile-time differences? diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index d4569eef..142d5d6e 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -13,6 +13,9 @@ import CSQLite import SQLite3 #endif +// https://bugs.swift.org/browse/SR-4429 +#if !os(Linux) + class CustomAggregationTests: SQLiteTestCase { override func setUp() { super.setUp() @@ -139,6 +142,7 @@ class CustomAggregationTests: SQLiteTestCase { XCTAssertEqual(TestObject.deinits, 3) // the initial value is still retained by the aggregate's state block, so deinits is one less than inits } } +#endif /// This class is used to test that aggregation state variables /// can be reference types and are properly memory managed when diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 8e702e9c..caa48acd 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -1,6 +1,9 @@ import XCTest import SQLite +// https://bugs.swift.org/browse/SR-4429 +#if !os(Linux) + class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> Expression typealias FunctionResultOptional = () -> Expression @@ -144,3 +147,5 @@ class CustomFunctionTruncation: SQLiteTestCase { XCTAssertEqual("töl-aa 12", result) } } + +#endif diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 3a58e2fc..c80dd0c7 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -290,6 +290,7 @@ class QueryTests: XCTestCase { ) } + #if !os(Linux) // depends on exact JSON serialization 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, @@ -307,6 +308,7 @@ class QueryTests: XCTestCase { insert ) } + #endif func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { assertSQL( From 7d95523caa831ac0c59c98dd5cbe49675dc74a25 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 15:50:03 +0200 Subject: [PATCH 130/391] Link to GH issue --- Sources/SQLite/Core/Connection.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 2 +- Tests/SQLiteTests/CustomAggregationTests.swift | 2 +- Tests/SQLiteTests/CustomFunctionsTests.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 39f19a5c..2c8c408c 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -643,7 +643,7 @@ public final class Connection { } if functions[function] == nil { - functions[function] = [:] // fails on Linux, https://bugs.swift.org/browse/SR-4429 + functions[function] = [:] // fails on Linux, https://github.com/stephencelis/SQLite.swift/issues/1071 } functions[function]?[argc] = box } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 6c4d170a..5bc4d42f 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -343,7 +343,7 @@ class ConnectionTests: SQLiteTestCase { } } - // https://bugs.swift.org/browse/SR-4429 + // https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) func test_createFunction_withArrayArguments() { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 142d5d6e..d5232260 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -13,7 +13,7 @@ import CSQLite import SQLite3 #endif -// https://bugs.swift.org/browse/SR-4429 +// https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) class CustomAggregationTests: SQLiteTestCase { diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index caa48acd..3d897f8e 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -// https://bugs.swift.org/browse/SR-4429 +// https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) class CustomFunctionNoArgsTests: SQLiteTestCase { From 5e5ba530c064f93cff583923ae2455f0aaa67d15 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 16:00:59 +0200 Subject: [PATCH 131/391] Link to Linux doc --- Documentation/Linux.md | 5 +++++ README.md | 1 + 2 files changed, 6 insertions(+) diff --git a/Documentation/Linux.md b/Documentation/Linux.md index 19189c53..c9e85254 100644 --- a/Documentation/Linux.md +++ b/Documentation/Linux.md @@ -1,5 +1,10 @@ # Linux +## Limitations + +* Custom functions are currently not supported and crash, caused by a bug in Swift. +See [#1071](https://github.com/stephencelis/SQLite.swift/issues/1071). + ## Debugging ### Create and launch docker container diff --git a/README.md b/README.md index ac044dc0..9242b200 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ syntax _and_ intent. - [Well-documented][See Documentation] - Extensively tested - [SQLCipher][] support via CocoaPods + - Works on [Linux](Documentation/Linux.md) (with some limitations) - Active support at [StackOverflow](https://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) From 0dd440cfefa79f9088547a0b5ee440736de87bf7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 16:04:47 +0200 Subject: [PATCH 132/391] FTS5 --- Documentation/Linux.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Linux.md b/Documentation/Linux.md index c9e85254..640ced6f 100644 --- a/Documentation/Linux.md +++ b/Documentation/Linux.md @@ -4,6 +4,7 @@ * Custom functions 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 8583eb66469d69cc8e003d18ac5d8a5391bc14c6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:37:19 +0200 Subject: [PATCH 133/391] Closes #285 --- SQLite.xcodeproj/project.pbxproj | 8 + Tests/SQLiteTests/QueryIntegrationTests.swift | 222 ++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 203 ---------------- 3 files changed, 230 insertions(+), 203 deletions(-) create mode 100644 Tests/SQLiteTests/QueryIntegrationTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 6aff8045..6efd3a47 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 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 */; }; + 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 */; }; @@ -60,6 +61,7 @@ 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 */; }; + 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; @@ -71,6 +73,7 @@ 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; @@ -231,6 +234,7 @@ 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 = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; 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 = ""; }; @@ -430,6 +434,7 @@ 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, + 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -861,6 +866,7 @@ 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, + 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -951,6 +957,7 @@ 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, + 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1011,6 +1018,7 @@ 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, + 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift new file mode 100644 index 00000000..9f5dc5aa --- /dev/null +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -0,0 +1,222 @@ +import XCTest +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif +@testable import SQLite + +class QueryIntegrationTests: SQLiteTestCase { + + let id = Expression("id") + let email = Expression("email") + let age = Expression("age") + + override func setUp() { + super.setUp() + + createUsersTable() + } + + // MARK: - + + func test_select() { + let managerId = Expression("manager_id") + let managers = users.alias("managers") + + let alice = try! db.run(users.insert(email <- "alice@example.com")) + _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + + for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + _ = user[users[managerId]] + } + } + + func test_prepareRowIterator() { + let names = ["a", "b", "c"] + try! insertUsers(names) + + let emailColumn = Expression("email") + let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } + + func test_ambiguousMap() { + let names = ["a", "b", "c"] + try! insertUsers(names) + + let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } + + func test_select_optional() { + let managerId = Expression("manager_id") + let managers = users.alias("managers") + + let alice = try! db.run(users.insert(email <- "alice@example.com")) + _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + + for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + _ = user[users[managerId]] + } + } + + func test_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("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) + let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, + date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) + + try db.run(table.insert(value)) + + let rows = try db.prepare(table) + let values: [TestCodable] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0].int, 5) + XCTAssertEqual(values[0].string, "6") + XCTAssertEqual(values[0].bool, true) + XCTAssertEqual(values[0].float, 7) + XCTAssertEqual(values[0].double, 8) + XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) + XCTAssertEqual(values[0].optional, "optional") + XCTAssertEqual(values[0].sub?.int, 1) + XCTAssertEqual(values[0].sub?.string, "2") + XCTAssertEqual(values[0].sub?.bool, true) + XCTAssertEqual(values[0].sub?.float, 3) + XCTAssertEqual(values[0].sub?.double, 4) + XCTAssertEqual(values[0].sub?.date, Date(timeIntervalSince1970: 0)) + XCTAssertNil(values[0].sub?.optional) + XCTAssertNil(values[0].sub?.sub) + } + + func test_scalar() { + XCTAssertEqual(0, try! db.scalar(users.count)) + XCTAssertEqual(false, try! db.scalar(users.exists)) + + 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_insert() { + 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"]])) + XCTAssertEqual(2, id) + } + + func test_upsert() throws { + guard db.satisfiesMinimumVersion(minor: 24) else { return } + let fetchAge = { () throws -> Int? in + try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } + } + + let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) + XCTAssertEqual(1, id) + XCTAssertEqual(30, try fetchAge()) + + let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) + XCTAssertEqual(1, nextId) + XCTAssertEqual(42, try fetchAge()) + } + + func test_update() { + let changes = try! db.run(users.update(email <- "alice@example.com")) + XCTAssertEqual(0, changes) + } + + func test_delete() { + let changes = try! db.run(users.delete()) + XCTAssertEqual(0, changes) + } + + func test_union() throws { + let expectedIDs = [ + try db.run(users.insert(email <- "alice@example.com")), + try db.run(users.insert(email <- "sally@example.com")) + ] + + let query1 = users.filter(email == "alice@example.com") + let query2 = users.filter(email == "sally@example.com") + + let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } + XCTAssertEqual(expectedIDs, actualIDs) + + let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") + let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") + + print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) + + let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } + XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) + } + + func test_no_such_column() throws { + let doesNotExist = Expression("doesNotExist") + try! insertUser("alice") + let row = try! db.pluck(users.filter(email == "alice@example.com"))! + + XCTAssertThrowsError(try row.get(doesNotExist)) { error in + if case QueryError.noSuchColumn(let name, _) = error { + XCTAssertEqual("\"doesNotExist\"", name) + } else { + XCTFail("unexpected error: \(error)") + } + } + } + + func test_catchConstraintError() { + try! db.run(users.insert(email <- "alice@example.com")) + do { + try db.run(users.insert(email <- "alice@example.com")) + XCTFail("expected error") + } catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { + // expected + } catch let error { + XCTFail("unexpected error: \(error)") + } + } + + // 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))) + XCTAssertEqual(1, result.count) + } +} + + +private extension Connection { + func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { + guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } + let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } + guard components.count == 3 else { return false } + + return components[1] >= minor && components[2] >= patch + } +} diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index c80dd0c7..e0749f2c 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -468,206 +468,3 @@ class QueryTests: XCTestCase { } } - -class QueryIntegrationTests: SQLiteTestCase { - - let id = Expression("id") - let email = Expression("email") - let age = Expression("age") - - override func setUp() { - super.setUp() - - createUsersTable() - } - - // MARK: - - - func test_select() { - let managerId = Expression("manager_id") - let managers = users.alias("managers") - - let alice = try! db.run(users.insert(email <- "alice@example.com")) - _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) - - for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { - _ = user[users[managerId]] - } - } - - func test_prepareRowIterator() { - let names = ["a", "b", "c"] - try! insertUsers(names) - - let emailColumn = Expression("email") - let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } - - XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) - } - - func test_ambiguousMap() { - let names = ["a", "b", "c"] - try! insertUsers(names) - - let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } - - XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) - } - - func test_select_optional() { - let managerId = Expression("manager_id") - let managers = users.alias("managers") - - let alice = try! db.run(users.insert(email <- "alice@example.com")) - _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) - - for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { - _ = user[users[managerId]] - } - } - - func test_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("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) - let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, - date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) - - try db.run(table.insert(value)) - - let rows = try db.prepare(table) - let values: [TestCodable] = try rows.map({ try $0.decode() }) - XCTAssertEqual(values.count, 1) - XCTAssertEqual(values[0].int, 5) - XCTAssertEqual(values[0].string, "6") - XCTAssertEqual(values[0].bool, true) - XCTAssertEqual(values[0].float, 7) - XCTAssertEqual(values[0].double, 8) - XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) - XCTAssertEqual(values[0].optional, "optional") - XCTAssertEqual(values[0].sub?.int, 1) - XCTAssertEqual(values[0].sub?.string, "2") - XCTAssertEqual(values[0].sub?.bool, true) - XCTAssertEqual(values[0].sub?.float, 3) - XCTAssertEqual(values[0].sub?.double, 4) - XCTAssertEqual(values[0].sub?.date, Date(timeIntervalSince1970: 0)) - XCTAssertNil(values[0].sub?.optional) - XCTAssertNil(values[0].sub?.sub) - } - - func test_scalar() { - XCTAssertEqual(0, try! db.scalar(users.count)) - XCTAssertEqual(false, try! db.scalar(users.exists)) - - 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_insert() { - 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"]])) - XCTAssertEqual(2, id) - } - - func test_upsert() throws { - guard db.satisfiesMinimumVersion(minor: 24) else { return } - let fetchAge = { () throws -> Int? in - try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } - } - - let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) - XCTAssertEqual(1, id) - XCTAssertEqual(30, try fetchAge()) - - let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) - XCTAssertEqual(1, nextId) - XCTAssertEqual(42, try fetchAge()) - } - - func test_update() { - let changes = try! db.run(users.update(email <- "alice@example.com")) - XCTAssertEqual(0, changes) - } - - func test_delete() { - let changes = try! db.run(users.delete()) - XCTAssertEqual(0, changes) - } - - func test_union() throws { - let expectedIDs = [ - try db.run(users.insert(email <- "alice@example.com")), - try db.run(users.insert(email <- "sally@example.com")) - ] - - let query1 = users.filter(email == "alice@example.com") - let query2 = users.filter(email == "sally@example.com") - - let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } - XCTAssertEqual(expectedIDs, actualIDs) - - let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") - let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") - - print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) - - let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } - XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) - } - - func test_no_such_column() throws { - let doesNotExist = Expression("doesNotExist") - try! insertUser("alice") - let row = try! db.pluck(users.filter(email == "alice@example.com"))! - - XCTAssertThrowsError(try row.get(doesNotExist)) { error in - if case QueryError.noSuchColumn(let name, _) = error { - XCTAssertEqual("\"doesNotExist\"", name) - } else { - XCTFail("unexpected error: \(error)") - } - } - } - - func test_catchConstraintError() { - try! db.run(users.insert(email <- "alice@example.com")) - do { - try db.run(users.insert(email <- "alice@example.com")) - XCTFail("expected error") - } catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { - // expected - } catch let error { - XCTFail("unexpected error: \(error)") - } - } -} - -private extension Connection { - func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { - guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } - let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } - guard components.count == 3 else { return false } - - return components[1] >= minor && components[2] >= patch - } -} From b94e7a9e453b07c8eea8486d266744e3a8c6751d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:42:45 +0200 Subject: [PATCH 134/391] Lint --- Tests/SQLiteTests/QueryIntegrationTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 9f5dc5aa..c98b1601 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -210,7 +210,6 @@ class QueryIntegrationTests: SQLiteTestCase { } } - private extension Connection { func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } From 723cdc8456cc87ce5dadbc6a25b3f4bbf46275c9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:52:21 +0200 Subject: [PATCH 135/391] Lint fixes, swap param order --- SQLite.xcodeproj/project.pbxproj | 8 +++ Sources/SQLite/Core/Backup.swift | 69 ++++++++++++------------- Sources/SQLite/Core/Connection.swift | 50 +++--------------- Sources/SQLite/Core/Result.swift | 45 ++++++++++++++++ Tests/SQLiteTests/ConnectionTests.swift | 10 ++-- 5 files changed, 97 insertions(+), 85 deletions(-) create mode 100644 Sources/SQLite/Core/Result.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fb40791a..b5929023 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -48,6 +48,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 */; }; + 19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; @@ -60,6 +61,7 @@ 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 */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; @@ -82,6 +84,7 @@ 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 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 */; }; @@ -241,6 +244,7 @@ 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 = ""; }; 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; }; @@ -456,6 +460,7 @@ EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, 02A43A9722738CF100FEC494 /* Backup.swift */, + 19A17E723300E5ED3771DCB5 /* Result.swift */, ); path = Core; sourceTree = ""; @@ -843,6 +848,7 @@ 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, + 19A17073552293CA063BEA66 /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -936,6 +942,7 @@ 02A43A9822738CF100FEC494 /* Backup.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, + 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -998,6 +1005,7 @@ 02A43A9922738CF100FEC494 /* Backup.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, + 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 1ea9637d..84ebf1e0 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -38,19 +38,19 @@ import SQLite3 /// /// See: public final class Backup { - + /// The name of the database to backup public enum DatabaseName { - + /// The main database case main - + /// The temporary database case temp - + /// A database added to the connection with ATTACH statement case attached(name: String) - + var name: String { switch self { case .main: @@ -62,16 +62,16 @@ public final class Backup { } } } - + /// Number of pages to copy while performing a backup step public enum Pages { - + /// Indicates all remaining pages should be copied case all - + /// Indicates the maximal number of pages to be copied in single step case limited(number: Int32) - + var number: Int32 { switch self { case .all: @@ -81,63 +81,60 @@ public final class Backup { } } } - + /// Total number of pages to copy /// /// See: public var pageCount: Int32 { return handle.map { sqlite3_backup_pagecount($0) } ?? 0 } - + /// Number of remaining pages to copy. /// /// See: public var remainingPages: Int32 { return handle.map { sqlite3_backup_remaining($0) } ?? 0 } - + private let targetConnection: Connection private let sourceConnection: Connection - + private var handle: OpaquePointer? - + /// Initializes a new SQLite backup. /// /// - Parameters: /// - /// - targetConnection: The connection to the database to save backup into. - /// - /// - targetName: The name of the database to save backup into. - /// - /// Default: `.main`. - /// /// - sourceConnection: The connection to the database to backup. - /// /// - sourceName: The name of the database to backup. - /// + /// Default: `.main`. + /// + /// - targetConnection: The connection to the database to save backup into. + /// - targetName: The name of the database to save backup into. /// Default: `.main`. /// /// - Returns: A new database backup. /// /// See: - public init(targetConnection: Connection, - targetName: DatabaseName = .main, - sourceConnection: Connection, - sourceName: DatabaseName = .main) throws { - + public init(sourceConnection: Connection, + sourceName: DatabaseName = .main, + targetConnection: Connection, + targetName: DatabaseName = .main) throws { + self.targetConnection = targetConnection self.sourceConnection = sourceConnection - + self.handle = sqlite3_backup_init(targetConnection.handle, targetName.name, sourceConnection.handle, sourceName.name) - - if self.handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), connection: targetConnection) { + + if handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), + connection: targetConnection) { throw error } } - + /// Performs a backup step. /// /// - Parameter pagesToCopy: The maximal number of pages to copy in one step @@ -147,17 +144,17 @@ public final class Backup { /// See: public func step(pagesToCopy pages: Pages = .all) throws { let status = sqlite3_backup_step(handle, pages.number) - + guard status != SQLITE_DONE else { finish() return } - + if let error = Result(errorCode: status, connection: targetConnection) { throw error } } - + /// Finalizes backup. /// /// See: @@ -165,11 +162,11 @@ public final class Backup { guard let handle = self.handle else { return } - + sqlite3_backup_finish(handle) self.handle = nil } - + deinit { finish() } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 5e34457c..a9012462 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -790,9 +790,9 @@ public final class Connection { } fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32 fileprivate var collations = [String: Collation]() - + // MARK: - Backup - + /// Prepares a new backup for current connection. /// /// - Parameters: @@ -808,16 +808,14 @@ public final class Connection { /// Default: `.main`. /// /// - Returns: A new database backup. - + public func backup(databaseName: Backup.DatabaseName = .main, usingConnection targetConnection: Connection, andDatabaseName targetDatabaseName: Backup.DatabaseName = .main) throws -> Backup { - return try Backup(targetConnection: targetConnection, - targetName: targetDatabaseName, - sourceConnection: self, - sourceName: databaseName) + try Backup(sourceConnection: self, sourceName: databaseName, targetConnection: targetConnection, + targetName: targetDatabaseName) } - + // MARK: - Error Handling func sync(_ block: () throws -> T) rethrows -> T { @@ -866,39 +864,3 @@ extension Connection.Location: CustomStringConvertible { } } - -public enum Result: Error { - - fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] - - /// Represents a SQLite specific [error code](https://sqlite.org/rescode.html) - /// - /// - message: English-language text that describes the error - /// - /// - code: SQLite [error code](https://sqlite.org/rescode.html#primary_result_code_list) - /// - /// - statement: the statement which produced the error - case error(message: String, code: Int32, statement: Statement?) - - init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { - guard !Result.successCodes.contains(errorCode) else { return nil } - - let message = String(cString: sqlite3_errmsg(connection.handle)) - self = .error(message: message, code: errorCode, statement: statement) - } - -} - -extension Result: CustomStringConvertible { - - public var description: String { - switch self { - case let .error(message, errorCode, statement): - if let statement = statement { - return "\(message) (\(statement)) (code: \(errorCode))" - } else { - return "\(message) (code: \(errorCode))" - } - } - } -} diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift new file mode 100644 index 00000000..3de4d25d --- /dev/null +++ b/Sources/SQLite/Core/Result.swift @@ -0,0 +1,45 @@ +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +public enum Result: Error { + + fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + + /// Represents a SQLite specific [error code](https://sqlite.org/rescode.html) + /// + /// - message: English-language text that describes the error + /// + /// - code: SQLite [error code](https://sqlite.org/rescode.html#primary_result_code_list) + /// + /// - statement: the statement which produced the error + case error(message: String, code: Int32, statement: Statement?) + + init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { + guard !Result.successCodes.contains(errorCode) else { return nil } + + let message = String(cString: sqlite3_errmsg(connection.handle)) + self = .error(message: message, code: errorCode, statement: statement) + } + +} + +extension Result: CustomStringConvertible { + + public var description: String { + switch self { + case let .error(message, errorCode, statement): + if let statement = statement { + return "\(message) (\(statement)) (code: \(errorCode))" + } else { + return "\(message) (code: \(errorCode))" + } + } + } +} diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 1cb70732..ae881289 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -147,15 +147,15 @@ class ConnectionTests: SQLiteTestCase { assertSQL("BEGIN EXCLUSIVE TRANSACTION") } - + func test_backup_copiesDatabase() throws { let target = try Connection() - - try InsertUsers("alice", "betsy") - + + try insertUsers("alice", "betsy") + let backup = try db.backup(usingConnection: target) try backup.step() - + let users = try target.prepare("SELECT email FROM users ORDER BY email") XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) } From bcbfe980c789f73de1fe8486d78f82fda67b95b5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:56:02 +0200 Subject: [PATCH 136/391] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 465438bc..fc09fa40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.13.1 (tba) +======================================== + +* Support for database backup ([#919][]) +* Support for custom SQL aggregates ([#881][]) + 0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== @@ -116,3 +122,5 @@ [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 [#797]: https://github.com/stephencelis/SQLite.swift/pull/797 [#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 From 2ae7b9b61dfdc7e593108f8048ba19da5b51d656 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 19:46:58 +0200 Subject: [PATCH 137/391] Missing target --- SQLite.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index b5929023..b30c18d2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; @@ -911,6 +912,7 @@ 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */, 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, + 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 5e64bb587b8b45a478634eed54df5f26d84b0b5e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 19:25:53 +0200 Subject: [PATCH 138/391] Split and simplify code, remove duplication --- .swiftlint.yml | 5 +- SQLite.xcodeproj/project.pbxproj | 8 + .../SQLite/Core/Connection+Aggregation.swift | 155 ++++++++++++ Sources/SQLite/Core/Connection.swift | 225 +++++------------- Sources/SQLite/Typed/CustomFunctions.swift | 65 ----- Sources/SQLite/Typed/Query.swift | 1 - 6 files changed, 226 insertions(+), 233 deletions(-) create mode 100644 Sources/SQLite/Core/Connection+Aggregation.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index b5e53d56..34bb253b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -38,5 +38,6 @@ line_length: ignores_comments: true file_length: - warning: 900 - error: 900 + warning: 500 + error: 500 + ignore_comment_only_lines: true diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index b30c18d2..5bb52f4e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 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 */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; @@ -67,6 +68,7 @@ 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 */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; @@ -75,6 +77,7 @@ 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 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 */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; @@ -237,6 +240,7 @@ 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 = ""; }; 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 = ""; }; @@ -462,6 +466,7 @@ 19A1710E73A46D5AC721CDA9 /* Errors.swift */, 02A43A9722738CF100FEC494 /* Backup.swift */, 19A17E723300E5ED3771DCB5 /* Result.swift */, + 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, ); path = Core; sourceTree = ""; @@ -850,6 +855,7 @@ 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, 19A17073552293CA063BEA66 /* Result.swift in Sources */, + 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -945,6 +951,7 @@ 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, + 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1008,6 +1015,7 @@ 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, + 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift new file mode 100644 index 00000000..257d48ed --- /dev/null +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -0,0 +1,155 @@ +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 { + private typealias Aggregate = @convention(block) (Int, OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void + + /// Creates or redefines a custom SQL aggregate. + /// + /// - Parameters: + /// + /// - aggregate: The name of the aggregate to create or redefine. + /// + /// - argumentCount: The number of arguments that the aggregate takes. If + /// `nil`, the aggregate may take any number of arguments. + /// + /// Default: `nil` + /// + /// - deterministic: Whether or not the aggregate is deterministic (_i.e._ + /// the aggregate always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - step: A block of code to run for each row of an aggregation group. + /// The block is called with an array of raw SQL values mapped to the + /// aggregate’s parameters, and an UnsafeMutablePointer to a state + /// variable. + /// + /// - final: A block of code to run after each row of an aggregation group + /// is processed. The block is called with an UnsafeMutablePointer to a + /// state variable, and should return a raw SQL value (or nil). + /// + /// - state: A block of code to run to produce a fresh state variable for + /// each aggregation group. The block should return an + /// UnsafeMutablePointer to the fresh state variable. + public func createAggregation( + _ functionName: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + step: @escaping ([Binding?], UnsafeMutablePointer) -> Void, + final: @escaping (UnsafeMutablePointer) -> Binding?, + state: @escaping () -> UnsafeMutablePointer) { + + let argc = argumentCount.map { Int($0) } ?? -1 + let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let nBytes = Int32(MemoryLayout>.size) + guard let aggregateContext = sqlite3_aggregate_context(context, nBytes) else { + fatalError("Could not get aggregate context") + } + let mutablePointer = aggregateContext.assumingMemoryBound(to: UnsafeMutableRawPointer.self) + if stepFlag > 0 { + let arguments = getArguments(argc: argc, argv: argv) + if aggregateContext.assumingMemoryBound(to: Int64.self).pointee == 0 { + mutablePointer.pointee = UnsafeMutableRawPointer(mutating: state()) + } + step(arguments, mutablePointer.pointee.assumingMemoryBound(to: T.self)) + } else { + let result = final(mutablePointer.pointee.assumingMemoryBound(to: T.self)) + set(result: result, on: context) + } + } + + func xStep(context: OpaquePointer?, argc: Int32, value: UnsafeMutablePointer?) { + unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(1, context, argc, value) + } + + func xFinal(context: OpaquePointer?) { + unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(0, context, 0, nil) + } + + let flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0) + let resultCode = sqlite3_create_function_v2( + handle, + functionName, + Int32(argc), + flags, + /* pApp */ unsafeBitCast(box, to: UnsafeMutableRawPointer.self), + /* xFunc */ nil, xStep, xFinal, /* xDestroy */ nil + ) + if let result = Result(errorCode: resultCode, connection: self) { + fatalError("Error creating function: \(result)") + } + register(functionName, argc: argc, value: box) + } + + func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, ptr) in + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let current = Unmanaged.fromOpaque(pointer).takeRetainedValue() + let next = reduce(current, bindings) + ptr.pointee = Unmanaged.passRetained(next).toOpaque() + } + + let final: (UnsafeMutablePointer) -> Binding? = { ptr in + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let obj = Unmanaged.fromOpaque(pointer).takeRetainedValue() + let value = result(obj) + ptr.deallocate() + return value + } + + let state: () -> UnsafeMutablePointer = { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = Unmanaged.passRetained(initialValue).toOpaque() + return pointer + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + + func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, pointer) in + let current = pointer.pointee + let next = reduce(current, bindings) + pointer.pointee = next + } + + let final: (UnsafeMutablePointer) -> Binding? = { pointer in + let value = result(pointer.pointee) + pointer.deallocate() + return value + } + + let state: () -> UnsafeMutablePointer = { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: initialValue) + return pointer + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + +} diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index a9012462..35996b29 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -35,7 +35,6 @@ import SQLite3 #endif /// A connection to SQLite. -// swiftlint:disable:next type_body_length public final class Connection { /// The location of a SQLite database. @@ -586,182 +585,42 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - // swiftlint:disable:next cyclomatic_complexity - public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, + public func createFunction(_ functionName: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 let box: Function = { context, argc, argv in - let arguments: [Binding?] = (0..?) { + unsafeBitCast(sqlite3_user_data(context), to: Function.self)(context, argc, value) } + let flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0) + let resultCode = sqlite3_create_function_v2( + handle, + functionName, + Int32(argc), + flags, + /* pApp */ unsafeBitCast(box, to: UnsafeMutableRawPointer.self), + xFunc, /*xStep*/ nil, /*xFinal*/ nil, /*xDestroy*/ nil + ) - if functions[function] == nil { - functions[function] = [:] // fails on Linux, https://github.com/stephencelis/SQLite.swift/issues/1071 + if let result = Result(errorCode: resultCode, connection: self) { + fatalError("Error creating function: \(result)") } - functions[function]?[argc] = box + register(functionName, argc: argc, value: box) } - /// Creates or redefines a custom SQL aggregate. - /// - /// - Parameters: - /// - /// - aggregate: The name of the aggregate to create or redefine. - /// - /// - argumentCount: The number of arguments that the aggregate takes. If - /// `nil`, the aggregate may take any number of arguments. - /// - /// Default: `nil` - /// - /// - deterministic: Whether or not the aggregate is deterministic (_i.e._ - /// the aggregate always returns the same result for a given input). - /// - /// Default: `false` - /// - /// - step: A block of code to run for each row of an aggregation group. - /// The block is called with an array of raw SQL values mapped to the - /// aggregate’s parameters, and an UnsafeMutablePointer to a state - /// variable. - /// - /// - final: A block of code to run after each row of an aggregation group - /// is processed. The block is called with an UnsafeMutablePointer to a - /// state variable, and should return a raw SQL value (or nil). - /// - /// - state: A block of code to run to produce a fresh state variable for - /// each aggregation group. The block should return an - /// UnsafeMutablePointer to the fresh state variable. - // swiftlint:disable:next cyclomatic_complexity - public func createAggregation( - _ aggregate: String, - argumentCount: UInt? = nil, - deterministic: Bool = false, - step: @escaping ([Binding?], UnsafeMutablePointer) -> Void, - final: @escaping (UnsafeMutablePointer) -> Binding?, - state: @escaping () -> UnsafeMutablePointer) { - - let argc = argumentCount.map { Int($0) } ?? -1 - let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in - let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? - let mutablePointer = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) - if stepFlag > 0 { - let arguments: [Binding?] = (0..?) -> Void fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void - fileprivate var functions = [String: [Int: Function]]() - fileprivate var aggregations = [String: [Int: Aggregate]]() + fileprivate var functions = [String: [Int: Any]]() /// Defines a new collating sequence. /// @@ -808,7 +667,6 @@ public final class Connection { /// Default: `.main`. /// /// - Returns: A new database backup. - public func backup(databaseName: Backup.DatabaseName = .main, usingConnection targetConnection: Connection, andDatabaseName targetDatabaseName: Backup.DatabaseName = .main) throws -> Backup { @@ -864,3 +722,40 @@ extension Connection.Location: CustomStringConvertible { } } + +func getArguments(argc: Int32, argv: UnsafeMutablePointer?) -> [Binding?] { + (0..( - _ aggregate: String, - argumentCount: UInt? = nil, - deterministic: Bool = false, - initialValue: T, - reduce: @escaping (T, [Binding?]) -> T, - result: @escaping (T) -> Binding? - ) { - - let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, ptr) in - let pointer = ptr.pointee.assumingMemoryBound(to: T.self) - let current = Unmanaged.fromOpaque(pointer).takeRetainedValue() - let next = reduce(current, bindings) - ptr.pointee = Unmanaged.passRetained(next).toOpaque() - } - - let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in - let pointer = ptr.pointee.assumingMemoryBound(to: T.self) - let obj = Unmanaged.fromOpaque(pointer).takeRetainedValue() - let value = result(obj) - ptr.deallocate() - return value - } - - let state: () -> UnsafeMutablePointer = { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.pointee = Unmanaged.passRetained(initialValue).toOpaque() - return pointer - } - - createAggregation(aggregate, step: step, final: final, state: state) - } - - func createAggregation( - _ aggregate: String, - argumentCount: UInt? = nil, - deterministic: Bool = false, - initialValue: T, - reduce: @escaping (T, [Binding?]) -> T, - result: @escaping (T) -> Binding? - ) { - - let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, pointer) in - let current = pointer.pointee - let next = reduce(current, bindings) - pointer.pointee = next - } - - let final: (UnsafeMutablePointer) -> Binding? = { pointer in - let value = result(pointer.pointee) - pointer.deallocate() - return value - } - - let state: () -> UnsafeMutablePointer = { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.initialize(to: initialValue) - return pointer - } - - createAggregation(aggregate, step: step, final: final, state: state) - } - } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 59b24a8d..2d88db63 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -21,7 +21,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -// swiftlint:disable file_length import Foundation public protocol QueryType: Expressible { From e5a10f5bcad9f9c25f948a9c90c5861e1bb83163 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 22:23:36 +0200 Subject: [PATCH 139/391] Use typealiases --- .../SQLite/Core/Connection+Aggregation.swift | 12 +-- Sources/SQLite/Core/Connection.swift | 74 ++++++++++--------- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index 257d48ed..106f292c 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -10,7 +10,7 @@ import SQLite3 #endif extension Connection { - private typealias Aggregate = @convention(block) (Int, OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void + private typealias Aggregate = @convention(block) (Int, Context, Int32, Argv) -> Void /// Creates or redefines a custom SQL aggregate. /// @@ -49,29 +49,29 @@ extension Connection { state: @escaping () -> UnsafeMutablePointer) { let argc = argumentCount.map { Int($0) } ?? -1 - let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let box: Aggregate = { (stepFlag: Int, context: Context, argc: Int32, argv: Argv) in let nBytes = Int32(MemoryLayout>.size) guard let aggregateContext = sqlite3_aggregate_context(context, nBytes) else { fatalError("Could not get aggregate context") } let mutablePointer = aggregateContext.assumingMemoryBound(to: UnsafeMutableRawPointer.self) if stepFlag > 0 { - let arguments = getArguments(argc: argc, argv: argv) + let arguments = argv.getBindings(argc: argc) if aggregateContext.assumingMemoryBound(to: Int64.self).pointee == 0 { mutablePointer.pointee = UnsafeMutableRawPointer(mutating: state()) } step(arguments, mutablePointer.pointee.assumingMemoryBound(to: T.self)) } else { let result = final(mutablePointer.pointee.assumingMemoryBound(to: T.self)) - set(result: result, on: context) + context.set(result: result) } } - func xStep(context: OpaquePointer?, argc: Int32, value: UnsafeMutablePointer?) { + func xStep(context: Context, argc: Int32, value: Argv) { unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(1, context, argc, value) } - func xFinal(context: OpaquePointer?) { + func xFinal(context: Context) { unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(0, context, 0, nil) } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 35996b29..6b5481e3 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -590,10 +590,10 @@ public final class Connection { deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 - let box: Function = { context, argc, argv in - set(result: block(getArguments(argc: argc, argv: argv)), on: context) + let box: Function = { (context: Context, argc, argv: Argv) in + context.set(result: block(argv.getBindings(argc: argc))) } - func xFunc(context: OpaquePointer?, argc: Int32, value: UnsafeMutablePointer?) { + func xFunc(context: Context, argc: Int32, value: Argv) { unsafeBitCast(sqlite3_user_data(context), to: Function.self)(context, argc, value) } let flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0) @@ -619,7 +619,7 @@ public final class Connection { functions[functionName]?[argc] = value } - fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void + fileprivate typealias Function = @convention(block) (Context, Int32, Argv) -> Void fileprivate var functions = [String: [Int: Any]]() /// Defines a new collating sequence. @@ -723,39 +723,45 @@ extension Connection.Location: CustomStringConvertible { } -func getArguments(argc: Int32, argv: UnsafeMutablePointer?) -> [Binding?] { - (0..? +extension Argv { + func getBindings(argc: Int32) -> [Binding?] { + (0.. Date: Mon, 23 Aug 2021 00:14:23 +0200 Subject: [PATCH 140/391] Add trigram tokenizer --- Sources/SQLite/Extensions/FTS4.swift | 9 +++++++-- Tests/SQLiteTests/FTS5Tests.swift | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 115b146a..0e48943a 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -95,10 +95,10 @@ extension VirtualTable { public struct Tokenizer { public static let Simple = Tokenizer("simple") - public static let Porter = Tokenizer("porter") - public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set = [], + public static func Unicode61(removeDiacritics: Bool? = nil, + tokenchars: Set = [], separators: Set = []) -> Tokenizer { var arguments = [String]() @@ -119,6 +119,11 @@ public struct Tokenizer { return Tokenizer("unicode61", arguments) } + // 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"]) + } + public static func Custom(_ name: String) -> Tokenizer { Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) } diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index 4cce4523..c271be9d 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -81,6 +81,16 @@ class FTS5Tests: XCTestCase { sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) } + func test_tokenizer_trigram() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=trigram case_sensitive 0)", + sql(config.tokenizer(.Trigram()))) + + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=trigram case_sensitive 1)", + sql(config.tokenizer(.Trigram(caseSensitive: true)))) + } + func test_column_size() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(columnsize=1)", From 3dc24496883090888fdbd658a766eb622b815aec Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 00:19:07 +0200 Subject: [PATCH 141/391] Add integration tests --- SQLite.xcodeproj/project.pbxproj | 8 +++ Tests/SQLiteTests/CipherTests.swift | 16 ++--- Tests/SQLiteTests/ConnectionTests.swift | 7 +- .../SQLiteTests/CustomAggregationTests.swift | 12 ++-- Tests/SQLiteTests/FTSIntegrationTests.swift | 64 +++++++++++++++++++ Tests/SQLiteTests/QueryIntegrationTests.swift | 11 ++-- Tests/SQLiteTests/SelectTests.swift | 12 ++-- Tests/SQLiteTests/StatementTests.swift | 6 +- Tests/SQLiteTests/TestHelpers.swift | 10 +-- 9 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 Tests/SQLiteTests/FTSIntegrationTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 5bb52f4e..92400180 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; + 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 */; }; @@ -80,8 +81,10 @@ 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 */; }; + 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 */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; @@ -236,6 +239,7 @@ 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; @@ -449,6 +453,7 @@ 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, + 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -887,6 +892,7 @@ 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, + 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -983,6 +989,7 @@ 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, + 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1047,6 +1054,7 @@ 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, + 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index 77e8e7f0..0995fca0 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -8,22 +8,22 @@ class CipherTests: XCTestCase { let db1 = try! Connection() let db2 = try! Connection() - override func setUp() { + override func setUpWithError() throws { // db - try! db1.key("hello") + try db1.key("hello") - try! db1.run("CREATE TABLE foo (bar TEXT)") - try! db1.run("INSERT INTO foo (bar) VALUES ('world')") + try db1.run("CREATE TABLE foo (bar TEXT)") + try db1.run("INSERT INTO foo (bar) VALUES ('world')") // db2 let key2 = keyData() - try! db2.key(Blob(bytes: key2.bytes, length: key2.length)) + 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')") + try db2.run("CREATE TABLE foo (bar TEXT)") + try db2.run("INSERT INTO foo (bar) VALUES ('world')") - super.setUp() + try super.setUpWithError() } func test_key() { diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ae881289..ae9d4dfd 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -15,10 +15,9 @@ import SQLite3 class ConnectionTests: SQLiteTestCase { - override func setUp() { - super.setUp() - - createUsersTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() } func test_init_withInMemory_returnsInMemoryConnection() { diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index d5232260..5cbfbbef 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -17,12 +17,12 @@ import SQLite3 #if !os(Linux) class CustomAggregationTests: SQLiteTestCase { - override func setUp() { - super.setUp() - createUsersTable() - try! insertUser("Alice", age: 30, admin: true) - try! insertUser("Bob", age: 25, admin: true) - try! insertUser("Eve", age: 28, admin: false) + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + try insertUser("Alice", age: 30, admin: true) + try insertUser("Bob", age: 25, admin: true) + try insertUser("Eve", age: 28, admin: false) } func testUnsafeCustomSum() { diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/FTSIntegrationTests.swift new file mode 100644 index 00000000..561c756f --- /dev/null +++ b/Tests/SQLiteTests/FTSIntegrationTests.swift @@ -0,0 +1,64 @@ +import XCTest +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif +@testable import SQLite + +class FTSIntegrationTests: SQLiteTestCase { + let email = Expression("email") + let index = VirtualTable("index") + + private func createIndex() throws { + try db.run(index.create(.FTS5(FTS5Config() + .column(email) + .tokenizer(.Unicode61()) + ))) + + for user in try db.prepare(users) { + try db.run(index.insert(email <- user[email])) + } + } + + private func createTrigramIndex() throws { + try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 34)) + try db.run(index.create(.FTS5(FTS5Config() + .column(email) + .tokenizer(.Trigram(caseSensitive: false)) + ))) + + for user in try db.prepare(users) { + try db.run(index.insert(email <- user[email])) + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + try insertUsers("John", "Paul", "George", "Ringo") + } + + func testMatch() throws { + try createIndex() + let matches = Array(try db.prepare(index.match("Paul"))) + XCTAssertEqual(matches.map { $0[email ]}, ["Paul@example.com"]) + } + + func testMatchPartial() throws { + try insertUsers("Paula") + try createIndex() + let matches = Array(try db.prepare(index.match("Pa*"))) + XCTAssertEqual(matches.map { $0[email ]}, ["Paul@example.com", "Paula@example.com"]) + } + + func testTrigramIndex() throws { + try createTrigramIndex() + let matches = Array(try db.prepare(index.match("Paul"))) + XCTAssertEqual(1, matches.count) + } +} diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index c98b1601..4b9c4bbf 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -16,10 +16,9 @@ class QueryIntegrationTests: SQLiteTestCase { let email = Expression("email") let age = Expression("age") - override func setUp() { - super.setUp() - - createUsersTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() } // MARK: - @@ -131,7 +130,7 @@ class QueryIntegrationTests: SQLiteTestCase { } func test_upsert() throws { - guard db.satisfiesMinimumVersion(minor: 24) else { return } + try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 24)) let fetchAge = { () throws -> Int? in try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } } @@ -210,7 +209,7 @@ class QueryIntegrationTests: SQLiteTestCase { } } -private extension Connection { +extension Connection { func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift index c66d401d..d1126b66 100644 --- a/Tests/SQLiteTests/SelectTests.swift +++ b/Tests/SQLiteTests/SelectTests.swift @@ -3,14 +3,14 @@ import XCTest class SelectTests: SQLiteTestCase { - override func setUp() { - super.setUp() - createUsersTable() - createUsersDataTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + try createUsersDataTable() } - func createUsersDataTable() { - try! db.execute(""" + func createUsersDataTable() throws { + try db.execute(""" CREATE TABLE users_name ( id INTEGER, user_id INTEGER REFERENCES users(id), diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 5a05675d..b4ad7c28 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -2,9 +2,9 @@ import XCTest import SQLite class StatementTests: SQLiteTestCase { - override func setUp() { - super.setUp() - createUsersTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() } func test_cursor_to_blob() { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 0aa918f0..07cc6f06 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -6,9 +6,9 @@ class SQLiteTestCase: XCTestCase { var db: Connection! let users = Table("users") - override func setUp() { - super.setUp() - db = try! Connection() + override func setUpWithError() throws { + try super.setUpWithError() + db = try Connection() trace = [String: Int]() db.trace { SQL in @@ -17,8 +17,8 @@ class SQLiteTestCase: XCTestCase { } } - func createUsersTable() { - try! db.execute(""" + func createUsersTable() throws { + try db.execute(""" CREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, From 7aaccafedd157148a5a69e3e4c1a767b6d704ef9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 00:21:03 +0200 Subject: [PATCH 142/391] Skip on Linux --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 7522fa45..c1373ad8 100644 --- a/Package.swift +++ b/Package.swift @@ -46,6 +46,7 @@ package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.g package.targets = [ .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ + "FTSIntegrationTests.swift", "FTS4Tests.swift", "FTS5Tests.swift" ]) From b1031e33bc8c08898858c3d2873e2985f8ff19cf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 00:49:30 +0200 Subject: [PATCH 143/391] Skip tests if not supported --- Tests/SQLiteTests/FTSIntegrationTests.swift | 30 ++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/FTSIntegrationTests.swift index 561c756f..d629ca8b 100644 --- a/Tests/SQLiteTests/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/FTSIntegrationTests.swift @@ -15,10 +15,16 @@ class FTSIntegrationTests: SQLiteTestCase { let index = VirtualTable("index") private func createIndex() throws { - try db.run(index.create(.FTS5(FTS5Config() - .column(email) - .tokenizer(.Unicode61()) - ))) + do { + try db.run(index.create(.FTS5( + FTS5Config() + .column(email) + .tokenizer(.Unicode61())) + )) + } catch let error as Result { + try XCTSkipIf(error.description.starts(with: "no such model")) + throw error + } for user in try db.prepare(users) { try db.run(index.insert(email <- user[email])) @@ -26,11 +32,17 @@ class FTSIntegrationTests: SQLiteTestCase { } private func createTrigramIndex() throws { - try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 34)) - try db.run(index.create(.FTS5(FTS5Config() - .column(email) - .tokenizer(.Trigram(caseSensitive: false)) - ))) + do { + try db.run(index.create(.FTS5( + FTS5Config() + .column(email) + .tokenizer(.Trigram(caseSensitive: false))) + )) + } catch let error as Result { + try XCTSkipIf(error.description.starts(with: "no such model") || + error.description.starts(with: "parse error in")) + throw error + } for user in try db.prepare(users) { try db.run(index.insert(email <- user[email])) From 7f98a7b34b362a8776a429fcbc2359ae11b78aee Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 01:12:21 +0200 Subject: [PATCH 144/391] Move into method --- Tests/SQLiteTests/FTSIntegrationTests.swift | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/FTSIntegrationTests.swift index d629ca8b..da9ba8dc 100644 --- a/Tests/SQLiteTests/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/FTSIntegrationTests.swift @@ -15,15 +15,12 @@ class FTSIntegrationTests: SQLiteTestCase { let index = VirtualTable("index") private func createIndex() throws { - do { + try createOrSkip { db in try db.run(index.create(.FTS5( FTS5Config() .column(email) .tokenizer(.Unicode61())) )) - } catch let error as Result { - try XCTSkipIf(error.description.starts(with: "no such model")) - throw error } for user in try db.prepare(users) { @@ -32,16 +29,12 @@ class FTSIntegrationTests: SQLiteTestCase { } private func createTrigramIndex() throws { - do { + try createOrSkip { db in try db.run(index.create(.FTS5( FTS5Config() .column(email) .tokenizer(.Trigram(caseSensitive: false))) )) - } catch let error as Result { - try XCTSkipIf(error.description.starts(with: "no such model") || - error.description.starts(with: "parse error in")) - throw error } for user in try db.prepare(users) { @@ -73,4 +66,15 @@ class FTSIntegrationTests: SQLiteTestCase { let matches = Array(try db.prepare(index.match("Paul"))) XCTAssertEqual(1, matches.count) } + + private func createOrSkip(_ createIndex: (Connection) throws -> Void) throws { + do { + try createIndex(db) + } catch let error as Result { + try XCTSkipIf(error.description.starts(with: "no such module:") || + error.description.starts(with: "parse error") + ) + throw error + } + } } From 7e6d8b98d47c3cf7cfcb60d63fff17868eefd8c0 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 26 Aug 2021 14:24:52 +0200 Subject: [PATCH 145/391] Reverting a change in `FailableIterator` --- Sources/SQLite/Core/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 48347c31..abeee24b 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -207,7 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { - try? failableNext() + try! failableNext() } } From 62fe865ae8b80bab15f373dfeefae04df3b555ea Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 26 Aug 2021 14:33:12 +0200 Subject: [PATCH 146/391] Fix linting --- Sources/SQLite/Core/Statement.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index abeee24b..29d6938d 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -207,6 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { + // swiftlint:disable:next force_cast try! failableNext() } } From 870cf7b2cebe315a5e05176ca05a111cd8ab39f6 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 26 Aug 2021 15:04:55 +0200 Subject: [PATCH 147/391] Fix linting --- Sources/SQLite/Core/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 29d6938d..1e2489b5 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -207,7 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { - // swiftlint:disable:next force_cast + // swiftlint:disable:next force_try try! failableNext() } } From 86c3f6a580fcc335694f12c90515d15671a35636 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 5 Sep 2021 17:23:39 +0200 Subject: [PATCH 148/391] Add SPM integration test --- .github/workflows/build.yml | 4 ++++ Package.swift | 6 +++++- Tests/SPM/.gitignore | 7 +++++++ Tests/SPM/Package.swift | 19 +++++++++++++++++++ Tests/SPM/Sources/test/main.swift | 11 +++++++++++ Tests/SPM/db.sqlite | Bin 0 -> 8192 bytes run-tests.sh | 2 ++ 7 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 Tests/SPM/.gitignore create mode 100644 Tests/SPM/Package.swift create mode 100644 Tests/SPM/Sources/test/main.swift create mode 100644 Tests/SPM/db.sqlite diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51654fba..60d487bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,10 @@ jobs: env: PACKAGE_MANAGER_COMMAND: test -Xswiftc -warnings-as-errors run: ./run-tests.sh + - name: "Run tests (SPM integration test)" + env: + SPM: run + run: ./run-tests.sh - name: "Run tests (BUILD_SCHEME: SQLite iOS)" env: BUILD_SCHEME: SQLite iOS diff --git a/Package.swift b/Package.swift index c1373ad8..f4938678 100644 --- a/Package.swift +++ b/Package.swift @@ -44,7 +44,11 @@ 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", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), + .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", diff --git a/Tests/SPM/.gitignore b/Tests/SPM/.gitignore new file mode 100644 index 00000000..bb460e7b --- /dev/null +++ b/Tests/SPM/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift new file mode 100644 index 00000000..810026ce --- /dev/null +++ b/Tests/SPM/Package.swift @@ -0,0 +1,19 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "test", + dependencies: [ + .package(path: "../..") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "test", + dependencies: [.product(name: "SQLite", package: "SQLite.swift")] + ) + ] +) diff --git a/Tests/SPM/Sources/test/main.swift b/Tests/SPM/Sources/test/main.swift new file mode 100644 index 00000000..94f12af8 --- /dev/null +++ b/Tests/SPM/Sources/test/main.swift @@ -0,0 +1,11 @@ +import SQLite + +let table = Table("test") +let name = Expression("name") + +let db = try Connection("db.sqlite", readonly: true) + +for row in try db.prepare(table) { + print(row[name]) +} + diff --git a/Tests/SPM/db.sqlite b/Tests/SPM/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..f7b2e84c051df3ffdddb463bdd0dd990358789cb GIT binary patch literal 8192 zcmeI#u?oU45C-5&YTcwlDXvE+U0lT7TCx>H&CoT~6a=fFc|#{($IZ=T=8p?MmqFk7GxXmQ(Y$<&{40?>$u9%~5P$## zAOHafKmY;|fB*y_0D)fwmSQ-`GO=-{Ia_D%E|e^Hs?dscv91({2~Rn{n9k;`Rjqjz t{^l}yQS$a10s#m>00Izz00bZa0SG_<0uX?}p9sWKrbpYxLZ+rUd;sf39n=5- literal 0 HcmV?d00001 diff --git a/run-tests.sh b/run-tests.sh index 32465388..5f397553 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -14,6 +14,8 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" +elif [ -n "$SPM" ]; then + cd Tests/SPM && swift run elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then swift ${PACKAGE_MANAGER_COMMAND} fi From 79eee35ca996dd0653bc06a823f4394c61b0a3d8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 5 Sep 2021 17:31:38 +0200 Subject: [PATCH 149/391] Lint fix --- .github/workflows/build.yml | 4 ++++ Tests/SPM/Sources/test/main.swift | 3 +-- run-tests.sh | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60d487bd..88c5803f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,3 +75,7 @@ jobs: sudo apt-get install -y libsqlite3-dev - name: Test run: swift test + - name: "Run tests (SPM integration test)" + env: + SPM: run + run: ./run-tests.sh diff --git a/Tests/SPM/Sources/test/main.swift b/Tests/SPM/Sources/test/main.swift index 94f12af8..939b3391 100644 --- a/Tests/SPM/Sources/test/main.swift +++ b/Tests/SPM/Sources/test/main.swift @@ -1,4 +1,4 @@ -import SQLite +import SQLite let table = Table("test") let name = Expression("name") @@ -8,4 +8,3 @@ let db = try Connection("db.sqlite", readonly: true) for row in try db.prepare(table) { print(row[name]) } - diff --git a/run-tests.sh b/run-tests.sh index 5f397553..49330c12 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -15,7 +15,7 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "$SPM" ]; then - cd Tests/SPM && swift run + cd Tests/SPM && swift ${SPM} elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then swift ${PACKAGE_MANAGER_COMMAND} fi From 09f730eaa8848ff3387bccaddbf573751694f876 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 6 Sep 2021 00:43:50 +0200 Subject: [PATCH 150/391] Update documentation - Closes #1076 - Clarify BusyHandler usage (Closes #786) - Integrate playground into project --- CHANGELOG.md | 4 + Documentation/Index.md | 90 ++++++++++++++++--- Documentation/Planning.md | 2 +- Documentation/Release.md | 1 + README.md | 9 +- SQLite.playground/Contents.swift | 78 +++++++++++++--- SQLite.playground/contents.xcplayground | 2 +- .../contents.xcworkspacedata | 7 ++ SQLite.xcodeproj/project.pbxproj | 2 + .../SQLite/Core/Connection+Aggregation.swift | 4 +- Tests/SPM/Package.swift | 5 +- 11 files changed, 167 insertions(+), 37 deletions(-) create mode 100644 SQLite.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/CHANGELOG.md b/CHANGELOG.md index fc09fa40..c9d09c12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ * Support for database backup ([#919][]) * Support for custom SQL aggregates ([#881][]) +* Restore previous iteration behavior ([#1075][]) +* Fix compilation on Linux ([#1077][]) 0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== @@ -124,3 +126,5 @@ [#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 +[#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 +[#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 diff --git a/Documentation/Index.md b/Documentation/Index.md index fce068a2..1e08f6c4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -77,9 +77,6 @@ The [Swift Package Manager][] is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. -It is the recommended approach for using SQLite.swift in OSX CLI -applications. - 1. Add the following to your `Package.swift` file: ```swift @@ -342,16 +339,15 @@ execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. -If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: +If you maintain multiple connections for a single database, consider setting a timeout +(in seconds) *or* a busy handler. There can only be one active at a time, so setting a busy +handler will effectively override `busyTimeout`. ```swift -db.busyTimeout = 5 +db.busyTimeout = 5 // error after 5 seconds (does multiple retries) db.busyHandler({ tries in - if tries >= 3 { - return false - } - return true + tries < 3 // error after 3 tries }) ``` @@ -656,12 +652,13 @@ do { } ``` -Multiple rows can be inserted at once by similarily calling `insertMany` with an array of per-row [setters](#setters). +Multiple rows can be inserted at once by similarily calling `insertMany` with an array of +per-row [setters](#setters). ```swift do { - let rowid = try db.run(users.insertMany([mail <- "alice@mac.com"], [email <- "geoff@mac.com"])) - print("inserted id: \(rowid)") + let lastRowid = try db.run(users.insertMany([mail <- "alice@mac.com"], [email <- "geoff@mac.com"])) + print("last inserted id: \(lastRowid)") } catch { print("insertion failed: \(error)") } @@ -799,6 +796,33 @@ for user in try db.prepare(users) { } ``` +Note that the iterator can throw *undeclared* database errors at any point during +iteration: + +```swift +let query = try db.prepare(users) +for user in query { + // 💥 can throw an error here +} +```` + +#### Failable iteration + +It is therefore recommended using the `RowIterator` API instead, +which has explicit error handling: + +```swift +let rowIterator = try db.prepareRowIterator(users) +for user in try Array(rowIterator) { + print("id: \(user[id]), email: \(user[email])") +} + +/// or using `map()` +let mapRowIterator = try db.prepareRowIterator(users) +let userIds = try mapRowIterator.map { $0[id] } + +``` + ### Plucking Rows We can pluck the first row by passing a query to the `pluck` function on a @@ -1285,7 +1309,6 @@ try db.run(users.addColumn(suffix)) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT ``` - #### Added Column Constraints The `addColumn` function shares several of the same [`column` function @@ -1337,6 +1360,13 @@ tables](#creating-a-table). // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` +### Renaming Columns + +Added in SQLite 3.25.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) + +### Dropping Columns + +Added in SQLite 3.35.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) ### Indexes @@ -1762,6 +1792,19 @@ for row in stmt.bind(kUTTypeImage) { /* ... */ } [UTTypeConformsTo]: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto +## Custom Aggregations + +We can create custom aggregation functions by calling `createAggregation`: + +```swift +let reduce: (String, [Binding?]) -> String = { (last, bindings) in + last + " " + (bindings.first as? String ?? "") +} + +db.createAggregation("customConcat", initialValue: "", reduce: reduce, result: { $0 }) +let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +``` + ## Custom Collations We can create custom collating sequences by calling `createCollation` on a @@ -1944,6 +1987,19 @@ using the following functions. let count = try stmt.scalar() as! Int64 ``` +## Online Database Backup + +To copy a database to another using the +[SQLite Online Backup API](https://sqlite.org/backup.html): + +```swift +// creates an in-memory copy of db.sqlite +let db = try Connection("db.sqlite") +let target = try Connection(.inMemory) + +let backup = try db.backup(usingConnection: target) +try backup.step() +``` ## Logging @@ -1955,6 +2011,14 @@ We can log SQL using the database’s `trace` function. #endif ``` +## Vacuum + +To run the [vacuum](https://www.sqlite.org/lang_vacuum.html) command: + +```swift +try db.vacuum() +``` + [ROWID]: https://sqlite.org/lang_createtable.html#rowid [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 62df1f24..44e02c7a 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.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.0) +> the [0.13.1 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.1) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/Documentation/Release.md b/Documentation/Release.md index 0177fc8f..87d715f6 100644 --- a/Documentation/Release.md +++ b/Documentation/Release.md @@ -1,6 +1,7 @@ # SQLite.swift Release checklist * [ ] Make sure current master branch has a green build +* [ ] Make sure `SQLite.playground` runs without errors * [ ] Make sure `CHANGELOG.md` is up-to-date * [ ] Update the version number in `SQLite.swift.podspec` * [ ] Run `pod lib lint` locally diff --git a/README.md b/README.md index 9242b200..ec92729f 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ Swift code. $ swift build ``` +See the [Tests/SPM](https://github.com/stephencelis/SQLite.swift/tree/master/Tests/SPM) folder for a small demo project which uses SPM. + [Swift Package Manager]: https://swift.org/package-manager ### Carthage @@ -175,8 +177,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift - requires version 1.6.1 or greater.) + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. ```sh # Using the default Ruby install will require you to use sudo when @@ -266,7 +267,8 @@ 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) + - [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 @@ -278,7 +280,6 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - [Squeal](https://github.com/nerdyc/Squeal) - [SwiftData](https://github.com/ryanfowler/SwiftData) - - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) [Swift]: https://swift.org/ [SQLite3]: https://www.sqlite.org diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 11e13139..ebd5b020 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -1,43 +1,93 @@ import SQLite -let db = try! Connection() +/// Create an in-memory database +let db = try Connection(.inMemory) +/// enable statement logging db.trace { print($0) } +/// define a "users" table with some fields let users = Table("users") let id = Expression("id") -let email = Expression("email") -let name = Expression("name") +let email = Expression("email") // non-null +let name = Expression("name") // nullable -try! db.run(users.create { t in +/// prepare the query +let statement = users.create { t in t.column(id, primaryKey: true) t.column(email, unique: true, check: email.like("%@%")) t.column(name) -}) +} + +/// …and run it +try db.run(statement) + +/// insert "alice" +let rowid = try db.run(users.insert(email <- "alice@mac.com")) + +/// insert multiple rows using `insertMany` +let lastRowid = try db.run(users.insertMany([ + [email <- "bob@mac.com"], + [email <- "mallory@evil.com"] +])) + + +let query = try db.prepare(users) +for user in query { + print("id: \(user[id]), email: \(user[email])") +} -let rowid = try! db.run(users.insert(email <- "alice@mac.com")) -let alice = users.filter(id == rowid) +// re-requery just rowid of Alice +let alice = try db.prepare(users.filter(id == rowid)) +for user in alice { + print("id: \(user[id]), email: \(user[email])") +} -for user in try! db.prepare(users) { +/// using the `RowIterator` API +let rowIterator = try db.prepareRowIterator(users) +for user in try Array(rowIterator) { print("id: \(user[id]), email: \(user[email])") } +/// also with `map()` +let mapRowIterator = try db.prepareRowIterator(users) +let userIds = try mapRowIterator.map { $0[id] } + +/// define a virtual tabe for the FTS index let emails = VirtualTable("emails") -let subject = Expression("subject") +let subject = Expression("subject") let body = Expression("body") -try! db.run(emails.create(.FTS4(subject, body))) +/// create the index +try db.run(emails.create(.FTS5( + FTS5Config() + .column(subject) + .column(body) +))) -try! db.run(emails.insert( +/// populate with data +try db.run(emails.insert( subject <- "Hello, world!", body <- "This is a hello world message." )) -let row = try! db.pluck(emails.match("hello")) +/// run a query +let ftsQuery = try db.prepare(emails.match("hello")) -let query = try! db.prepare(emails.match("hello")) -for row in query { +for row in ftsQuery { print(row[subject]) } + +/// custom aggregations +let reduce: (String, [Binding?]) -> String = { (last, bindings) in + last + " " + (bindings.first as? String ?? "") +} + +db.createAggregation("customConcat", + initialValue: "users:", + reduce: reduce, + result: { $0 }) +let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +print(result) diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground index fd676d5b..441c60ef 100644 --- a/SQLite.playground/contents.xcplayground +++ b/SQLite.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/SQLite.playground/playground.xcworkspace/contents.xcworkspacedata b/SQLite.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/SQLite.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 92400180..ba14e9ae 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -256,6 +256,7 @@ 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 = ""; }; + 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 = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; @@ -386,6 +387,7 @@ children = ( EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, + 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, 3D67B3E41DB2469200A4F4C6 /* Frameworks */, diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index 106f292c..4eea76c3 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -90,7 +90,7 @@ extension Connection { register(functionName, argc: argc, value: box) } - func createAggregation( + public func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, @@ -122,7 +122,7 @@ extension Connection { createAggregation(aggregate, step: step, final: final, state: state) } - func createAggregation( + public func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 810026ce..265021e5 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -6,11 +6,12 @@ import PackageDescription let package = Package( name: "test", dependencies: [ + // for testing from same repository .package(path: "../..") + // normally this would be: + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")] From ab67978f154c404e05a6a634cca30fa0206d1dd0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 6 Sep 2021 01:00:54 +0200 Subject: [PATCH 151/391] Update screenshot --- Documentation/Resources/playground@2x.png | Bin 124729 -> 409028 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Documentation/Resources/playground@2x.png b/Documentation/Resources/playground@2x.png index 32646d6aa411b7b5a7163fc692c7252fe8ee8cce..da132718cf4bc7d2cafdff82eb5020abcb46ec93 100644 GIT binary patch literal 409028 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μ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 mauvc0dsvBsb|L^lwr~O9ysL| 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

    2tG!%YcIj$@$EG)wF{Hf_B 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

    D9$zF2T`qMbO!>79MgheSx8`$ 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

    lbErWo0fYUhNnO45# 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%0A`zqOC=oLR_v5FR_8z+($29av1W18O7 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 CF|3yW From 84a878807af8e6f317a2451937567ec82a6f80d9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 7 Sep 2021 12:57:15 +0200 Subject: [PATCH 152/391] Clarify usage --- Documentation/Index.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 1e08f6c4..1f702653 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -812,15 +812,24 @@ It is therefore recommended using the `RowIterator` API instead, which has explicit error handling: ```swift +// option 1: convert results into an Array of rows let rowIterator = try db.prepareRowIterator(users) for user in try Array(rowIterator) { print("id: \(user[id]), email: \(user[email])") } -/// or using `map()` +/// option 2: transform results using `map()` let mapRowIterator = try db.prepareRowIterator(users) let userIds = try mapRowIterator.map { $0[id] } +/// option 3: handle each row individually with `failableNext()` +do { + while let row = try rowIterator.failableNext() { + // Handle row + } +} catch { + // Handle error +} ``` ### Plucking Rows From e191c7d04583a1fd4e0f95e73bec2161a30e4857 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 7 Sep 2021 13:04:24 +0200 Subject: [PATCH 153/391] Add failableNext() example to playground --- SQLite.playground/Contents.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index ebd5b020..c089076d 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -54,6 +54,16 @@ for user in try Array(rowIterator) { let mapRowIterator = try db.prepareRowIterator(users) let userIds = try mapRowIterator.map { $0[id] } +/// using `failableNext()` on `RowIterator` +let iterator = try db.prepareRowIterator(users) +do { + while let row = try rowIterator.failableNext() { + print(row) + } +} catch { + // Handle error +} + /// define a virtual tabe for the FTS index let emails = VirtualTable("emails") From 818af0882507b0e468c9ec40fa2149a52a4ad171 Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Wed, 3 Nov 2021 12:59:38 -0700 Subject: [PATCH 154/391] Add platform versions to the SPM manifest --- Package.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Package.swift b/Package.swift index f4938678..47c00b37 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,12 @@ import PackageDescription let package = Package( name: "SQLite.swift", + platforms: [ + .iOS(.v9), + .macOS(.v10_15), + .watchOS(.v3), + .tvOS(.v9) + ], products: [ .library( name: "SQLite", @@ -46,6 +52,12 @@ package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.g package.targets = [ .target( name: "SQLite", + platforms: [ + .iOS(.v9), + .macOS(.v10_15), + .watchOS(.v3), + .tvOS(.v9) + ], dependencies: [.product(name: "CSQLite", package: "CSQLite")], exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] ), From 9af505317fff1d9129a4c905fa2e8757c3484fc0 Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Wed, 3 Nov 2021 13:00:23 -0700 Subject: [PATCH 155/391] Move XCode deployment version from targets to project --- SQLite.xcodeproj/project.pbxproj | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ba14e9ae..aecb424a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1099,7 +1099,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1121,7 +1120,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1135,7 +1133,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1149,7 +1146,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1173,7 +1169,6 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Debug; }; @@ -1197,7 +1192,6 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Release; }; @@ -1259,8 +1253,10 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 9.1; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Debug; }; @@ -1315,9 +1311,11 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 9.1; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Release; }; @@ -1334,7 +1332,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1358,7 +1355,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; From d6eb85b381da062ffe2db30739f868607dc1c4c9 Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Fri, 5 Nov 2021 12:13:00 -0700 Subject: [PATCH 156/391] Fix test platform versions --- Package.swift | 6 ------ Tests/SPM/Package.swift | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Package.swift b/Package.swift index 47c00b37..abce8395 100644 --- a/Package.swift +++ b/Package.swift @@ -52,12 +52,6 @@ package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.g package.targets = [ .target( name: "SQLite", - platforms: [ - .iOS(.v9), - .macOS(.v10_15), - .watchOS(.v3), - .tvOS(.v9) - ], dependencies: [.product(name: "CSQLite", package: "CSQLite")], exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] ), diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 265021e5..91c0c6d1 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -5,6 +5,12 @@ import PackageDescription let package = Package( name: "test", + platforms: [ + .iOS(.v9), + .macOS(.v10_15), + .watchOS(.v3), + .tvOS(.v9) + ], dependencies: [ // for testing from same repository .package(path: "../..") From d02d0b4090f855acca24e105f2f684c7f71dc9a1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:20:10 +0100 Subject: [PATCH 157/391] Move OSX deployment target back to 10.10 --- Package.swift | 2 +- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- Tests/SPM/Package.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index abce8395..24898bca 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ let package = Package( name: "SQLite.swift", platforms: [ .iOS(.v9), - .macOS(.v10_15), + .macOS(.v10_10), .watchOS(.v3), .tvOS(.v9) ], diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index f273ffd8..80871b0b 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -20,7 +20,7 @@ Pod::Spec.new do |s| ios_deployment_target = '9.0' tvos_deployment_target = '9.1' - osx_deployment_target = '10.15' + osx_deployment_target = '10.10' watchos_deployment_target = '3.0' s.ios.deployment_target = ios_deployment_target diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index aecb424a..ecc1195c 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1304,7 +1304,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 91c0c6d1..dd1cd45c 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -7,7 +7,7 @@ let package = Package( name: "test", platforms: [ .iOS(.v9), - .macOS(.v10_15), + .macOS(.v10_10), .watchOS(.v3), .tvOS(.v9) ], From 34798be5eb2cc057040903839ba732b5cab7c02c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:32:17 +0100 Subject: [PATCH 158/391] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d09c12..a36c6468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * Support for custom SQL aggregates ([#881][]) * Restore previous iteration behavior ([#1075][]) * Fix compilation on Linux ([#1077][]) +* Align platform versions in SPM manifest and Xcode ([#1094][]) +* Revert OSX deployment target back to 10.10 ([#1095][]) 0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== @@ -128,3 +130,5 @@ [#919]: https://github.com/stephencelis/SQLite.swift/pull/919 [#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 +[#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 \ No newline at end of file From 883117cbc847508192d2961f7833e9da5a4c5f6d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:42:45 +0100 Subject: [PATCH 159/391] "macos-latest" is not the latest :( https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources --- .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 88c5803f..a633f184 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ env: IOS_VERSION: 14.4 jobs: build: - runs-on: macos-latest + runs-on: macos-11 steps: - uses: actions/checkout@v2 - name: Install From 13d78942f39ab4254ac6856e7f88f5032946ef3b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:49:43 +0100 Subject: [PATCH 160/391] iOS 15.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0b9a1606..c62da000 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 12 -IOS_VERSION = 14.4 +IOS_VERSION = 15.0 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else From 7c0106255f742218b3d99d86ed08a31b42882cf7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:58:38 +0100 Subject: [PATCH 161/391] 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 a633f184..84dc9aba 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: 14.4 + IOS_VERSION: 15.0 jobs: build: runs-on: macos-11 From 47c70041ac7237b5c1ccdcf6114e6bb78c0d9dc6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 22:07:30 +0100 Subject: [PATCH 162/391] YML insanity --- .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 84dc9aba..baa8c2d8 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.0" jobs: build: runs-on: macos-11 From db48bb0bf98568c0e29976cd5b95c898c013c277 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 17 Nov 2021 00:46:07 +0100 Subject: [PATCH 163/391] Update Index.md Clarify SQLCipher integration (#1097) --- Documentation/Index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 1f702653..22c5fea5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -170,6 +170,8 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby 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.0' end ``` From 60a65015f6402b7c34b9a924f755ca0a73afeeaa Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 17 Nov 2021 00:55:18 +0100 Subject: [PATCH 164/391] Bump version --- CHANGELOG.md | 7 ++++--- Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a36c6468..d89e33f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ -0.13.1 (tba) +0.13.1 (17-11-2021), [diff][diff-0.13.1] ======================================== * Support for database backup ([#919][]) * Support for custom SQL aggregates ([#881][]) -* Restore previous iteration behavior ([#1075][]) +* Restore previous behavior in `FailableIterator` ([#1075][]) * Fix compilation on Linux ([#1077][]) * Align platform versions in SPM manifest and Xcode ([#1094][]) * Revert OSX deployment target back to 10.10 ([#1095][]) @@ -94,6 +94,7 @@ [diff-0.12.0]: https://github.com/stephencelis/SQLite.swift/compare/0.11.6...0.12.0 [diff-0.12.2]: https://github.com/stephencelis/SQLite.swift/compare/0.12.0...0.12.2 [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 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -131,4 +132,4 @@ [#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 -[#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 \ No newline at end of file +[#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 diff --git a/Documentation/Index.md b/Documentation/Index.md index 22c5fea5..96e5775c 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.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.1") ] ``` @@ -102,7 +102,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.0 + github "stephencelis/SQLite.swift" ~> 0.13.1 ``` 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.0' + pod 'SQLite.swift', '~> 0.13.1' end ``` @@ -146,7 +146,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.0' + pod 'SQLite.swift/standalone', '~> 0.13.1' end ``` @@ -156,7 +156,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.0' + pod 'SQLite.swift/standalone', '~> 0.13.1' 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.0' + pod 'SQLite.swift/SQLCipher', '~> 0.13.1' end ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 44e02c7a..bebfb503 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.1 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.1) +> the [0.13.2 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.2) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index ec92729f..bc8eac31 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.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.1") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.0 + github "stephencelis/SQLite.swift" ~> 0.13.1 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.0' + pod 'SQLite.swift', '~> 0.13.1' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 80871b0b..90a8a348 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.0" + s.version = "0.13.1" 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 ecc1195c..aee41218 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.0; + MARKETING_VERSION = 0.13.1; 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.0; + MARKETING_VERSION = 0.13.1; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; From a2bfb9eb3b0641873c886384ed96fd44f70dba0e Mon Sep 17 00:00:00 2001 From: Dylan Nunns Date: Thu, 2 Dec 2021 19:05:01 -0400 Subject: [PATCH 165/391] Closing bracket position Updated position of closing bracket where `onConflictOf` parameter. In this particular area it was closing `upsert()` and making `onConflictOf` a parameter of `.run()` --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 96e5775c..061b448b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1174,7 +1174,7 @@ 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 -try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice"), onConflictOf: email) +try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice", onConflictOf: email)) // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') ON CONFLICT (\"email\") DO UPDATE SET \"name\" = \"excluded\".\"name\" ``` From b9489a7cc11f18a15e744193e29de1430ddd35b2 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Mon, 17 Jan 2022 17:08:36 +0100 Subject: [PATCH 166/391] Native user_version support in Connection --- Sources/SQLite/Core/Connection.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 6b5481e3..5b1fc50a 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -151,6 +151,13 @@ public final class Connection { public var totalChanges: Int { 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 { return Int32(try! scalar("PRAGMA user_version") as! Int64)} + set { try! run("PRAGMA user_version = \(newValue)") } + } // MARK: - Execute From 9e162a84c964562cf6248890722c77011f81eaa4 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Mon, 17 Jan 2022 17:30:00 +0100 Subject: [PATCH 167/391] Refactoring userVersion + adding tests --- Sources/SQLite/Core/Connection.swift | 20 +++++++++++++++----- Tests/SQLiteTests/ConnectionTests.swift | 5 +++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 5b1fc50a..4b1c712a 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -151,12 +151,22 @@ public final class Connection { public var totalChanges: Int { Int(sqlite3_total_changes(handle)) } - - /// The user version of the database. + + /// Gets the user version of the database. + /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) + /// - Returns: the user version of the database + public func getUserVersion() throws -> Int32? { + guard let userVersion = try scalar("PRAGMA user_version") as? Int64 else { + return nil + } + return Int32(userVersion) + } + + /// Sets the user version of the database. /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - public var userVersion: Int32 { - get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} - set { try! run("PRAGMA user_version = \(newValue)") } + /// - Parameter userVersion: the new user version of the database + public func setUserVersion(to userVersion: Int32) throws { + try run("PRAGMA user_version = \(userVersion)") } // MARK: - Execute diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ae9d4dfd..707cbc43 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -96,6 +96,11 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.totalChanges) } + func test_userVersion() { + try! db.setUserVersion(to: 2) + XCTAssertEqual(2, try! db.getUserVersion()!) + } + func test_prepare_preparesAndReturnsStatements() { _ = try! db.prepare("SELECT * FROM users WHERE admin = 0") _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", 0) From d52eaaf1795e05dadb5c1c0e59cf56579c7f7112 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 18 Jan 2022 13:28:31 +0100 Subject: [PATCH 168/391] Set userVersion as a property --- Sources/SQLite/Core/Connection.swift | 25 ++++++++++++------------- Tests/SQLiteTests/ConnectionTests.swift | 4 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 4b1c712a..becc932e 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -152,21 +152,20 @@ public final class Connection { Int(sqlite3_total_changes(handle)) } - /// Gets the user version of the database. + /// The user version of the database. /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - /// - Returns: the user version of the database - public func getUserVersion() throws -> Int32? { - guard let userVersion = try scalar("PRAGMA user_version") as? Int64 else { - return nil + public var userVersion: Int32? { + get { + guard let userVersion = try? scalar("PRAGMA user_version") as? Int64 else { + return nil + } + return Int32(userVersion) + } + set { + if let userVersion = newValue { + _ = try? run("PRAGMA user_version = \(userVersion)") + } } - return Int32(userVersion) - } - - /// Sets the user version of the database. - /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - /// - Parameter userVersion: the new user version of the database - public func setUserVersion(to userVersion: Int32) throws { - try run("PRAGMA user_version = \(userVersion)") } // MARK: - Execute diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 707cbc43..136271a4 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -97,8 +97,8 @@ class ConnectionTests: SQLiteTestCase { } func test_userVersion() { - try! db.setUserVersion(to: 2) - XCTAssertEqual(2, try! db.getUserVersion()!) + db.userVersion = 2 + XCTAssertEqual(2, db.userVersion!) } func test_prepare_preparesAndReturnsStatements() { From 80c8b30a4e372f92745e197047c9e8549fc4347d Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 13:14:00 +0100 Subject: [PATCH 169/391] More compact version --- Sources/SQLite/Core/Connection.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index becc932e..043cfafc 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -156,10 +156,7 @@ public final class Connection { /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) public var userVersion: Int32? { get { - guard let userVersion = try? scalar("PRAGMA user_version") as? Int64 else { - return nil - } - return Int32(userVersion) + (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) } set { if let userVersion = newValue { From d00473986112b4bce7bb7f15dc68483adf29c67e Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 14:09:11 +0100 Subject: [PATCH 170/391] Fixing tests for Linux --- Tests/SQLiteTests/QueryTests.swift | 23 ++++++++++++++--------- Tests/SQLiteTests/TestHelpers.swift | 13 ++++++++++++- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index e0749f2c..f225adb4 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -391,15 +391,20 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) let update = try emails.update(value) - let encodedJSON = try JSONEncoder().encode(value1) - let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - assertSQL( - """ - UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, - \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)' - """.replacingOccurrences(of: "\n", with: ""), - update - ) + + // NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix, + // and extract JSON to decode it and check the decoded object. + + 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\" = '" + let expectedSuffix = "'" + + let sql = update.asSQL() + XCTAssert(sql.hasPrefix(expectedPrefix)) + XCTAssert(sql.hasSuffix(expectedSuffix)) + + let extractedJSON = String(sql[sql.index(sql.startIndex, offsetBy: expectedPrefix.count) ..< sql.index(sql.endIndex, offsetBy: -expectedSuffix.count)]) + let decodedJSON = try JSONDecoder().decode(TestCodable.self, from: extractedJSON.data(using: .utf8)!) + XCTAssertEqual(decodedJSON, value1) } func test_delete_compilesDeleteExpression() { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 07cc6f06..53dabe79 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -105,7 +105,7 @@ let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") let _view = View("view") // avoid Mac XCTestCase collision -class TestCodable: Codable { +class TestCodable: Codable, Equatable { let int: Int let string: String let bool: Bool @@ -125,4 +125,15 @@ class TestCodable: Codable { self.optional = optional self.sub = sub } + + static func == (lhs: TestCodable, rhs: TestCodable) -> Bool { + lhs.int == rhs.int && + lhs.string == rhs.string && + lhs.bool == rhs.bool && + lhs.float == rhs.float && + lhs.double == rhs.double && + lhs.date == rhs.date && + lhs.optional == rhs.optional && + lhs.sub == rhs.sub + } } From 34d3a9713e77d2a85b4140d505d3dbd3918117ff Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 14:50:37 +0100 Subject: [PATCH 171/391] Fixing Linting --- Tests/SQLiteTests/QueryTests.swift | 19 +++++++++++++------ Tests/SQLiteTests/TestHelpers.swift | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index f225adb4..ad0f3b25 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -391,18 +391,25 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), 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, // and extract JSON to decode it and check the decoded object. - - 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\" = '" + + 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\" = ' + """.replacingOccurrences(of: "\n", with: "") let expectedSuffix = "'" - + let sql = update.asSQL() XCTAssert(sql.hasPrefix(expectedPrefix)) XCTAssert(sql.hasSuffix(expectedSuffix)) - - let extractedJSON = String(sql[sql.index(sql.startIndex, offsetBy: expectedPrefix.count) ..< sql.index(sql.endIndex, offsetBy: -expectedSuffix.count)]) + + let extractedJSON = String(sql[ + sql.index(sql.startIndex, offsetBy: expectedPrefix.count) ..< + sql.index(sql.endIndex, offsetBy: -expectedSuffix.count) + ]) let decodedJSON = try JSONDecoder().decode(TestCodable.self, from: extractedJSON.data(using: .utf8)!) XCTAssertEqual(decodedJSON, value1) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 53dabe79..4d8f78fa 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -125,7 +125,7 @@ class TestCodable: Codable, Equatable { self.optional = optional self.sub = sub } - + static func == (lhs: TestCodable, rhs: TestCodable) -> Bool { lhs.int == rhs.int && lhs.string == rhs.string && From 85921d83ce4921201a6d4106bc609f277253d4d8 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 15:05:28 +0100 Subject: [PATCH 172/391] Oups --- Tests/SQLiteTests/QueryTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index ad0f3b25..f4942b88 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -398,7 +398,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', \"sub\" = ' """.replacingOccurrences(of: "\n", with: "") let expectedSuffix = "'" From 608684aeea5b6a5c5e8b87866600447c412d3b2e Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 15:19:40 +0100 Subject: [PATCH 173/391] Updating documentation --- Documentation/Index.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 061b448b..4b4f45db 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1457,21 +1457,11 @@ try db.run(users.drop(ifExists: true)) ### Migrations and Schema Versioning -You can add a convenience property on `Connection` to query and set the +You can use the convenience property on `Connection` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_user_version). This is a great way to manage your schema’s version over migrations. - -```swift -extension Connection { - public var userVersion: Int32 { - get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} - set { try! run("PRAGMA user_version = \(newValue)") } - } -} -``` - -Then you can conditionally run your migrations along the lines of: +You can conditionally run your migrations along the lines of: ```swift if db.userVersion == 0 { From 946f69e79c2503d2dff58813d1abf78280205fa0 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 15:31:17 +0100 Subject: [PATCH 174/391] Bump version number --- CHANGELOG.md | 9 +++++++++ Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d89e33f4..11d0218e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.13.2 (25-01-2022), [diff][diff-0.13.2] +======================================== + +* Closing bracket position ([#1100][]) +* Native user_version support in Connection ([#1105][]) + 0.13.1 (17-11-2021), [diff][diff-0.13.1] ======================================== @@ -95,6 +101,7 @@ [diff-0.12.2]: https://github.com/stephencelis/SQLite.swift/compare/0.12.0...0.12.2 [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 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -133,3 +140,5 @@ [#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 +[#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 +[#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 diff --git a/Documentation/Index.md b/Documentation/Index.md index 4b4f45db..52affae4 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.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") ] ``` @@ -102,7 +102,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.1 + github "stephencelis/SQLite.swift" ~> 0.13.2 ``` 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.1' + pod 'SQLite.swift', '~> 0.13.2' end ``` @@ -146,7 +146,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.1' + pod 'SQLite.swift/standalone', '~> 0.13.2' end ``` @@ -156,7 +156,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.1' + pod 'SQLite.swift/standalone', '~> 0.13.2' 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.1' + pod 'SQLite.swift/SQLCipher', '~> 0.13.2' end ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index bebfb503..5b50219a 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.2 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.2) +> the [0.13.3 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.3) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index bc8eac31..a2b5e537 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.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.1 + github "stephencelis/SQLite.swift" ~> 0.13.2 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.1' + pod 'SQLite.swift', '~> 0.13.2' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 90a8a348..23146eee 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.1" + s.version = "0.13.2" 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 aee41218..26c5e975 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.1; + MARKETING_VERSION = 0.13.2; 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.1; + MARKETING_VERSION = 0.13.2; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; From 5f5ad81ac0d0a0f3e56e39e646e8423c617df523 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 16:57:56 +0100 Subject: [PATCH 175/391] Reset userVersion if set to nil --- Sources/SQLite/Core/Connection.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 043cfafc..72e8d857 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -159,9 +159,7 @@ public final class Connection { (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) } set { - if let userVersion = newValue { - _ = try? run("PRAGMA user_version = \(userVersion)") - } + _ = try? run("PRAGMA user_version = \(newValue ?? 0)") } } 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 176/391] 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 177/391] 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 178/391] 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 179/391] 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 180/391] 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 181/391] 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 182/391] 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 183/391] 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 184/391] 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 185/391] 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 186/391] 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 187/391] 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 188/391] 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 189/391] 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 190/391] 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 191/391] 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 192/391] 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 193/391] 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 194/391] 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 195/391] 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 196/391] 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 197/391] 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 198/391] 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 199/391] 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 200/391] 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 201/391] 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 202/391] 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 203/391] 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 204/391] 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 205/391] 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 206/391] 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 207/391] 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 208/391] 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 209/391] 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 210/391] 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 211/391] 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 212/391] 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 213/391] 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 214/391] 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 215/391] 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 216/391] 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 217/391] 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 218/391] 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 219/391] 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 220/391] 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 221/391] 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 222/391] 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 223/391] 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 224/391] 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 225/391] 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 226/391] 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 227/391] 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 228/391] 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 229/391] 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 230/391] 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 231/391] 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 232/391] 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 233/391] 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 234/391] 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 235/391] 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 236/391] 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 237/391] 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 238/391] 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 239/391] 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 240/391] 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 241/391] 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 242/391] 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 243/391] 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 244/391] 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 245/391] 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 246/391] 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 247/391] 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 248/391] 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 249/391] 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 250/391] 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 251/391] 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 252/391] 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 253/391] 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 254/391] 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 255/391] 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 256/391] 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 257/391] 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 258/391] 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 259/391] 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 260/391] 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 261/391] 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 262/391] 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 263/391] 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 264/391] 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 265/391] 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 266/391] 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 267/391] 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 268/391] 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 269/391] 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 270/391] 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 271/391] 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 272/391] 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 273/391] 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 274/391] 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 275/391] 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 276/391] 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 277/391] 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 278/391] 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 279/391] 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 280/391] 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 281/391] 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 282/391] 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 283/391] 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 284/391] 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 285/391] 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 286/391] 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 287/391] 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 288/391] 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 289/391] 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 290/391] 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 291/391] 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 292/391] 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 293/391] 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 294/391] 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 295/391] 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 296/391] 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 297/391] 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 298/391] 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 299/391] 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 300/391] 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 301/391] 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 302/391] 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 303/391] 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 304/391] 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 305/391] 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 306/391] 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 307/391] 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 308/391] 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 309/391] 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 310/391] 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 311/391] 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 312/391] 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 313/391] 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 314/391] 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 315/391] 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 316/391] 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 317/391] 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 318/391] 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 319/391] 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 320/391] 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 321/391] 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 322/391] 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 323/391] 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 324/391] 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 325/391] 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 326/391] 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 327/391] 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 328/391] 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 329/391] 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 330/391] 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 331/391] 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 332/391] 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 333/391] 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 334/391] 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 335/391] 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 336/391] 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 337/391] 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 338/391] 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 339/391] 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 340/391] 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 341/391] 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 342/391] 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 343/391] 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 344/391] 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 345/391] 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 346/391] 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 347/391] 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 348/391] 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 349/391] 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 350/391] 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 351/391] 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 352/391] 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 353/391] 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 354/391] 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 355/391] 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 356/391] 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 357/391] 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 358/391] 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 359/391] 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 360/391] 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 361/391] 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 362/391] 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 363/391] 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 364/391] 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 365/391] 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 366/391] 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 367/391] 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 368/391] 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 369/391] 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 370/391] 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 371/391] 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 372/391] 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 373/391] 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 374/391] 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 375/391] 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 376/391] 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 377/391] 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 378/391] 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 379/391] 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 380/391] 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 381/391] 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 382/391] 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 383/391] 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 384/391] 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 385/391] 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 386/391] 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 387/391] 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 388/391] 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 389/391] 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 390/391] 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 391/391] 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