SQLite.swift/SQLite.playground/Contents.swift

219 строки
9.8 KiB
Swift
Исходник Постоянная ссылка Ответственный История

Этот файл содержит неоднозначные символы Юникода!

Этот файл содержит неоднозначные символы Юникода, которые могут быть перепутаны с другими в текущей локали. Если это намеренно, можете спокойно проигнорировать это предупреждение. Используйте кнопку Экранировать, чтобы подсветить эти символы.

/*:
> _Note_: This playground must be running inside the Xcode project to run. Build the OS X framework prior to use. (You may have to close and reopen the project after building it.)
# SQLite.swift
This playground contains sample code to explore [SQLite.swift](https://github.com/stephencelis/SQLite.swift), a [Swift](https://developer.apple.com/swift) wrapper for [SQLite3](https://sqlite.org).
Lets get started by importing the framework and opening a new in-memory database connection using the `Database` class.
*/
import SQLite
let db = Database()
/*:
This implicitly opens a database in memory (using `.InMemory`). To open a database at a specific location, pass the path as a parameter during instantiation, *e.g.*,
Database("path/to/database.sqlite3")
Pass `.Temporary` to open a temporary, disk-backed database, instead.
Once we initialize a database connection, we can execute SQL statements directly against it. Lets create a table.
*/
db.execute(
"CREATE TABLE users (" +
"id INTEGER PRIMARY KEY, " +
"email TEXT NOT NULL UNIQUE, " +
"age INTEGER, " +
"admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " +
"manager_id INTEGER, " +
"FOREIGN KEY(manager_id) REFERENCES users(id)" +
")"
)
/*:
The `execute` function can run multiple SQL statements at once as a convenience and will throw an assertion failure if an error occurs during execution. This is useful for seeding and migrating databases with well-tested statements that are guaranteed to succeed (or where failure can be graceful and silent).
Its generally safer to prepare SQL statements individually. Lets build a `Statement` object and insert a couple rows.
*/
let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
for (email, admin) in ["alice@acme.com": 1, "betsy@acme.com": 0] {
stmt.run(email, admin)
}
/*:
Prepared statements can bind and escape input values safely. In this case, `email` and `admin` columns are bound with different values over two executions.
The `Database` class exposes information about recently run queries via several properties: `totalChanges` returns the total number of changes (inserts, updates, and deletes) since the connection was opened; `changes` returns the number of changes from the last statement that modified the database; `lastInsertRowid` returns the rowid of the last insert.
*/
db.totalChanges
db.changes
db.lastInsertRowid
/*:
## Querying
`Statement` objects act as both sequences _and_ generators. We can iterate over a select statements rows directly using a `for``in` loop.
*/
for row in db.prepare("SELECT id, email FROM users") {
println("id: \(row[0]), email: \(row[1])")
}
/*:
Single, scalar values can be plucked directly from a statement.
*/
let count = db.prepare("SELECT count(*) FROM users")
count.scalar()
db.scalar("SELECT email FROM users WHERE id = ?", 1)
/*:
> ### Experiment
>
> Try plucking a single row by taking advantage of the fact that `Statement` conforms to the `GeneratorType` protocol.
>
> Also try using the `Array` initializer to return an array of all rows at once.
## Transactions & Savepoints
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. In the following example we prepare two statements: one to insert a manager into the database, and onegiven a managers rowidto insert a managed user into the database.
*/
let sr = db.prepare("INSERT INTO users (email, admin) VALUES (?, 1)")
let jr = db.prepare("INSERT INTO users (email, admin, manager_id) VALUES (?, 0, ?)")
/*:
Statements can be chained with other statements using the `&&` and `||` operators. The right-hand side is an auto-closure and therefore has access to database information at the time of execution. In this case, we insert Dolly, a supervisor, and immediately reference her rowid when we insert her assistant, Emery.
*/
db.transaction()
&& sr.run("dolly@acme.com")
&& jr.run("emery@acme.com", db.lastInsertRowid)
&& db.commit()
|| db.rollback()
/*:
Our database has a uniqueness constraint on email address, so lets see what happens when we insert Fiona, who also claims to be managing Emery.
*/
let txn = db.transaction()
&& sr.run("fiona@acme.com")
&& jr.run("emery@acme.com", db.lastInsertRowid)
&& db.commit()
txn || db.rollback()
count.scalar()
txn.failed
txn.reason
/*:
This time, our transaction fails because Emery has already been added to the database. The addition of Fiona has been rolled back, and well need to get to the bottom of this discrepancy (or make some schematic changes to our database to allow for multiple managers per user).
> ### Experiment
>
> Transactions cant be nested, but savepoints can! Try calling the `savepoint` function instead, which shares semantics with `transaction`, but can successfully run in layers.
## Query Building
SQLite.swift provides a powerful, type-safe query builder. With only a small amount of boilerplate to map our columns to types, we can ensure the queries we build are valid upon compilation.
*/
let id = Expression<Int64>("id")
let email = Expression<String>("email")
let age = Expression<Int?>("age")
let admin = Expression<Bool>("admin")
let managerId = Expression<Int64?>("manager_id")
/*:
The query-building interface is provided via the `Query` struct. We can access this interface by subscripting our database connection with a table name.
*/
let users = db["users"]
/*:
From here, we can build a variety of queries. For example, we can build and run an `INSERT` statement by calling the querys `insert` function. Lets add a few new rows this way.
*/
users.insert(email <- "giles@acme.com", age <- 42, admin <- true).rowid
users.insert(email <- "haley@acme.com", age <- 30, admin <- true).rowid
users.insert(email <- "inigo@acme.com", age <- 24).rowid
/*:
No room for syntax errors! Try changing an input to the wrong type and see what happens.
The `insert` function can return a `rowid` (which will be `nil` in the case of failure) and the just-run `statement`. It can also return a `Statement` object directly, making it easy to run in a transaction.
*/
db.transaction()
&& users.insert(email <- "julie@acme.com")
&& users.insert(email <- "kelly@acme.com", managerId <- db.lastInsertRowid)
&& db.commit()
|| db.rollback()
/*:
`Query` objects can also build `SELECT` statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement.
*/
// SELECT * FROM users
for user in users {
println(user[email])
}
/*:
You may notice that iteration works a little differently here. Rather than arrays of raw values, we are given `Row` objects, which can be subscripted with the same expressions we prepared above. This gives us a little more powerful of a mapping to work with and pass around.
Queries can be used and reused, and can quickly return rows, counts and other aggregate values.
*/
// SELECT * FROM users LIMIT 1
users.first
// SELECT count(*) FROM users
users.count
users.min(age)
users.max(age)
users.average(age)
/*:
> ### Experiment
>
> In addition to `first`, you can also try plucking the `last` row from the result set in an optimized fashion.
>
> The example above uses the computed variable `count`, but `Query` has a `count` function, as well. (The computed variable is actually a convenience wrapper around `count(*)`.) Try counting the distinct ages in our group of users.
>
> Try calling the `sum` and `total` functions. Note the differences!
Queries can be refined using a collection of chainable helper functions. Lets filter our query to the administrator subset.
*/
let admins = users.filter(admin)
/*:
Filtered queries will in turn filter their aggregate functions.
*/
// SELECT count(*) FROM users WHERE admin
admins.count
/*:
Alongside `filter`, we can use the `select`, `join`, `group`, `order`, and `limit` functions to compose rich queries with safety and ease. Lets say we want to order our results by email, then age, and return no more than three rows.
*/
let ordered = admins.order(email.asc, age.asc).limit(3)
// SELECT * FROM users WHERE admin ORDER BY email ASC, age ASC LIMIT 3
for admin in ordered {
println(admin[id])
println(admin[age])
}
/*:
> ### Experiment
>
> Try using the `select` function to specify which columns are returned.
>
> Try using the `group` function to group users by a column.
>
> Try to return results by a column in descending order.
>
> Try using an alternative `limit` function to add an `OFFSET` clause to the query.
We can further filter by chaining additional conditions onto the query. Lets find administrators that havent (yet) provided their ages.
*/
let agelessAdmins = admins.filter(age == nil)
// SELECT count(*) FROM users WHERE (admin AND age IS NULL)
agelessAdmins.count
/*:
Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our querys `update` interface to (temporarily) revoke their privileges while we wait for them to update their profiles.
*/
// UPDATE users SET admin = 0 WHERE (admin AND age IS NULL)
agelessAdmins.update(admin <- false).changes
/*:
If we ever need to remove rows from our database, we can use the `delete` function, which will be scoped to a querys filters. **Be careful!** You may just want to archive the records, instead.
We dont archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, _carefully_ scope a query to match her and delete her record.
*/
// DELETE FROM users WHERE (email = 'alice@acme.com')
users.filter(email == "alice@acme.com").delete().changes
/*:
And thats that.
## & More
Weve only explored the surface to SQLite.swift. Dive into the code to discover more!
*/