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]
diff --git a/.github/issue_template.md b/.github/issue_template.md
new file mode 100644
index 00000000..f9622b9b
--- /dev/null
+++ b/.github/issue_template.md
@@ -0,0 +1,20 @@
+> Issues are used to track bugs and feature requests.
+> Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift).
+
+## Build Information
+
+- Include the SQLite.swift version, commit or branch experiencing the issue.
+- Mention Xcode and OS X versions affected.
+- How do do you integrate SQLite.swift in your project?
+ - manual
+ - CocoaPods
+ - Carthage
+ - Swift Package manager
+
+## General guidelines
+
+- Be as descriptive as possible.
+- Provide as much information needed to _reliably reproduce_ the issue.
+- Attach screenshots if possible.
+- Better yet: attach GIFs or link to video.
+- Even better: link to a sample project exhibiting the issue.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..b74ffd8d
--- /dev/null
+++ b/.github/pull_request_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.
+
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..3c6970f5
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,84 @@
+name: Build and test
+on: [push, pull_request]
+env:
+ IOS_SIMULATOR: "iPhone 16"
+ IOS_VERSION: "18.4"
+jobs:
+ build:
+ runs-on: macos-15
+ steps:
+ - uses: actions/checkout@v2
+ - 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 (SPM integration test)"
+ env:
+ SPM: run
+ run: ./run-tests.sh
+ - 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 (CARTHAGE_PLATFORM: visionOS)"
+ env:
+ CARTHAGE_PLATFORM: visionOS
+ 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
+ - name: "Run tests (SPM integration test)"
+ 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/.gitignore b/.gitignore
index e3ecdc74..e7b2ad4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,31 @@
+# OS X
+.DS_Store
+
# Xcode
build/
-timeline.xctimeline
-xcuserdata/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata
+*.xccheckout
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
-# System
-.DS_Store
+# Carthage
+/Carthage/
+
+# Makefile
+bin/
+
+# Swift Package Manager
+.build
+Packages/
+.swiftpm/
diff --git a/.swiftlint.yml b/.swiftlint.yml
new file mode 100644
index 00000000..1bb21cd5
--- /dev/null
+++ b/.swiftlint.yml
@@ -0,0 +1,47 @@
+opt_in_rules:
+ - shorthand_optional_binding
+disabled_rules: # rule identifiers to exclude from running
+ - todo
+ - operator_whitespace
+ - 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
+excluded: # paths to ignore during linting. overridden by `included`.
+
+identifier_name:
+ excluded:
+ - db
+ - in
+ - to
+ - by
+ - or
+ - eq
+ - gt
+ - lt
+ - fn
+ - a
+ - b
+ - q
+ - SQLITE_TRANSIENT
+
+type_body_length:
+ warning: 350
+ error: 350
+
+function_body_length:
+ warning: 60
+ error: 60
+
+line_length:
+ warning: 150
+ error: 150
+ ignores_comments: true
+
+file_length:
+ warning: 500
+ error: 500
+ ignore_comment_only_lines: true
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 23250f09..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: objective-c
-osx_image: xcode61
-env:
- - BUILD_PLATFORM=iOS
- - BUILD_PLATFORM=Mac
-before_install:
- - gem install xcpretty --no-document
-script:
- - make test
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..b77a8b07
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,274 @@
+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][])
+
+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]
+========================================
+
+* 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]
+========================================
+
+* 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]
+========================================
+
+* 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).
+
+* 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][])
+* 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][])
+* 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]
+========================================
+
+* 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]
+========================================
+
+* Closing bracket position ([#1100][])
+* Native user_version support in Connection ([#1105][])
+
+0.13.1 (17-11-2021), [diff][diff-0.13.1]
+========================================
+
+* Support for database backup ([#919][])
+* Support for custom SQL aggregates ([#881][])
+* 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][])
+
+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
+
+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][])
+
+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]
+========================================
+
+* Collate `.nocase` strictly enforces `NOT NULL` even when using Optional ([#697][])
+* Fix transactions not being rolled back when committing fails ([#426][])
+* Add possibility to have expression on right hand side of like ([#591][])
+* Added Date and Time functions ([#142][])
+* Add Swift4 Coding support ([#733][])
+* Preliminary Linux support ([#315][], [#681][])
+* Add `RowIterator` for more safety ([#647][], [#726][])
+* Make `Row.get` throw instead of crash ([#649][])
+* Fix create/drop index functions ([#666][])
+* Revert deployment target to 8.0 ([#625][], [#671][], [#717][])
+* Added support for the union query clause ([#723][])
+* Add support for `ORDER` and `LIMIT` on `UPDATE` and `DELETE` ([#657][], [#722][])
+* Swift 4 support ([#668][])
+
+0.11.3 (30-03-2017), [diff][diff-0.11.3]
+========================================
+
+* Fix compilation problems when using Carthage ([#615][])
+* Add `WITHOUT ROWID` table option ([#541][])
+* Argument count fixed for binary custom functions ([#481][])
+* Documentation updates
+* Tested with Xcode 8.3 / iOS 10.3
+
+0.11.2 (25-12-2016), [diff][diff-0.11.2]
+========================================
+
+* Fixed SQLCipher integration with read-only databases ([#559][])
+* Preliminary Swift Package Manager support ([#548][], [#560][])
+* Fixed null pointer when fetching an empty BLOB ([#561][])
+* Allow `where` as alias for `filter` ([#571][])
+
+0.11.1 (06-12-2016), [diff][diff-0.11.1]
+========================================
+
+* Integrate SQLCipher via CocoaPods ([#546][], [#553][])
+* Made lastInsertRowid consistent with other SQLite wrappers ([#532][])
+* Fix for `~=` operator used with Double ranges
+* Various documentation updates
+
+0.11.0 (19-10-2016)
+===================
+
+* Swift3 migration ([diff][diff-0.11.0])
+
+
+[diff-0.11.0]: https://github.com/stephencelis/SQLite.swift/compare/0.10.1...0.11.0
+[diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1
+[diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2
+[diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3
+[diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4
+[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
+[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
+[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
+[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
+[#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
+[#541]: https://github.com/stephencelis/SQLite.swift/issues/541
+[#546]: https://github.com/stephencelis/SQLite.swift/issues/546
+[#548]: https://github.com/stephencelis/SQLite.swift/pull/548
+[#553]: https://github.com/stephencelis/SQLite.swift/pull/553
+[#559]: https://github.com/stephencelis/SQLite.swift/pull/559
+[#560]: https://github.com/stephencelis/SQLite.swift/pull/560
+[#561]: https://github.com/stephencelis/SQLite.swift/issues/561
+[#571]: https://github.com/stephencelis/SQLite.swift/issues/571
+[#591]: https://github.com/stephencelis/SQLite.swift/pull/591
+[#615]: https://github.com/stephencelis/SQLite.swift/pull/615
+[#625]: https://github.com/stephencelis/SQLite.swift/issues/625
+[#647]: https://github.com/stephencelis/SQLite.swift/pull/647
+[#649]: https://github.com/stephencelis/SQLite.swift/pull/649
+[#657]: https://github.com/stephencelis/SQLite.swift/issues/657
+[#666]: https://github.com/stephencelis/SQLite.swift/pull/666
+[#668]: https://github.com/stephencelis/SQLite.swift/pull/668
+[#671]: https://github.com/stephencelis/SQLite.swift/issues/671
+[#681]: https://github.com/stephencelis/SQLite.swift/issues/681
+[#697]: https://github.com/stephencelis/SQLite.swift/issues/697
+[#717]: https://github.com/stephencelis/SQLite.swift/issues/717
+[#722]: https://github.com/stephencelis/SQLite.swift/pull/722
+[#723]: https://github.com/stephencelis/SQLite.swift/pull/723
+[#733]: https://github.com/stephencelis/SQLite.swift/pull/733
+[#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
+[#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
+[#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
+[#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
+[#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
+[#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
+[#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
+[#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
+[#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/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..6969a705
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,108 @@
+# Contributing
+
+The where and when to open an [issue](#issues) or [pull
+request](#pull-requests).
+
+
+## Issues
+
+Issues are used to track **bugs** and **feature requests**. Need **help** or
+have a **general question**? [Ask on Stack Overflow][] (tag `sqlite.swift`).
+
+Before reporting a bug or requesting a feature, [run a few searches][Search] to
+see if a similar issue has already been opened and ensure you’re not submitting
+a duplicate.
+
+If you find a similar issue, read the existing conversation and see if it
+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]: https://stackoverflow.com/questions/tagged/sqlite.swift
+[Search]: https://github.com/stephencelis/SQLite.swift/search?type=Issues
+
+
+### Bugs
+
+Think you’ve discovered a new **bug**? Let’s try troubleshooting a few things
+first.
+
+ - **Is it an installation issue?**
+
+ If this is your first time building SQLite.swift in your project, you may
+ encounter a build error, _e.g._:
+
+ No such module 'SQLite'
+
+ Please carefully re-read the [installation instructions][] to make sure
+ everything is in order.
+
+ - **Have you read the documentation?**
+
+ If you can’t seem to get something working, check
+ [the documentation][See Documentation] to see if the solution is there.
+
+ - **Are you up-to-date?**
+
+ If you’re perusing [the documentation][See Documentation] online and find
+ that an example is just not working, please upgrade to the latest version
+ of SQLite.swift and try again before continuing.
+
+ - **Is it an unhelpful build error?**
+
+ While Swift error messaging is improving with each release, complex
+ expressions still lend themselves to misleading errors. If you encounter an
+ error on a complex line, breaking it down into smaller pieces generally
+ yields a more understandable error.
+
+ - **Is it an _even more_ unhelpful build error?**
+
+ Have you updated Xcode recently? Did your project stop building out of the
+ blue?
+
+ Hold down the **option** key and select **Clean Build Folder…** from the
+ **Product** menu (⌥⇧⌘K).
+
+Made it through everything above and still having trouble? Sorry!
+[Open an issue][]! And _please_:
+
+ - Be as descriptive as possible.
+ - Provide as much information needed to _reliably reproduce_ the issue.
+ - Attach screenshots if possible.
+ - Better yet: attach GIFs or link to video.
+ - Even better: link to a sample project exhibiting the issue.
+ - Include the SQLite.swift commit or branch experiencing the issue.
+ - Include devices and operating systems affected.
+ - Include build information: the Xcode and macOS versions affected.
+
+[installation instructions]: Documentation/Index.md#installation
+[See Documentation]: Documentation/Index.md#sqliteswift-documentation
+[Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new
+
+
+### Feature Requests
+
+Have an innovative **feature request**? [Open an issue][]! Be thorough! Provide
+context and examples. Be open to discussion.
+
+
+## Pull Requests
+
+Interested in contributing but don’t know where to start? Try the [`help
+wanted`][help wanted] label.
+
+Ready to submit a fix or a feature? [Submit a pull request][]! And _please_:
+
+ - If code changes, run the tests and make sure everything still works.
+ - Write new tests for new functionality.
+ - Update documentation comments where applicable.
+ - Maintain the existing style.
+ - Don’t forget to have fun.
+
+While we cannot guarantee a merge to every pull request, we do read each one
+and love your input.
+
+
+[help wanted]: https://github.com/stephencelis/SQLite.swift/labels/help%20wanted
+[Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork
diff --git a/Documentation/Index.md b/Documentation/Index.md
index 38b9ab49..0e8261f2 100644
--- a/Documentation/Index.md
+++ b/Documentation/Index.md
@@ -1,24 +1,37 @@
# 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-Memory Databases](#in-memory-databases)
- - [A Note on Thread-Safety](#a-note-on-thread-safety)
+ - [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)
- [Column Constraints](#column-constraints)
- [Table Constraints](#table-constraints)
- [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)
@@ -27,161 +40,421 @@
- [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)
- [Aggregation](#aggregation)
+ - [Upserting Rows](#upserting-rows)
- [Updating Rows](#updating-rows)
- [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)
+ - [SchemaChanger](#schemachanger)
+ - [Adding Columns](#adding-columns-1)
+ - [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)
- - [Dropping Tables](#dropping-tables)
- [Migrations and Schema Versioning](#migrations-and-schema-versioning)
- [Custom Types](#custom-types)
- [Date-Time Values](#date-time-values)
- [Binary Data](#binary-data)
- - [Custom Type Caveats](#custom-type-caveats)
+ - [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)
+ - [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
## Installation
-> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode 6.1](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.
+
+ 1. Add the following to your `Package.swift` file:
+
+ ```swift
+ dependencies: [
+ .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4")
+ ]
+ ```
+
+ 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
+install SQLite.swift with Carthage:
+ 1. Make sure Carthage is [installed][Carthage Installation].
+
+ 2. Update your Cartfile to include the following:
+
+ ```ruby
+ github "stephencelis/SQLite.swift" ~> 0.15.4
+ ```
+
+ 3. Run `carthage update` and [add the appropriate framework][Carthage Usage].
+
+
+[Carthage]: https://github.com/Carthage/Carthage
+[Carthage Installation]: https://github.com/Carthage/Carthage#installing-carthage
+[Carthage Usage]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application
+
+
+### CocoaPods
+
+[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).
+
+ ```sh
+ # Using the default Ruby install will require you to use sudo when
+ # installing and updating gems.
+ [sudo] gem install cocoapods
+ ```
+
+ 2. Update your Podfile to include the following:
+
+ ```ruby
+ use_frameworks!
+
+ target 'YourAppTargetName' do
+ pod 'SQLite.swift', '~> 0.15.4'
+ end
+ ```
+
+ 3. Run `pod install --repo-update`.
+
+
+#### Requiring a specific version of SQLite
+
+If you want to use a more recent version of SQLite than what is provided
+with the OS you can require the `standalone` subspec:
+
+```ruby
+target 'YourAppTargetName' do
+ pod 'SQLite.swift/standalone', '~> 0.15.4'
+end
+```
+
+By default this will use the most recent version of SQLite without any
+extras. If you want you can further customize this by adding another
+dependency to sqlite3 or one of its subspecs:
+
+```ruby
+target 'YourAppTargetName' do
+ pod 'SQLite.swift/standalone', '~> 0.15.4'
+ pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled
+end
+```
+
+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](https://github.com/stephencelis/SQLite.swift/issues/1084)):
+
+```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.15.4'
+end
+```
+
+This will automatically add a dependency to the SQLCipher pod as well as
+extend `Connection` with methods to change the database key:
+
+```swift
+import SQLite
+
+let db = try Connection("path/to/encrypted.sqlite3")
+try db.key("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
+[CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started
+[sqlite3pod]: https://github.com/clemensg/sqlite3pod
+[SQLCipher]: https://www.zetetic.net/sqlcipher/
+
+### Manual
To install SQLite.swift as an Xcode sub-project:
- 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) the project first.)
+ 1. Drag the **SQLite.xcodeproj** file into your own project.
+ ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or
+ [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip)
+ the project first.)
+
+ 
- 
+ 2. In your target’s **General** tab, click the **+** button under **Linked
+ Frameworks and Libraries**.
- 2. In your target’s **Build Phases**, add **SQLite iOS** (or **SQLite Mac**) to the **Target Dependencies** build phase.
+ 3. Select the appropriate **SQLite.framework** for your platform.
- 3. Add the appropriate **SQLite.framework** product to the **Link Binary With Libraries** build phase.
+ 4. **Add**.
- 4. Add the same **SQLite.framework** to a **Copy Files** build phase with a **Frameworks** destination. (Add a new build phase if need be.)
+You should now be able to `import SQLite` from any of your target’s source
+files and begin using SQLite.swift.
-You should now be able to `import SQLite` from any of your target’s source files and begin using SQLite.swift.
+Some additional steps are required to install the application on an actual
+device:
+ 5. In the **General** tab, click the **+** button under **Embedded
+ Binaries**.
+
+ 6. Select the appropriate **SQLite.framework** for your platform.
+
+ 7. **Add**.
## Getting Started
-To use SQLite.swift classes or structures in your target’s source file, first import the `SQLite` module.
+To use SQLite.swift classes or structures in your target’s source file, first
+import the `SQLite` module.
-``` swift
+```swift
import SQLite
```
### Connecting to a Database
-Database connections are established using the `Database` class. A database is initialized with a path. SQLite will attempt to create the database file if it does not already exist.
+Database connections are established using the `Connection` class. A
+connection is initialized with a path to a database. SQLite will attempt to
+create the database file if it does not already exist.
-``` swift
-let db = Database("path/to/db.sqlite3")
+```swift
+let db = try Connection("path/to/db.sqlite3")
```
#### Read-Write Databases
-On iOS, you can create a writable database in your app’s **Documents** directory.
+On iOS, you can create a writable database in your app’s **Documents**
+directory.
-``` swift
+```swift
let path = NSSearchPathForDirectoriesInDomains(
- .DocumentDirectory, .UserDomainMask, true
-).first as String
+ .documentDirectory, .userDomainMask, true
+).first!
+
+let db = try Connection("\(path)/db.sqlite3")
+```
-let db = Database("\(path)/db.sqlite3")
+If you have bundled it in your application, you can use FileManager to copy it to the Documents directory:
+
+```swift
+func copyDatabaseIfNeeded(sourcePath: String) -> Bool {
+ 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 {
+ try FileManager.default.copyItem(atPath: sourcePath, toPath: destinationPath)
+ return true
+ } catch {
+ print("error during file copy: \(error)")
+ return false
+ }
+}
```
-On OS X, you can use your app’s **Application Support** directory:
+On macOS, you can use your app’s **Application Support** directory:
-``` swift
+
+```swift
+// set the path corresponding to application support
var path = NSSearchPathForDirectoriesInDomains(
- .ApplicationSupportDirectory, .UserDomainMask, true
-).first as String + NSBundle.mainBundle().bundleIdentifier!
+ .applicationSupportDirectory, .userDomainMask, true
+).first! + "/" + Bundle.main.bundleIdentifier!
-// create parent directory iff it doesn't exist
-NSFileManager.defaultManager().createDirectoryAtPath(
- path, withIntermediateDirectories: true, attributes: nil, error: nil
+// create parent directory inside application support if it doesn’t exist
+try FileManager.default.createDirectory(
+atPath: path, withIntermediateDirectories: true, attributes: nil
)
-let db = Database("\(path)/db.sqlite3")
+let db = try Connection("\(path)/db.sqlite3")
```
-
#### Read-Only Databases
-If you bundle a database with your app, you can establish a _read-only_ connection to it.
+If you bundle a database with your app (_i.e._, you’ve copied a database file
+into your Xcode project and added it to your application target), you can
+establish a _read-only_ connection to it.
-``` swift
-let path = NSBundle.mainBundle().pathForResource("db", ofType: "sqlite3")!
+```swift
+let path = Bundle.main.path(forResource: "db", ofType: "sqlite3")!
-let db = Database(path, readonly: true)
+let db = try Connection(path, readonly: true)
```
-> _Note_: Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations).
+> _Note:_ Signed applications cannot modify their bundle resources. If you
+> bundle a database file with your app for the purpose of bootstrapping, copy
+> it to a writable location _before_ establishing a connection (see
+> [Read-Write Databases](#read-write-databases), above, for typical, writable
+> locations).
+>
+> See these two Stack Overflow questions for more information about iOS apps
+> with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios),
+> [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift).
+> We welcome 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 database](https://www.sqlite.org/inmemorydb.html).
+If you omit the path, SQLite.swift will provision an [in-memory
+database](https://www.sqlite.org/inmemorydb.html).
-``` swift
-let db = Database() // equivalent to `Database(":memory:")`
+```swift
+let db = try Connection() // equivalent to `Connection(.inMemory)`
```
To create a temporary, disk-backed database, pass an empty file name.
-``` swift
-let db = Database("")
+```swift
+let db = try Connection(.temporary)
```
-In-memory databases are automatically deleted when the database connection is closed.
+In-memory databases are automatically deleted when the database connection is
+closed.
+#### URI parameters
-### A Note on Thread-Safety
+We can pass `.uri` to the `Connection` initializer to control more aspects of
+the database connection with the help of `URIQueryParameter`s:
-> _Note:_ Every database comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints, however, do not block other threads from executing statements within the transaction.
+```swift
+let db = try Connection(.uri("file.sqlite", parameters: [.cache(.private), .noLock(true)]))
+```
+
+See [Uniform Resource Identifiers](https://www.sqlite.org/uri.html#recognized_query_parameters) for more details.
+
+#### Thread-Safety
+
+Every Connection comes equipped with its own serial queue for statement
+execution and can be safely accessed across threads. Threads that open
+transactions and savepoints will block other threads from executing
+statements while the transaction is open.
+
+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 // error after 5 seconds (does multiple retries)
+
+db.busyHandler({ tries in
+ tries < 3 // error after 3 tries
+})
+```
+
+> _Note:_ The default timeout is 0, so if you see `database is locked`
+> errors, you may be trying to access the same database simultaneously from
+> multiple connections.
## Building Type-Safe SQL
-SQLite.swift comes with a typed expression layer that directly maps [Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) to their [SQLite counterparts](https://www.sqlite.org/datatype3.html).
+SQLite.swift comes with a typed expression layer that directly maps
+[Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/)
+to their [SQLite counterparts](https://www.sqlite.org/datatype3.html).
| Swift Type | SQLite Type |
| --------------- | ----------- |
-| `Int` | `INTEGER` |
+| `Int64`* | `INTEGER` |
| `Double` | `REAL` |
| `String` | `TEXT` |
-| `Bool` | `BOOLEAN` |
| `nil` | `NULL` |
-| `SQLite.Blob`* | `BLOB` |
+| `SQLite.Blob`† | `BLOB` |
+| `URL` | `TEXT` |
+| `UUID` | `TEXT` |
+| `Date` | `TEXT` |
-> *SQLite.swift defines its own `Blob` structure, which safely wraps the underlying bytes.
+> *While `Int64` is the basic, raw type (to preserve 64-bit integers on
+> 32-bit platforms), `Int` and `Bool` work transparently.
>
-> See [Custom Types](#custom-types) for more information about extending other classes and structures to work with SQLite.swift.
+> †SQLite.swift defines its own `Blob` structure, which safely wraps the
+> underlying bytes.
+>
+> See [Custom Types](#custom-types) for more information about extending
+> other classes and structures to work with SQLite.swift.
+>
+> See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed
+> layer and execute raw SQL, instead.
-These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`Query`](#queries)), can create and execute SQL statements.
+These expressions (in the form of the structure,
+[`Expression`](#expressions)) build on one another and, with a query
+([`QueryType`](#queries)), can create and execute SQL statements.
### Expressions
-Expressions are generic structures associated with a type ([built-in](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and (optionally) values to bind to that SQL. Typically, you will only explicitly create expressions to describe your columns, and typically only once per column.
+Expressions are generic structures associated with a type ([built-in
+](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and
+(optionally) values to bind to that SQL. Typically, you will only explicitly
+create expressions to describe your columns, and typically only once per
+column.
-``` swift
-let id = Expression("id")
+```swift
+let id = Expression("id")
let email = Expression("email")
let balance = Expression("balance")
let verified = Expression("verified")
@@ -189,155 +462,222 @@ let verified = Expression("verified")
Use optional generics for expressions that can evaluate to `NULL`.
-``` swift
+```swift
let name = Expression("name")
```
-> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`.
+> _Note:_ The default `Expression` initializer is for [quoted
+> identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column
+> names). To build a literal SQL expression, use `init(literal:)`.
+>
### Compound Expressions
-Expressions can be combined with other expressions and types using [filters](#filter-operators-and-functions), and [other operators](#other-operators) and [functions](#core-sqlite-functions). These building blocks can create complex SQLite statements.
+Expressions can be combined with other expressions and types using
+[filter operators and functions](#filter-operators-and-functions)
+(as well as other [non-filter operators](#other-operators) and
+[functions](#core-sqlite-functions)). These building blocks can create complex SQLite statements.
### Queries
-Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a `Query` by subscripting a database connection with a table name.
+Queries are structures that reference a database and table name, and can be
+used to build a variety of statements using expressions. We can create a
+query by initializing a `Table`, `View`, or `VirtualTable`.
-``` swift
-let users = db["users"]
+```swift
+let users = Table("users")
```
-Assuming [the table exists](#creating-a-table), we can immediately [insert](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and [delete](#deleting-rows) rows.
+Assuming [the table exists](#creating-a-table), we can immediately [insert
+](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and
+[delete](#deleting-rows) rows.
## Creating a Table
-We can run [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create(table:)` function on a database connection. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates.
+We can build [`CREATE TABLE`
+statements](https://www.sqlite.org/lang_createtable.html) by calling the
+`create` function on a `Table`. The following is a basic example of
+SQLite.swift code (using the [expressions](#expressions) and
+[query](#queries) above) and the corresponding SQL it generates.
-``` swift
-db.create(table: users) { t in // CREATE TABLE "users" (
+```swift
+try db.run(users.create { t in // CREATE TABLE "users" (
t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL,
t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL,
t.column(name) // "name" TEXT
-} // )
+}) // )
```
-> _Note:_ `Expression` structures (in this case, the `id` and `email` columns), generate `NOT NULL` constraints automatically, while `Expression` structures (`name`) do not.
+> _Note:_ `Expression` structures (in this case, the `id` and `email`
+> columns), generate `NOT NULL` constraints automatically, while
+> `Expression` structures (`name`) do not.
### Create Table Options
-The `create(table:)` function has several default parameters we can override.
+The `Table.create` function has several default parameters we can override.
- - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to create a temporary table that will automatically drop when the database connection closes). Default: `false`.
+ - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to
+ create a temporary table that will automatically drop when the database
+ connection closes). Default: `false`.
- ``` swift
- db.create(table: users, temporary: true) { t in /* ... */ }
+ ```swift
+ try db.run(users.create(temporary: true) { t in /* ... */ })
// CREATE TEMPORARY TABLE "users" -- ...
```
- - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`.
+ - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE`
+ statement (which will bail out gracefully if the table already exists).
+ Default: `false`.
- ``` swift
- db.create(table: users, ifNotExists: true) { t in /* ... */ }
+ ```swift
+ try db.run(users.create(ifNotExists: true) { t in /* ... */ })
// CREATE TABLE "users" IF NOT EXISTS -- ...
```
### Column Constraints
-The `column` function is used for a single column definition. It takes an [expression](#expressions) describing the column name and type, and accepts several parameters that map to various column constraints and clauses.
+The `column` function is used for a single column definition. It takes an
+[expression](#expressions) describing the column name and type, and accepts
+several parameters that map to various column constraints and clauses.
- - `primaryKey` adds an `INTEGER PRIMARY KEY` constraint to a single column. (See the `primaryKey` function under [Table Constraints](#table-constraints) for non-integer primary keys).
+ - `primaryKey` adds a `PRIMARY KEY` constraint to a single column.
- ``` swift
+ ```swift
t.column(id, primaryKey: true)
// "id" INTEGER PRIMARY KEY NOT NULL
+
+ t.column(id, primaryKey: .autoincrement)
+ // "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
```
- > _Note:_ The `primaryKey` parameter cannot be used alongside `defaultValue` or `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints).
+ > _Note:_ The `primaryKey` parameter cannot be used alongside
+ > `references`. If you need to create a column that has a default value
+ > and is also a primary and/or foreign key, use the `primaryKey` and
+ > `foreignKey` functions mentioned under
+ > [Table Constraints](#table-constraints).
>
- > Primary keys cannot be optional (`Expression`).
+ > Primary keys cannot be optional (_e.g._, `Expression`).
+ >
+ > Only an `INTEGER PRIMARY KEY` can take `.autoincrement`.
- - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` function under [Table Constraints](#table-constraints) for uniqueness over multiple columns).
+ - `unique` adds a `UNIQUE` constraint to the column. (See the `unique`
+ function under [Table Constraints](#table-constraints) for uniqueness
+ over multiple columns).
- ``` swift
+ ```swift
t.column(email, unique: true)
// "email" TEXT UNIQUE NOT NULL
```
- - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` function under [Table Constraints](#table-constraints).)
+ - `check` attaches a `CHECK` constraint to a column definition in the form
+ of a boolean expression (`Expression`). Boolean expressions can be
+ easily built using
+ [filter operators and functions](#filter-operators-and-functions).
+ (See also the `check` function under
+ [Table Constraints](#table-constraints).)
- ``` swift
- t.column(email, check: like("%@%", email))
+ ```swift
+ t.column(email, check: email.like("%@%"))
// "email" TEXT NOT NULL CHECK ("email" LIKE '%@%')
```
- - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value (or expression) matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows).
+ - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_
+ accepts a value (or expression) matching the column’s type. This value is
+ used if none is explicitly provided during
+ [an `INSERT`](#inserting-rows).
- ``` swift
+ ```swift
t.column(name, defaultValue: "Anonymous")
// "name" TEXT DEFAULT 'Anonymous'
```
- > _Note:_ The `defaultValue` parameter cannot be used alongside `primaryKey` and `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints).
+ > _Note:_ The `defaultValue` parameter cannot be used alongside
+ > `primaryKey` and `references`. If you need to create a column that has
+ > a default value and is also a primary and/or foreign key, use the
+ > `primaryKey` and `foreignKey` functions mentioned under
+ > [Table Constraints](#table-constraints).
- - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration.
+ - `collate` adds a `COLLATE` clause to `Expression` (and
+ `Expression`) column definitions with
+ [a collating sequence](https://www.sqlite.org/datatype3.html#collation)
+ defined in the `Collation` enumeration.
- ``` swift
- t.column(email, collate: .NoCase)
- // "email" TEXT NOT NULL COLLATE NOCASE
- ```
+ ```swift
+ t.column(email, collate: .nocase)
+ // "email" TEXT NOT NULL COLLATE "NOCASE"
- - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.)
+ t.column(name, collate: .rtrim)
+ // "name" TEXT COLLATE "RTRIM"
+ ```
- ``` swift
- t.column(user_id, references: users[id])
- // "user_id" INTEGER REFERENCES "users"("id")
+ - `references` adds a `REFERENCES` clause to `Expression` (and
+ `Expression`) column definitions and accepts a table
+ (`SchemaType`) or namespaced column expression. (See the `foreignKey`
+ function under [Table Constraints](#table-constraints) for non-integer
+ foreign key support.)
- t.column(user_id, references: users)
- // "user_id" INTEGER REFERENCES "users"
- // -- assumes "users" has a PRIMARY KEY
+ ```swift
+ t.column(user_id, references: users, id)
+ // "user_id" INTEGER REFERENCES "users" ("id")
```
- > _Note:_ The `references` parameter cannot be used alongside `primaryKey` and `defaultValue`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints).
+ > _Note:_ The `references` parameter cannot be used alongside
+ > `primaryKey` and `defaultValue`. If you need to create a column that
+ > has a default value and is also a primary and/or foreign key, use the
+ > `primaryKey` and `foreignKey` functions mentioned under
+ > [Table Constraints](#table-constraints).
### Table Constraints
-Additional constraints may be provided outside the scope of a single column using the following functions.
+Additional constraints may be provided outside the scope of a single column
+using the following functions.
- - `primaryKey` adds a `PRIMARY KEY` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports all SQLite types, [ascending and descending orders](#sorting-rows), and composite (multiple column) keys.
+ - `primaryKey` adds a `PRIMARY KEY` constraint to the table. Unlike [the
+ column constraint, above](#column-constraints), it supports all SQLite
+ types, [ascending and descending orders](#sorting-rows), and composite
+ (multiple column) keys.
- ``` swift
+ ```swift
t.primaryKey(email.asc, name)
// PRIMARY KEY("email" ASC, "name")
```
- - `unique` adds a `UNIQUE` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports composite (multiple column) constraints.
+ - `unique` adds a `UNIQUE` constraint to the table. Unlike
+ [the column constraint, above](#column-constraints), it
+ supports composite (multiplecolumn) constraints.
- ``` swift
+ ```swift
t.unique(local, domain)
// UNIQUE("local", "domain")
```
- - `check` adds a `CHECK` constraint to the table in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` parameter under [Column Constraints](#column-constraints).)
+ - `check` adds a `CHECK` constraint to the table in the form of a boolean
+ expression (`Expression`). Boolean expressions can be easily built
+ using [filter operators and functions](#filter-operators-and-functions).
+ (See also the `check` parameter under
+ [Column Constraints](#column-constraints).)
- ``` swift
+ ```swift
t.check(balance >= 0)
// CHECK ("balance" >= 0.0)
```
- - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, and both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions).
+ - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the
+ `references` constraint, above](#column-constraints), it supports all
+ SQLite types, both [`ON UPDATE` and `ON DELETE`
+ actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and
+ composite (multiple column) keys.
- ``` swift
- t.foreignKey(user_id, on: users[id], delete: .SetNull)
+ ```swift
+ t.foreignKey(user_id, references: users, id, delete: .setNull)
// FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL
```
- > _Note:_ Composite foreign keys are not supported at this time. If you add support, please [submit a pull request](https://github.com/stephencelis/SQLite.swift/fork).
-
@@ -345,97 +685,108 @@ Additional constraints may be provided outside the scope of a single column usin
## Inserting Rows
-We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator.
+We can insert rows into a table by calling a [query’s](#queries) `insert`
+function with a list of [setters](#setters)—typically [typed column
+expressions](#expressions) and values (which can also be expressions)—each
+joined by the `<-` operator.
-``` swift
-users.insert(email <- "alice@mac.com", name <- "Alice")?
+```swift
+try db.run(users.insert(email <- "alice@mac.com", name <- "Alice"))
// INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice')
-```
-The `insert` function can return several different types that are useful in different contexts.
-
- - An `Int?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure), for simplicity.
-
- ``` swift
- if let insertID = users.insert(email <- "alice@mac.com") {
- println("inserted id: \(insertID)")
- }
- ```
-
- We can use the optional nature of the value to disambiguate with a simple `?` or `!`.
-
- ``` swift
- // ignore failure
- users.insert(email <- "alice@mac.com")?
+try db.run(users.insert(or: .replace, email <- "alice@mac.com", name <- "Alice B."))
+// INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.')
+```
- // assertion on failure
- users.insert(email <- "alice@mac.com")!
- ```
+The `insert` function, when run successfully, returns an `Int64` representing
+the inserted row’s [`ROWID`][ROWID].
- - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
+```swift
+do {
+ let rowid = try db.run(users.insert(email <- "alice@mac.com"))
+ print("inserted id: \(rowid)")
+} catch {
+ print("insertion failed: \(error)")
+}
+```
- ``` swift
- db.transaction(
- users.insert(email <- "alice@mac.com"),
- users.insert(email <- "betty@mac.com")
- )
- // BEGIN DEFERRED TRANSACTION;
- // INSERT INTO "users" ("email") VALUES ('alice@mac.com');
- // INSERT INTO "users" ("email") VALUES ('betty@mac.com');
- // COMMIT TRANSACTION;
- ```
+Multiple rows can be inserted at once by similarly calling `insertMany` with an array of
+per-row [setters](#setters).
- - A tuple of the above [`ROWID`][ROWID] and statement: `(ID: Int?, Statement)`, for flexibility.
+```swift
+do {
+ 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)")
+}
+```
- ``` swift
- let (ID, statement) = users.insert(email <- "alice@mac.com")
- if let ID = ID {
- println("inserted id: \(ID)")
- } else if statement.failed {
- println("insertion failed: \(statement.reason)")
- }
- ```
-The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns.
+The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions
+follow similar patterns.
-> _Note:_ If `insert` is called without any arguments, the statement will run with a `DEFAULT VALUES` clause. The table must not have any constraints that aren’t fulfilled by default values.
+> _Note:_ If `insert` is called without any arguments, the statement will run
+> with a `DEFAULT VALUES` clause. The table must not have any constraints
+> that aren’t fulfilled by default values.
>
-> ``` swift
-> timestamps.insert()!
+> ```swift
+> try db.run(timestamps.insert())
> // INSERT INTO "timestamps" DEFAULT VALUES
> ```
+### Handling SQLite errors
+
+You can pattern match on the error to selectively catch SQLite errors. For example, to
+specifically handle constraint errors ([SQLITE_CONSTRAINT](https://sqlite.org/rescode.html#constraint)):
+
+```swift
+do {
+ try db.run(users.insert(email <- "alice@mac.com"))
+ try db.run(users.insert(email <- "alice@mac.com"))
+} catch let Result.error(message, code, statement) where code == SQLITE_CONSTRAINT {
+ print("constraint failed: \(message), in \(statement)")
+} catch let error {
+ print("insertion failed: \(error)")
+}
+```
+
+The `Result.error` type contains the English-language text that describes the error (`message`),
+the error `code` (see [SQLite result code list](https://sqlite.org/rescode.html#primary_result_code_list)
+for details) and a optional reference to the `statement` which produced the error.
### Setters
-SQLite.swift typically uses the `<-` operator to set values during [inserts](#inserting-rows) and [updates](#updating-rows).
+SQLite.swift typically uses the `<-` operator to set values during [inserts
+](#inserting-rows) and [updates](#updating-rows).
-``` swift
-views.update(count <- 0)
-// UPDATE "views" SET "count" = 0 WHERE ("id" = 1)
+```swift
+try db.run(counter.update(count <- 0))
+// UPDATE "counters" SET "count" = 0 WHERE ("id" = 1)
```
-There are also a number of convenience setters that take the existing value into account using native Swift operators.
+There are also a number of convenience setters that take the existing value
+into account using native Swift operators.
For example, to atomically increment a column, we can use `++`:
-``` swift
-views.update(count++) // equivalent to `views.update(count -> count + 1)`
-// UPDATE "views" SET "count" = "count" + 1 WHERE ("id" = 1)
+```swift
+try db.run(counter.update(count++)) // equivalent to `counter.update(count -> count + 1)`
+// UPDATE "counters" SET "count" = "count" + 1 WHERE ("id" = 1)
```
To take an amount and “move” it via transaction, we can use `-=` and `+=`:
-``` swift
+```swift
let amount = 100.0
-db.transaction(
- alice.update(balance -= amount),
- betty.update(balance += amount)
-)
-// BEGIN DEFERRED TRANSACTION;
-// UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1);
-// UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2);
-// COMMIT TRANSACTION;
+try db.transaction {
+ try db.run(alice.update(balance -= amount))
+ try db.run(betty.update(balance += amount))
+}
+// BEGIN DEFERRED TRANSACTION
+// UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1)
+// UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2)
+// COMMIT TRANSACTION
```
@@ -452,7 +803,7 @@ db.transaction(
| `<<=` | `Int -> Int` |
| `>>=` | `Int -> Int` |
| `&=` | `Int -> Int` |
-| `||=` | `Int -> Int` |
+| `\|\|=` | `Int -> Int` |
| `^=` | `Int -> Int` |
| `+=` | `String -> String` |
@@ -467,46 +818,105 @@ db.transaction(
## Selecting Rows
-[`Query` structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access.
+[Query structures](#queries) are `SELECT` statements waiting to happen. They
+execute via [iteration](#iterating-and-accessing-values) and [other means
+](#plucking-values) of sequence access.
### Iterating and Accessing Values
-[Queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned.
+Prepared [queries](#queries) execute lazily upon iteration. Each row is
+returned as a `Row` object, which can be subscripted with a [column
+expression](#expressions) matching one of the columns returned.
-``` swift
-for user in users {
- println("id: \(user[id]), email: \(user[email]), name: \(user[name])")
+```swift
+for user in try db.prepare(users) {
+ print("id: \(user[id]), email: \(user[email]), name: \(user[name])")
// id: 1, email: alice@mac.com, name: Optional("Alice")
}
// SELECT * FROM "users"
```
-`Expression` column values are _automatically unwrapped_ (we’ve made a promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped.
+`Expression` column values are _automatically unwrapped_ (we’ve made a
+promise to the compiler that they’ll never be `NULL`), while `Expression`
+values remain wrapped.
+
+⚠ Column subscripts on `Row` will force try and abort execution in error cases.
+If you want to handle this yourself, use `Row.get(_ column: Expression)`:
+
+```swift
+for user in try db.prepare(users) {
+ do {
+ print("name: \(try user.get(name))")
+ } catch {
+ // handle
+ }
+}
+```
+
+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
+// 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])")
+}
+
+/// 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
-We can pluck the first row by calling the `first` computed property on [`Query`](#queries).
+We can pluck the first row by passing a query to the `pluck` function on a
+database connection.
-``` swift
-if let user = users.first { /* ... */ } // Row
+```swift
+if let user = try db.pluck(users) { /* ... */ } // Row
// SELECT * FROM "users" LIMIT 1
```
-To collect all rows into an array, we can simply wrap the sequence (though this is not always the most memory-efficient idea).
+To collect all rows into an array, we can simply wrap the sequence (though
+this is not always the most memory-efficient idea).
-``` swift
-let all = Array(users)
+```swift
+let all = Array(try db.prepare(users))
// SELECT * FROM "users"
```
### Building Complex Queries
-[`Query`](#queries) structures have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement.
+[Queries](#queries) have a number of chainable functions that can be used
+(with [expressions](#expressions)) to add and modify [a number of
+clauses](https://www.sqlite.org/lang_select.html) to the underlying
+statement.
-``` swift
+```swift
let query = users.select(email) // SELECT "email" FROM "users"
.filter(name != nil) // WHERE "name" IS NOT NULL
.order(email.desc, name) // ORDER BY "email" DESC, "name"
@@ -516,56 +926,71 @@ let query = users.select(email) // SELECT "email" FROM "users"
#### Selecting Columns
-By default, [`Query`](#queries) objects select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead.
+By default, [queries](#queries) select every column of the result set (using
+`SELECT *`). We can use the `select` function with a list of
+[expressions](#expressions) to return specific columns instead.
-``` swift
-let query = users.select(id, email)
+```swift
+for user in try db.prepare(users.select(id, email)) {
+ print("id: \(user[id]), email: \(user[email])")
+ // id: 1, email: alice@mac.com
+}
// SELECT "id", "email" FROM "users"
```
-
#### Joining Other Tables
We can join tables using a [query’s](#queries) `join` function.
-``` swift
+```swift
users.join(posts, on: user_id == users[id])
// SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id")
```
-The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.Inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require [namespacing](#column-namespacing), and sometimes require [aliasing](#table-aliasing).
+The `join` function takes a [query](#queries) object (for the table being
+joined on), a join condition (`on`), and is prefixed with an optional join
+type (default: `.inner`). Join conditions can be built using [filter
+operators and functions](#filter-operators-and-functions), generally require
+[namespacing](#column-namespacing), and sometimes require
+[aliasing](#table-aliasing).
##### Column Namespacing
-When joining tables, column names can become ambiguous. _E.g._, both tables may have an `id` column.
+When joining tables, column names can become ambiguous. _E.g._, both tables
+may have an `id` column.
-``` swift
+```swift
let query = users.join(posts, on: user_id == id)
// assertion failure: ambiguous column 'id'
```
We can disambiguate by namespacing `id`.
-``` swift
+```swift
let query = users.join(posts, on: user_id == users[id])
// SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id")
```
-Namespacing is achieved by subscripting a [query](#queries) with a [column expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`).
+Namespacing is achieved by subscripting a [query](#queries) with a [column
+expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`).
> _Note:_ We can namespace all of a table’s columns using `*`.
>
-> ``` swift
+> ```swift
> let query = users.select(users[*])
> // SELECT "users".* FROM "users"
> ```
@@ -573,20 +998,24 @@ Namespacing is achieved by subscripting a [query](#queries) with a [column expre
##### Table Aliasing
-Occasionally, we need to join a table to itself, in which case we must alias the table with another name. We can achieve this using the [query’s](#queries) `alias` function.
+Occasionally, we need to join a table to itself, in which case we must alias
+the table with another name. We can achieve this using the
+[query’s](#queries) `alias` function.
-``` swift
+```swift
let managers = users.alias("managers")
-let query = users.join(managers, on: managers[id] == users[manager_id])
+let query = users.join(managers, on: managers[id] == users[managerId])
// SELECT * FROM "users"
-// INNER JOIN "users" AS "managers" ON ("managers"."id" = "users"."manager_id")
+// INNER JOIN ("users") AS "managers" ON ("managers"."id" = "users"."manager_id")
```
-If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set.
+If query results can have ambiguous column names, row values should be
+accessed with namespaced [column expressions](#expressions). In the above
+case, `SELECT *` immediately namespaces all columns of the result set.
-``` swift
-let user = query.first!
+```swift
+let user = try db.pluck(query)
user[id] // fatal error: ambiguous column 'id'
// (please disambiguate: ["users"."id", "managers"."id"])
@@ -597,33 +1026,41 @@ user[managers[id]] // returns "managers"."id"
#### Filtering Rows
-SQLite.swift filters rows using a [query’s](#queries) `filter` function with a boolean [expression](#expressions) (`Expression`).
+SQLite.swift filters rows using a [query’s](#queries) `filter` function with
+a boolean [expression](#expressions) (`Expression`).
-``` swift
+```swift
users.filter(id == 1)
// SELECT * FROM "users" WHERE ("id" = 1)
-users.filter(contains([1, 2, 3, 4, 5], id))
+users.filter([1, 2, 3, 4, 5].contains(id))
// SELECT * FROM "users" WHERE ("id" IN (1, 2, 3, 4, 5))
-users.filter(like("%@mac.com", email))
+users.filter(email.like("%@mac.com"))
// SELECT * FROM "users" WHERE ("email" LIKE '%@mac.com')
-users.filter(verified && lower(name) == "alice")
+users.filter(verified && name.lowercaseString == "alice")
// SELECT * FROM "users" WHERE ("verified" AND (lower("name") == 'alice'))
users.filter(verified || balance >= 10_000)
// SELECT * FROM "users" WHERE ("verified" OR ("balance" >= 10000.0))
```
-You can build your own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions).
+We can build our own boolean expressions by using one of the many [filter
+operators and functions](#filter-operators-and-functions).
-> _Note:_ SQLite.swift defines `filter` instead of `where` because `where` is [a reserved keyword](https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_906).
+Instead of `filter` we can also use the `where` function which is an alias:
+```swift
+users.where(id == 1)
+// SELECT * FROM "users" WHERE ("id" = 1)
+```
##### Filter Operators and Functions
-SQLite.swift defines a number of operators for building filtering predicates. Operators and functions work together in a type-safe manner, so attempting to equate or compare different types will prevent compilation.
+SQLite.swift defines a number of operators for building filtering predicates.
+Operators and functions work together in a type-safe manner, so attempting to
+equate or compare different types will prevent compilation.
###### Infix Filter Operators
@@ -638,9 +1075,12 @@ SQLite.swift defines a number of operators for building filtering predicates. Op
| `<=` | `Comparable -> Bool` | `<=` |
| `~=` | `(Interval, Comparable) -> Bool` | `BETWEEN` |
| `&&` | `Bool -> Bool` | `AND` |
-| `||` | `Bool -> Bool` | `OR` |
+| `\|\|`| `Bool -> Bool` | `OR` |
+| `===` | `Equatable -> Bool` | `IS` |
+| `!==` | `Equatable -> Bool` | `IS NOT` |
-> *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` accordingly.
+> * When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT`
+> accordingly.
###### Prefix Filter Operators
@@ -671,16 +1111,18 @@ We can pre-sort returned rows using the [query’s](#queries) `order` function.
_E.g._, to return users sorted by `email`, then `name`, in ascending order:
-``` swift
+```swift
users.order(email, name)
// SELECT * FROM "users" ORDER BY "email", "name"
```
The `order` function takes a list of [column expressions](#expressions).
-`Expression` objects have two computed properties to assist sorting: `asc` and `desc`. These properties append the expression with `ASC` and `DESC` to mark ascending and descending order respectively.
+`Expression` objects have two computed properties to assist sorting: `asc`
+and `desc`. These properties append the expression with `ASC` and `DESC` to
+mark ascending and descending order respectively.
-``` swift
+```swift
users.order(email.desc, name.asc)
// SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC
```
@@ -688,9 +1130,10 @@ users.order(email.desc, name.asc)
#### Limiting and Paging Results
-We can limit and skip returned rows using a [query’s](#queries) `limit` function (and its optional `offset` parameter).
+We can limit and skip returned rows using a [query’s](#queries) `limit`
+function (and its optional `offset` parameter).
-``` swift
+```swift
users.limit(5)
// SELECT * FROM "users" LIMIT 5
@@ -699,314 +1142,523 @@ users.limit(5, offset: 5)
```
+#### Recursive and Hierarchical Queries
+
+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
+
+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"
+```
+
+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 =
+ 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,
+ 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"
+// JOIN "managers" ON "chain"."manager_id" = "managers"."id"
+// )
+// SELECT * FROM "chain"
+```
+
+
#### Aggregation
-[`Query`](#queries) structures come with a number of functions that quickly return aggregate values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query.
+[Queries](#queries) come with a number of functions that quickly return
+aggregate scalar values from the table. These mirror the [core aggregate
+functions](#aggregate-sqlite-functions) and are executed immediately against
+the query.
-``` swift
-users.count
+```swift
+let count = try db.scalar(users.count)
// SELECT count(*) FROM "users"
```
Filtered queries will appropriately filter aggregate values.
-``` swift
-users.filter(name != nil).count
+```swift
+let count = try db.scalar(users.filter(name != nil).count)
// SELECT count(*) FROM "users" WHERE "name" IS NOT NULL
```
- - `count` as a computed property (see examples above) returns the total number of rows matching the query.
+ - `count` as a computed property on a query (see examples above) returns
+ the total number of rows matching the query.
- `count` as a function takes a [column name](#expressions) and returns the total number of rows where that column is not `NULL`.
+ `count` as a computed property on a column expression returns the total
+ number of rows where that column is not `NULL`.
- ``` swift
- users.count(name) // -> Int
+ ```swift
+ let count = try db.scalar(users.select(name.count)) // -> Int
// SELECT count("name") FROM "users"
```
- - `max` takes a comparable column expression and returns the largest value if any exists.
+ - `max` takes a comparable column expression and returns the largest value
+ if any exists.
- ``` swift
- users.max(id) // -> Int?
+ ```swift
+ let max = try db.scalar(users.select(id.max)) // -> Int64?
// SELECT max("id") FROM "users"
```
- - `min` takes a comparable column expression and returns the smallest value if any exists.
+ - `min` takes a comparable column expression and returns the smallest value
+ if any exists.
- ``` swift
- users.min(id) // -> Int?
+ ```swift
+ let min = try db.scalar(users.select(id.min)) // -> Int64?
// SELECT min("id") FROM "users"
```
- - `average` takes a numeric column expression and returns the average row value (as a `Double`) if any exists.
+ - `average` takes a numeric column expression and returns the average row
+ value (as a `Double`) if any exists.
- ``` swift
- users.average(balance) // -> Double?
+ ```swift
+ let average = try db.scalar(users.select(balance.average)) // -> Double?
// SELECT avg("balance") FROM "users"
```
- - `sum` takes a numeric column expression and returns the sum total of all rows if any exist.
+ - `sum` takes a numeric column expression and returns the sum total of all
+ rows if any exist.
- ``` swift
- users.sum(balance) // -> Double?
+ ```swift
+ let sum = try db.scalar(users.select(balance.sum)) // -> Double?
// SELECT sum("balance") FROM "users"
```
- - `total`, like `sum`, takes a numeric column expression and returns the sum total of all rows, but in this case always returns a `Double`, and returns `0.0` for an empty query.
+ - `total`, like `sum`, takes a numeric column expression and returns the
+ sum total of all rows, but in this case always returns a `Double`, and
+ returns `0.0` for an empty query.
- ``` swift
- users.total(balance) // -> Double
+ ```swift
+ let total = try db.scalar(users.select(balance.total)) // -> Double
// SELECT total("balance") FROM "users"
```
-> _Note:_ Most of the above aggregate functions (except `max` and `min`) can be called with a `distinct` parameter to aggregate `DISTINCT` values only.
+> _Note:_ Expressions can be prefixed with a `DISTINCT` clause by calling the
+> `distinct` computed property.
>
-> ``` swift
-> users.count(distinct: name)
+> ```swift
+> let count = try db.scalar(users.select(name.distinct.count) // -> Int
> // 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
-We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator.
+We can update a table’s rows by calling a [query’s](#queries) `update`
+function with a list of [setters](#setters)—typically [typed column
+expressions](#expressions) and values (which can also be expressions)—each
+joined by the `<-` operator.
-When an unscoped query calls `update`, it will update _every_ row in the table.
+When an unscoped query calls `update`, it will update _every_ row in the
+table.
-``` swift
-users.update(email <- "alice@me.com")?
+```swift
+try db.run(users.update(email <- "alice@me.com"))
// UPDATE "users" SET "email" = 'alice@me.com'
```
-Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#filtering-rows).
+Be sure to scope `UPDATE` statements beforehand using [the `filter` function
+](#filtering-rows).
-``` swift
+```swift
let alice = users.filter(id == 1)
-alice.update(email <- "alice@me.com")?
+try db.run(alice.update(email <- "alice@me.com"))
// UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1)
```
-Like [`insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can return several different types that are useful in different contexts.
-
- - An `Int?` representing the number of updated rows (or `nil` on failure), for simplicity.
+The `update` function returns an `Int` representing the number of updated
+rows.
- ``` swift
- if alice.update(email <- "alice@me.com") > 0 {
- println("updated Alice")
+```swift
+do {
+ if try db.run(alice.update(email <- "alice@me.com")) > 0 {
+ print("updated alice")
+ } else {
+ print("alice not found")
}
- ```
-
- We can use the optional nature of the value to disambiguate with a simple `?` or `!`.
-
- ``` swift
- // ignore failure
- alice.update(email <- "alice@me.com")?
-
- // assertion on failure
- alice.update(email <- "alice@me.com")!
- ```
-
- - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
-
- - A tuple of the above number of updated rows and statement: `(changes: Int?, Statement)`, for flexibility.
+} catch {
+ print("update failed: \(error)")
+}
+```
## Deleting Rows
-We can delete rows from a table by calling a [query’s](#queries) `delete` function.
+We can delete rows from a table by calling a [query’s](#queries) `delete`
+function.
-When an unscoped query calls `delete`, it will delete _every_ row in the table.
+When an unscoped query calls `delete`, it will delete _every_ row in the
+table.
-``` swift
-users.delete()?
+```swift
+try db.run(users.delete())
// DELETE FROM "users"
```
-Be sure to scope `DELETE` statements beforehand using [the `filter` function](#filtering-rows).
+Be sure to scope `DELETE` statements beforehand using
+[the `filter` function](#filtering-rows).
-``` swift
+```swift
let alice = users.filter(id == 1)
-alice.delete()?
+try db.run(alice.delete())
// DELETE FROM "users" WHERE ("id" = 1)
```
-Like [`insert`](#inserting-rows) and [`update`](#updating-rows), `delete` can return several different types that are useful in different contexts.
+The `delete` function returns an `Int` representing the number of deleted
+rows.
- - An `Int?` representing the number of deleted rows (or `nil` on failure), for simplicity.
-
- ``` swift
- if alice.delete() > 0 {
- println("deleted Alice")
+```swift
+do {
+ if try db.run(alice.delete()) > 0 {
+ print("deleted alice")
+ } else {
+ print("alice not found")
}
- ```
+} catch {
+ print("delete failed: \(error)")
+}
+```
- We can use the optional nature of the value to disambiguate with a simple `?` or `!`.
- ``` swift
- // ignore failure
- alice.delete()?
+## Transactions and Savepoints
- // assertion on failure
- alice.delete()!
- ```
+Using the `transaction` and `savepoint` functions, we can run a series of
+statements in a transaction. If a single statement fails or the block throws
+an error, the changes will be rolled back.
- - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
+```swift
+try db.transaction {
+ let rowid = try db.run(users.insert(email <- "betty@icloud.com"))
+ try db.run(users.insert(email <- "cathy@icloud.com", managerId <- rowid))
+}
+// BEGIN DEFERRED TRANSACTION
+// INSERT INTO "users" ("email") VALUES ('betty@icloud.com')
+// INSERT INTO "users" ("email", "manager_id") VALUES ('cathy@icloud.com', 2)
+// COMMIT TRANSACTION
+```
- - A tuple of the above number of deleted rows and statement: `(changes: Int?, Statement)`, for flexibility.
+> _Note:_ Transactions run in a serial queue.
+## Querying the Schema
-## Transactions and Savepoints
+We can obtain generic information about objects in the current schema with a `SchemaReader`:
-Using the `transaction` and `savepoint` functions, we can run a series of statements, commiting the changes to the database if they all succeed. If a single statement fails, we bail out early and roll back.
+```swift
+let schema = db.schema
+```
-``` swift
-db.transaction(
- users.insert(email <- "betty@icloud.com"),
- users.insert(email <- "cathy@icloud.com", manager_id <- db.lastID)
-)
+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)
```
-> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This means we can use the `lastID` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID].
+### Indexes and Columns
+Specialized methods are available to get more detailed information:
-## Altering the Schema
+```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)")
+}
+```
-SQLite.swift comes with several functions (in addition to `create(table:)`) for altering a database schema in a type-safe manner.
+## Altering the Schema
+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 rename a table by calling the `rename(table:to:)` function on a database connection.
+We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename`
+function on a `Table` or `VirtualTable`.
-``` swift
-db.rename(users, to: "users_old")
+```swift
+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
-We can add columns to a table by calling `alter` function on a database connection. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports.
+We can add columns to a table by calling `addColumn` function on a `Table`.
+SQLite.swift enforces
+[the same limited subset](https://www.sqlite.org/lang_altertable.html) of
+`ALTER TABLE` that SQLite supports.
-``` swift
-db.alter(table: users, add: suffix)
+```swift
+try db.run(users.addColumn(suffix))
// ALTER TABLE "users" ADD COLUMN "suffix" TEXT
```
-
#### Added Column Constraints
-The `alter` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table).
+The `addColumn` function shares several of the same [`column` function
+parameters](#column-constraints) used when [creating
+tables](#creating-a-table).
- - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). (See also the `check` function under [Table Constraints](#table-constraints).)
+ - `check` attaches a `CHECK` constraint to a column definition in the form
+ of a boolean expression (`Expression`). (See also the `check`
+ function under [Table Constraints](#table-constraints).)
- ``` swift
- let check = contains(["JR", "SR"], suffix)
- db.alter(table: users, add: suffix, check: check)
- // ALTER TABLE "users"
- // ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR'))
+ ```swift
+ try db.run(users.addColumn(suffix, check: ["JR", "SR"].contains(suffix)))
+ // ALTER TABLE "users" ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR'))
```
- - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows).
+ - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_
+ accepts a value matching the column’s type. This value is used if none is
+ explicitly provided during [an `INSERT`](#inserting-rows).
- ``` swift
- db.alter(table: users, add: suffix, defaultValue: "SR")
+ ```swift
+ try db.run(users.addColumn(suffix, defaultValue: "SR"))
// ALTER TABLE "users" ADD COLUMN "suffix" TEXT DEFAULT 'SR'
```
- > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), default values may not be expression structures (including `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`).
+ > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints),
+ > default values may not be expression structures (including
+ > `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`).
-
+ ```swift
+ try db.run(users.addColumn(email, collate: .nocase))
+ // ALTER TABLE "users" ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE"
- - `references` adds a `REFERENCES` clause to `Int` (and `Int?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.)
+ try db.run(users.addColumn(name, collate: .rtrim))
+ // ALTER TABLE "users" ADD COLUMN "name" TEXT COLLATE "RTRIM"
+ ```
- ``` swift
- db.alter(table: posts, add: user_id, references: users[id])
- // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users"("id")
+ - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column
+ definitions and accepts a table or namespaced column expression. (See the
+ `foreignKey` function under [Table Constraints](#table-constraints) for
+ non-integer foreign key support.)
- db.alter(table: posts, add: user_id, references: users)
- // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users"
- // -- assumes "users" has a PRIMARY KEY
+ ```swift
+ try db.run(posts.addColumn(userId, references: users, id)
+ // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id")
```
+### SchemaChanger
+
+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.add(column: newColumn)
+}
+```
+
+#### Renaming Columns
+
+```swift
+let schemaChanger = SchemaChanger(connection: db)
+try schemaChanger.alter(table: "users") { table in
+ table.rename(column: "old_name", to: "new_name")
+}
+```
+
+#### Dropping Columns
+
+```swift
+let schemaChanger = SchemaChanger(connection: db)
+try schemaChanger.alter(table: "users") { table in
+ table.drop(column: "email")
+}
+```
+
+#### Renaming/Dropping Tables
+
+```swift
+let schemaChanger = SchemaChanger(connection: db)
+
+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
#### Creating Indexes
-We can run [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `create(index:)` function on a database connection.
+We can build
+[`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html)
+by calling the `createIndex` function on a `SchemaType`.
-``` swift
-db.create(index: users, on: email)
+```swift
+try db.run(users.createIndex(email))
// CREATE INDEX "index_users_on_email" ON "users" ("email")
```
-The index name is generated automatically based on the table and column names.
+The index name is generated automatically based on the table and column
+names.
-The `create(index:)` function has a couple default parameters we can override.
+The `createIndex` function has a couple default parameters we can override.
- `unique` adds a `UNIQUE` constraint to the index. Default: `false`.
- ``` swift
- db.create(index: users, on: email, unique: true)
+ ```swift
+ try db.run(users.createIndex(email, unique: true))
// CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email")
```
- - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`.
+ - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE`
+ statement (which will bail out gracefully if the table already exists).
+ Default: `false`.
- ``` swift
- db.create(index: users, on: email, ifNotExists: true)
+ ```swift
+ try db.run(users.createIndex(email, ifNotExists: true))
// CREATE INDEX IF NOT EXISTS "index_users_on_email" ON "users" ("email")
```
#### Dropping Indexes
-We can run [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `drop(index:)` function on a database connection.
+We can build
+[`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by
+calling the `dropIndex` function on a `SchemaType`.
-``` swift
-db.drop(index: users, on: email)
+```swift
+try db.run(users.dropIndex(email))
// DROP INDEX "index_users_on_email"
```
-The `drop(index:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement.
+The `dropIndex` function has one additional parameter, `ifExists`, which
+(when `true`) adds an `IF EXISTS` clause to the statement.
-``` swift
-db.drop(index: users, on: email, ifExists: true)
+```swift
+try db.run(users.dropIndex(email, ifExists: true))
// DROP INDEX IF EXISTS "index_users_on_email"
```
-
-### Dropping Tables
-
-We can run [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `drop(table:)` function on a database connection.
-
-``` swift
-db.drop(table: users)
-// DROP TABLE "users"
-```
-
-The `drop(table:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement.
-
-``` swift
-db.drop(table: users, ifExists: true)
-// DROP TABLE IF EXISTS "users"
-```
-
-
### Migrations and Schema Versioning
-SQLite.swift provides a convenience property on `Database` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_schema_version). This is a great way to manage your schema’s version over migrations.
+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.
+You can conditionally run your migrations along the lines of:
-``` swift
+```swift
if db.userVersion == 0 {
// handle first migration
db.userVersion = 1
@@ -1017,171 +1669,194 @@ if db.userVersion == 1 {
}
```
+For more complex migration requirements check out the schema management
+system [SQLiteMigrationManager.swift][].
## Custom Types
-SQLite.swift supports serializing and deserializing any custom type as long as it conforms to the `Value` protocol.
+SQLite.swift supports serializing and deserializing any custom type as long
+as it conforms to the `Value` protocol.
-> ``` swift
-> protocol Value {
-> typealias Datatype: Binding
-> class var declaredDatatype: String { get }
-> class func fromDatatypeValue(datatypeValue: Datatype) -> Self
-> var datatypeValue: Datatype { get }
-> }
-> ```
-
-The `Datatype` must be one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL](#building-type-safe-sql) for a list of types).
+```swift
+protocol Value {
+ typealias Datatype: Binding
+ class var declaredDatatype: String { get }
+ class func fromDatatypeValue(datatypeValue: Datatype) -> Self
+ var datatypeValue: Datatype { get }
+}
+```
-> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to directly map SQLite types to Swift types. **Do _not_** conform custom types to the `Binding` protocol.
+The `Datatype` must be one of the basic Swift types that values are bridged
+through before serialization and deserialization (see [Building Type-Safe SQL
+](#building-type-safe-sql) for a list of types).
-Once extended, the type can be used [_almost_](#custom-type-caveats) wherever typed expressions can be.
+> ⚠ _Note:_ `Binding` is a protocol that SQLite.swift uses internally to
+> directly map SQLite types to Swift types. **Do _not_** conform custom types
+> to the `Binding` protocol.
### Date-Time Values
-In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can transparently bridge `NSDate` objects through Swift’s `String` or `Int` types.
+In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can
+transparently bridge `Date` objects through Swift’s `String` types.
-To serialize `NSDate` objects as `TEXT` values (in ISO 8601), we’ll use `String`.
+We can use these types directly in SQLite statements.
-``` swift
-extension NSDate: Value {
- class var declaredDatatype: String {
- return String.declaredDatatype
- }
- class func fromDatatypeValue(stringValue: String) -> NSDate {
- return SQLDateFormatter.dateFromString(stringValue)!
- }
- var datatypeValue: String {
- return SQLDateFormatter.stringFromDate(self)
- }
-}
+```swift
+let published_at = Expression("published_at")
-let SQLDateFormatter: NSDateFormatter = {
- let formatter = NSDateFormatter()
- formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
- formatter.timeZone = NSTimeZone(abbreviation: "UTC")
- return formatter
-}()
+let published = posts.filter(published_at <= Date())
+// SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18T12:45:30.000'
+
+let startDate = Date(timeIntervalSince1970: 0)
+let published = posts.filter(startDate...Date() ~= published_at)
+// SELECT * FROM "posts" WHERE "published_at" BETWEEN '1970-01-01T00:00:00.000' AND '2014-11-18T12:45:30.000'
```
-We can also treat them as `INTEGER` values using `Int`.
-``` swift
-extension NSDate: Value {
- class var declaredDatatype: String {
- return Int.declaredDatatype
+### Binary Data
+
+We can bridge any type that can be initialized from and encoded to `Data`.
+
+```swift
+extension UIImage: Value {
+ public class var declaredDatatype: String {
+ return Blob.declaredDatatype
}
- class func fromDatatypeValue(intValue: Int) -> Self {
- return self(timeIntervalSince1970: NSTimeInterval(intValue))
+ public class func fromDatatypeValue(blobValue: Blob) -> UIImage {
+ return UIImage(data: Data.fromDatatypeValue(blobValue))!
}
- var datatypeValue: Int {
- return Int(timeIntervalSince1970)
+ public var datatypeValue: Blob {
+ return UIImagePNGRepresentation(self)!.datatypeValue
}
+
}
```
-> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` helpers return `TEXT` values. Because of this (and the fact that Unix time is far less human-readable when we’re faced with the raw data), we recommend using the `TEXT` extension.
+> _Note:_ See the [Archives and Serializations Programming Guide][] for more
+> information on encoding and decoding custom types.
-Once defined, we can use these types directly in SQLite statements.
-``` swift
-let published_at = Expression("published_at")
+[Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html
-let published = posts.filter(published_at <= Date())
-// extension where Datatype == String:
-// SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18 12:45:30'
-// extension where Datatype == Int:
-// SELECT * FROM "posts" WHERE "published_at" <= 1416314730
-```
+## Codable Types
+[Codable types][Encoding and Decoding Custom Types] were introduced as a part
+of Swift 4 to allow serializing and deserializing types. SQLite.swift supports
+the insertion, updating, and retrieval of basic Codable types.
-### Binary Data
+[Encoding and Decoding Custom Types]: https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
-Any object that can be encoded and decoded can be stored as a blob of data in SQL.
+### Inserting Codable Types
-We can create an `NSData` bridge rather trivially.
+Queries have a method to allow inserting an [Encodable][] type.
-``` swift
-extension NSData: Value {
- class var declaredDatatype: String {
- return Blob.declaredDatatype
- }
- class func fromDatatypeValue(blobValue: Blob) -> Self {
- return self(bytes: blobValue.bytes, blob: blobValue.length)
- }
- var datatypeValue: Blob {
- return Blob(bytes: bytes, length: length)
- }
+```swift
+struct User: Encodable {
+ let name: String
}
+try db.run(users.insert(User(name: "test")))
+
```
-We can bridge any type that can be initialized from and encoded to `NSData`.
+There are two other parameters also available to this method:
-``` swift
-// assumes NSData conformance, above
-extension UIImage: Value {
- class var declaredDatatype: String {
- return NSData.declaredDatatype
- }
- class func fromDatatypeValue(blobValue: Blob) -> Self {
- return self(data: NSData.fromDatatypeValue(blobValue))
- }
- var datatypeValue: Blob {
- return UIImagePNGRepresentation(self).datatypeValue
- }
-}
-```
+- `userInfo` is a dictionary that is passed to the encoder and made available
+ to encodable types to allow customizing their behavior.
-> _Note:_ See the [Archives and Serializations Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i) for more information on encoding and decoding custom types.
+- `otherSetters` allows you to specify additional setters on top of those
+ that are generated from the encodable types themselves.
+[Encodable]: https://developer.apple.com/documentation/swift/encodable
-### Custom Type Caveats
+### Updating Codable Types
-Swift does _not_ currently support generic subscripting, which means we cannot, by default, subscript Expressions with custom types to:
+Queries have a method to allow updating an Encodable type.
- 1. **Namespace expressions**. Use the `namespace` function, instead:
+```swift
+try db.run(users.filter(id == userId).update(user))
- ``` swift
- let avatar = Expression("avatar")
- users[avatar] // fails to compile
- users.namespace(avatar) // "users"."avatar"
- ```
+```
- 2. **Access column data**. Use the `get` function, instead:
+> ⚠ Unless filtered, using the update method on an instance of a Codable
+> type updates all table rows.
- ``` swift
- let user = users.first!
- user[avatar] // fails to compile
- user.get(avatar) // UIImage?
- ```
+There are two other parameters also available to this method:
-We can, of course, write extensions, but they’re rather wordy.
+- `userInfo` is a dictionary that is passed to the encoder and made available
+ to encodable types to allow customizing their behavior.
-``` swift
-extension Query {
- subscript(column: Expression) -> Expression {
- return namespace(column)
- }
- subscript(column: Expression) -> Expression {
- return namespace(column)
- }
+- `otherSetters` allows you to specify additional setters on top of those
+ that are generated from the encodable types themselves.
+
+### Retrieving Codable Types
+
+Rows have a method to decode a [Decodable][] type.
+
+```swift
+let loadedUsers: [User] = try db.prepare(users).map { row in
+ return try row.decode()
+}
+```
+
+You can also create a decoder to use manually yourself. This can be useful
+for example if you are using the
+[Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide
+subclasses behind a super class. For example, you may want to encode an Image
+type that can be multiple different formats such as PNGImage, JPGImage, or
+HEIFImage. You will need to determine the correct subclass before you know
+which type to decode.
+
+```swift
+enum ImageCodingKeys: String, CodingKey {
+ case kind
}
-extension Row {
- subscript(column: Expression) -> UIImage {
- return get(column)
- }
- subscript(column: Expression) -> UIImage? {
- return get(column)
+enum ImageKind: Int, Codable {
+ case png, jpg, heif
+}
+
+let loadedImages: [Image] = try db.prepare(images).map { row in
+ let decoder = row.decoder()
+ let container = try decoder.container(keyedBy: ImageCodingKeys.self)
+ switch try container.decode(ImageKind.self, forKey: .kind) {
+ case .png:
+ return try PNGImage(from: decoder)
+ case .jpg:
+ return try JPGImage(from: decoder)
+ case .heif:
+ return try HEIFImage(from: decoder)
}
}
```
+Both of the above methods also have the following optional parameter:
+
+- `userInfo` is a dictionary that is passed to the decoder and made available
+ to decodable types to allow customizing their behavior.
+
+[Decodable]: https://developer.apple.com/documentation/swift/decodable
+
+### Restrictions
+
+There are a few restrictions on using Codable types:
+
+- The encodable and decodable objects can only use the following types:
+ - Int, Bool, Float, Double, String, 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.
+- The Codable types may not try to access nested containers or nested unkeyed
+ containers
+- The Codable types may not access single value containers or unkeyed
+ containers
+- The Codable types may not access super decoders or encoders
## Other Operators
-In addition to [filter operators](#filtering-infix-operators), SQLite.swift defines a number of operators that can modify expression values with arithmetic, bitwise operations, and concatenation.
+In addition to [filter operators](#filtering-infix-operators), SQLite.swift
+defines a number of operators that can modify expression values with
+arithmetic, bitwise operations, and concatenation.
###### Other Infix Operators
@@ -1196,10 +1871,11 @@ In addition to [filter operators](#filtering-infix-operators), SQLite.swift defi
| `<<` | `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)`.
+> _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which
+> expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`.
###### Other Prefix Operators
@@ -1212,97 +1888,370 @@ In addition to [filter operators](#filtering-infix-operators), SQLite.swift defi
## Core SQLite Functions
-Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) have been surfaced in and type-audited for SQLite.swift.
+Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html)
+have been surfaced in and type-audited for SQLite.swift.
> _Note:_ SQLite.swift aliases the `??` operator to the `ifnull` function.
>
-> ``` swift
+> ```swift
> name ?? email // ifnull("name", "email")
> ```
## Aggregate SQLite Functions
-Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift.
+Most of SQLite’s
+[aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been
+surfaced in and type-audited for SQLite.swift.
+
+## 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)
+functions are available:
+
+```swift
+DateFunctions.date("now")
+// date('now')
+Date().date
+// date('2007-01-09T09:41:00.000')
+Expression("date").date
+// date("date")
+```
+
+## Custom SQL Functions
+
+We can create custom SQL functions by calling `createFunction` on a database
+connection.
+
+For example, to give queries access to
+[`MobileCoreServices.UTTypeConformsTo`][UTTypeConformsTo], we can
+write the following:
+
+```swift
+import MobileCoreServices
+
+let typeConformsTo: (Expression, Expression) -> Expression = (
+ try db.createFunction("typeConformsTo", deterministic: true) { UTI, conformsToUTI in
+ return UTTypeConformsTo(UTI, conformsToUTI)
+ }
+)
+```
+
+> _Note:_ The optional `deterministic` parameter is an optimization that
+> causes the function to be created with
+> [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/c_deterministic.html).
+
+Note `typeConformsTo`’s signature:
+
+```swift
+(Expression, Expression) -> Expression
+```
+
+Because of this, `createFunction` expects a block with the following
+signature:
+
+```swift
+(String, String) -> Bool
+```
+
+Once assigned, the closure can be called wherever boolean expressions are
+accepted.
+
+```swift
+let attachments = Table("attachments")
+let UTI = Expression("UTI")
+
+let images = attachments.filter(typeConformsTo(UTI, kUTTypeImage))
+// SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image')
+```
+
+> _Note:_ The return type of a function must be
+> [a core SQL type](#building-type-safe-sql) or [conform to `Value`](#custom-types).
+
+We can create loosely-typed functions by handling an array of raw arguments,
+instead.
+
+```swift
+db.createFunction("typeConformsTo", deterministic: true) { args in
+ guard let UTI = args[0] as? String, conformsToUTI = args[1] as? String else { return nil }
+ return UTTypeConformsTo(UTI, conformsToUTI)
+}
+```
+
+Creating a loosely-typed function cannot return a closure and instead must be
+wrapped manually or executed [using raw SQL](#executing-arbitrary-sql).
+
+```swift
+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](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) { /* ... */ }
+> someObj.statement.reset()
+> ```
+
+[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
+database connection.
+
+```swift
+try db.createCollation("NODIACRITIC") { lhs, rhs in
+ return lhs.compare(rhs, options: .diacriticInsensitiveSearch)
+}
+```
+
+We can reference a custom collation using the `Custom` member of the
+`Collation` enumeration.
+
+```swift
+restaurants.order(collate(.custom("NODIACRITIC"), name))
+// SELECT * FROM "restaurants" ORDER BY "name" COLLATE "NODIACRITIC"
+```
+
+
+## Full-text Search
+
+We can create a virtual table using the [FTS4
+module](http://www.sqlite.org/fts3.html) by calling `create` on a
+`VirtualTable`.
+
+```swift
+let emails = VirtualTable("emails")
+let subject = Expression("subject")
+let body = Expression("body")
+
+try db.run(emails.create(.FTS4(subject, body)))
+// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body")
+```
+
+We can specify a [tokenizer](http://www.sqlite.org/fts3.html#tokenizer) using the `tokenize` parameter.
+
+```swift
+try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter)))
+// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter)
+```
+
+We can set the full range of parameters by creating a `FTS4Config` object.
+
+```swift
+let emails = VirtualTable("emails")
+let subject = Expression("subject")
+let body = Expression("body")
+let config = FTS4Config()
+ .column(subject)
+ .column(body, [.unindexed])
+ .languageId("lid")
+ .order(.desc)
+
+try db.run(emails.create(.FTS4(config))
+// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", notindexed="body", languageid="lid", order="desc")
+```
+
+Once we insert a few rows, we can search using the `match` function, which
+takes a table or column as its first argument and a query string as its
+second.
+
+```swift
+try db.run(emails.insert(
+ subject <- "Just Checking In",
+ body <- "Hey, I was just wondering...did you get my last email?"
+))
+
+let wonderfulEmails: QueryType = emails.match("wonder*")
+// SELECT * FROM "emails" WHERE "emails" MATCH 'wonder*'
+
+let replies = emails.filter(subject.match("Re:*"))
+// SELECT * FROM "emails" WHERE "subject" MATCH 'Re:*'
+```
+
+### FTS5
+
+When linking against a version of SQLite with
+[FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual
+table in a similar fashion.
+
+```swift
+let emails = VirtualTable("emails")
+let subject = Expression("subject")
+let body = Expression("body")
+let config = FTS5Config()
+ .column(subject)
+ .column(body, [.unindexed])
+
+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:\"*"))
+// SELECT * FROM "emails" WHERE "emails" MATCH 'subject:"Re:"*'
+```
## Executing Arbitrary SQL
-Though we recommend you stick with SQLite.swift’s type-safe system whenever possible, it is possible to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions.
+Though we recommend you stick with SQLite.swift’s
+[type-safe system](#building-type-safe-sql) whenever possible, it is possible
+to simply and safely prepare and execute raw SQL statements via a `Database` connection
+using the following functions.
- `execute` runs an arbitrary number of SQL statements as a convenience.
- ``` swift
- db.execute(
- "BEGIN TRANSACTION;" +
- "CREATE TABLE users (" +
- "id INTEGER PRIMARY KEY NOT NULL," +
- "email TEXT UNIQUE NOT NULL," +
- "name TEXT" +
- ");" +
- "CREATE TABLE posts (" +
- "id INTEGER PRIMARY KEY NOT NULL," +
- "title TEXT NOT NULL," +
- "body TEXT NOT NULL," +
- "published_at DATETIME" +
- ");" +
- "PRAGMA user_version = 1;" +
- "COMMIT TRANSACTION;"
+ ```swift
+ try db.execute("""
+ BEGIN TRANSACTION;
+ CREATE TABLE users (
+ id INTEGER PRIMARY KEY NOT NULL,
+ email TEXT UNIQUE NOT NULL,
+ name TEXT
+ );
+ CREATE TABLE posts (
+ id INTEGER PRIMARY KEY NOT NULL,
+ title TEXT NOT NULL,
+ body TEXT NOT NULL,
+ published_at DATETIME
+ );
+ PRAGMA user_version = 1;
+ COMMIT TRANSACTION;
+ """
)
```
- - `prepare` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), and returns the statement for deferred execution.
+ - `prepare` prepares a single `Statement` object from a SQL string,
+ optionally binds values to it (using the statement’s `bind` function),
+ and returns the statement for deferred execution.
- ``` swift
- let stmt = db.prepare("INSERT INTO users (email) VALUES (?)")
+ ```swift
+ let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)")
```
- Once prepared, statements may be executed using `run`, binding any unbound parameters.
+ Once prepared, statements may be executed using `run`, binding any
+ unbound parameters.
- ``` swift
- stmt.run("alice@mac.com")
- db.lastChanges // -> {Some 1}
+ ```swift
+ try stmt.run("alice@mac.com")
+ db.changes // -> {Some 1}
```
- Statements with results may be iterated over.
+ Statements with results may be iterated over, using the columnNames if
+ useful.
- ``` swift
- let stmt = db.prepare("SELECT id, email FROM users")
+ ```swift
+ let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
- println("id: \(row[0]), email: \(row[1])")
- // id: Optional(1), email: Optional("alice@mac.com")
+ for (index, name) in stmt.columnNames.enumerated() {
+ print ("\(name):\(row[index]!)")
+ // id: Optional(1), email: Optional("alice@mac.com")
+ }
}
```
- - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement.
+ - `run` prepares a single `Statement` object from a SQL string, optionally
+ binds values to it (using the statement’s `bind` function), executes,
+ and returns the statement.
- ``` swift
- db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com")
+ ```swift
+ try db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com")
```
- - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row.
+ - `scalar` prepares a single `Statement` object from a SQL string,
+ optionally binds values to it (using the statement’s `bind` function),
+ executes, and returns the first value of the first row.
- ``` swift
- db.scalar("SELECT count(*) FROM users") as Int
+ ```swift
+ let count = try db.scalar("SELECT count(*) FROM users") as! Int64
```
- Statements also have a `scalar` function, which can optionally re-bind values at execution.
+ Statements also have a `scalar` function, which can optionally re-bind
+ values at execution.
- ``` swift
- let stmt = db.prepare("SELECT count (*) FROM users")
- stmt.scalar() as Int
+ ```swift
+ let stmt = try db.prepare("SELECT count (*) FROM users")
+ 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()
+```
+
+## 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'
+```
+
+When compiled for SQLCipher, we 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.
-``` swift
+```swift
#if DEBUG
- db.trace(println)
+ db.trace { print($0) }
#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/Linux.md b/Documentation/Linux.md
new file mode 100644
index 00000000..0c88ff5c
--- /dev/null
+++ b/Documentation/Linux.md
@@ -0,0 +1,29 @@
+# Linux
+
+## Limitations
+
+* 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).
+
+## 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/Documentation/Planning.md b/Documentation/Planning.md
new file mode 100644
index 00000000..cdfca9c4
--- /dev/null
+++ b/Documentation/Planning.md
@@ -0,0 +1,33 @@
+# SQLite.swift Planning
+
+This document captures both near term steps (aka Roadmap) and feature
+requests. The goal is to add some visibility and guidance for future
+additions and Pull Requests, as well as to keep the Issues list clear of
+enhancement requests so that bugs are more visible.
+
+> ⚠ This document is currently not actively maintained. See
+> 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
+
+_Lists agreed upon next steps in approximate priority order._
+
+## Feature Requests
+
+_A gathering point for ideas for new features. In general, the corresponding
+issue will be closed once it is added here, with the assumption that it will
+be referred to when it comes time to add the corresponding feature._
+
+### Features
+
+ * provide separate threads for update vs read, so updates don't block reads,
+ per [#236](https://github.com/stephencelis/SQLite.swift/issues/236)
+ * expose triggers, per
+ [#164](https://github.com/stephencelis/SQLite.swift/issues/164)
+
+## Suspended Feature Requests
+
+_Features that are not actively being considered, perhaps because of no clean
+type-safe way to implement them with the current Swift, or bugs, or just
+general uncertainty._
diff --git a/Documentation/Release.md b/Documentation/Release.md
new file mode 100644
index 00000000..8ec67970
--- /dev/null
+++ b/Documentation/Release.md
@@ -0,0 +1,13 @@
+# 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
+* [ ] 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`
+* [ ] 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/Documentation/Resources/installation@2x.png b/Documentation/Resources/installation@2x.png
index 6bf073ea..6b31f459 100644
Binary files a/Documentation/Resources/installation@2x.png and b/Documentation/Resources/installation@2x.png differ
diff --git a/Documentation/Resources/playground@2x.png b/Documentation/Resources/playground@2x.png
index b1c62e23..da132718 100644
Binary files a/Documentation/Resources/playground@2x.png and b/Documentation/Resources/playground@2x.png differ
diff --git a/Documentation/Upgrading.md b/Documentation/Upgrading.md
new file mode 100644
index 00000000..0e12aacf
--- /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 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
new file mode 100644
index 00000000..2770e85d
--- /dev/null
+++ b/Gemfile
@@ -0,0 +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
new file mode 100644
index 00000000..7dac2c18
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,122 @@
+GIT
+ remote: https://github.com/jberkel/CocoaPods.git
+ revision: 32a90c184bc5dc9ec8b7b9b8ad08e98b7253dec2
+ 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:
+ CFPropertyList (3.0.7)
+ base64
+ nkf
+ rexml
+ activesupport (7.2.2.1)
+ base64
+ benchmark (>= 0.3)
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.3.1)
+ connection_pool (>= 2.2.5)
+ drb
+ i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
+ minitest (>= 5.1)
+ 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)
+ benchmark (0.4.0)
+ bigdecimal (3.1.9)
+ claide (1.1.0)
+ cocoapods-core (1.16.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.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.17.2)
+ ffi (1.17.2-arm64-darwin)
+ fourflusher (2.3.1)
+ fuzzy_match (2.0.4)
+ gh_inspector (1.1.3)
+ httpclient (2.9.0)
+ mutex_m
+ i18n (1.14.7)
+ concurrent-ruby (~> 1.0)
+ json (2.12.0)
+ logger (1.7.0)
+ minitest (5.25.5)
+ molinillo (0.8.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.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.27.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.4.0)
+ rexml (>= 3.3.6, < 4.0)
+
+PLATFORMS
+ arm64-darwin-23
+ ruby
+
+DEPENDENCIES
+ cocoapods!
+
+BUNDLED WITH
+ 2.6.9
diff --git a/LICENSE.txt b/LICENSE.txt
index 51aec40c..13303c11 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
(The MIT License)
-Copyright (c) 2014 Stephen Celis ()
+Copyright (c) 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
diff --git a/Makefile b/Makefile
index 8863c45c..52a25a12 100644
--- a/Makefile
+++ b/Makefile
@@ -1,28 +1,76 @@
-BUILD_TOOL = xcodebuild
-BUILD_PLATFORM ?= Mac
-BUILD_ARGUMENTS = -scheme 'SQLite $(BUILD_PLATFORM)'
+XCODEBUILD = xcodebuild
+BUILD_SCHEME = SQLite Mac
+IOS_SIMULATOR = iPhone 14
+IOS_VERSION = 16.4
-XCPRETTY := $(shell command -v xcpretty)
-
-default: test
-
-build:
- $(BUILD_TOOL) $(BUILD_ARGUMENTS)
+# 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
-test:
-ifdef XCPRETTY
- @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) test | $(XCPRETTY) -c
+ifeq ($(BUILD_SCHEME),SQLite iOS)
+ BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)"
else
- $(BUILD_TOOL) $(BUILD_ARGUMENTS) test
+ BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)"
endif
+test: $(XCBEAUTIFY)
+ set -o pipefail; \
+ $(XCODEBUILD) $(BUILD_ARGUMENTS) test | $(XCBEAUTIFY)
+
+build: $(XCBEAUTIFY)
+ set -o pipefail; \
+ $(XCODEBUILD) $(BUILD_ARGUMENTS) | $(XCBEAUTIFY)
+
+lint: $(SWIFTLINT)
+ $< --strict
+
+lint-fix: $(SWIFTLINT)
+ $< --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 '^ *//|^$$' SQLite\ Common/*.{swift,h,c} | 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/Package.swift b/Package.swift
new file mode 100644
index 00000000..56925d18
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,60 @@
+// 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(.v12),
+ .macOS(.v10_13),
+ .watchOS(.v4),
+ .tvOS(.v12),
+ .visionOS(.v1)
+ ],
+ products: [
+ .library(
+ name: "SQLite",
+ targets: ["SQLite"]
+ )
+ ],
+ dependencies: deps,
+ targets: targets + testTargets
+)
+
+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 114b937a..a52b7ae5 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,11 @@
-# SQLite.swift [![Build Status][0.1]][0.2]
+# SQLite.swift
-A type-safe, [Swift][1.1]-language layer over [SQLite3][1.2].
+![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]
-[SQLite.swift][1.3] provides compile-time confidence in SQL statement
-syntax _and_ intent.
-
-[0.1]: https://img.shields.io/travis/stephencelis/SQLite.swift.svg?style=flat
-[0.2]: https://travis-ci.org/stephencelis/SQLite.swift
-[1.1]: https://developer.apple.com/swift/
-[1.2]: http://www.sqlite.org
-[1.3]: https://github.com/stephencelis/SQLite.swift
+A type-safe, [Swift][]-language layer over [SQLite3][].
+[SQLite.swift][] provides compile-time confidence in SQL statement
+syntax _and_ intent.
## Features
@@ -19,126 +14,235 @@ syntax _and_ intent.
- A flexible, chainable, lazy-executing query layer
- Automatically-typed data access
- A lightweight, uncomplicated query and parameter binding interface
- - Transactions with implicit commit/rollback
- Developer-friendly error handling and debugging
+ - [Full-text search][] support
- [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),
+ and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift)
+ (_experimental_)
+
+[SQLCipher]: https://www.zetetic.net/sqlcipher/
+[Full-text search]: Documentation/Index.md#full-text-search
+[Schema query/migration]: Documentation/Index.md#querying-the-schema
[See Documentation]: Documentation/Index.md#sqliteswift-documentation
## Usage
-``` swift
+```swift
import SQLite
-let db = Database("path/to/db.sqlite3")
+// Wrap everything in a do...catch to handle errors
+do {
+ let db = try Connection("path/to/db.sqlite3")
+
+ let users = Table("users")
+ 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)
+ 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)
+}
+```
-let users = db["users"]
-let id = Expression("id")
-let name = Expression("name")
-let email = Expression("email")
+Note that `Expression` should be written as `SQLite.Expression` to avoid
+conflicts with the `SwiftUI.Expression` if you are using SwiftUI too.
-db.create(table: users) { 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
-// )
-
-var alice: Query?
-if let insertedID = users.insert(name <- "Alice", email <- "alice@mac.com") {
- println("inserted id: \(insertedID)")
- // inserted id: 1
- alice = users.filter(id == insertedID)
-}
-// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com')
+SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C
+API.
-for user in users {
- println("id: \(user[id]), name: \(user[name]), email: \(user[email])")
- // id: 1, name: Optional("Alice"), email: alice@mac.com
+```swift
+// 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)
}
-// SELECT * FROM "users"
+```
+
+[Read the documentation][See Documentation] or explore more,
+interactively, from the Xcode project’s playground.
-alice?.update(email <- replace(email, "mac.com", "me.com"))?
-// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com')
-// WHERE ("id" = 1)
+
-alice?.delete()?
-// DELETE FROM "users" WHERE ("id" = 1)
+## Installation
-users.count
-// SELECT count(*) FROM "users"
-```
+### Swift Package Manager
-SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C
-API.
+The [Swift Package Manager][] is a tool for managing the distribution of
+Swift code.
-``` swift
-let stmt = db.prepare("INSERT INTO users (email) VALUES (?)")
-for email in ["betty@icloud.com", "cathy@icloud.com"] {
- stmt.run(email)
-}
+1. Add the following to your `Package.swift` file:
-db.totalChanges // 3
-db.lastChanges // {Some 1}
-db.lastID // {Some 3}
+ ```swift
+ dependencies: [
+ .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4")
+ ]
+ ```
-for row in db.prepare("SELECT id, email FROM users") {
- println("id: \(row[0]), email: \(row[1])")
- // id: Optional(2), email: Optional("betty@icloud.com")
- // id: Optional(3), email: Optional("cathy@icloud.com")
-}
+2. Build your project:
-db.scalar("SELECT count(*) FROM users") // {Some 2}
-```
+ ```sh
+ $ swift build
+ ```
-[Read the documentation][See Documentation] or explore more,
-interactively, from the Xcode project’s playground.
+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
-## Installation
+[Carthage][] is a simple, decentralized dependency manager for Cocoa. To
+install SQLite.swift with Carthage:
+
+ 1. Make sure Carthage is [installed][Carthage Installation].
+
+ 2. Update your Cartfile to include the following:
-> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode
-> 6.1](https://developer.apple.com/xcode/downloads/)) or greater.
+ ```ruby
+ github "stephencelis/SQLite.swift" ~> 0.15.4
+ ```
-To install SQLite.swift:
+ 3. Run `carthage update` and
+ [add the appropriate framework][Carthage Usage].
+
+
+[Carthage]: https://github.com/Carthage/Carthage
+[Carthage Installation]: https://github.com/Carthage/Carthage#installing-carthage
+[Carthage Usage]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application
+
+
+### CocoaPods
+
+[CocoaPods][] is a dependency manager for Cocoa projects. To install
+SQLite.swift with CocoaPods:
+
+ 1. Make sure CocoaPods is [installed][CocoaPods Installation].
+
+ ```sh
+ # Using the default Ruby install will require you to use sudo when
+ # installing and updating gems.
+ [sudo] gem install cocoapods
+ ```
+
+ 2. Update your Podfile to include the following:
+
+ ```ruby
+ use_frameworks!
+
+ target 'YourAppTargetName' do
+ pod 'SQLite.swift', '~> 0.15.0'
+ end
+ ```
+
+ 3. Run `pod install --repo-update`.
+
+[CocoaPods]: https://cocoapods.org
+[CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started
+
+### Manual
+
+To install SQLite.swift as an Xcode sub-project:
1. Drag the **SQLite.xcodeproj** file into your own project.
- ([Submodule][4.2], clone, or [download][4.3] the project first.)
+ ([Submodule][], clone, or [download][] the project first.)
- 
+ 
- 2. In your target’s **Build Phases**, add **SQLite iOS** (or **SQLite Mac**)
- to the **Target Dependencies** build phase.
+ 2. In your target’s **General** tab, click the **+** button under **Linked
+ Frameworks and Libraries**.
- 3. Add the appropriate **SQLite.framework** product to the
- **Link Binary With Libraries** build phase.
+ 3. Select the appropriate **SQLite.framework** for your platform.
- 4. Add the same **SQLite.framework** to a **Copy Files** build phase with a
- **Frameworks** destination. (Add a new build phase if need be.)
+ 4. **Add**.
-[4.1]: https://developer.apple.com/xcode/downloads/
-[4.2]: http://git-scm.com/book/en/Git-Tools-Submodules
-[4.3]: https://github.com/stephencelis/SQLite.swift/archive/master.zip
+Some additional steps are required to install the application on an actual
+device:
+
+ 5. In the **General** tab, click the **+** button under **Embedded
+ Binaries**.
+
+ 6. Select the appropriate **SQLite.framework** for your platform.
+
+ 7. **Add**.
+
+
+[Xcode]: https://developer.apple.com/xcode/downloads/
+[Submodule]: https://git-scm.com/book/en/Git-Tools-Submodules
+[download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip
## Communication
- - Found a **bug** or have a **feature request**? [Open an issue][5.1].
- - Want to **contribute**? [Submit a pull request][5.2].
+[Read the contributing guidelines][]. The _TL;DR_ (but please; _R_):
+
+ - Need **help** or have a **general question**? [Ask on Stack
+ Overflow][] (tag `sqlite.swift`).
+ - Found a **bug** or have a **feature request**? [Open an issue][].
+ - Want to **contribute**? [Submit a pull request][].
-[5.1]: https://github.com/stephencelis/SQLite.swift/issues/new
-[5.2]: https://github.com/stephencelis/SQLite.swift/fork
+[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
+[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))
@@ -146,21 +250,44 @@ To install SQLite.swift:
## License
-SQLite.swift is available under the MIT license. See [the LICENSE file][7.1]
-for more information.
+SQLite.swift is available under the MIT license. See [the LICENSE
+file](./LICENSE.txt) for more information.
+
+## Related
-[7.1]: ./LICENSE.txt
+These projects enhance or use SQLite.swift:
+ - [SQLiteMigrationManager.swift][] (inspired by
+ [FMDBMigrationManager][])
## Alternatives
-Looking for something else? Try another Swift wrapper (or [FMDB][8.1]):
+Looking for something else? Try another Swift wrapper (or [FMDB][]):
- - [Camembert](https://github.com/remirobert/Camembert)
- - [EonilSQLite3](https://github.com/Eonil/SQLite3)
+ - [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)
- - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite)
-[8.1]: https://github.com/ccgus/fmdb
+[Swift]: https://swift.org/
+[SQLite3]: https://www.sqlite.org
+[SQLite.swift]: https://github.com/stephencelis/SQLite.swift
+
+[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
+
+[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
+[CarthageLink]: https://github.com/Carthage/Carthage
+
+[GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg
+[GitterLink]: https://gitter.im/stephencelis/SQLite.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
+[FMDBMigrationManager]: https://github.com/layerhq/FMDBMigrationManager
diff --git a/SQLite Common Tests/DatabaseTests.swift b/SQLite Common Tests/DatabaseTests.swift
deleted file mode 100644
index 4683f8dc..00000000
--- a/SQLite Common Tests/DatabaseTests.swift
+++ /dev/null
@@ -1,196 +0,0 @@
-import XCTest
-import SQLite
-
-class DatabaseTests: XCTestCase {
-
- let db = Database()
-
- override func setUp() {
- super.setUp()
-
- CreateUsersTable(db)
- }
-
- func test_readonly_returnsFalseOnReadWriteConnections() {
- XCTAssert(!db.readonly)
- }
-
- func test_readonly_returnsTrueOnReadOnlyConnections() {
- let db = Database(readonly: true)
- XCTAssert(db.readonly)
- }
-
- func test_lastID_returnsNilOnNewConnections() {
- XCTAssert(db.lastID == nil)
- }
-
- func test_lastID_returnsLastIDAfterInserts() {
- InsertUser(db, "alice")
- XCTAssert(db.lastID! == 1)
- }
-
- func test_lastChanges_returnsZeroOnNewConnections() {
- XCTAssertEqual(0, db.lastChanges)
- }
-
- func test_lastChanges_returnsNumberOfChanges() {
- InsertUser(db, "alice")
- XCTAssertEqual(1, db.lastChanges)
- InsertUser(db, "betsy")
- XCTAssertEqual(1, db.lastChanges)
- }
-
- func test_totalChanges_returnsTotalNumberOfChanges() {
- XCTAssertEqual(0, db.totalChanges)
- InsertUser(db, "alice")
- XCTAssertEqual(1, db.totalChanges)
- InsertUser(db, "betsy")
- XCTAssertEqual(2, db.totalChanges)
- }
-
- func test_prepare_preparesAndReturnsStatements() {
- db.prepare("SELECT * FROM users WHERE admin = 0")
- db.prepare("SELECT * FROM users WHERE admin = ?", false)
- db.prepare("SELECT * FROM users WHERE admin = ?", [false])
- db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": false])
- // no-op assert-nothing-asserted
- }
-
- func test_run_preparesRunsAndReturnsStatements() {
- ExpectExecutions(db, ["SELECT * FROM users WHERE admin = 0": 4]) { db in
- db.run("SELECT * FROM users WHERE admin = 0")
- db.run("SELECT * FROM users WHERE admin = ?", false)
- db.run("SELECT * FROM users WHERE admin = ?", [false])
- db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": false])
- }
- }
-
- func test_scalar_preparesRunsAndReturnsScalarValues() {
- XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as Int)
- XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", false) as Int)
- XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [false]) as Int)
- XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": false]) as Int)
- }
-
- func test_transaction_beginsAndCommitsStatements() {
- let fulfilled = [
- "BEGIN DEFERRED TRANSACTION": 1,
- "COMMIT TRANSACTION": 1,
- "ROLLBACK TRANSACTION": 0
- ]
- ExpectExecutions(db, fulfilled) { db in
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- db.transaction(stmt.bind("alice@example.com", true))
- }
- }
-
- func test_transaction_executesBeginDeferred() {
- ExpectExecutions(db, ["BEGIN DEFERRED TRANSACTION": 1]) { db in
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- db.transaction(.Deferred, stmt.bind("alice@example.com", true))
- }
- }
-
- func test_transaction_executesBeginImmediate() {
- ExpectExecutions(db, ["BEGIN IMMEDIATE TRANSACTION": 1]) { db in
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- db.transaction(.Immediate, stmt.bind("alice@example.com", true))
- }
- }
-
- func test_transaction_executesBeginExclusive() {
- ExpectExecutions(db, ["BEGIN EXCLUSIVE TRANSACTION": 1]) { db in
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- db.transaction(.Exclusive, stmt.bind("alice@example.com", true))
- }
- }
-
- func test_transaction_rollsBackOnFailure() {
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- db.transaction(stmt.bind("alice@example.com", true))
- let fulfilled = [
- "COMMIT TRANSACTION": 0,
- "ROLLBACK TRANSACTION": 1,
- "INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)": 1
- ]
- var txn: Statement!
- ExpectExecutions(db, fulfilled) { db in
- txn = db.transaction(
- stmt.bind("alice@example.com", true),
- stmt.bind("alice@example.com", true)
- )
- return
- }
- XCTAssertTrue(txn.failed)
- XCTAssert(txn.reason!.lowercaseString.rangeOfString("unique") != nil)
- }
-
- func test_savepoint_nestsAndNamesSavepointsAutomatically() {
- let fulfilled = [
- "SAVEPOINT '1'": 1,
- "SAVEPOINT '2'": 2,
- "RELEASE SAVEPOINT '2'": 2,
- "RELEASE SAVEPOINT '1'": 1,
- ]
- ExpectExecutions(db, fulfilled) { db in
- db.savepoint(
- db.savepoint(
- InsertUser(db, "alice"),
- InsertUser(db, "betsy"),
- InsertUser(db, "cindy")
- ),
- db.savepoint(
- InsertUser(db, "donna"),
- InsertUser(db, "emery"),
- InsertUser(db, "flint")
- )
- )
- return
- }
- }
-
- func test_savepoint_rollsBackOnFailure() {
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- let fulfilled = [
- "SAVEPOINT '1'": 1,
- "SAVEPOINT '2'": 1,
- "RELEASE SAVEPOINT '2'": 0,
- "RELEASE SAVEPOINT '1'": 0,
- "ROLLBACK TO SAVEPOINT '1'": 1,
- "INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)": 2
- ]
- ExpectExecutions(db, fulfilled) { db in
- db.savepoint(
- db.savepoint(
- stmt.run("alice@example.com", true),
- stmt.run("alice@example.com", true),
- stmt.run("alice@example.com", true)
- ),
- db.savepoint(
- stmt.run("alice@example.com", true),
- stmt.run("alice@example.com", true),
- stmt.run("alice@example.com", true)
- )
- )
- return
- }
- }
-
- func test_savepoint_quotesNames() {
- let fulfilled = [
- "SAVEPOINT 'That''s all, Folks!'": 1,
- "RELEASE SAVEPOINT 'That''s all, Folks!'": 1
- ]
- ExpectExecutions(db, fulfilled) { db in
- db.savepoint("That's all, Folks!", db.run("SELECT 1"))
- return
- }
- }
-
- func test_userVersion_getsAndSetsUserVersion() {
- XCTAssertEqual(0, db.userVersion)
- db.userVersion = 1
- XCTAssertEqual(1, db.userVersion)
- }
-
-}
diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift
deleted file mode 100644
index 039599ae..00000000
--- a/SQLite Common Tests/ExpressionTests.swift
+++ /dev/null
@@ -1,704 +0,0 @@
-import XCTest
-import SQLite
-
-class ExpressionTests: XCTestCase {
-
- let db = Database()
- var users: Query { return db["users"] }
-
- func ExpectExecutionMatches(SQL: String, _ expression: Expressible) {
- ExpectExecution(db, "SELECT \(SQL) FROM \"users\"", users.select(expression))
- }
-
- let stringA = Expression(value: "A")
- let stringB = Expression(value: "B")
-
- let int1 = Expression(value: 1)
- let int2 = Expression(value: 2)
-
- let double1 = Expression(value: 1.5)
- let double2 = Expression(value: 2.5)
-
- let bool0 = Expression(value: false)
- let bool1 = Expression(value: true)
-
- override func setUp() {
- super.setUp()
-
- CreateUsersTable(db)
- }
-
- func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() {
- ExpectExecutionMatches("('A' || 'A')", stringA + stringA)
- ExpectExecutionMatches("('A' || 'B')", stringA + stringB)
- ExpectExecutionMatches("('B' || 'A')", stringB + stringA)
- ExpectExecutionMatches("('B' || 'B')", stringB + stringB)
- ExpectExecutionMatches("('A' || 'B')", stringA + "B")
- ExpectExecutionMatches("('B' || 'A')", stringB + "A")
- ExpectExecutionMatches("('B' || 'A')", "B" + stringA)
- ExpectExecutionMatches("('A' || 'B')", "A" + stringB)
- }
-
- func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() {
- ExpectExecutionMatches("(1 + 1)", int1 + int1)
- ExpectExecutionMatches("(1 + 2)", int1 + int2)
- ExpectExecutionMatches("(2 + 1)", int2 + int1)
- ExpectExecutionMatches("(2 + 2)", int2 + int2)
- ExpectExecutionMatches("(1 + 2)", int1 + 2)
- ExpectExecutionMatches("(2 + 1)", int2 + 1)
- ExpectExecutionMatches("(2 + 1)", 2 + int1)
- ExpectExecutionMatches("(1 + 2)", 1 + int2)
- }
-
- func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() {
- ExpectExecutionMatches("(1.5 + 1.5)", double1 + double1)
- ExpectExecutionMatches("(1.5 + 2.5)", double1 + double2)
- ExpectExecutionMatches("(2.5 + 1.5)", double2 + double1)
- ExpectExecutionMatches("(2.5 + 2.5)", double2 + double2)
- ExpectExecutionMatches("(1.5 + 2.5)", double1 + 2.5)
- ExpectExecutionMatches("(2.5 + 1.5)", double2 + 1.5)
- ExpectExecutionMatches("(2.5 + 1.5)", 2.5 + double1)
- ExpectExecutionMatches("(1.5 + 2.5)", 1.5 + double2)
- }
-
- func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() {
- ExpectExecutionMatches("(1 - 1)", int1 - int1)
- ExpectExecutionMatches("(1 - 2)", int1 - int2)
- ExpectExecutionMatches("(2 - 1)", int2 - int1)
- ExpectExecutionMatches("(2 - 2)", int2 - int2)
- ExpectExecutionMatches("(1 - 2)", int1 - 2)
- ExpectExecutionMatches("(2 - 1)", int2 - 1)
- ExpectExecutionMatches("(2 - 1)", 2 - int1)
- ExpectExecutionMatches("(1 - 2)", 1 - int2)
- }
-
- func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() {
- ExpectExecutionMatches("(1.5 - 1.5)", double1 - double1)
- ExpectExecutionMatches("(1.5 - 2.5)", double1 - double2)
- ExpectExecutionMatches("(2.5 - 1.5)", double2 - double1)
- ExpectExecutionMatches("(2.5 - 2.5)", double2 - double2)
- ExpectExecutionMatches("(1.5 - 2.5)", double1 - 2.5)
- ExpectExecutionMatches("(2.5 - 1.5)", double2 - 1.5)
- ExpectExecutionMatches("(2.5 - 1.5)", 2.5 - double1)
- ExpectExecutionMatches("(1.5 - 2.5)", 1.5 - double2)
- }
-
- func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() {
- ExpectExecutionMatches("(1 * 1)", int1 * int1)
- ExpectExecutionMatches("(1 * 2)", int1 * int2)
- ExpectExecutionMatches("(2 * 1)", int2 * int1)
- ExpectExecutionMatches("(2 * 2)", int2 * int2)
- ExpectExecutionMatches("(1 * 2)", int1 * 2)
- ExpectExecutionMatches("(2 * 1)", int2 * 1)
- ExpectExecutionMatches("(2 * 1)", 2 * int1)
- ExpectExecutionMatches("(1 * 2)", 1 * int2)
- }
-
- func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() {
- ExpectExecutionMatches("(1.5 * 1.5)", double1 * double1)
- ExpectExecutionMatches("(1.5 * 2.5)", double1 * double2)
- ExpectExecutionMatches("(2.5 * 1.5)", double2 * double1)
- ExpectExecutionMatches("(2.5 * 2.5)", double2 * double2)
- ExpectExecutionMatches("(1.5 * 2.5)", double1 * 2.5)
- ExpectExecutionMatches("(2.5 * 1.5)", double2 * 1.5)
- ExpectExecutionMatches("(2.5 * 1.5)", 2.5 * double1)
- ExpectExecutionMatches("(1.5 * 2.5)", 1.5 * double2)
- }
-
- func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() {
- ExpectExecutionMatches("(1 / 1)", int1 / int1)
- ExpectExecutionMatches("(1 / 2)", int1 / int2)
- ExpectExecutionMatches("(2 / 1)", int2 / int1)
- ExpectExecutionMatches("(2 / 2)", int2 / int2)
- ExpectExecutionMatches("(1 / 2)", int1 / 2)
- ExpectExecutionMatches("(2 / 1)", int2 / 1)
- ExpectExecutionMatches("(2 / 1)", 2 / int1)
- ExpectExecutionMatches("(1 / 2)", 1 / int2)
- }
-
- func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() {
- ExpectExecutionMatches("(1.5 / 1.5)", double1 / double1)
- ExpectExecutionMatches("(1.5 / 2.5)", double1 / double2)
- ExpectExecutionMatches("(2.5 / 1.5)", double2 / double1)
- ExpectExecutionMatches("(2.5 / 2.5)", double2 / double2)
- ExpectExecutionMatches("(1.5 / 2.5)", double1 / 2.5)
- ExpectExecutionMatches("(2.5 / 1.5)", double2 / 1.5)
- ExpectExecutionMatches("(2.5 / 1.5)", 2.5 / double1)
- ExpectExecutionMatches("(1.5 / 2.5)", 1.5 / double2)
- }
-
- func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() {
- ExpectExecutionMatches("(1 % 1)", int1 % int1)
- ExpectExecutionMatches("(1 % 2)", int1 % int2)
- ExpectExecutionMatches("(2 % 1)", int2 % int1)
- ExpectExecutionMatches("(2 % 2)", int2 % int2)
- ExpectExecutionMatches("(1 % 2)", int1 % 2)
- ExpectExecutionMatches("(2 % 1)", int2 % 1)
- ExpectExecutionMatches("(2 % 1)", 2 % int1)
- ExpectExecutionMatches("(1 % 2)", 1 % int2)
- }
-
- func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() {
- ExpectExecutionMatches("(1 << 1)", int1 << int1)
- ExpectExecutionMatches("(1 << 2)", int1 << int2)
- ExpectExecutionMatches("(2 << 1)", int2 << int1)
- ExpectExecutionMatches("(2 << 2)", int2 << int2)
- ExpectExecutionMatches("(1 << 2)", int1 << 2)
- ExpectExecutionMatches("(2 << 1)", int2 << 1)
- ExpectExecutionMatches("(2 << 1)", 2 << int1)
- ExpectExecutionMatches("(1 << 2)", 1 << int2)
- }
-
- func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() {
- ExpectExecutionMatches("(1 >> 1)", int1 >> int1)
- ExpectExecutionMatches("(1 >> 2)", int1 >> int2)
- ExpectExecutionMatches("(2 >> 1)", int2 >> int1)
- ExpectExecutionMatches("(2 >> 2)", int2 >> int2)
- ExpectExecutionMatches("(1 >> 2)", int1 >> 2)
- ExpectExecutionMatches("(2 >> 1)", int2 >> 1)
- ExpectExecutionMatches("(2 >> 1)", 2 >> int1)
- ExpectExecutionMatches("(1 >> 2)", 1 >> int2)
- }
-
- func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() {
- ExpectExecutionMatches("(1 & 1)", int1 & int1)
- ExpectExecutionMatches("(1 & 2)", int1 & int2)
- ExpectExecutionMatches("(2 & 1)", int2 & int1)
- ExpectExecutionMatches("(2 & 2)", int2 & int2)
- ExpectExecutionMatches("(1 & 2)", int1 & 2)
- ExpectExecutionMatches("(2 & 1)", int2 & 1)
- ExpectExecutionMatches("(2 & 1)", 2 & int1)
- ExpectExecutionMatches("(1 & 2)", 1 & int2)
- }
-
- func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() {
- ExpectExecutionMatches("(1 | 1)", int1 | int1)
- ExpectExecutionMatches("(1 | 2)", int1 | int2)
- ExpectExecutionMatches("(2 | 1)", int2 | int1)
- ExpectExecutionMatches("(2 | 2)", int2 | int2)
- ExpectExecutionMatches("(1 | 2)", int1 | 2)
- ExpectExecutionMatches("(2 | 1)", int2 | 1)
- ExpectExecutionMatches("(2 | 1)", 2 | int1)
- ExpectExecutionMatches("(1 | 2)", 1 | int2)
- }
-
- func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() {
- ExpectExecutionMatches("(~((1 & 1)) & (1 | 1))", int1 ^ int1)
- ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", int1 ^ int2)
- ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", int2 ^ int1)
- ExpectExecutionMatches("(~((2 & 2)) & (2 | 2))", int2 ^ int2)
- ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", int1 ^ 2)
- ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", int2 ^ 1)
- ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", 2 ^ int1)
- ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", 1 ^ int2)
- }
-
- func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() {
- ExpectExecutionMatches("~(1)", ~int1)
- ExpectExecutionMatches("~(2)", ~int2)
- }
-
- func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() {
- ExpectExecutionMatches("(0 = 0)", bool0 == bool0)
- ExpectExecutionMatches("(0 = 1)", bool0 == bool1)
- ExpectExecutionMatches("(1 = 0)", bool1 == bool0)
- ExpectExecutionMatches("(1 = 1)", bool1 == bool1)
- ExpectExecutionMatches("(0 = 1)", bool0 == true)
- ExpectExecutionMatches("(1 = 0)", bool1 == false)
- ExpectExecutionMatches("(1 = 0)", true == bool0)
- ExpectExecutionMatches("(0 = 1)", false == bool1)
- }
-
- func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() {
- ExpectExecutionMatches("(0 != 0)", bool0 != bool0)
- ExpectExecutionMatches("(0 != 1)", bool0 != bool1)
- ExpectExecutionMatches("(1 != 0)", bool1 != bool0)
- ExpectExecutionMatches("(1 != 1)", bool1 != bool1)
- ExpectExecutionMatches("(0 != 1)", bool0 != true)
- ExpectExecutionMatches("(1 != 0)", bool1 != false)
- ExpectExecutionMatches("(1 != 0)", true != bool0)
- ExpectExecutionMatches("(0 != 1)", false != bool1)
- }
-
- func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() {
- ExpectExecutionMatches("(1 > 1)", int1 > int1)
- ExpectExecutionMatches("(1 > 2)", int1 > int2)
- ExpectExecutionMatches("(2 > 1)", int2 > int1)
- ExpectExecutionMatches("(2 > 2)", int2 > int2)
- ExpectExecutionMatches("(1 > 2)", int1 > 2)
- ExpectExecutionMatches("(2 > 1)", int2 > 1)
- ExpectExecutionMatches("(2 > 1)", 2 > int1)
- ExpectExecutionMatches("(1 > 2)", 1 > int2)
- }
-
- func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() {
- ExpectExecutionMatches("(1 >= 1)", int1 >= int1)
- ExpectExecutionMatches("(1 >= 2)", int1 >= int2)
- ExpectExecutionMatches("(2 >= 1)", int2 >= int1)
- ExpectExecutionMatches("(2 >= 2)", int2 >= int2)
- ExpectExecutionMatches("(1 >= 2)", int1 >= 2)
- ExpectExecutionMatches("(2 >= 1)", int2 >= 1)
- ExpectExecutionMatches("(2 >= 1)", 2 >= int1)
- ExpectExecutionMatches("(1 >= 2)", 1 >= int2)
- }
-
- func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() {
- ExpectExecutionMatches("(1 < 1)", int1 < int1)
- ExpectExecutionMatches("(1 < 2)", int1 < int2)
- ExpectExecutionMatches("(2 < 1)", int2 < int1)
- ExpectExecutionMatches("(2 < 2)", int2 < int2)
- ExpectExecutionMatches("(1 < 2)", int1 < 2)
- ExpectExecutionMatches("(2 < 1)", int2 < 1)
- ExpectExecutionMatches("(2 < 1)", 2 < int1)
- ExpectExecutionMatches("(1 < 2)", 1 < int2)
- }
-
- func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() {
- ExpectExecutionMatches("(1 <= 1)", int1 <= int1)
- ExpectExecutionMatches("(1 <= 2)", int1 <= int2)
- ExpectExecutionMatches("(2 <= 1)", int2 <= int1)
- ExpectExecutionMatches("(2 <= 2)", int2 <= int2)
- ExpectExecutionMatches("(1 <= 2)", int1 <= 2)
- ExpectExecutionMatches("(2 <= 1)", int2 <= 1)
- ExpectExecutionMatches("(2 <= 1)", 2 <= int1)
- ExpectExecutionMatches("(1 <= 2)", 1 <= int2)
- }
-
- func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() {
- ExpectExecutionMatches("-(1)", -int1)
- ExpectExecutionMatches("-(2)", -int2)
- }
-
- func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() {
- ExpectExecutionMatches("-(1.5)", -double1)
- ExpectExecutionMatches("-(2.5)", -double2)
- }
-
- func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() {
- ExpectExecutionMatches("1 BETWEEN 0 AND 5", 0...5 ~= int1)
- ExpectExecutionMatches("2 BETWEEN 0 AND 5", 0...5 ~= int2)
- }
-
- func test_likeOperator_withStringExpression_buildsLikeExpression() {
- ExpectExecutionMatches("('A' LIKE 'B%')", like("B%", stringA))
- ExpectExecutionMatches("('B' LIKE 'A%')", like("A%", stringB))
- }
-
- func test_globOperator_withStringExpression_buildsGlobExpression() {
- ExpectExecutionMatches("('A' GLOB 'B*')", glob("B*", stringA))
- ExpectExecutionMatches("('B' GLOB 'A*')", glob("A*", stringB))
- }
-
- func test_matchOperator_withStringExpression_buildsMatchExpression() {
- ExpectExecutionMatches("('A' MATCH 'B')", match("B", stringA))
- ExpectExecutionMatches("('B' MATCH 'A')", match("A", stringB))
- }
-
- func test_collateOperator_withStringExpression_buildsCollationExpression() {
- ExpectExecutionMatches("('A' COLLATE BINARY)", collate(.Binary, stringA))
- ExpectExecutionMatches("('B' COLLATE NOCASE)", collate(.NoCase, stringB))
- ExpectExecutionMatches("('A' COLLATE RTRIM)", collate(.RTrim, stringA))
- }
-
- func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() {
- ExpectExecutionMatches("(0 AND 0)", bool0 && bool0)
- ExpectExecutionMatches("(0 AND 1)", bool0 && bool1)
- ExpectExecutionMatches("(1 AND 0)", bool1 && bool0)
- ExpectExecutionMatches("(1 AND 1)", bool1 && bool1)
- ExpectExecutionMatches("(0 AND 1)", bool0 && true)
- ExpectExecutionMatches("(1 AND 0)", bool1 && false)
- ExpectExecutionMatches("(1 AND 0)", true && bool0)
- ExpectExecutionMatches("(0 AND 1)", false && bool1)
- }
-
- func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() {
- ExpectExecutionMatches("(0 OR 0)", bool0 || bool0)
- ExpectExecutionMatches("(0 OR 1)", bool0 || bool1)
- ExpectExecutionMatches("(1 OR 0)", bool1 || bool0)
- ExpectExecutionMatches("(1 OR 1)", bool1 || bool1)
- ExpectExecutionMatches("(0 OR 1)", bool0 || true)
- ExpectExecutionMatches("(1 OR 0)", bool1 || false)
- ExpectExecutionMatches("(1 OR 0)", true || bool0)
- ExpectExecutionMatches("(0 OR 1)", false || bool1)
- }
-
- func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() {
- ExpectExecutionMatches("NOT (0)", !bool0)
- ExpectExecutionMatches("NOT (1)", !bool1)
- }
-
- func test_absFunction_withNumberExpressions_buildsAbsExpression() {
- let int1 = Expression(value: -1)
- let int2 = Expression(value: -2)
-
- ExpectExecutionMatches("abs(-1)", abs(int1))
- ExpectExecutionMatches("abs(-2)", abs(int2))
- }
-
- func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() {
- let int1 = Expression(value: nil as Int?)
- let int2 = Expression(value: nil as Int?)
- let int3 = Expression(value: 3)
-
- ExpectExecutionMatches("coalesce(NULL, NULL, 3)", coalesce(int1, int2, int3))
- }
-
- func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() {
- let int1 = Expression(value: nil as Int?)
- let int2 = Expression(value: 2)
- let int3 = Expression(value: 3)
-
- ExpectExecutionMatches("ifnull(NULL, 1)", ifnull(int1, 1))
- ExpectExecutionMatches("ifnull(NULL, 1)", int1 ?? 1)
- ExpectExecutionMatches("ifnull(NULL, 2)", ifnull(int1, int2))
- ExpectExecutionMatches("ifnull(NULL, 2)", int1 ?? int2)
- ExpectExecutionMatches("ifnull(NULL, 3)", ifnull(int1, int3))
- ExpectExecutionMatches("ifnull(NULL, 3)", int1 ?? int3)
- }
-
- func test_lengthFunction_withValueExpression_buildsLengthIntExpression() {
- ExpectExecutionMatches("length('A')", length(stringA))
- ExpectExecutionMatches("length('B')", length(stringB))
- }
-
- func test_lowerFunction_withStringExpression_buildsLowerStringExpression() {
- ExpectExecutionMatches("lower('A')", lower(stringA))
- ExpectExecutionMatches("lower('B')", lower(stringB))
- }
-
- func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() {
- ExpectExecutionMatches("ltrim('A')", ltrim(stringA))
- ExpectExecutionMatches("ltrim('B')", ltrim(stringB))
- }
-
- func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() {
- ExpectExecutionMatches("ltrim('A', 'A?')", ltrim(stringA, "A?"))
- ExpectExecutionMatches("ltrim('B', 'B?')", ltrim(stringB, "B?"))
- }
-
- func test_randomFunction_buildsRandomIntExpression() {
- ExpectExecutionMatches("random()", random)
- }
-
- func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() {
- ExpectExecutionMatches("replace('A', 'A', 'B')", replace(stringA, "A", "B"))
- ExpectExecutionMatches("replace('B', 'B', 'A')", replace(stringB, "B", "A"))
- }
-
- func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() {
- ExpectExecutionMatches("round(1.5)", round(double1))
- ExpectExecutionMatches("round(2.5)", round(double2))
- }
-
- func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() {
- ExpectExecutionMatches("round(1.5, 1)", round(double1, 1))
- ExpectExecutionMatches("round(2.5, 1)", round(double2, 1))
- }
-
- func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() {
- ExpectExecutionMatches("rtrim('A')", rtrim(stringA))
- ExpectExecutionMatches("rtrim('B')", rtrim(stringB))
- }
-
- func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() {
- ExpectExecutionMatches("rtrim('A', 'A?')", rtrim(stringA, "A?"))
- ExpectExecutionMatches("rtrim('B', 'B?')", rtrim(stringB, "B?"))
- }
-
- func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() {
- ExpectExecutionMatches("substr('A', 1)", substr(stringA, 1))
- ExpectExecutionMatches("substr('B', 1)", substr(stringB, 1))
- }
-
- func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() {
- ExpectExecutionMatches("substr('A', 1, 2)", substr(stringA, 1, 2))
- ExpectExecutionMatches("substr('B', 1, 2)", substr(stringB, 1, 2))
- }
-
- func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() {
- ExpectExecutionMatches("substr('A', 1, 2)", substr(stringA, 1..<3))
- ExpectExecutionMatches("substr('B', 1, 2)", substr(stringB, 1..<3))
- }
-
- func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() {
- ExpectExecutionMatches("trim('A')", trim(stringA))
- ExpectExecutionMatches("trim('B')", trim(stringB))
- }
-
- func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() {
- ExpectExecutionMatches("trim('A', 'A?')", trim(stringA, "A?"))
- ExpectExecutionMatches("trim('B', 'B?')", trim(stringB, "B?"))
- }
-
- func test_upperFunction_withStringExpression_buildsLowerStringExpression() {
- ExpectExecutionMatches("upper('A')", upper(stringA))
- ExpectExecutionMatches("upper('B')", upper(stringB))
- }
-
- let id = Expression("id")
- let age = Expression("age")
- let email = Expression("email")
- let email2 = Expression("email")
- let salary = Expression("salary")
- let salary2 = Expression("salary")
- let admin = Expression("admin")
- let admin2 = Expression("admin")
-
- func test_countFunction_withExpression_buildsCountExpression() {
- ExpectExecutionMatches("count(\"id\")", count(id))
- ExpectExecutionMatches("count(\"age\")", count(age))
- ExpectExecutionMatches("count(\"email\")", count(email))
- ExpectExecutionMatches("count(\"email\")", count(email2))
- ExpectExecutionMatches("count(\"salary\")", count(salary))
- ExpectExecutionMatches("count(\"salary\")", count(salary2))
- ExpectExecutionMatches("count(\"admin\")", count(admin))
- ExpectExecutionMatches("count(\"admin\")", count(admin2))
- ExpectExecutionMatches("count(DISTINCT \"id\")", count(distinct: id))
- ExpectExecutionMatches("count(DISTINCT \"age\")", count(distinct: age))
- ExpectExecutionMatches("count(DISTINCT \"email\")", count(distinct: email))
- ExpectExecutionMatches("count(DISTINCT \"email\")", count(distinct: email2))
- ExpectExecutionMatches("count(DISTINCT \"salary\")", count(distinct: salary))
- ExpectExecutionMatches("count(DISTINCT \"salary\")", count(distinct: salary2))
- ExpectExecutionMatches("count(DISTINCT \"admin\")", count(distinct: admin))
- ExpectExecutionMatches("count(DISTINCT \"admin\")", count(distinct: admin2))
- }
-
- func test_countFunction_withStar_buildsCountExpression() {
- ExpectExecutionMatches("count(*)", count(*))
- }
-
- func test_maxFunction_withExpression_buildsMaxExpression() {
- ExpectExecutionMatches("max(\"id\")", max(id))
- ExpectExecutionMatches("max(\"age\")", max(age))
- ExpectExecutionMatches("max(\"email\")", max(email))
- ExpectExecutionMatches("max(\"email\")", max(email2))
- ExpectExecutionMatches("max(\"salary\")", max(salary))
- ExpectExecutionMatches("max(\"salary\")", max(salary2))
- }
-
- func test_minFunction_withExpression_buildsMinExpression() {
- ExpectExecutionMatches("min(\"id\")", min(id))
- ExpectExecutionMatches("min(\"age\")", min(age))
- ExpectExecutionMatches("min(\"email\")", min(email))
- ExpectExecutionMatches("min(\"email\")", min(email2))
- ExpectExecutionMatches("min(\"salary\")", min(salary))
- ExpectExecutionMatches("min(\"salary\")", min(salary2))
- }
-
- func test_averageFunction_withExpression_buildsAverageExpression() {
- ExpectExecutionMatches("avg(\"id\")", average(id))
- ExpectExecutionMatches("avg(\"age\")", average(age))
- ExpectExecutionMatches("avg(\"salary\")", average(salary))
- ExpectExecutionMatches("avg(\"salary\")", average(salary2))
- ExpectExecutionMatches("avg(DISTINCT \"id\")", average(distinct: id))
- ExpectExecutionMatches("avg(DISTINCT \"age\")", average(distinct: age))
- ExpectExecutionMatches("avg(DISTINCT \"salary\")", average(distinct: salary))
- ExpectExecutionMatches("avg(DISTINCT \"salary\")", average(distinct: salary2))
- }
-
- func test_sumFunction_withExpression_buildsSumExpression() {
- ExpectExecutionMatches("sum(\"id\")", sum(id))
- ExpectExecutionMatches("sum(\"age\")", sum(age))
- ExpectExecutionMatches("sum(\"salary\")", sum(salary))
- ExpectExecutionMatches("sum(\"salary\")", sum(salary2))
- ExpectExecutionMatches("sum(DISTINCT \"id\")", sum(distinct: id))
- ExpectExecutionMatches("sum(DISTINCT \"age\")", sum(distinct: age))
- ExpectExecutionMatches("sum(DISTINCT \"salary\")", sum(distinct: salary))
- ExpectExecutionMatches("sum(DISTINCT \"salary\")", sum(distinct: salary2))
- }
-
- func test_totalFunction_withExpression_buildsTotalExpression() {
- ExpectExecutionMatches("total(\"id\")", total(id))
- ExpectExecutionMatches("total(\"age\")", total(age))
- ExpectExecutionMatches("total(\"salary\")", total(salary))
- ExpectExecutionMatches("total(\"salary\")", total(salary2))
- ExpectExecutionMatches("total(DISTINCT \"id\")", total(distinct: id))
- ExpectExecutionMatches("total(DISTINCT \"age\")", total(distinct: age))
- ExpectExecutionMatches("total(DISTINCT \"salary\")", total(distinct: salary))
- ExpectExecutionMatches("total(DISTINCT \"salary\")", total(distinct: salary2))
- }
-
- func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() {
- ExpectExecutionMatches("(\"id\" IN (1, 2, 3))", contains([1, 2, 3], id))
- ExpectExecutionMatches("(\"age\" IN (20, 30, 40))", contains([20, 30, 40], age))
- }
-
- func test_plusEquals_withStringExpression_buildsSetter() {
- let SQL = "UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")"
- ExpectExecution(db, SQL, users.update(email += email))
- ExpectExecution(db, SQL, users.update(email += email2))
- ExpectExecution(db, SQL, users.update(email2 += email))
- ExpectExecution(db, SQL, users.update(email2 += email2))
- }
-
- func test_plusEquals_withStringValue_buildsSetter() {
- let SQL = "UPDATE \"users\" SET \"email\" = (\"email\" || '.com')"
- ExpectExecution(db, SQL, users.update(email += ".com"))
- ExpectExecution(db, SQL, users.update(email2 += ".com"))
- }
-
- func test_plusEquals_withNumberExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", users.update(age += age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + \"id\")", users.update(age += id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + \"age\")", users.update(id += age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + \"id\")", users.update(id += id))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary += salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary += salary2))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary2 += salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary2 += salary2))
- }
-
- func test_plusEquals_withNumberValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id += 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age += 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", users.update(salary += 100))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", users.update(salary2 += 100))
- }
-
- func test_minusEquals_withNumberExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", users.update(age -= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - \"id\")", users.update(age -= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - \"age\")", users.update(id -= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - \"id\")", users.update(id -= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary -= salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary -= salary2))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary2 -= salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary2 -= salary2))
- }
-
- func test_minusEquals_withNumberValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id -= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age -= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", users.update(salary -= 100))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", users.update(salary2 -= 100))
- }
-
- func test_timesEquals_withNumberExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", users.update(age *= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * \"id\")", users.update(age *= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * \"age\")", users.update(id *= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * \"id\")", users.update(id *= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary *= salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary *= salary2))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary2 *= salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary2 *= salary2))
- }
-
- func test_timesEquals_withNumberValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * 1)", users.update(id *= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * 1)", users.update(age *= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", users.update(salary *= 100))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", users.update(salary2 *= 100))
- }
-
- func test_divideEquals_withNumberExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", users.update(age /= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / \"id\")", users.update(age /= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / \"age\")", users.update(id /= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / \"id\")", users.update(id /= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary /= salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary /= salary2))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary2 /= salary))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary2 /= salary2))
- }
-
- func test_divideEquals_withNumberValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / 1)", users.update(id /= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / 1)", users.update(age /= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", users.update(salary /= 100))
- ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", users.update(salary2 /= 100))
- }
-
- func test_moduloEquals_withIntegerExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", users.update(age %= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % \"id\")", users.update(age %= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % \"age\")", users.update(id %= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % \"id\")", users.update(id %= id))
- }
-
- func test_moduloEquals_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % 10)", users.update(age %= 10))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % 10)", users.update(id %= 10))
- }
-
- func test_rightShiftEquals_withIntegerExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", users.update(age >>= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> \"id\")", users.update(age >>= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> \"age\")", users.update(id >>= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> \"id\")", users.update(id >>= id))
- }
-
- func test_rightShiftEquals_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", users.update(age >>= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> 1)", users.update(id >>= 1))
- }
-
- func test_leftShiftEquals_withIntegerExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", users.update(age <<= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << \"id\")", users.update(age <<= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << \"age\")", users.update(id <<= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << \"id\")", users.update(id <<= id))
- }
-
- func test_leftShiftEquals_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << 1)", users.update(age <<= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << 1)", users.update(id <<= 1))
- }
-
- func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", users.update(age &= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & \"id\")", users.update(age &= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & \"age\")", users.update(id &= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & \"id\")", users.update(id &= id))
- }
-
- func test_bitwiseAndEquals_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & 1)", users.update(age &= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & 1)", users.update(id &= 1))
- }
-
- func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", users.update(age |= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | \"id\")", users.update(age |= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | \"age\")", users.update(id |= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | \"id\")", users.update(id |= id))
- }
-
- func test_bitwiseOrEquals_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | 1)", users.update(age |= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | 1)", users.update(id |= 1))
- }
-
- func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", users.update(age ^= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & \"id\")) & (\"age\" | \"id\"))", users.update(age ^= id))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & \"age\")) & (\"id\" | \"age\"))", users.update(id ^= age))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & \"id\")) & (\"id\" | \"id\"))", users.update(id ^= id))
- }
-
- func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", users.update(age ^= 1))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & 1)) & (\"id\" | 1))", users.update(id ^= 1))
- }
-
- func test_postfixPlus_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age++))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age++))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id++))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id++))
- }
-
- func test_postfixMinus_withIntegerValue_buildsSetter() {
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age--))
- ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age--))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id--))
- ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id--))
- }
-
- func test_precedencePreserved() {
- let n = Expression(value: 1)
- ExpectExecutionMatches("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n)
- ExpectExecutionMatches("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n))
- }
-
-}
diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift
deleted file mode 100644
index c855f45c..00000000
--- a/SQLite Common Tests/QueryTests.swift
+++ /dev/null
@@ -1,464 +0,0 @@
-import XCTest
-import SQLite
-
-class QueryTests: XCTestCase {
-
- let db = Database()
- var users: Query { return db["users"] }
-
- let id = Expression("id")
- let email = Expression("email")
- let age = Expression("age")
- let salary = Expression("salary")
- let admin = Expression("admin")
- let manager_id = Expression("manager_id")
-
- override func setUp() {
- super.setUp()
-
- CreateUsersTable(db)
- }
-
- func test_select_withExpression_compilesSelectClause() {
- let query = users.select(email)
-
- let SQL = "SELECT \"email\" FROM \"users\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_select_withVariadicExpressions_compilesSelectClause() {
- let query = users.select(email, count(*))
-
- let SQL = "SELECT \"email\", count(*) FROM \"users\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_select_withStar_resetsSelectClause() {
- let query = users.select(email)
-
- let SQL = "SELECT * FROM \"users\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(all: *) {} }
- }
-
- func test_selectDistinct_withExpression_compilesSelectClause() {
- let query = users.select(distinct: age)
-
- let SQL = "SELECT DISTINCT \"age\" FROM \"users\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_selectDistinct_withStar_compilesSelectClause() {
- let query = users.select(distinct: *)
-
- let SQL = "SELECT DISTINCT * FROM \"users\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_join_compilesJoinClause() {
- let managers = db["users"].alias("managers")
-
- let query = users.join(managers, on: managers[id] == users[manager_id])
-
- let SQL = "SELECT * FROM \"users\" " +
- "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_join_withExplicitType_compilesJoinClauseWithType() {
- let managers = db["users"].alias("managers")
-
- let query = users.join(.LeftOuter, managers, on: managers[id] == users[manager_id])
-
- let SQL = "SELECT * FROM \"users\" " +
- "LEFT OUTER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_join_withTableCondition_compilesJoinClauseWithTableCondition() {
- var managers = db["users"].alias("managers")
- managers = managers.filter(managers[admin])
-
- let query = users.join(managers, on: managers[id] == users[manager_id])
-
- let SQL = "SELECT * FROM \"users\" " +
- "INNER JOIN \"users\" AS \"managers\" " +
- "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") " +
- "AND \"managers\".\"admin\")"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_join_whenChained_compilesAggregateJoinClause() {
- let managers = users.alias("managers")
- let managed = users.alias("managed")
-
- let middleManagers = users
- .join(managers, on: managers[id] == users[manager_id])
- .join(managed, on: managed[manager_id] == users[id])
-
- let SQL = "SELECT * FROM \"users\" " +
- "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\") " +
- "INNER JOIN \"users\" AS \"managed\" ON (\"managed\".\"manager_id\" = \"users\".\"id\")"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in middleManagers {} }
- }
-
- func test_join_withNamespacedStar_expandsColumnNames() {
- let managers = db["users"].alias("managers")
-
- let aliceID = users.insert(email <- "alice@example.com")!
- users.insert(email <- "betty@example.com", manager_id <- aliceID)!
-
- let query = users
- .select(users[*], managers[*])
- .join(managers, on: managers[id] == users[manager_id])
-
- let SQL = "SELECT \"users\".*, \"managers\".* FROM \"users\" " +
- "INNER JOIN \"users\" AS \"managers\" " +
- "ON (\"managers\".\"id\" = \"users\".\"manager_id\")"
- ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } }
- }
-
- func test_namespacedColumnRowValueAccess() {
- let aliceID = users.insert(email <- "alice@example.com")!
- let bettyID = users.insert(email <- "betty@example.com", manager_id <- aliceID)!
-
- let alice = users.first!
- XCTAssertEqual(aliceID, alice[id])
-
- let managers = db["users"].alias("managers")
- let query = users.join(managers, on: managers[id] == users[manager_id])
-
- let betty = query.first!
- XCTAssertEqual(alice[email], betty[managers[email]])
- }
-
- func test_filter_compilesWhereClause() {
- let query = users.filter(admin == true)
-
- let SQL = "SELECT * FROM \"users\" WHERE (\"admin\" = 1)"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_filter_whenChained_compilesAggregateWhereClause() {
- let query = users
- .filter(email == "alice@example.com")
- .filter(age >= 21)
-
- let SQL = "SELECT * FROM \"users\" " +
- "WHERE ((\"email\" = 'alice@example.com') " +
- "AND (\"age\" >= 21))"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_group_withSingleExpressionName_compilesGroupClause() {
- let query = users.group(age)
-
- let SQL = "SELECT * FROM \"users\" GROUP BY \"age\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_group_withVariadicExpressionNames_compilesGroupClause() {
- let query = users.group(age, admin)
-
- let SQL = "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() {
- let query = users.group(age, having: age >= 30)
-
- let SQL = "SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() {
- let query = users.group([age, admin], having: age >= 30)
-
- let SQL = "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_order_withSingleExpressionName_compilesOrderClause() {
- let query = users.order(age)
-
- let SQL = "SELECT * FROM \"users\" ORDER BY \"age\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_order_withVariadicExpressionNames_compilesOrderClause() {
- let query = users.order(age, email)
-
- let SQL = "SELECT * FROM \"users\" ORDER BY \"age\", \"email\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_order_withExpressionAndSortDirection_compilesOrderClause() {
- let query = users.order(age.desc, email.asc)
-
- let SQL = "SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_limit_compilesLimitClause() {
- let query = users.limit(5)
-
- let SQL = "SELECT * FROM \"users\" LIMIT 5"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_limit_withOffset_compilesOffsetClause() {
- let query = users.limit(5, offset: 5)
-
- let SQL = "SELECT * FROM \"users\" LIMIT 5 OFFSET 5"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_limit_whenChained_overridesLimit() {
- let query = users.limit(5).limit(10)
-
- var SQL = "SELECT * FROM \"users\" LIMIT 10"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
-
- SQL = "SELECT * FROM \"users\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} }
- }
-
- func test_limit_whenChained_withOffset_overridesOffset() {
- let query = users.limit(5, offset: 5).limit(10, offset: 10)
-
- var SQL = "SELECT * FROM \"users\" LIMIT 10 OFFSET 10"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
-
- SQL = "SELECT * FROM \"users\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} }
- }
-
- func test_alias_compilesAliasInSelectClause() {
- let managers = users.alias("managers")
- var SQL = "SELECT * FROM \"users\" AS \"managers\""
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in managers {} }
- }
-
- func test_subscript_withExpression_returnsNamespacedExpression() {
- ExpectExecution(db, "SELECT \"users\".\"admin\" FROM \"users\"", users.select(users[admin]))
- ExpectExecution(db, "SELECT \"users\".\"salary\" FROM \"users\"", users.select(users[salary]))
- ExpectExecution(db, "SELECT \"users\".\"age\" FROM \"users\"", users.select(users[age]))
- ExpectExecution(db, "SELECT \"users\".\"email\" FROM \"users\"", users.select(users[email]))
- ExpectExecution(db, "SELECT \"users\".* FROM \"users\"", users.select(users[*]))
- }
-
- func test_subscript_withAliasAndExpression_returnsAliasedExpression() {
- let managers = users.alias("managers")
- ExpectExecution(db, "SELECT \"managers\".\"admin\" FROM \"users\" AS \"managers\"", managers.select(managers[admin]))
- ExpectExecution(db, "SELECT \"managers\".\"salary\" FROM \"users\" AS \"managers\"", managers.select(managers[salary]))
- ExpectExecution(db, "SELECT \"managers\".\"age\" FROM \"users\" AS \"managers\"", managers.select(managers[age]))
- ExpectExecution(db, "SELECT \"managers\".\"email\" FROM \"users\" AS \"managers\"", managers.select(managers[email]))
- ExpectExecution(db, "SELECT \"managers\".* FROM \"users\" AS \"managers\"", managers.select(managers[*]))
- }
-
- func test_SQL_compilesProperly() {
- var managers = users.alias("managers")
- // TODO: automatically namespace in the future?
- managers = managers.filter(managers[admin] == true)
-
- let query = users
- .select(users[email], count(users[email]))
- .join(.LeftOuter, managers, on: managers[id] == users[manager_id])
- .filter(21..<32 ~= users[age])
- .group(users[age], having: count(users[email]) > 1)
- .order(users[email].desc)
- .limit(1, offset: 2)
-
- let SQL = "SELECT \"users\".\"email\", count(\"users\".\"email\") FROM \"users\" " +
- "LEFT OUTER JOIN \"users\" AS \"managers\" " +
- "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") AND (\"managers\".\"admin\" = 1)) " +
- "WHERE \"users\".\"age\" BETWEEN 21 AND 32 " +
- "GROUP BY \"users\".\"age\" HAVING (count(\"users\".\"email\") > 1) " +
- "ORDER BY \"users\".\"email\" DESC " +
- "LIMIT 1 " +
- "OFFSET 2"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_first_withAnEmptyQuery_returnsNil() {
- XCTAssert(users.first == nil)
- }
-
- func test_first_returnsTheFirstRow() {
- InsertUsers(db, "alice", "betsy")
- ExpectExecutions(db, ["SELECT * FROM \"users\" LIMIT 1": 1]) { _ in
- XCTAssertEqual(1, self.users.first![self.id])
- }
- }
-
- func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() {
- ExpectExecutions(db, ["SELECT * FROM \"users\" LIMIT 1": 2]) { _ in
- XCTAssertTrue(self.users.isEmpty)
- InsertUser(self.db, "alice")
- XCTAssertFalse(self.users.isEmpty)
- }
- }
-
- func test_insert_insertsRows() {
- let SQL = "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)"
-
- ExpectExecutions(db, [SQL: 1]) { _ in
- XCTAssertEqual(1, self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID!)
- }
-
- XCTAssert(self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID == nil)
- }
-
- func test_insert_withQuery_insertsRows() {
- db.execute("CREATE TABLE \"emails\" (\"email\" TEXT)")
- let emails = db["emails"]
- let admins = users.select(email).filter(admin == true)
-
- ExpectExecution(db, "INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)", emails.insert(admins))
- }
-
- func test_insert_insertsDefaultRow() {
- db.execute("CREATE TABLE \"timestamps\" (\"id\" INTEGER PRIMARY KEY, \"timestamp\" TEXT DEFAULT CURRENT_DATETIME)")
- let table = db["timestamps"]
-
- ExpectExecutions(db, ["INSERT INTO \"timestamps\" DEFAULT VALUES": 1]) { _ in
- XCTAssertEqual(1, table.insert().ID!)
- }
- }
-
- func test_replace_replaceRows() {
- let SQL = "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)"
-
- ExpectExecutions(db, [SQL: 1]) { _ in
- XCTAssertEqual(1, self.users.replace(self.email <- "alice@example.com", self.age <- 30).ID!)
- }
-
- XCTAssertEqual(1, self.users.replace(self.id <- 1, self.email <- "bob@example.com", self.age <- 30).ID!)
- }
-
- func test_update_updatesRows() {
- InsertUsers(db, "alice", "betsy")
- InsertUser(db, "dolly", admin: true)
-
- XCTAssertEqual(2, users.filter(!admin).update(age <- 30, admin <- true).changes!)
- XCTAssertEqual(0, users.filter(!admin).update(age <- 30, admin <- true).changes!)
- }
-
- func test_delete_deletesRows() {
- InsertUser(db, "alice", age: 20)
- XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes!)
-
- InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(2, users.delete().changes!)
- XCTAssertEqual(0, users.delete().changes!)
- }
-
- func test_count_returnsCount() {
- XCTAssertEqual(0, users.count)
-
- InsertUser(db, "alice")
- XCTAssertEqual(1, users.count)
- XCTAssertEqual(0, users.filter(age != nil).count)
- }
-
- func test_count_withExpression_returnsCount() {
- InsertUser(db, "alice", age: 20)
- InsertUser(db, "betsy", age: 20)
- InsertUser(db, "cindy")
-
- XCTAssertEqual(2, users.count(age))
- XCTAssertEqual(1, users.count(distinct: age))
- }
-
- func test_max_withInt_returnsMaximumInt() {
- XCTAssert(users.max(age) == nil)
-
- InsertUser(db, "alice", age: 20)
- InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(30, users.max(age)!)
- }
-
- func test_min_withInt_returnsMinimumInt() {
- XCTAssert(users.min(age) == nil)
-
- InsertUser(db, "alice", age: 20)
- InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(20, users.min(age)!)
- }
-
- func test_averageWithInt_returnsDouble() {
- XCTAssert(users.average(age) == nil)
-
- InsertUser(db, "alice", age: 20)
- InsertUser(db, "betsy", age: 50)
- InsertUser(db, "cindy", age: 50)
- XCTAssertEqual(40.0, users.average(age)!)
- XCTAssertEqual(35.0, users.average(distinct: age)!)
- }
-
- func test_sum_returnsSum() {
- XCTAssert(users.sum(age) == nil)
-
- InsertUser(db, "alice", age: 20)
- InsertUser(db, "betsy", age: 30)
- InsertUser(db, "cindy", age: 30)
- XCTAssertEqual(80, users.sum(age)!)
- XCTAssertEqual(50, users.sum(distinct: age)!)
- }
-
- func test_total_returnsTotal() {
- XCTAssertEqual(0.0, users.total(age))
-
- InsertUser(db, "alice", age: 20)
- InsertUser(db, "betsy", age: 30)
- InsertUser(db, "cindy", age: 30)
- XCTAssertEqual(80.0, users.total(age))
- XCTAssertEqual(50.0, users.total(distinct: age))
- }
-
- func test_row_withBoundColumn_returnsValue() {
- InsertUser(db, "alice", age: 20)
- XCTAssertEqual(21, users.select(age + 1).first![age + 1]!)
- }
-
- func test_valueExtension_serializesAndDeserializes() {
- let id = Expression("id")
- let timestamp = Expression("timestamp")
- let touches = db["touches"]
- db.create(table: touches) { t in
- t.column(id, primaryKey: true)
- t.column(timestamp)
- }
-
- let date = NSDate(timeIntervalSince1970: 0)
- touches.insert(timestamp <- date)!
- XCTAssertEqual(touches.first!.get(timestamp)!, date)
-
- XCTAssertNil(touches.filter(id == touches.insert()!).first!.get(timestamp))
-
- XCTAssert(touches.filter(timestamp < NSDate()).first != nil)
- }
-
-}
-
-private let formatter: NSDateFormatter = {
- let formatter = NSDateFormatter()
- formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
- formatter.timeZone = NSTimeZone(abbreviation: "UTC")
- return formatter
-}()
-
-extension NSDate: Value {
-
- public typealias Datatype = String
-
- public class var declaredDatatype: String { return Datatype.declaredDatatype }
-
- public class func fromDatatypeValue(datatypeValue: Datatype) -> NSDate {
- return formatter.dateFromString(datatypeValue)!
- }
-
- public var datatypeValue: Datatype {
- return formatter.stringFromDate(self)
- }
-
-}
diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift
deleted file mode 100644
index ba47e765..00000000
--- a/SQLite Common Tests/SchemaTests.swift
+++ /dev/null
@@ -1,388 +0,0 @@
-import XCTest
-import SQLite
-
-private let id = Expression("id")
-private let email = Expression("email")
-private let age = Expression("age")
-private let salary = Expression("salary")
-private let admin = Expression("admin")
-private let manager_id = Expression("manager_id")
-
-class SchemaTests: XCTestCase {
-
- let db = Database()
- var users: Query { return db["users"] }
-
- override func setUp() {
- super.setUp()
- db.run("PRAGMA foreign_keys = ON")
- db.trace(println)
- }
-
- func test_createTable_createsTable() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"age\" INTEGER)",
- db.create(table: users) { t in t.column(age) }
- )
- }
-
- func test_createTable_temporary_createsTemporaryTable() {
- ExpectExecution(db, "CREATE TEMPORARY TABLE \"users\" (\"age\" INTEGER)",
- db.create(table: users, temporary: true) { t in t.column(age) }
- )
- }
-
- func test_createTable_ifNotExists_createsTableIfNotExists() {
- ExpectExecution(db, "CREATE TABLE IF NOT EXISTS \"users\" (\"age\" INTEGER)",
- db.create(table: users, ifNotExists: true) { t in t.column(age) }
- )
- }
-
- func test_createTable_column_buildsColumnDefinition() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)",
- db.create(table: users) { t in
- t.column(email)
- }
- )
- }
-
- func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY NOT NULL)",
- db.create(table: users) { t in
- t.column(id, primaryKey: true)
- }
- )
- }
-
- func test_createTable_column_withPrimaryKey_buildsPrimaryKeyAutoincrementClause() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
- db.create(table: users) { t in
- t.column(id, primaryKey: .Autoincrement)
- }
- )
- }
-
- func test_createTable_column_withNullFalse_buildsNotNullClause() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)",
- db.create(table: users) { t in
- t.column(email)
- }
- )
- }
-
- func test_createTable_column_withUnique_buildsUniqueClause() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL UNIQUE)",
- db.create(table: users) { t in
- t.column(email, unique: true)
- }
- )
- }
-
- func test_createTable_column_withCheck_buildsCheckClause() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" BOOLEAN NOT NULL CHECK (\"admin\" IN (0, 1)))",
- db.create(table: users) { t in
- t.column(admin, check: contains([false, true], admin))
- }
- )
- }
-
- func test_createTable_column_withDefaultValue_buildsDefaultClause() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"salary\" REAL NOT NULL DEFAULT 0.0)",
- db.create(table: users) { t in
- t.column(salary, defaultValue: 0)
- }
- )
- }
-
- func test_createTable_stringColumn_collation_buildsCollateClause() {
- ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL COLLATE NOCASE)",
- db.create(table: users) { t in
- t.column(email, collate: .NoCase)
- }
- )
- }
-
- func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() {
- let users = self.users
- ExpectExecution(db, "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER PRIMARY KEY NOT NULL, " +
- "\"manager_id\" INTEGER REFERENCES \"users\"(\"id\")" +
- ")",
- db.create(table: users) { t in
- t.column(id, primaryKey: true)
- t.column(manager_id, references: users[id])
- }
- )
- }
-
- func test_createTable_intColumn_referencingQuery_buildsReferencesClause() {
- let users = self.users
- ExpectExecution(db, "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER PRIMARY KEY NOT NULL, " +
- "\"manager_id\" INTEGER REFERENCES \"users\"" +
- ")",
- db.create(table: users) { t in
- t.column(id, primaryKey: true)
- t.column(manager_id, references: users)
- }
- )
- }
-
- func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() {
- let users = self.users
- ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, PRIMARY KEY(\"email\"))",
- db.create(table: users) { t in
- t.column(email)
- t.primaryKey(email)
- }
- )
- }
-
- func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() {
- let users = self.users
- ExpectExecution(db, "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, PRIMARY KEY(\"id\", \"email\")" +
- ")",
- db.create(table: users) { t in
- t.column(id)
- t.column(email)
- t.primaryKey(id, email)
- }
- )
- }
-
- func test_createTable_unique_buildsUniqueTableConstraint() {
- let users = self.users
- ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, UNIQUE(\"email\"))",
- db.create(table: users) { t in
- t.column(email)
- t.unique(email)
- }
- )
- }
-
- func test_createTable_unique_buildsCompositeUniqueTableConstraint() {
- let users = self.users
- ExpectExecution(db, "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, UNIQUE(\"id\", \"email\")" +
- ")",
- db.create(table: users) { t in
- t.column(id)
- t.column(email)
- t.unique(id, email)
- }
- )
- }
-
- func test_createTable_check_buildsCheckTableConstraint() {
- let users = self.users
- ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" BOOLEAN NOT NULL, CHECK (\"admin\" IN (0, 1)))",
- db.create(table: users) { t in
- t.column(admin)
- t.check(contains([false, true], admin))
- }
- )
- }
-
- func test_createTable_foreignKey_referencingNamespacedColumn_buildsForeignKeyTableConstraint() {
- let users = self.users
- ExpectExecution(db,
- "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER PRIMARY KEY NOT NULL, " +
- "\"manager_id\" INTEGER, " +
- "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\")" +
- ")",
- db.create(table: users) { t in
- t.column(id, primaryKey: true)
- t.column(manager_id)
- t.foreignKey(manager_id, references: users[id])
- }
- )
- }
-
- func test_createTable_foreignKey_referencingTable_buildsForeignKeyTableConstraint() {
- let users = self.users
- ExpectExecution(db,
- "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER PRIMARY KEY NOT NULL, " +
- "\"manager_id\" INTEGER, " +
- "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"" +
- ")",
- db.create(table: users) { t in
- t.column(id, primaryKey: true)
- t.column(manager_id)
- t.foreignKey(manager_id, references: users)
- }
- )
- }
-
- func test_createTable_foreignKey_withUpdateDependency_buildsUpdateDependency() {
- let users = self.users
- ExpectExecution(db,
- "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER PRIMARY KEY NOT NULL, " +
- "\"manager_id\" INTEGER, " +
- "FOREIGN KEY(\"manager_id\") REFERENCES \"users\" ON UPDATE CASCADE" +
- ")",
- db.create(table: users) { t in
- t.column(id, primaryKey: true)
- t.column(manager_id)
- t.foreignKey(manager_id, references: users, update: .Cascade)
- }
- )
- }
-
- func test_create_foreignKey_withDeleteDependency_buildsDeleteDependency() {
- let users = self.users
- ExpectExecution(db,
- "CREATE TABLE \"users\" (" +
- "\"id\" INTEGER PRIMARY KEY NOT NULL, " +
- "\"manager_id\" INTEGER, " +
- "FOREIGN KEY(\"manager_id\") REFERENCES \"users\" ON DELETE CASCADE" +
- ")",
- db.create(table: users) { t in
- t.column(id, primaryKey: true)
- t.column(manager_id)
- t.foreignKey(manager_id, references: users, delete: .Cascade)
- }
- )
- }
-
- func test_createTable_withQuery_createsTableWithQuery() {
- CreateUsersTable(db)
- ExpectExecution(db,
- "CREATE TABLE \"emails\" AS SELECT \"email\" FROM \"users\"",
- db.create(table: db["emails"], from: users.select(email))
- )
- ExpectExecution(db,
- "CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"",
- db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email))
- )
- }
-
- func test_alterTable_renamesTable() {
- CreateUsersTable(db)
- ExpectExecution(db, "ALTER TABLE \"users\" RENAME TO \"people\"", db.rename(table: users, to: "people") )
- }
-
- func test_alterTable_addsNotNullColumn() {
- CreateUsersTable(db)
- let column = Expression("bonus")
-
- ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL NOT NULL DEFAULT 0.0",
- db.alter(table: users, add: column, defaultValue: 0)
- )
- }
-
- func test_alterTable_addsRegularColumn() {
- CreateUsersTable(db)
- let column = Expression("bonus")
-
- ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL",
- db.alter(table: users, add: column)
- )
- }
-
- func test_alterTable_withDefaultValue_addsRegularColumn() {
- CreateUsersTable(db)
- let column = Expression("bonus")
-
- ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL DEFAULT 0.0",
- db.alter(table: users, add: column, defaultValue: 0)
- )
- }
-
- func test_alterTable_withForeignKey_addsRegularColumn() {
- CreateUsersTable(db)
- let column = Expression("parent_id")
-
- ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"parent_id\" INTEGER REFERENCES \"users\"(\"id\")",
- db.alter(table: users, add: column, references: users[id])
- )
- }
-
- func test_dropTable_dropsTable() {
- CreateUsersTable(db)
- ExpectExecution(db, "DROP TABLE \"users\"", db.drop(table: users) )
- ExpectExecution(db, "DROP TABLE IF EXISTS \"users\"", db.drop(table: users, ifExists: true) )
- }
-
- func test_index_executesIndexStatement() {
- CreateUsersTable(db)
- ExpectExecution(db,
- "CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")",
- db.create(index: users, on: email)
- )
- }
-
- func test_index_withUniqueness_executesUniqueIndexStatement() {
- CreateUsersTable(db)
- ExpectExecution(db,
- "CREATE UNIQUE INDEX \"index_users_on_email\" ON \"users\" (\"email\")",
- db.create(index: users, unique: true, on: email)
- )
- }
-
- func test_index_ifNotExists_executesIndexStatement() {
- CreateUsersTable(db)
- ExpectExecution(db,
- "CREATE INDEX IF NOT EXISTS \"index_users_on_email\" ON \"users\" (\"email\")",
- db.create(index: users, ifNotExists: true, on: email)
- )
- }
-
- func test_index_withMultipleColumns_executesCompoundIndexStatement() {
- CreateUsersTable(db)
- ExpectExecution(db,
- "CREATE INDEX \"index_users_on_age_DESC_email\" ON \"users\" (\"age\" DESC, \"email\")",
- db.create(index: users, on: age.desc, email)
- )
- }
-
-// func test_index_withFilter_executesPartialIndexStatementWithWhereClause() {
-// if SQLITE_VERSION >= "3.8" {
-// CreateUsersTable(db)
-// ExpectExecution(db,
-// "CREATE INDEX index_users_on_age ON \"users\" (age) WHERE admin",
-// db.create(index: users.filter(admin), on: age)
-// )
-// }
-// }
-
- func test_dropIndex_dropsIndex() {
- CreateUsersTable(db)
- db.create(index: users, on: email)
-
- ExpectExecution(db, "DROP INDEX \"index_users_on_email\"", db.drop(index: users, on: email))
- ExpectExecution(db, "DROP INDEX IF EXISTS \"index_users_on_email\"", db.drop(index: users, ifExists: true, on: email))
- }
-
- func test_createView_withQuery_createsViewWithQuery() {
- CreateUsersTable(db)
- ExpectExecution(db,
- "CREATE VIEW \"emails\" AS SELECT \"email\" FROM \"users\"",
- db.create(view: db["emails"], from: users.select(email))
- )
- ExpectExecution(db,
- "CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"",
- db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email))
- )
- }
-
- func test_dropView_dropsView() {
- CreateUsersTable(db)
- db.create(view: db["emails"], from: users.select(email))
-
- ExpectExecution(db, "DROP VIEW \"emails\"", db.drop(view: db["emails"]))
- ExpectExecution(db, "DROP VIEW IF EXISTS \"emails\"", db.drop(view: db["emails"], ifExists: true))
- }
-
- func test_quotedIdentifiers() {
- let table = db["table"]
- let column = Expression("My lil' primary key, \"Kiwi\"")
-
- ExpectExecution(db, "CREATE TABLE \"table\" (\"My lil' primary key, \"\"Kiwi\"\"\" INTEGER NOT NULL)",
- db.create(table: db["table"]) { $0.column(column) }
- )
- }
-
-}
diff --git a/SQLite Common Tests/StatementTests.swift b/SQLite Common Tests/StatementTests.swift
deleted file mode 100644
index 0f9bd12b..00000000
--- a/SQLite Common Tests/StatementTests.swift
+++ /dev/null
@@ -1,199 +0,0 @@
-import XCTest
-import SQLite
-
-class StatementTests: XCTestCase {
-
- let db = Database()
-
- override func setUp() {
- super.setUp()
-
- CreateUsersTable(db)
- }
-
- func test_bind_withVariadicParameters_bindsParameters() {
- let stmt = db.prepare("SELECT ?, ?, ?, ?, ?")
- ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in
- withBlob { blob in
- stmt.bind(false, 1, 2.0, "3", blob).run()
- return
- }
- }
- }
-
- func test_bind_withArrayOfParameters_bindsParameters() {
- let stmt = db.prepare("SELECT ?, ?, ?, ?, ?")
- ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in
- withBlob { blob in
- stmt.bind([false, 1, 2.0, "3", blob]).run()
- return
- }
- }
- }
-
- func test_bind_withNamedParameters_bindsParameters() {
- let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4, ?5")
- ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in
- withBlob { blob in
- stmt.bind(["?1": false, "?2": 1, "?3": 2.0, "?4": "3", "?5": blob]).run()
- return
- }
- }
- }
-
- func test_bind_withBlob_bindsBlob() {
- let stmt = db.prepare("SELECT ?")
- ExpectExecutions(db, ["SELECT x'34'": 1]) { _ in
- withBlob { blob in
- stmt.bind(blob).run()
- return
- }
- }
- }
-
- func test_bind_withBool_bindsInt() {
- let stmt = db.prepare("SELECT ?")
- ExpectExecutions(db, ["SELECT 0": 1, "SELECT 1": 1]) { _ in
- stmt.bind(false).run()
- stmt.bind(true).run()
- }
- }
-
- func test_bind_withDouble_bindsDouble() {
- let stmt = db.prepare("SELECT ?")
- ExpectExecutions(db, ["SELECT 2.0": 1]) { _ in
- stmt.bind(2.0).run()
- return
- }
- }
-
- func test_bind_withInt_bindsInt() {
- let stmt = db.prepare("SELECT ?")
- ExpectExecutions(db, ["SELECT 3": 1]) { _ in
- stmt.bind(3).run()
- return
- }
- }
-
- func test_bind_withString() {
- let stmt = db.prepare("SELECT ?")
- ExpectExecutions(db, ["SELECT '4'": 1]) { _ in
- stmt.bind("4").run()
- return
- }
- }
-
- func test_run_withNoParameters() {
- let stmt = db.prepare(
- "INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)"
- )
- stmt.run()
- XCTAssertEqual(1, db.totalChanges)
- }
-
- func test_run_withVariadicParameters() {
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- stmt.run("alice@example.com", true)
- XCTAssertEqual(1, db.totalChanges)
- }
-
- func test_run_withArrayOfParameters() {
- let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
- stmt.run(["alice@example.com", true])
- XCTAssertEqual(1, db.totalChanges)
- }
-
- func test_run_withNamedParameters() {
- let stmt = db.prepare(
- "INSERT INTO users (email, admin) VALUES ($email, $admin)"
- )
- stmt.run(["$email": "alice@example.com", "$admin": true])
- XCTAssertEqual(1, db.totalChanges)
- }
-
- func test_scalar_withNoParameters() {
- let zero = db.prepare("SELECT 0")
- XCTAssertEqual(0, zero.scalar() as Int)
- }
-
- func test_scalar_withNoParameters_retainsBindings() {
- let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?", 21)
- XCTAssertEqual(0, count.scalar() as Int)
-
- InsertUser(db, "alice", age: 21)
- XCTAssertEqual(1, count.scalar() as Int)
- }
-
- func test_scalar_withVariadicParameters() {
- InsertUser(db, "alice", age: 21)
- let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?")
- XCTAssertEqual(1, count.scalar(21) as Int)
- }
-
- func test_scalar_withArrayOfParameters() {
- InsertUser(db, "alice", age: 21)
- let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?")
- XCTAssertEqual(1, count.scalar([21]) as Int)
- }
-
- func test_scalar_withNamedParameters() {
- InsertUser(db, "alice", age: 21)
- let count = db.prepare("SELECT count(*) FROM users WHERE age >= $age")
- XCTAssertEqual(1, count.scalar(["$age": 21]) as Int)
- }
-
- func test_scalar_withParameters_updatesBindings() {
- InsertUser(db, "alice", age: 21)
- let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?")
- XCTAssertEqual(1, count.scalar(21) as Int)
- XCTAssertEqual(0, count.scalar(22) as Int)
- }
-
- func test_scalar_boolReturnValue() {
- InsertUser(db, "alice", admin: true)
- XCTAssertEqual(true, db.scalar("SELECT admin FROM users") as Bool)
- }
-
- func test_scalar_doubleReturnValue() {
- XCTAssertEqual(2.0, db.scalar("SELECT 2.0") as Double)
- }
-
- func test_scalar_intReturnValue() {
- XCTAssertEqual(3, db.scalar("SELECT 3") as Int)
- }
-
- func test_scalar_stringReturnValue() {
- XCTAssertEqual("4", db.scalar("SELECT '4'") as String)
- }
-
- func test_generate_allowsIteration() {
- InsertUsers(db, "alice", "betsy", "cindy")
- var count = 0
- for row in db.prepare("SELECT id FROM users") {
- XCTAssertEqual(1, row.count)
- count++
- }
- XCTAssertEqual(3, count)
- }
-
- func test_row_returnsArrayOfValues() {
- InsertUser(db, "alice")
- let stmt = db.prepare("SELECT id, email FROM users")
- stmt.next()
-
- let row = stmt.row!
-
- XCTAssertEqual(1, row[0] as Int)
- XCTAssertEqual("alice@example.com", row[1] as String)
- }
-
-}
-
-func withBlob(block: Blob -> ()) {
- let length = 1
- let buflen = Int(length) + 1
- let buffer = UnsafeMutablePointer<()>.alloc(buflen)
- memcpy(buffer, "4", UInt(length))
- block(Blob(bytes: buffer, length: length))
- buffer.dealloc(buflen)
-}
diff --git a/SQLite Common Tests/TestHelper.swift b/SQLite Common Tests/TestHelper.swift
deleted file mode 100644
index b9d36e6e..00000000
--- a/SQLite Common Tests/TestHelper.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-import SQLite
-import XCTest
-
-func Trace(SQL: String) {
- println(SQL)
-}
-
-func CreateUsersTable(db: Database) {
- db.execute(
- "CREATE TABLE \"users\" (" +
- "id INTEGER PRIMARY KEY, " +
- "email TEXT NOT NULL UNIQUE, " +
- "age INTEGER, " +
- "salary REAL, " +
- "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " +
- "manager_id INTEGER, " +
- "FOREIGN KEY(manager_id) REFERENCES users(id)" +
- ")"
- )
- db.trace(Trace)
-}
-
-func InsertUser(db: Database, name: String, age: Int? = nil, admin: Bool? = false) -> Statement {
- return db.run(
- "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)",
- ["\(name)@example.com", age, admin]
- )
-}
-
-func InsertUsers(db: Database, names: String...) {
- for name in names { InsertUser(db, name) }
-}
-
-
-func ExpectExecutions(db: Database, statements: [String: Int], block: Database -> ()) {
- var fulfilled = [String: Int]()
- for (SQL, _) in statements { fulfilled[SQL] = 0 }
- db.trace { SQL in
- Trace(SQL)
- if let count = fulfilled[SQL] { fulfilled[SQL] = count + 1 }
- }
-
- block(db)
-
- XCTAssertEqual(statements, fulfilled)
-}
-
-func ExpectExecution(db: Database, SQL: String, statement: @autoclosure () -> Statement) {
- ExpectExecutions(db, [SQL: 1]) { _ in
- statement()
- return
- }
-}
-
-func ExpectExecution(db: Database, SQL: String, query: Query) {
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
-}
diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift
deleted file mode 100644
index dbfe55a3..00000000
--- a/SQLite Common/Database.swift
+++ /dev/null
@@ -1,367 +0,0 @@
-//
-// SQLite.Database
-// Copyright (c) 2014 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
-
-/// A connection (handle) to a SQLite database.
-public final class Database {
-
- internal let handle: COpaquePointer = nil
-
- /// Whether or not the database was opened in a read-only state.
- public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 }
-
- /// Instantiates a new connection to a database.
- ///
- /// :param: path The path to the database. Creates a new database if it
- /// doesn’t already exist (unless in read-only mode). Pass
- /// ":memory:" (or nothing) to open a new, in-memory
- /// database. Pass "" (or nil) to open a temporary,
- /// file-backed database. Default: ":memory:".
- ///
- /// :param: readonly Whether or not to open the database in a read-only
- /// state. Default: false.
- ///
- /// :returns: A new database connection.
- public init(_ path: String? = ":memory:", readonly: Bool = false) {
- let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE
- try(sqlite3_open_v2(path ?? "", &handle, flags, nil))
- }
-
- deinit { try(sqlite3_close(handle)) } // sqlite3_close_v2 in Yosemite/iOS 8?
-
- // MARK: -
-
- /// The last row ID inserted into the database via this connection.
- public var lastID: Int? {
- let lastID = Int(sqlite3_last_insert_rowid(handle))
- return lastID == 0 ? nil : lastID
- }
-
- /// The last number of changes (inserts, updates, or deletes) made to the
- /// database via this connection.
- public var lastChanges: Int {
- return 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)) }
-
- // MARK: - Execute
-
- /// Executes a batch of SQL statements.
- ///
- /// :param: SQL A batch of zero or more semicolon-separated SQL statements.
- public func execute(SQL: String) {
- try(sqlite3_exec(handle, SQL, nil, nil, nil))
- }
-
- // MARK: - Prepare
-
- /// Prepares a single SQL statement (with optional parameter bindings).
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A list of parameters to bind to the statement.
- ///
- /// :returns: A prepared statement.
- public func prepare(statement: String, _ bindings: Binding?...) -> Statement {
- if !bindings.isEmpty { return prepare(statement, bindings) }
- return Statement(self, statement)
- }
-
- /// Prepares a single SQL statement and binds parameters to it.
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A list of parameters to bind to the statement.
- ///
- /// :returns: A prepared statement.
- public func prepare(statement: String, _ bindings: [Binding?]) -> Statement {
- return prepare(statement).bind(bindings)
- }
-
- /// Prepares a single SQL statement and binds parameters to it.
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A dictionary of named parameters to bind to the
- /// statement.
- ///
- /// :returns: A prepared statement.
- public func prepare(statement: String, _ bindings: [String: Binding?]) -> Statement {
- return prepare(statement).bind(bindings)
- }
-
- // MARK: - Run
-
- /// Runs a single SQL statement (with optional parameter bindings).
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A list of parameters to bind to the statement.
- ///
- /// :returns: The statement.
- public func run(statement: String, _ bindings: Binding?...) -> Statement {
- return run(statement, bindings)
- }
-
- /// Prepares, binds, and runs a single SQL statement.
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A list of parameters to bind to the statement.
- ///
- /// :returns: The statement.
- public func run(statement: String, _ bindings: [Binding?]) -> Statement {
- return prepare(statement).run(bindings)
- }
-
- /// Prepares, binds, and runs a single SQL statement.
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A dictionary of named parameters to bind to the
- /// statement.
- ///
- /// :returns: The statement.
- public func run(statement: String, _ bindings: [String: Binding?]) -> Statement {
- return prepare(statement).run(bindings)
- }
-
- // MARK: - Scalar
-
- /// Runs a single SQL statement (with optional parameter bindings),
- /// returning the first value of the first row.
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A list of parameters to bind to the statement.
- ///
- /// :returns: The first value of the first row returned.
- public func scalar(statement: String, _ bindings: Binding?...) -> Binding? {
- return scalar(statement, bindings)
- }
-
- /// Prepares, binds, and runs a single SQL statement, returning the first
- /// value of the first row.
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A list of parameters to bind to the statement.
- ///
- /// :returns: The first value of the first row returned.
- public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? {
- return prepare(statement).scalar(bindings)
- }
-
- /// Prepares, binds, and runs a single SQL statement, returning the first
- /// value of the first row.
- ///
- /// :param: statement A single SQL statement.
- ///
- /// :param: bindings A dictionary of named parameters to bind to the
- /// statement.
- ///
- /// :returns: The first value of the first row returned.
- public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? {
- return prepare(statement).scalar(bindings)
- }
-
- // MARK: - Transactions
-
- /// The mode in which a transaction acquires a lock.
- public enum TransactionMode: String {
-
- /// Defers locking the database till the first read/write executes.
- case Deferred = "DEFERRED"
-
- /// Immediately acquires a reserved lock on the database.
- case Immediate = "IMMEDIATE"
-
- /// Immediately acquires an exclusive lock on all databases.
- case Exclusive = "EXCLUSIVE"
-
- }
-
- /// Runs a series of statements in a transaction. The first statement to
- /// fail will short-circuit the rest and roll back the changes. A successful
- /// transaction will automatically be committed.
- ///
- /// :param: statements Statements to run in the transaction.
- ///
- /// :returns: The last statement executed, successful or not.
- public func transaction(statements: (@autoclosure () -> Statement)...) -> Statement {
- return transaction(.Deferred, statements)
- }
-
- /// Runs a series of statements in a transaction. The first statement to
- /// fail will short-circuit the rest and roll back the changes. A successful
- /// transaction will automatically be committed.
- ///
- /// :param: mode The mode in which the transaction will acquire a
- /// lock.
- ///
- /// :param: statements Statements to run in the transaction.
- ///
- /// :returns: The last statement executed, successful or not.
- public func transaction(mode: TransactionMode, _ statements: (@autoclosure () -> Statement)...) -> Statement {
- return transaction(mode, statements)
- }
-
- private func transaction(mode: TransactionMode, _ statements: [@autoclosure () -> Statement]) -> Statement {
- var transaction = run("BEGIN \(mode.rawValue) TRANSACTION")
- // FIXME: rdar://15217242 // for statement in statements { transaction = transaction && statement() }
- for idx in 0.. Statement)...) -> Statement {
- let transaction = savepoint("\(++saveName)", statements)
- --saveName
- return transaction
- }
-
- /// Runs a series of statements in a new savepoint. The first statement to
- /// fail will short-circuit the rest and roll back the changes. A successful
- /// savepoint will automatically be committed.
- ///
- /// :param: name The name of the savepoint.
- ///
- /// :param: statements Statements to run in the savepoint.
- ///
- /// :returns: The last statement executed, successful or not.
- public func savepoint(name: String, _ statements: (@autoclosure () -> Statement)...) -> Statement {
- return savepoint(name, statements)
- }
-
- private func savepoint(name: String, _ statements: [@autoclosure () -> Statement]) -> Statement {
- let quotedName = quote(literal: name)
- var savepoint = run("SAVEPOINT \(quotedName)")
- // FIXME: rdar://15217242 // for statement in statements { savepoint = savepoint && statement() }
- for idx in 0.. Bool)?) {
- if let callback = callback {
- SQLiteBusyHandler(handle) { callback(Int($0)) ? 1 : 0 }
- } else {
- SQLiteBusyHandler(handle, nil)
- }
- }
-
- /// Sets a handler to call when a statement is executed with the compiled
- /// SQL.
- ///
- /// :param: callback This block is executed as a statement is executed with
- /// the compiled SQL as an argument. E.g., pass println to
- /// act as a logger.
- public func trace(callback: (String -> ())?) {
- if let callback = callback {
- SQLiteTrace(handle) { callback(String.fromCString($0)!) }
- } else {
- SQLiteTrace(handle, nil)
- }
- }
-
- // MARK: - Error Handling
-
- /// Returns the last error produced on this connection.
- public var lastError: String {
- return String.fromCString(sqlite3_errmsg(handle))!
- }
-
- internal func try(block: @autoclosure () -> Int32) {
- perform { if block() != SQLITE_OK { assertionFailure("\(self.lastError)") } }
- }
-
- // MARK: - Threading
-
- private let queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL)
-
- internal func perform(block: () -> ()) { dispatch_sync(queue, block) }
-
-}
-
-extension Database: DebugPrintable {
-
- public var debugDescription: String {
- return "Database(\(String.fromCString(sqlite3_db_filename(handle, nil))!))"
- }
-
-}
-
-internal func quote(#literal: String) -> String {
- return quote(literal, "'")
-}
-
-internal func quote(#identifier: String) -> String {
- return quote(identifier, "\"")
-}
-
-private func quote(string: String, mark: Character) -> String {
- let escaped = Array(string).reduce("") { string, character in
- string + (character == mark ? "\(mark)\(mark)" : "\(character)")
- }
- return "\(mark)\(escaped)\(mark)"
-}
diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift
deleted file mode 100644
index 94db6ff9..00000000
--- a/SQLite Common/Expression.swift
+++ /dev/null
@@ -1,806 +0,0 @@
-//
-// SQLite.Expression
-// Copyright (c) 2014 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.
-//
-
-public struct Expression {
-
- public let SQL: String
- public let bindings: [Binding?]
-
- public init(literal SQL: String = "", _ bindings: [Binding?] = []) {
- (self.SQL, self.bindings) = (SQL, bindings)
- }
-
- public init(_ identifier: String) {
- self.init(literal: quote(identifier: identifier))
- }
-
- public init(_ expression: Expression) {
- self.init(literal: expression.SQL, expression.bindings)
- }
-
- public init(value: V?) {
- self.init(binding: value?.datatypeValue)
- }
-
- private init(binding: Binding?) {
- self.init(literal: "?", [binding])
- }
-
- public var asc: Expression<()> {
- return join(" ", [self, Expression(literal: "ASC")])
- }
-
- public var desc: Expression<()> {
- return join(" ", [self, Expression(literal: "DESC")])
- }
-
- // naïve compiler for statements that can't be bound, e.g., CREATE TABLE
- internal func compile() -> String {
- var idx = 0
- return Array(SQL).reduce("") { SQL, character in
- let string = String(character)
- return SQL + (string == "?" ? transcode(self.bindings[idx++]) : string)
- }
- }
-
-}
-
-public protocol Expressible {
-
- var expression: Expression<()> { get }
-
-}
-
-extension Bool: Expressible {
-
- public var expression: Expression<()> {
- return Expression(binding: self)
- }
-
-}
-
-extension Double: Expressible {
-
- public var expression: Expression<()> {
- return Expression(binding: self)
- }
-
-}
-
-extension Int: Expressible {
-
- public var expression: Expression<()> {
- return Expression(binding: self)
- }
-
-}
-
-extension String: Expressible {
-
- public var expression: Expression<()> {
- return Expression(binding: self)
- }
-
-}
-
-extension Expression: Expressible {
-
- public var expression: Expression<()> {
- return Expression<()>(self)
- }
-
-}
-
-// MARK: - Expressions
-
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) }
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) }
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) }
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) }
-public func + (lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) }
-public func + (lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) }
-public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs }
-public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs }
-
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(binding: rhs) }
-public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(binding: rhs) }
-public func + (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs }
-public func + (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs }
-
-public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(binding: rhs) }
-public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(binding: rhs) }
-public func - (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) - rhs }
-public func - (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) - rhs }
-
-public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(binding: rhs) }
-public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(binding: rhs) }
-public func * (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) * rhs }
-public func * (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) * rhs }
-
-public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(binding: rhs) }
-public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(binding: rhs) }
-public func / (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) / rhs }
-public func / (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) / rhs }
-
-public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) }
-public func % (lhs: Expression, rhs: Expression