Upgrade Playground
It's not as pretty, but it's the only supported solution. Signed-off-by: Stephen Celis <stephen@stephencelis.com>
This commit is contained in:
Родитель
3abd6213b2
Коммит
d287345320
Двоичные данные
Documentation/Resources/playground@2x.png
Двоичные данные
Documentation/Resources/playground@2x.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 91 KiB После Ширина: | Высота: | Размер: 122 KiB |
|
@ -0,0 +1,218 @@
|
|||
/*:
|
||||
> _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).
|
||||
|
||||
Let’s 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:"`. To open a database at a specific location, pass the path as a parameter during instantiation, *e.g.*,
|
||||
|
||||
Database("path/to/database.sqlite3")
|
||||
|
||||
Pass `nil` or an empty string (`""`) to open a temporary, disk-backed database, instead.
|
||||
|
||||
Once we instantiate a database connection, we can execute SQL statements directly against it. Let’s 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).
|
||||
|
||||
It’s generally safer to prepare SQL statements individually. Let’s instantiate 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 statement’s 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 one—given a manager’s rowid—to 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 let’s 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 we’ll 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 can’t 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 manager_id = 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 query’s `insert` function. Let’s 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", manager_id <- 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. Let’s 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. Let’s 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. Let’s find administrators that haven’t (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 query’s `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 query’s filters. **Be careful!** You may just want to archive the records, instead.
|
||||
|
||||
We don’t 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 that’s that.
|
||||
|
||||
## & More…
|
||||
|
||||
We’ve only explored the surface to SQLite.swift. Dive into the code to discover more!
|
||||
*/
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<aside>
|
||||
<h4>Note</h4>
|
||||
<p>
|
||||
This playground must be running inside the Xcode project to run. Build the iOS framework prior to use.
|
||||
</p>
|
||||
</aside>
|
||||
<h3>SQLite.swift</h3>
|
||||
<p>
|
||||
This playground contains sample code to explore <a href='https://github.com/stephencelis/SQLite.swift'>SQLite.swift</a>, a <a href='https://developer.apple.com/swift'>Swift</a> wrapper for <a href='https://sqlite.org'>SQLite3</a>.
|
||||
</p>
|
||||
<p>
|
||||
Let’s get started by importing the framework and opening a new in-memory database connection using the <code>Database</code> class.
|
||||
</p>
|
||||
</section>
|
|
@ -1,14 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<meta name='apple-mobile-web-app-capable' content='yes'>
|
||||
<meta name='viewport' content='width = device-width, maximum-scale=1.0'>
|
||||
<section>
|
||||
<p>
|
||||
This implicitly opens a database in <code>":memory:"</code>. To open a database at a specific location, pass the path as a parameter during instantiation, <em>e.g.</em>, <code>Database("path/to/database.sqlite3")</code>. Pass <code>nil</code> or an empty string (<code>""</code>) to open a temporary, disk-backed database, instead.
|
||||
</p>
|
||||
<p>
|
||||
Once we instantiate a database connection, we can execute SQL statements directly against it. Let’s create a table.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
The query-building interface is provided via the <code>Query</code> struct. We can access this interface by subscripting our database connection with a table name.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
From here, we can build a variety of queries. For example, we can build and run an <code>INSERT</code> statement by calling the query’s <code>insert</code> function. Let’s add a few new rows this way.
|
||||
</p>
|
||||
</section>
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
No room for syntax errors! Try changing an input to the wrong type and see what happens.
|
||||
</p>
|
||||
<p>
|
||||
The <code>insert</code> function can return an <code>ID</code> (which will be <code>nil</code> in the case of failure) and the just-run <code>statement</code>. It can also return a <code>Statement</code> object directly, making it easy to run in a transaction.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
<code>Query</code> objects can also build <code>SELECT</code> statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement.
|
||||
</p>
|
||||
</section>
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
You may notice that iteration works a little differently here. Rather than arrays of raw values, we are given <code>Row</code> 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.
|
||||
</p>
|
||||
<p>
|
||||
Queries can be used and reused, and can quickly return rows, counts and other aggregate values.
|
||||
</p>
|
||||
</section>
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<aside>
|
||||
<h4>Experiment</h4>
|
||||
<!-- <p>
|
||||
In addition to <code>first</code>, you can also try plucking the <code>last</code> row from the result set in an optimized fashion.
|
||||
</p> -->
|
||||
<!-- <p>
|
||||
The example above uses the computed variable <code>count</code>, but <code>Query</code> has a <code>count</code> function, as well. (The computed variable is actually a convenience wrapper around <code>count("*")</code>.) Try counting the distinct ages in our group of users.
|
||||
</p> -->
|
||||
<p>
|
||||
Try calling the <code>sum</code> and <code>total</code> functions. Note the differences!
|
||||
</p>
|
||||
</aside>
|
||||
<p>
|
||||
Queries can be refined using a collection of chainable helper functions. Let’s filter our query to the administrator subset.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
Filtered queries will in turn filter their aggregate functions.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
Alongside <code>filter</code>, we can use the <code>select</code>, <code>join</code>, <code>group</code>, <code>order</code>, and <code>limit</code> functions to compose rich queries with safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows.
|
||||
</p>
|
||||
</section>
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<aside>
|
||||
<h4>Experiment</h4>
|
||||
<p>
|
||||
Try using the <code>select</code> function to specify which columns are returned.
|
||||
</p>
|
||||
<p>
|
||||
Try using the <code>group</code> function to group users by a column.
|
||||
</p>
|
||||
<p>
|
||||
Try to return results by a column in descending order.
|
||||
</p>
|
||||
<p>
|
||||
Try using an alternative <code>limit</code> function to add an <code>OFFSET</code> clause to the query.
|
||||
</p>
|
||||
</aside>
|
||||
<p>
|
||||
We can further filter by chaining additional conditions onto the query. Let’s find administrators that haven’t (yet) provided their ages.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our query’s <code>update</code> interface to (temporarily) revoke their privileges while we wait for them to update their profiles.
|
||||
</p>
|
||||
</section>
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
The <code>execute</code> 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).
|
||||
</p>
|
||||
<p>
|
||||
It’s generally safer to prepare SQL statements individually. Let’s instantiate a <code>Statement</code> object and insert a couple rows.
|
||||
</p>
|
||||
</section>
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
If we ever need to remove rows from our database, we can use the <code>delete</code> function, which will be scoped to a query’s filters. <strong>Be careful!</strong> You may just want to archive the records, instead.
|
||||
</p>
|
||||
<p>
|
||||
We don’t archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, <em>carefully</em> scope a query to match her and delete her record.
|
||||
</p>
|
||||
</section>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
And that’s that.
|
||||
</p>
|
||||
<h3>& More…</h3>
|
||||
<p>
|
||||
We’ve only explored the surface to SQLite.swift. Dive into the code to discover more!
|
||||
</p>
|
||||
</section>
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
Prepared statements can bind and escape input values safely. In this case, <code>email</code> and <code>admin</code> columns are bound with different values over two executions.
|
||||
</p>
|
||||
<p>
|
||||
The <code>Database</code> class exposes information about recently run queries via several properties: <code>totalChanges</code> returns the total number of changes (inserts, updates, and deletes) since the connection was opened; <code>lastChanges</code> returns the number of changes from the last statement that modified the database; <code>lastID</code> returns the row ID of the last insert.
|
||||
</p>
|
||||
</section>
|
|
@ -1,10 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<h3>Querying</h3>
|
||||
<p>
|
||||
<code>Statement</code> objects act as both sequences <em>and</em> generators. We can iterate over a select statement’s rows directly using a <code>for</code>–<code>in</code> loop.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
Single, scalar values can be plucked directly from a statement.
|
||||
</p>
|
||||
</section>
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<aside>
|
||||
<h4>Experiment</h4>
|
||||
<p>
|
||||
Try plucking a single row by taking advantage of the fact that <code>Statement</code> conforms to the <code>GeneratorType</code> protocol.
|
||||
</p>
|
||||
<p>
|
||||
Also try using the <code>Array</code> initializer to return an array of all rows at once.
|
||||
</p>
|
||||
</aside>
|
||||
<h3>Transactions & Savepoints</h3>
|
||||
<p>
|
||||
Using the <code>transaction</code> and <code>savepoint</code> 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 one—given a manager’s row ID—to insert a managed user into the database.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
Arguments sent to <code>transaction</code> are auto-closures and therefore have access to <code>Database</code> information at the time of execution. In this case, we insert Dolly, a supervisor, and immediately reference her row ID when we insert her assistant, Emery.
|
||||
</p>
|
||||
</section>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
Our database has a uniqueness constraint on email address, so let’s see what happens when we insert Fiona, who also claims to be managing Emery.
|
||||
</p>
|
||||
</section>
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset='utf-8'>
|
||||
<link rel='stylesheet' type='text/css' href='style-1.1.15.css'>
|
||||
<meta id='xcode-display' name='xcode-display' content='render'>
|
||||
<section>
|
||||
<p>
|
||||
This time, our transaction fails because Emery has already been added to the database. The addition of Fiona has been rolled back, and we’ll need to get to the bottom of this discrepancy (or make some schematic changes to our database to allow for multiple managers per user).
|
||||
</p>
|
||||
<aside>
|
||||
<h4>Experiment</h4>
|
||||
<p>
|
||||
Transactions can’t be nested, but savepoints can! Try calling the <code>savepoint</code> function instead, which shares semantics with <code>transaction</code>, but can successfully run in layers.
|
||||
</p>
|
||||
</aside>
|
||||
<h3>Query Building</h3>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</section>
|
|
@ -1,65 +0,0 @@
|
|||
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,
|
||||
abbr,acronym,address,big,cite,code,del,dfn,em,figure,font,img,ins,kbd,q,s,
|
||||
samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,
|
||||
fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
padding: 0;
|
||||
vertical-align: baseline
|
||||
}
|
||||
body {
|
||||
background-color: rgba(255,255,255,0.65);
|
||||
color: rgba(0,0,0,1);
|
||||
font-family: Helvetica,sans-serif;
|
||||
font-size: 62.5%;
|
||||
margin-left: 15px;
|
||||
}
|
||||
section {
|
||||
padding: 20px 25px 20px 35px
|
||||
}
|
||||
h3 {
|
||||
color: rgba(128,128,128,1);
|
||||
display: block;
|
||||
font-size: 2.2em;
|
||||
font-weight: 100;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
p {
|
||||
color: rgba(65,65,65,1);
|
||||
font-size: 1.4em;
|
||||
line-height: 145%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
a {
|
||||
color: rgba(0,136,204,1);
|
||||
text-decoration: none
|
||||
}
|
||||
code {
|
||||
color: rgba(128,128,128,1);
|
||||
font-family: Menlo,monospace;
|
||||
font-size: .9em;
|
||||
word-wrap: break-word
|
||||
}
|
||||
h4 {
|
||||
color: rgba(128,128,128,1);
|
||||
font-size: .6em;
|
||||
font-weight: normal;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase
|
||||
}
|
||||
aside {
|
||||
background-color: rgba(249,249,249,1);
|
||||
border-left: 5px solid rgba(238,238,238,1);
|
||||
font-size: 1.2em;
|
||||
margin: 25px 45px 35px 35px;
|
||||
padding: 15px 15px 7px;
|
||||
|
||||
}
|
||||
aside p {
|
||||
font-size: 1em;
|
||||
margin-bottom: 8px
|
||||
}
|
|
@ -1,49 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='3.0' sdk='iphonesimulator'>
|
||||
<sections>
|
||||
<documentation relative-path='fragment-0.html'/>
|
||||
<code source-file-name='section-2.swift'/>
|
||||
<documentation relative-path='fragment-1.html'/>
|
||||
<code source-file-name='section-4.swift'/>
|
||||
<documentation relative-path='fragment-2.html'/>
|
||||
<code source-file-name='section-6.swift'/>
|
||||
<documentation relative-path='fragment-3.html'/>
|
||||
<code source-file-name='section-8.swift'/>
|
||||
<documentation relative-path='fragment-4.html'/>
|
||||
<code source-file-name='section-10.swift'/>
|
||||
<documentation relative-path='fragment-5.html'/>
|
||||
<code source-file-name='section-12.swift'/>
|
||||
<documentation relative-path='fragment-6.html'/>
|
||||
<code source-file-name='section-14.swift'/>
|
||||
<documentation relative-path='fragment-7.html'/>
|
||||
<code source-file-name='section-16.swift'/>
|
||||
<documentation relative-path='fragment-8.html'/>
|
||||
<code source-file-name='section-18.swift'/>
|
||||
<documentation relative-path='fragment-9.html'/>
|
||||
<code source-file-name='section-20.swift'/>
|
||||
<documentation relative-path='fragment-10.html'/>
|
||||
<code source-file-name='section-22.swift'/>
|
||||
<documentation relative-path='fragment-11.html'/>
|
||||
<code source-file-name='section-24.swift'/>
|
||||
<documentation relative-path='fragment-12.html'/>
|
||||
<code source-file-name='section-26.swift'/>
|
||||
<documentation relative-path='fragment-13.html'/>
|
||||
<code source-file-name='section-28.swift'/>
|
||||
<documentation relative-path='fragment-14.html'/>
|
||||
<code source-file-name='section-30.swift'/>
|
||||
<documentation relative-path='fragment-15.html'/>
|
||||
<code source-file-name='section-32.swift'/>
|
||||
<documentation relative-path='fragment-16.html'/>
|
||||
<code source-file-name='section-34.swift'/>
|
||||
<documentation relative-path='fragment-17.html'/>
|
||||
<code source-file-name='section-36.swift'/>
|
||||
<documentation relative-path='fragment-18.html'/>
|
||||
<code source-file-name='section-38.swift'/>
|
||||
<documentation relative-path='fragment-19.html'/>
|
||||
<code source-file-name='section-40.swift'/>
|
||||
<documentation relative-path='fragment-20.html'/>
|
||||
<code source-file-name='section-42.swift'/>
|
||||
<documentation relative-path='fragment-21.html'/>
|
||||
</sections>
|
||||
<playground version='5.0' target-platform='osx' display-mode='rendered'>
|
||||
<timeline fileName='timeline.xctimeline'/>
|
||||
</playground>
|
|
@ -1,3 +0,0 @@
|
|||
for row in db.prepare("SELECT id, email FROM users") {
|
||||
println("id: \(row[0]), email: \(row[1])")
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let count = db.prepare("SELECT count(*) FROM users")
|
||||
count.scalar()
|
||||
|
||||
db.scalar("SELECT email FROM users WHERE id = ?", 1)
|
|
@ -1,2 +0,0 @@
|
|||
let sr = db.prepare("INSERT INTO users (email, admin) VALUES (?, 1)")
|
||||
let jr = db.prepare("INSERT INTO users (email, admin, manager_id) VALUES (?, 0, ?)")
|
|
@ -1,4 +0,0 @@
|
|||
db.transaction() &&
|
||||
sr.run("dolly@acme.com") &&
|
||||
jr.run("emery@acme.com", db.lastId) &&
|
||||
db.commit() || db.rollback()
|
|
@ -1,10 +0,0 @@
|
|||
let txn = db.transaction() &&
|
||||
sr.run("fiona@acme.com") &&
|
||||
jr.run("emery@acme.com", db.lastId) &&
|
||||
db.commit()
|
||||
txn || db.rollback()
|
||||
|
||||
count.scalar()
|
||||
|
||||
txn.failed
|
||||
txn.reason
|
|
@ -1,3 +0,0 @@
|
|||
import SQLite
|
||||
|
||||
let db = Database()
|
|
@ -1,5 +0,0 @@
|
|||
let id = Expression<Int>("id")
|
||||
let email = Expression<String>("email")
|
||||
let age = Expression<Int?>("age")
|
||||
let admin = Expression<Bool>("admin")
|
||||
let manager_id = Expression<Int?>("manager_id")
|
|
@ -1 +0,0 @@
|
|||
let users = db["users"]
|
|
@ -1,3 +0,0 @@
|
|||
users.insert(email <- "giles@acme.com", age <- 42, admin <- true).id
|
||||
users.insert(email <- "haley@acme.com", age <- 30, admin <- true).id
|
||||
users.insert(email <- "inigo@acme.com", age <- 24).id
|
|
@ -1,4 +0,0 @@
|
|||
db.transaction() &&
|
||||
users.insert(email <- "julie@acme.com") &&
|
||||
users.insert(email <- "kelly@acme.com", manager_id <- db.lastId) &&
|
||||
db.commit() || db.rollback()
|
|
@ -1,4 +0,0 @@
|
|||
// SELECT * FROM users
|
||||
for user in users {
|
||||
println(user[email])
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// SELECT * FROM users LIMIT 1
|
||||
users.first
|
||||
|
||||
// SELECT count(*) FROM users
|
||||
users.count
|
||||
|
||||
users.min(age)
|
||||
users.max(age)
|
||||
users.average(age)
|
|
@ -1 +0,0 @@
|
|||
let admins = users.filter(admin)
|
|
@ -1,2 +0,0 @@
|
|||
// SELECT count(*) FROM users WHERE admin
|
||||
admins.count
|
|
@ -1,7 +0,0 @@
|
|||
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])
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let agelessAdmins = admins.filter(age == nil)
|
||||
|
||||
// SELECT count(*) FROM users WHERE (admin AND age IS NULL)
|
||||
agelessAdmins.count
|
|
@ -1,10 +0,0 @@
|
|||
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)" +
|
||||
")"
|
||||
)
|
|
@ -1,2 +0,0 @@
|
|||
// UPDATE users SET admin = 0 WHERE (admin AND age IS NULL)
|
||||
agelessAdmins.update(admin <- false).changes
|
|
@ -1,2 +0,0 @@
|
|||
// DELETE FROM users WHERE (email = 'alice@acme.com')
|
||||
users.filter(email == "alice@acme.com").delete().changes
|
|
@ -1,4 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
db.totalChanges
|
||||
db.lastChanges
|
||||
db.lastId
|
|
@ -134,6 +134,7 @@
|
|||
DC475E9E19F2199900788FBD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = "<group>"; };
|
||||
DC5B12121ABE3298000DA146 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; };
|
||||
DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
DC97EC9F1ADE955E00F550A6 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = "<group>"; };
|
||||
DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../Vendor/fts3_tokenizer.h; sourceTree = "<group>"; };
|
||||
DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -145,7 +146,6 @@
|
|||
DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = macosx.modulemap; sourceTree = "<group>"; };
|
||||
DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = "<group>"; };
|
||||
DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = "<group>"; };
|
||||
DCBC8C301ABE3CDA002B4631 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = "<group>"; };
|
||||
DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = "<group>"; };
|
||||
DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = "<group>"; };
|
||||
DCC6B3801A9191C300734B78 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -245,7 +245,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DC37744719C8F50B004FCF85 /* README.md */,
|
||||
DCBC8C301ABE3CDA002B4631 /* SQLite.playground */,
|
||||
DC97EC9F1ADE955E00F550A6 /* SQLite.playground */,
|
||||
DC37742D19C8CC90004FCF85 /* SQLite */,
|
||||
DC10500F19C904DD00D8CA30 /* SQLite Tests */,
|
||||
DCC6B3A11A91949C00734B78 /* SQLiteCipher */,
|
||||
|
|
Загрузка…
Ссылка в новой задаче