diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fbba1c5..3c6970f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,19 +1,13 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "iPhone 15" - IOS_VERSION: "17.2" + IOS_SIMULATOR: "iPhone 16" + IOS_VERSION: "18.4" 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)" @@ -82,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 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 diff --git a/Documentation/Index.md b/Documentation/Index.md index fcd2d642..0e8261f2 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) @@ -108,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") ] ``` @@ -129,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]. @@ -159,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 ``` @@ -173,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 ``` @@ -183,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 ``` @@ -199,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 ``` @@ -1583,6 +1584,17 @@ 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 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/Gemfile b/Gemfile index eb6036cd..2770e85d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ source "https://rubygems.org" -gem 'cocoapods', :git => 'https://github.com/SagarSDagdu/CocoaPods.git', tag: '1.15.2.1-sagard' +# 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 a58783ea..7dac2c18 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,12 @@ GIT - remote: https://github.com/SagarSDagdu/CocoaPods.git - revision: d96f491f79abd2804d1359c5228cce404dd365b7 - tag: 1.15.2.1-sagard + remote: https://github.com/jberkel/CocoaPods.git + revision: 32a90c184bc5dc9ec8b7b9b8ad08e98b7253dec2 + branch: watchos-fourflusher specs: - cocoapods (1.15.2.1.pre.sagard) + cocoapods (1.16.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) + 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) @@ -19,8 +19,8 @@ GIT gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) + ruby-macho (~> 4.1.0) + xcodeproj (>= 1.27.0, < 2.0) GEM remote: https://rubygems.org/ @@ -29,26 +29,29 @@ 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-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -68,41 +71,45 @@ GEM 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.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) + drb (2.2.3) 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) - ruby-macho (2.5.1) + rexml (3.4.1) + ruby-macho (4.1.0) + 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 @@ -112,4 +119,4 @@ DEPENDENCIES cocoapods! BUNDLED WITH - 2.5.4 + 2.6.9 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/Package.swift b/Package.swift index 238661ae..56925d18 100644 --- a/Package.swift +++ b/Package.swift @@ -1,13 +1,45 @@ // 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", condition: .when(platforms: [.linux, .windows, .android])) + ], + 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: [ - .iOS(.v11), + .iOS(.v12), .macOS(.v10_13), .watchOS(.v4), - .tvOS(.v11), + .tvOS(.v12), .visionOS(.v1) ], products: [ @@ -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/README.md b/README.md index a4d713b6..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. @@ -128,7 +131,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 +155,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 +186,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.14.0' + pod 'SQLite.swift', '~> 0.15.0' end ``` @@ -226,8 +229,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 +236,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 @@ -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 diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 184ce23e..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 @@ -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,22 +47,20 @@ 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 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' } @@ -87,18 +70,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 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/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 0eebbdd5..023acc08 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -28,8 +28,8 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index 4eea76c3..a1abb74a 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -3,8 +3,8 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index 8a25e51d..0c674ee6 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -3,8 +3,8 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f2c3b781..57521f83 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -28,8 +28,8 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index ee59e5d1..9a72e4c5 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -2,8 +2,8 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 6cb2e5d3..82d535b9 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,8 +26,8 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index c27ccf05..e3c84589 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,8 +26,8 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index af7b5e27..6fae532a 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -43,19 +43,30 @@ public class SchemaChanger: CustomStringConvertible { public enum Operation { 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) /// 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 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): 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: ", ") + + ")" default: return nil } } @@ -89,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 @@ -99,21 +110,73 @@ 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)) } + 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)) } } + public class CreateTableDefinition { + fileprivate var columnDefinitions: [ColumnDefinition] = [] + fileprivate var indexDefinitions: [IndexDefinition] = [] + + let name: String + let ifNotExists: Bool + + init(name: String, ifNotExists: Bool) { + self.name = name + self.ifNotExists = ifNotExists + } + + public func add(column: ColumnDefinition) { + 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) + } + + var operations: [Operation] { + precondition(!columnDefinitions.isEmpty) + return [ + .createTable(columns: columnDefinitions, ifNotExists: ifNotExists) + ] + indexDefinitions.map { .addIndex($0, ifNotExists: ifNotExists) } + } + + 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 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 +204,15 @@ public class SchemaChanger: CustomStringConvertible { } } + public func create(table: String, ifNotExists: Bool = false, block: CreateTableDefinitionBlock) throws { + let createTableDefinition = CreateTableDefinition(name: table, ifNotExists: ifNotExists) + 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) } @@ -151,6 +223,12 @@ 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. + @discardableResult + public func run(_ sql: String, _ bindings: Binding?...) throws -> Statement { + return try connection.run(sql, bindings) + } + private func run(table: String, operation: Operation) throws { try operation.validate() @@ -263,7 +341,9 @@ extension TableDefinition { func apply(_ operation: SchemaChanger.Operation?) -> TableDefinition { switch operation { case .none: return self + 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 }, @@ -280,3 +360,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/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 80f9e199..d7803999 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 } @@ -117,17 +117,31 @@ 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 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 +149,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 +260,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)) @@ -270,12 +288,16 @@ 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, + origin: origin) } public let table: String @@ -284,6 +306,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) @@ -297,6 +326,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) @@ -345,6 +381,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: " ") @@ -376,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 1989ad6f..995cd4d7 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -19,9 +19,9 @@ 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 }) - 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]) ) } } @@ -95,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 @@ -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/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 e162cf34..919042ab 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -136,7 +136,7 @@ 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"), @@ -147,12 +147,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() 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/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")]) diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift index 940a30ca..0e185da5 100644 --- a/Tests/SQLiteTests/Core/Connection+AttachTests.swift +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -6,8 +6,8 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif @@ -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/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift index 2bcdb6af..d1d4ab04 100644 --- a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -6,8 +6,8 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 6a1d94ae..abdb3e1b 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -7,8 +7,8 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif @@ -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/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/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index d3c8bb1f..03415f3a 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -6,8 +6,8 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 5f212505..3c90d941 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -5,8 +5,8 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif @@ -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..b3b9e617 100644 --- a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift @@ -3,15 +3,15 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif @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..f5a6de42 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, @@ -124,6 +124,78 @@ 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) + """) + + 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 @@ -154,4 +226,158 @@ 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, unique: true)) + 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, + 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").filter { !$0.isInternal } + XCTAssertEqual(indexes, [ + IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil, origin: .createIndex) + ]) + } + + func test_create_table_add_column_expression() throws { + try schemaChanger.create(table: "foo") { table in + 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") + 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)) + } + + 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)") + } + } + } + + 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)) + } + } + + 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) + } + + func test_run_arbitrary_sql() throws { + try schemaChanger.run("DROP TABLE users") + XCTAssertEqual(0, try schema.objectDefinitions(name: "users", type: .table).count) + } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 8b7e27e3..f3a0ba83 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)), @@ -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) @@ -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 d57ebff7..8963070f 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)), + references: .init(fromColumn: "manager_id", toTable: "users", toColumn: "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 ) ]) } @@ -163,12 +189,12 @@ 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") 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) ]) } @@ -238,7 +264,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..e62b2a23 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")! @@ -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 - } } diff --git a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift index 71cbba9c..b052d236 100644 --- a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift @@ -7,14 +7,14 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else 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 0f46b380..fd9ae6b9 100644 --- a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift @@ -2,11 +2,11 @@ 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 = () -> 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..cde5a0c3 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -3,8 +3,8 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif @@ -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..257b5245 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -3,8 +3,8 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) -import CSQLite +#elseif canImport(SwiftToolchainCSQLite) +import SwiftToolchainCSQLite #else import SQLite3 #endif @@ -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() { diff --git a/run-tests.sh b/run-tests.sh index 1bf8de96..3ffba810 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,15 +8,18 @@ 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 - fi + case "$VALIDATOR_SUBSPEC" in + none) + bundle exec pod lib lint --no-subspecs --fail-fast + ;; + *) + 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} fi