{"id":19881279,"url":"https://github.com/nike-inc/sqift","last_synced_at":"2025-08-30T07:13:16.304Z","repository":{"id":26337414,"uuid":"103461476","full_name":"Nike-Inc/SQift","owner":"Nike-Inc","description":"Powerful Swift wrapper for SQLite","archived":false,"fork":false,"pushed_at":"2025-01-28T17:39:12.000Z","size":1000,"stargazers_count":171,"open_issues_count":2,"forks_count":21,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-07-30T08:38:03.209Z","etag":null,"topics":["backup","carthage","cocoapods","database","database-migrations","ios","macos","sqlite","swift","tvos","watchos","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Nike-Inc.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-09-13T23:19:58.000Z","updated_at":"2025-07-08T06:46:37.000Z","dependencies_parsed_at":"2024-12-25T09:11:23.631Z","dependency_job_id":"742d66e4-e7eb-46c4-a52f-e06b81ef88f7","html_url":"https://github.com/Nike-Inc/SQift","commit_stats":{"total_commits":324,"total_committers":13,"mean_commits":"24.923076923076923","dds":0.404320987654321,"last_synced_commit":"276010dd7c4b0818c4670b37c19d454cd05879c5"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/Nike-Inc/SQift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FSQift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FSQift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FSQift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FSQift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nike-Inc","download_url":"https://codeload.github.com/Nike-Inc/SQift/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FSQift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272817380,"owners_count":24997975,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backup","carthage","cocoapods","database","database-migrations","ios","macos","sqlite","swift","tvos","watchos","xcode"],"created_at":"2024-11-12T17:13:45.617Z","updated_at":"2025-08-30T07:13:16.253Z","avatar_url":"https://github.com/Nike-Inc.png","language":"Swift","readme":"# SQift\n\n[![Build Status](https://travis-ci.org/Nike-Inc/SQift.svg?branch=master)](https://travis-ci.org/Nike-Inc/SQift)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/SQift.svg)](https://img.shields.io/cocoapods/v/SQift.svg)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Platform](https://img.shields.io/cocoapods/p/SQift.svg?style=flat)](http://cocoadocs.org/docsets/SQift)\n\nSQift is a lightweight Swift wrapper for SQLite.\n\n## Features\n\n- [X] On-Disk, In-Memory and Temporary Database Connections\n- [X] SQL Statement Execution\n- [X] Generic Parameter Binding and Value Extraction\n- [X] Codable and Codable Collection Bindings\n- [X] Simple Query APIs for Values, Rows, and Collections\n- [X] Transactions and Savepoints\n- [X] Tracing and Trace Event Support\n- [X] Scalar and Aggregate Functions\n- [X] Commit, Rollback, Update, Authorizer Hooks\n- [X] WAL Checkpointing\n- [X] ConnectionQueue for Serial Execution per Database Connection\n- [X] ConnectionPool for Parallel Execution of Read-Only Connections\n- [X] Top-Level Database to Simplify Thread-Safe Reads and Writes\n- [X] Database Migration Support\n- [X] Database Backups\n- [x] Comprehensive Unit Test Coverage\n- [x] Complete Documentation\n\n## Requirements\n\n- iOS 10.0+, macOS 10.12+, tvOS 10.0+, watchOS 3.0+\n- Xcode 10.2+\n- Swift 5.0+\n\n## Migration Guides\n\n- [SQift 4.0 Migration Guide](Documentation/SQift%204.0%20Migration%20Guide.md)\n\n## Communication\n\n- Need help? Open an issue.\n- Have a feature request? Open an issue.\n- Find a bug? Open an issue.\n- Want to contribute? Fork the repo and submit a pull request.\n\n---\n\n## Installation\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/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.\n\n\u003eXcode 11+ is required to provide SwiftPM support for iOS, watchOS, and tvOS platforms.\nIn Xcode menu `File -\u003e Swift Packages -\u003e Add Package Dependency...` enter repository URL `https://github.com/nike-inc/SQift.git`.\n\nOr, alternatively, in a `Package.swift` file:\n\n```swift\nlet package = Package(\n    name: \"MyPackage\",\n    dependencies: [\n\t\t.package(url: \"https://github.com/nike-inc/SQift\", from: \"5.1\"),\n    ],\n    products: [\n        // ...\n    ],\n    targets: [\n        .target(\n            name: \"YourTarget\",\n            dependencies: [\"SQift\"]\n        ),\n    // ...\n```\n\n### CocoaPods\n\n[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects.\nYou can install it with the following command:\n\n```bash\n$ gem install cocoapods\n```\n\n\u003e CocoaPods 1.3+ is required to build SQift.\n\nTo integrate SQift into your Xcode project using CocoaPods, specify it in your `Podfile`:\n\n```ruby\nplatform :ios, '11.0'\nuse_frameworks!\n\ntarget '\u003cYour Target Name\u003e' do\n    pod 'SQift', '~\u003e 4.0'\nend\n```\n\nThen, run the following command:\n\n```bash\n$ pod install\n```\n\n### Carthage\n\n[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.\n\nYou can install Carthage with [Homebrew](http://brew.sh/) using the following command:\n\n```bash\n$ brew update\n$ brew install carthage\n```\n\nTo integrate SQift into your Xcode project using Carthage, specify it in your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile):\n\n```\ngithub \"Nike-Inc/SQift\" ~\u003e 4.0\n```\n\nRun `carthage update` to build the framework and drag the built `SQift.framework` into your Xcode project.\n\n---\n\n## Usage\n\nSQift is designed to make it as easy as possible to work with SQLite from Swift.\nIt does not, however, eliminate the need to understand how SQLite actually works.\nBefore diving into SQift, it is recommended to first have a firm grasp on what SQLite is, how it works and how to use it.\n\n- [SQLite Docs](https://www.sqlite.org/docs.html)\n- [SQLite Tutorial](http://zetcode.com/db/sqlite/)\n\nSQift heavily leverages the new error handling model released with Swift 2.0.\nIt was designed from the ground up to throw in all applicable cases.\nThis makes it easy to wrap all your SQift calls in the do-catch paradigm.\n\n### Creating a Database Connection\n\nCreating a database connection is simple.\n\n```swift\nlet onDiskConnection = try Connection(storageLocation: .onDisk(\"path_to_db\"))\nlet inMemoryConnection = try Connection(storageLocation: .inMemory)\nlet tempConnection = try Connection(storageLocation: .temporary)\n```\n\nThere are also convenience parameters to make it easy to customize the flags when initializing the database connection:\n\n```swift\nlet connection = try Connection(\n    storageLocation: .onDisk(\"path_to_db\"),\n    readOnly: true,\n    multiThreaded: false,\n    sharedCache: false\n)\n```\n\n\u003e In most cases, the default values are preferred.\n\u003e For more details about creating a database connection, please refer to the SQLite [documentation](https://www.sqlite.org/c3ref/open.html).\n\n### Executing Statements\n\nTo execute a SQL statement on the `Connection`, you need to first create a `Connection`, then call `execute`.\n\n```swift\nlet connection = try Connection(storageLocation: .onDisk(\"path_to_db\"))\n\ntry connection.execute(\"PRAGMA foreign_keys = true\")\ntry connection.execute(\"PRAGMA journal_mode = WAL\")\n\ntry connection.execute(\"CREATE TABLE cars(id INTEGER PRIMARY KEY, name TEXT, price INTEGER)\")\n\ntry connection.execute(\"INSERT INTO cars VALUES(1, 'Audi', 52642)\")\ntry connection.execute(\"INSERT INTO cars VALUES(2, 'Mercedes', 57127)\")\n\ntry connection.execute(\"UPDATE cars SET name = 'Honda' where id = 1\")\ntry connection.execute(\"UPDATE cars SET price = 61_999 where name = 'Mercedes'\")\n\ntry connection.execute(\"DELETE FROM cars where name = 'Mercedes'\")\ntry connection.execute(\"DROP TABLE cars\")\n```\n\n### Bindings\n\nMost Swift data types cannot be directly stored inside the database.\nThey need to be converted to a data type supported by SQLite.\nIn order to support moving Swift data types into the database and back out again, SQift leverage three powerful protocols: `Bindable`, `Extractable`, and `Binding`.\n\n#### Bindable Protocol\n\nThe `Bindable` protocol handles converting Swift data types into a `BindingValue` enumeration type which can be stored in the database.\n\n```swift\npublic protocol Bindable {\n    var bindingValue: BindingValue { get }\n}\n```\n\n#### Extractable Protocol\n\nWhile the `Bindable` protocol helps move Swift data types into the database, the `Extractable` protocol allows SQift to extract the values from the database `Connection` and convert them back to the requested Swift data type.\n\n```swift\npublic protocol Extractable {\n    typealias BindingType\n    typealias DataType = Self\n    static func fromBindingValue(_ value: Any) -\u003e DataType?\n}\n```\n\n#### Binding Protocol\n\nTo extend Swift data types to be able to be inserted into the database and also be extracted safely, the `Binding` protocol forces the data type to conform to both the `Bindable` and `Extractable` protocols.\n\n```swift\npublic protocol Binding: Bindable, Extractable {}\n```\n\nIn order to make it as easy as possible to use SQift, SQift extends the following Swift data types to conform to the `Binding` protocol:\n\n- **NULL:** `NSNull`\n- **INTEGER:** `Bool`, `Int8`, `Int16`, `Int32`, `Int64`, `Int`, `UInt8`, `UInt16`, `UInt32`, `UInt64`, `UInt`\n- **REAL:** `Float`, `Double`\n- **TEXT:** `String`, `URL`, `Date`\n- **BLOB:** `Data`\n\n\u003e Additional Swift data types can easily add `Bindable` protocol conformance if necessary.\n\n### Binding Parameters to a Statement\n\nSafely binding parameters to a `Statement` is easy thanks to the `Binding` protocol.\nFirst you need to `prepare` a `Statement` object, then `bind` the parameters and `run` it using method chaining.\n\n```swift\nlet connection = try Connection(storageLocation: .onDisk(\"path_to_db\"))\n\ntry connection.prepare(\"INSERT INTO cars VALUES(?, ?, ?)\").bind(1, \"Audi\", 52_642).run()\ntry connection.prepare(\"INSERT INTO cars VALUES(:id, :name, :price)\").bind([\":id\": 1, \":name\": \"Audi\", \":price\": 52_642]).run()\n```\n\nThere are also convenience methods on the `Connection` for preparing a `Statement`, binding parameters and running it all in a single method named `run`.\n\n```swift\nlet connection = try Connection(storageLocation: .onDisk(\"path_to_db\"))\n\ntry connection.run(\"INSERT INTO cars VALUES(?, ?, ?)\", 1, \"Audi\", 52_642)\ntry connection.run(\"INSERT INTO cars VALUES(:id, :name, :price)\", parameters: [\":id\": 1, \":name\": \"Audi\", \":price\": 52_642])\n```\n\n\u003e It is very important to properly escape all parameter values provided in a SQL statement.\n\u003e When in doubt, always use the provided bind functionality.\n\n### Querying Data\n\nQuerying data from the database makes extensive use of the `Binding` protocol.\nIt extracts the original values from the database, then uses the `Binding` protocol along with generics to convert the final Swift type.\n\n#### Single Values\n\nExtracting a single value from the database can be done using the `query` API.\n\n```swift\nlet synchronous: Int? = try db.query(\"PRAGMA synchronous\")\nlet minPrice: UInt? = try db.query(\"SELECT avg(price) FROM cars WHERE price \u003e ?\", 40_000)\n```\n\n#### Multiple Values\n\nYou can also use the `query` API to extract multiple values through the `Row` type.\n\n```swift\nif let row = try db.query(\"SELECT name, type, price FROM cars WHERE type = ? LIMIT 1\", \"Sedan\") {\n    let name: String = row[0]\n    let type: String = row[1]\n    let price: UInt = row[2]\n}\n```\n\nThe values can be accessed by index or by name.\n\n```swift\nif let row = try db.query(\"SELECT name, type, price FROM cars WHERE type = ? LIMIT 1\", \"Sedan\") {\n    let name: String = row[\"name\"]\n    let type: String = row[\"type\"]\n    let price: UInt = row[\"price\"]\n}\n```\n\n\u003e The `Row` type supports both optional and non-optional value extraction through index and name subscripts.\n\u003e The non-optional subscripts are certainly the safest, but not always the most convenient.\n\u003e It is up to you to decide which makes more sense to use in each situation.\n\u003e Generally, try to use the optional types where the SQL statement is decoupled from the row value extraction.\n\u003e In these cases, the `ExpressibleByRow` type can be useful to help handle optionality.\n\n#### ExpressibleByRow Types\n\nIn many cases, you want to construct model objects from a row in a result set.\nThe `ExpressibleByRow` type was designed for this use case.\n\n```swift\nprotocol ExpressibleByRow {\n    init(row: Row) throws\n}\n```\n\nTo make use of the `ExpressibleByRow` protocol, first create your model object and conform to the protocol.\n\n```swift\nstruct Car {\n    let name: String\n    let type: String\n    let price: UInt\n}\n\nextension Car: ExpressibleByRow {\n    init(row: Row) throws {\n        guard\n            let name: String = row[0],\n            let type: String = row[1],\n            let price: UInt = row[2]\n        else {\n            throw ExpressibleByRowError(type: Car.self, row: row)\n        }\n\n        self.name = name\n        self.type = type\n        self.price = price\n    }\n}\n```\n\nThen, you can use the `query` API to automatically convert the result set into a `Car`.\n\n```swift\nlet car: Car? = try db.query(\"SELECT name, type, price FROM cars WHERE type = ? LIMIT 1\", \"Sedan\")\n```\n\n#### Multiple Rows\n\nThe `query` APIs also support querying for result sets with multiple rows.\n\n```swift\nlet names: [String] = try db.query(\"SELECT name FROM cars\")\nlet cars: [Car] = try db.query(\"SELECT * FROM cars WHERE price \u003e ?\", [20_000])\n```\n\nIn addition to array result sets, you can also create dictionary result sets.\n\n```swift\nlet sql = \"SELECT name, price FROM cars WHERE price \u003e ?\"\nlet prices: [String: UInt] = try db.query(sql, 20_000) { ($0[0], $0[1]) }\n```\n\n### Dates\n\nSQift has full binding support for `Date` objects which allows you to easily leverage the builtin date functionality in SQLite.\nYou can insert dates easily into the database, run queries against them, and then extract them back out.\nSQift handles this through the `Date` binding which leverages the `bindingDateFormatter`.\nBy default, all `Date` types are stored in the database as `TEXT`, so make sure you map your column types accordingly.\n\n```swift\nlet date1975: Date!\nlet date1983: Date!\nlet date1992: Date!\nlet date2001: Date!\n\ntry connection.execute(\n\t\"CREATE TABLE cars(id INTEGER PRIMARY KEY, name TEXT, release_date TEXT)\"\n)\n\ntry connection.execute(\"INSERT INTO cars(?, ?)\", \"70s car\", date1975)\ntry connection.execute(\"INSERT INTO cars(?, ?)\", \"80s car\", date1983)\ntry connection.execute(\"INSERT INTO cars(?, ?)\", \"90s car\", date1992)\ntry connection.execute(\"INSERT INTO cars(?, ?)\", \"00s car\", date2001)\n```\n\nOnce you have your dates stored in the database, you can run date range queries to narrow down your data.\n\n```swift\nlet date1980: Date!\nlet date2000: Date!\n\nlet carCount: Int = try connection.query(\n    \"SELECT count(*) FROM cars WHERE release_date \u003e= date(?) AND release_date \u003c= date(?)\",\n    date1980, \n    date2000\n)\n\nprint(\"Total Cars from the 80s and 90s: \\(carCount)\") // should equal 2\n```\n\n\u003e You can swap the default date formatting, but be careful when doing so.\n\u003e You need to make sure the new date format complies with the SQLite [requirements](https://www.sqlite.org/lang_datefunc.html) so date range queries will continue to work as expected.\n\nYou can also extract dates out of each row as an `Date`.\n\n```swift\nlet sql = \"SELECT release_date WHERE name = ? LIMIT 1\"\nlet releaseDate: Date = try connection.query(sql, \"80s car\")\n```\n\n### Custom Bindings\n\nSQift has support for many common primitive types, but what about when you want to store a custom type in the database?\nThis is where custom bindings come into play.\nAll you need to do to store your own custom types in the database is conform to the `Binding` protocol.\n\n```swift\nenum DownloadState: Int {\n    case pending, downloading, downloaded, failed\n}\n\nextension DownloadState: Binding {\n    typealias BindingType = Int64\n\n    var bindingValue: BindingValue { return .integer(Int64(rawValue)) }\n\n    static func fromBindingValue(_ value: Any) -\u003e AssetType? {\n        guard let value = value as? Int64, let rawValue = Int(exactly: value) else { return nil }\n        return DownloadState(rawValue: rawValue)\n    }\n}\n\ntry connection.execute(\"CREATE TABLE downloads(name TEXT PRIMARY KEY, state INTEGER NOT NULL)\")\ntry connection.run(\"INSERT INTO downloads VALUES(?, ?)\", \"image1\", DownloadState.pending)\n\nif let state: DownloadState? = try connection.query(\"SELECT state FROM downloads WHERE name = 'image1') {\n    print(state)\n}\n\n// Output\n// DownloadState.pending\n```\n\n#### Codable Bindings\n\nSQift also supports `Codable` bindings out-of-the-box.\nFor example, let's say we have a `Person` object that is `Codable`.\nWe can store `Person` instances directly in the database without having to create a custom binding.\n\n```swift\nstruct Employee {\n    let id: Int64\n    let firstName: String\n    let lastName: String\n    let age: UInt\n}\n\nlet phil = Person(id: 1, firstName: \"Phil\", lastName: \"Knight\", age: 79)\n\ntry connection.execute(\"CREATE TABLE employees(id INTEGER PRIMARY KEY, employee BLOB NOT NULL)\")\ntry connection.run(\"INSERT INTO employees(employee) VALUES(?)\", phil)\n\nif let employee1: Employee? = try connection.query(\"SELECT employee FROM employees WHERE id = 1) {\n    print(employee.firstName)\n}\n\n// Output\n// \"phil\"\n```\n\n\u003e You need to think through whether it makes sense for you to use `Codable` bindings or not.\n\u003e While they are very convenient, the information inside them cannot be queried.\n\u003e In the above example, you could not run a query such as `SELECT count(1) FROM employees where firstName = 'Phil'`.\n\u003e If your use case does not require you to run such a search, then `Codable` bindings may be a useful choice.\n\n#### Codable Collections\n\nSQift also supports `Codable` collections through the `ArrayBinding`, `SetBinding`, and `DictionaryBinding` types.\n\n```swift\nlet points: ArrayBinding = [\n    CGPoint(x: 1.0, y: 2.0),\n    CGPoint(x: 3.0, y: 4.0),\n    CGPoint(x: 5.0, y: 6.0),\n    CGPoint(x: 7.0, y: 8.0),\n    CGPoint(x: 9.0, y: 10.0)\n]\n\ntry connection.execute(\"CREATE TABLE stream(id INTEGER PRIMARY KEY, data BLOB NOT NULL)\")\ntry connection.run(\"INSERT INTO stream(data) VALUES(?)\", points)\n\nlet pointsQueried: ArrayBinding\u003cCGPoint\u003e? = try connection.query(\"SELECT data FROM stream WHERE id = 1\")\n\npointsQueried?.elements.forEach { print(\"(\\($0.x), \\($0.y))\") }\n\n// Output\n// (1.0, 2.0)\n// (3.0, 4.0)\n// (5.0, 6.0)\n// (7.0, 8.0)\n// (9.0, 10.0)\n```\n\n\u003e Codable collections can be useful in situations where you are writing large streams of data that are never partially queried.\n\u003e If you only write the data in one pass, and only query the data as the entire set, codable collections might be a good option.\n\n### Transactions\n\nChanges cannot be made to the database except within a transaction.\nBy default, any command that changes the database will automatically start a transaction if one is not already in effect.\nTransactions can also be started manually in SQift when multiple operations need to be run inside a single transaction.\n\n```swift\ntry connection.execute(\"CREATE TABLE cars(id INTEGER PRIMARY KEY, name TEXT, price INTEGER)\")\n\ntry connection.transaction {\n    try connection.prepare(\"INSERT INTO cars VALUES(?, ?, ?)\").bind(1, \"Audi\", 52642).run()\n    try connection.prepare(\"INSERT IN cars VALUES(?, ?, ?)\").bind(2, \"Mercedes\", 57127).run()\n}\n```\n\n\u003e If any error occurs within the transaction, all the changes are automatically rolled back by SQift.\n\n### Tracing\n\nWhen debugging SQL statements, sometimes it can be helpful to be able to print out what is actually being executed by SQLite.\nSQift allows you to do this through the `traceEvent` API by registering a closure to run for each statement execution.\n\n```swift\nlet connection = try Connection(storageLocation: storageLocation)\n\nconnection.traceEvent { event in\n    if case .statement(_, let sql) = event {\n        print(sql)\n    }\n}\n\ntry connection.execute(\"CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT)\")\ntry connection.prepare(\"INSERT INTO employees VALUES(?, ?)\").bind(1, \"Bill Bowerman\").run()\ntry connection.prepare(\"INSERT INTO employees VALUES(?, ?)\").bind(2, \"Phil Knight\").run()\nlet employees: [Employee] = try connection.query(\"SELECT * FROM employees\")\n\n// Output\n// \"CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT)\"\n// \"INSERT INTO employees VALUES(1, 'Bill Bowerman')\"\n// \"INSERT INTO employees VALUES(2, 'Phil Knight')\"\n// \"SELECT * FROM employees\"\n```\n\n\u003e The `traceEvent` API allows you to be more selective about the types of statements you want to trace.\n\u003e You can select which types of statements you want by using the trace event masks.\n\n### Collations\n\nSQift supports custom collation functions for cases where the three built-in collating functions are not sufficient.\nA couple real-world examples of custom cases might include: diacritic aware sorting and numerical sorting.\n\n```swift\nlet connection = try Connection(storageLocation: storageLocation)\n\nconnection.createCollation(named: \"NUMERIC\") { lhs, rhs in\n    return lhs.compare(rhs, options: .numeric, locale: .autoupdatingCurrent)\n}\n\ntry connection.execute(\"CREATE TABLE values(text TEXT COLLATE 'NUMERIC' NOT NULL)\")\n\nlet values = [\"string 1\", \"string 21\", \"string 12\", \"string 11\", \"string 02\"]\n\ntry values.forEach { try connection.run(\"INSERT INTO values(text) VALUES(?)\", $0) }\nlet extractedValues: [String] = try connection.query(\"SELECT * FROM values ORDER BY text\")\n\nextractedValues.forEach { print($0) }\n\n// Output\n// \"string 1\"\n// \"string 02\"\n// \"string 11\"\n// \"string 12\"\n// \"string 21\"\n```\n\n### Functions\n\nWhile SQLite is a very robust library, sometimes you will run into cases where you need to extend the functionality of SQLite where it is limited.\nFor example, you may need to create a custom function to determine what month of a calendar year a particular date falls within.\nSQLite cannot do this directly since it lacks calendar support.\n\nSQift supports custom scalar and aggregate functions.\nThe following is a simple example of how you could extend SQLite to support a `strip_unicode` function.\n\n```swift\ntry connection.addScalarFunction(named: \"strip_unicode\", argumentCount: 1) { _, values in\n    guard\n        let value = values.first, value.isText,\n        let valueData = value.text.data(using: .ascii, allowLossyConversion: true),\n        let asciiValue = String(data: valueData, encoding: .ascii)\n    else { return .null }\n\n    return .text(asciiValue)\n}\n\nlet sql = \"SELECT strip_unicode(?)\"\n\nlet result1: String? = try connection.prepare(sql, \"å\").query()\nlet result2: String? = try connection.prepare(sql, \"ć\").query()\nlet result3: String? = try connection.prepare(sql, \"áč\").query()\n\n// result1 = \"a\"\n// result2 = \"c\"\n// result3 = \"ac\"\n```\n\n\u003e For more advanced examples of scalar and aggregate functions, please refer to the test suite.\n\n---\n\n## Advanced\n\n### Hooks\n\nSQift has support built in for commit, rollback, update, and authorizer hooks.\nThe commit hook is used to determine whether a commit should be executed or rolled back.\nThe rollback hook is called when a commit is rolled back.\n\n```swift\nvar shouldCancelCommit = false\n\nconnection.commitHook { return shouldCancelCommit }\nconnection.rollbackHook { print(\"rollback occurred\") }\n```\n\nUpdate hooks can be used to react to `.insert`, `.update`, or `.delete` operations.\n\n```swift\nconnection.updateHook { type, databaseName, tableName, rowID in\n    var message = \"\\(type) row \\(rowID)\"\n\n    if let databaseName = databaseName { message += \" on \\(databaseName)\" }\n    if let tableName = tableName { message += \".\\(tableName)\" }\n\n    print(message)\n    \n    // Could update the file system, invalidate a cache, send notifications, etc.\n}\n\ntry connection.execute(\"\"\"\n    INSERT INTO employee(name) VALUES('Phil Knight');\n    UPDATE person SET name = 'Bill Bowerman' WHERE id = 1;\n    DELETE FROM person WHERE id = 1\n    \"\"\"\n)\n\n// Output\n// \"insert row 1 on main.person\"\n// \"update row 1 on main.person\"\n// \"delete row 1 on main.person\"\n```\n\nThe authorizer hook is the most complex of the four.\nIt allows you to control what statements are allowed to run on a connection.\nFor example, you could disable all actions on a particular connection other than select statements.\n\n```swift\ntry connection.authorizer { action, p1, p2, p3, p4 in\n    guard action == .select else { return .deny }\n    return .ok\n}\n```\n\n### Checkpoints\n\nDatabases with a `WAL` journal mode use checkpoint operations to move updates from the WAL file into the database.\nSQift supports checkpoints and busy timeouts and handlers which can be useful in certain situations.\nFor example, you may want to use a `WAL` database for performance reasons, then transfer it to a remote server or different device.\nBefore doing this, it is wise to checkpoint the database and also vacuum it.\n\n```swift\ntry connection.busyHandler(.timeout(1.0)) // 1 second\nlet checkpointResult = try connection.checkpoint(mode: .truncate)\n\ntry connection.execute(\"VACUUM\")\n```\n\n\u003e Checkpointing is a very complex process.\n\u003e Before using the `checkpoint` APIs, make sure to read through the SQLite [documentation](https://sqlite.org/c3ref/wal_checkpoint_v2.html).\n\n### Thread Safety\n\nThread-safety is a complex topic when it comes to SQLite.\nAs a general rule, it is NEVER safe to access a database `Connection` from multiple threads simultaneously.\nEach connection should be accessed serially to guarantee safety. \n\nIf you wish to access the database in parallel, there are a few things you need to know.\nFirst off, you'll need to use Write-Ahead Logging by setting the journal mode to `WAL`.\nBy changing the database to a `WAL` journal mode, the database can be read during a write and written during a read in parallel using multiple connections.\n\n```swift\ntry connection.execute(\"PRAGMA journal_mode = WAL\")\n```\n\nAnother important note is that SQLite can only perform write operations serially, no matter how many connections you create.\nTherefore, you should only ever create a single connection for writing if possible.\nYou can use as many reader connections as you wish.\nFor more information about thread-safety and WAL journal modes, please refer to the following:\n\n- [Write-Ahead Logging](https://www.sqlite.org/wal.html)\n- [SQLite and Multiple Threads](http://www.sqlite.org/threadsafe.html)\n- [SQLite WAL mode with multiple transactions in multiple threads](http://stackoverflow.com/questions/14234007/sqlite-wal-mode-with-multiple-transactions-in-multiple-threads)\n\n#### Connection Queue\n\nThe `ConnectionQueue` class in SQift was designed to help guarantee thread-safety for a database `Connection` that could be accessed from multiple threads.\nIt executes all operations on an internal serial dispatch queue.\nThis ensures all operations on the connection operation in a serial fashion.\nThe `ConnectionQueue` also supports executing logic inside a transaction and savepoint.\n\n```swift\nlet queue = try ConnectionQueue(connection: Connection(storageLocation: .onDisk(\"path_to_db\")))\n\ntry queue.execute { connection in\n    try connection.execute(\"PRAGMA foreign_keys = true\")\n    try connection.execute(\"PRAGMA journal_mode = WAL\")\n    try connection.execute(\"CREATE TABLE cars(id INTEGER PRIMARY KEY, name TEXT, price INTEGER)\")\n}\n\ntry queue.executeInTransaction { connection in\n    try connection.execute(\"INSERT INTO cars VALUES(1, 'Audi', 52642)\")\n    try connection.execute(\"INSERT INTO cars VALUES(2, 'Mercedes', 57127)\")\n}\n\ntry queue.executeInSavepoint(\"drop_cars_table\") { connection in\n\ttry connection.execute(\"DROP TABLE cars\")\n}\n```\n\n#### Connection Pool\n\nThe `ConnectionPool` class allows multiple read-only connections to access a database simultaneously in a thread-safe manner.\nInternally, the pool manages two different sets of connections, ones that are available and ones that are currently busy executing SQL logic.\nThe pool will reuse available connections when they are available, and initializes new connections when all available connections are busy until the max connection count is reached.\n\n```swift\nlet pool = try ConnectionPool(storageLocation: .onDisk(\"path_to_db\"))\n\ntry pool.execute { connection in\n    let count: Int = try connection.query(\"SELECT count(*) FROM cars\")\n}\n```\n\nSince SQLite has no limit on the maximum number of open connections to a single database, the `ConnectionPool` will initialize as many connections as needed within a small amount of time.\nEach time a connection is executed, the internal drain delay timer starts up.\nWhen the drain delay timer fires, it will drain the available connections if there are no more busy connections.\nIf there are still busy connections, the timer is restarted.\nThis allows the `ConnectionPool` to spin up as many connections as necessary for very small amounts of time.\n\n\u003e The thread-safety is guaranteed by the connection pool by always executing the SQL closure inside a connection queue.\n\u003e This ensures all SQL closures executed on the connection are done so in a serial fashion, thus guaranteeing the thread-safety of each connection.\n\n#### Database\n\nThe `Database` class is a lightweight way to create a single writable connection queue and connection pool for all read statements.\nThe read and write APIs are designed to make it simple to execute SQL statements on the appropriate type of `Connection` in a thread-safe manner.\n\n```swift\nlet database = try Database(storageLocation: .onDisk(\"path_to_db\"))\n\ntry database.executeWrite { connection in\n    try connection.execute(\"PRAGMA foreign_keys = true\")\n    try connection.execute(\"PRAGMA journal_mode = WAL\")\n    try connection.execute(\"CREATE TABLE cars(id INTEGER PRIMARY KEY, name TEXT, price INTEGER)\")\n}\n\ntry database.executeRead { connection in\n    let count: Int = try connection.query(\"SELECT count(*) FROM cars\")\n}\n```\n\nThis is the easiest way to operate in a 100% thread-safe manner without having to deal with the underlying complexities of the `ConnectionQueue` and `ConnectionPool` classes.\n\nAnother important consideration when using the `Database` type is whether or not to use the shared cache.\nIf you are using a `WAL` journal mode, it is best to set the `sharedCache` parameter to `true`.\nThis allows the reader pool to have access to the recent changes made by the writer connection at all times.\nIf you don't use the shared cache, the readers will not always have access to the latest written changes.\nThis can happen when other long lived read operations are running while changes are being written by a different connection.\n\n\u003e We would like to encourage everyone to use a `Database` object rather than working directly with connection queues or connection pools.\n\n#### Table Lock Policy\n\nTable lock errors are `SQLITE_ERROR` types thrown by `execute`, `prepare`, and `step` operations.\nThese errors can occur when the database is configured with a WAL journal mode as well as a shared cache.\nWhen one connection has obtained a lock on a table, another connection running on a different thread will receive table lock errors until the previous lock is released.\nIn these situations, there are a couple of ways to proceed.\nThe error can either be immediately thrown and handled by the client, or the calling thread can poll the operation until the lock is released.\n\nThe `TableLockPolicy` defines two different ways to handle table lock errors.\nThe first option is to poll on the calling thread at a specified interval until the lock is released.\nThe other option is to immediately fast fail by throwing the table lock error as soon as it is encountered.\n`Connection`, `ConnectionPool`, and `Database` types are set to `.fastFail` by default.\n\nIn order to enable polling for table lock errors, all that needs to be done is to set the policy in the `Connection` or `Database` initializer.\n\n```swift\nlet connection = try Connection(storageLocation: storageLocation, tableLockPolicy: .poll(0.01))\nlet database = try Database(storageLocation: .onDisk(\"path_to_db\"), tableLockPolicy: .poll(0.01))\n```\n\n\u003e When using a WAL journal mode and a shared cache, it is recommended to use a `.poll` table lock policy with a poll interval of `10 ms`. \n\n### Migrations\n\nProduction applications generally need to migrate the database schema from time-to-time.\nWhether it requires some new tables or possibly alterations to a table, you need to have a way to manage the migration logic.\nSQift has migration support already built-in for you through the `Migrator` class.\nAll you need to do is create the `Migrator` instance and tell it to run.\nEverything else is handled internally by SQift.\n\n```swift\nlet connection = try Connection(storageLocation: .onDisk(\"path_to_db\"))\nlet migrator = Migrator(connection: connection, desiredSchemaVersion: 2)\n\ntry migrator.runMigrationsIfNecessary(\n    migrationSQLForSchemaVersion: { version in\n        var SQL: String = \"\"\n\n        switch version {\n        case 1:\n            return \"CREATE TABLE cars(id INTEGER PRIMARY KEY, name TEXT, price INTEGER)\"\n\n        case 2:\n            return \"CREATE TABLE person(id INTEGER PRIMARY KEY, name TEXT, address TEXT)\"\n\n        default:\n            break\n        }\n\n        return SQL\n    },\n    willMigrateToSchemaVersion: { version in\n        print(\"Will migrate to schema version: \\(version)\")\n    },\n    didMigrateToSchemaVersion: { version in\n        print(\"Did migrate to schema version: \\(version)\")\n    }\n)\n```\n\nAll migrations must start at 1 and increment by 1 with each iteration.\nFor example, the first time you create a `Migrator`, you want to set the `desiredSchemaVersion` to 1 and implement the `migrationSQLForSchemaVersion` closure to return your initial database schema SQL.\nThen, each time you need to migrate your database, bump the `desiredSchemaVersion` by 1 and add the new case to your `migrationSQLForSchemaVersion` schema closure.\nIn a production application, it would be easiest to write actual SQL files, add them to your bundle and load the SQL string from the file for the required version.\n\n### Backups\n\nIt can often be wise to run scheduled backups of your database allowing users to restore from a backup if corruption is detected.\nSQift makes it extremely to backup a database safely using the SQLite backup APIs.\n\n```swift\nlet sourceConnection = try Connection(storageLocation: sourceLocation)\nlet destinationConnection = try Connection(storageLocation: destinationLocation)\n\nlet progress = try sourceConnection.backup(to: destinationConnection) { result in\n    print(result)\n}\n```\n\nThe `progress` instance vended by the `backup` API can be used to monitor progress as well as cancel the backup operation.\n\n\u003e The backup operation, by default, happens as an iterative process.\n\u003e It backs up the specified page size in each operation until it completes.\n\u003e Passing a `pageSize` of `-1` causes the backup to be performed in a single operation.\n\u003e It is recommended to use the default `pageSize` of `100` and allow the operation to iterate until complete.\n\n---\n\n## FAQ\n\n### Why not use CoreData?\n\nThere are many trade-offs between CoreData and SQift.\nSQift was certainly not created as a replacement for CoreData.\nIt was created to make working with SQLite from Swift as easy and painless as possible.\nAnyone trying to decide between using CoreData and using SQift needs to consider the pros and cons carefully before making a decision.\nBoth have significant learning curves and require significant amounts of forethought and architectural design before being integrated to an application or framework.\n\n### Should I use SQift in my project?\n\nMaybe.\nThe most important question to ask first is whether you really need a database.\nThere are many other ways to store data which are much less complicated.\nIf you do have a large amount of data that needs to be indexed for queries, then a database is probably the best option.\n\nOnce you know you need a database, then you need to decide whether you need a key-value store, or full relational query power.\nIf you only need a key-value store, there are other libraries out there that are less complex and more tailored to your needs.\nIf you need the full power of SQLite, then SQift is going to be a good option.\n\n### Any plans to add a Swift DSL on top of SQL in SQift?\n\nThis is something that we've considered multiple times and haven't dove into yet.\nCurrently, we do not have any plans to build a DSL, but it's not off the table.\nIf we do decide to try to add a DSL to SQift, we'll need to make sure we do not remove users too far from SQL.\n\nThe main goal of SQift is to make it as easy and convenient as possible to use SQLite with Swift.\nConvenience, however, does not mean abstraction.\nSQLite is very complicated, and the goal of SQift is not to simplify it, but enable it.\nAnyone looking to use SQift in their project needs to have a firm understanding of SQLite and how it works.\nThis is absolutely by design.\n\n---\n\n## License\n\nSQift is released under the New BSD License.\nSee LICENSE for details.\n\n## Creators\n\n- [Dave Camp](https://github.com/atomiccat) ([@thinbits](https://twitter.com/thinbits))\n- [Christian Noon](https://github.com/cnoon) ([@Christian_Noon](https://twitter.com/Christian_Noon))\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnike-inc%2Fsqift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnike-inc%2Fsqift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnike-inc%2Fsqift/lists"}