Simplify query change interface

The fact that `insert()`, `update()`, and `delete()` have a number of
overloads that can only be disambiguated by specifying a type, a tuple
member, or `!` is a big point of confusion for new users of
SQLite.swift. The overloads add some interesting patterns to the mix,
but aren't worth the pain points.

If we eliminate the overloads, we can insert/update/delete in place.
This allows for subtle bugs to be introduced into apps (where the
developer doesn't check for a rowid or that an update/delete was
successful), but is a tradeoff we'll have to make. It doesn't make sense
to enforce a different kind of interface/access at the expense of
confusion.

Given:

    let user = email <- "alice@mac.com")

The old way:

    users.insert(user)!
    let rowid = users.insert(user)!
    if let rowid = users.insert(user) { /* ... */ }
    let (rowid, statement) = users.insert(user)
    // etc.

The new way:

    users.insert(user)
    let rowid = users.insert(user).rowid!
    if let rowid = users.insert(user).rowid { /* ... */ }
    let (rowid, statement) = users.insert(user)
    // etc.

Slightly and rarely more verbose and readable, with less confusing
compiler errors and hand-holding.

Signed-off-by: Stephen Celis <stephen@stephencelis.com>
This commit is contained in:
Stephen Celis 2015-05-02 10:37:37 -04:00
Родитель 14573e4c83
Коммит a5011f4a21
6 изменённых файлов: 222 добавлений и 277 удалений

Просмотреть файл

@ -393,62 +393,33 @@ 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 [querys](#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 [querys](#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")?
users.insert(email <- "alice@mac.com", name <- "Alice")
// INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice')
users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.")
// INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.')
```
The `insert` function can return several different types that are useful in different contexts.
The `insert` function returns a tuple with an `Int64?` representing the inserted rows [`ROWID`][ROWID] (or `nil` on failure) and the associated `Statement`.
- An `Int64?` representing the inserted rows [`ROWID`][ROWID] (or `nil` on failure), for simplicity.
``` swift
if let insertId = users.insert(email <- "alice@mac.com") {
println("inserted id: \(insertId)")
}
```
If a value is always expected, we can disambiguate with a `!`.
``` swift
users.insert(email <- "alice@mac.com")!
```
- A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
``` swift
db.transaction()
&& users.insert(email <- "alice@mac.com")
&& users.insert(email <- "betty@mac.com")
&& db.commit() || db.rollback()
// BEGIN DEFERRED TRANSACTION;
// INSERT INTO "users" ("email") VALUES ('alice@mac.com');
// INSERT INTO "users" ("email") VALUES ('betty@mac.com');
// COMMIT TRANSACTION;
```
- A tuple of the above [`ROWID`][ROWID] and statement: `(rowid: Int64?, statement: Statement)`, for flexibility.
``` swift
let (rowid, statement) = users.insert(email <- "alice@mac.com")
if let rowid = rowid {
println("inserted id: \(rowid)")
} else if statement.failed {
println("insertion failed: \(statement.reason)")
}
```
``` swift
let insert = users.insert(email <- "alice@mac.com")
if let rowid = insert.rowid {
println("inserted id: \(rowid)")
} else if insert.statement.failed {
println("insertion failed: \(insert.statement.reason)")
}
```
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 arent fulfilled by default values.
>
> ``` swift
> timestamps.insert()!
> timestamps.insert()
> // INSERT INTO "timestamps" DEFAULT VALUES
> ```
@ -816,12 +787,12 @@ users.filter(name != nil).count
## Updating Rows
We can update a tables rows by calling a [querys](#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 tables rows by calling a [querys](#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.
``` swift
users.update(email <- "alice@me.com")?
users.update(email <- "alice@me.com")
// UPDATE "users" SET "email" = 'alice@me.com'
```
@ -829,29 +800,20 @@ Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#f
``` swift
let alice = users.filter(id == 1)
alice.update(email <- "alice@me.com")?
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.
The `update` function returns a tuple with an `Int?` representing the number of updates (or `nil` on failure) and the associated `Statement`.
- An `Int?` representing the number of updated rows (or `nil` on failure), for simplicity.
``` swift
if alice.update(email <- "alice@me.com") > 0 {
println("updated Alice")
}
```
If a value is always expected, we can disambiguate with a `!`.
``` swift
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.
``` swift
let update = alice.update(email <- "alice@me.com")
if let changes = update.changes where changes > 0 {
println("updated alice")
} else if update.statement.failed {
println("update failed: \(update.statement.reason)")
}
```
## Deleting Rows
@ -861,7 +823,7 @@ We can delete rows from a table by calling a [querys](#queries) `delete` func
When an unscoped query calls `delete`, it will delete _every_ row in the table.
``` swift
users.delete()?
users.delete()
// DELETE FROM "users"
```
@ -869,34 +831,25 @@ Be sure to scope `DELETE` statements beforehand using [the `filter` function](#f
``` swift
let alice = users.filter(id == 1)
alice.delete()?
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 a tuple with an `Int?` representing the number of deletes (or `nil` on failure) and the associated `Statement`.
- An `Int?` representing the number of deleted rows (or `nil` on failure), for simplicity.
``` swift
if alice.delete() > 0 {
println("deleted Alice")
}
```
If a value is always expected, we can disambiguate with a `!`.
``` swift
alice.delete()!
```
- A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
- A tuple of the above number of deleted rows and statement: `(changes: Int?, Statement)`, for flexibility.
``` swift
let delete = delete.update(email <- "alice@me.com")
if let changes = delete.changes where changes > 0 {
println("deleted alice")
} else if delete.statement.failed {
println("delete failed: \(delete.statement.reason)")
}
```
## Transactions and Savepoints
Using the `transaction` and `savepoint` functions, we can run a series of statements, committing the changes to the database if they all succeed. If a single statement fails, we can bail out early and roll back.
Using the `transaction` and `savepoint` functions, we can run a series of statements chained together (using `&&`). If a single statement fails, we can short-circuit the series (using `||`) and roll back the changes.
``` swift
db.transaction()
@ -905,18 +858,21 @@ db.transaction()
&& db.commit() || db.rollback()
```
The former statement can also be written as
> _Note:_ Each statement is captured in an auto-closure and wont execute till the preceding statement succeeds. This is why we can use the `lastInsertRowid` property on `Database` to reference the previous statements insert [`ROWID`][ROWID].
For more complex transactions and savepoints, block helpers exist. Using a block helper, the former statement can be written (more verbosely) as follows:
``` swift
db.transaction { _ in
for obj in objects {
stmt.run(obj.email)
db.transaction { txn in
if let rowid = users.insert(email <- "betty@icloud.com").rowid {
if users.insert(email <- "cathy@icloud.com", manager_id <- db.lastInsertRowid).rowid != nil {
return .Commit
}
}
return .Commit || .Rollback
return .Rollback
}
```
> _Note:_ Each statement is captured in an auto-closure and wont execute till the preceding statement succeeds. This means we can use the `lastInsertRowid` property on `Database` to reference the previous statements insert [`ROWID`][ROWID].
## Altering the Schema

Просмотреть файл

@ -542,209 +542,209 @@ class ExpressionTests: SQLiteTestCase {
}
func test_plusEquals_withStringExpression_buildsSetter() {
users.update(email += email)!
users.update(email += email2)!
users.update(email2 += email)!
users.update(email2 += email2)!
users.update(email += email)
users.update(email += email2)
users.update(email2 += email)
users.update(email2 += email2)
AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")", 4)
}
func test_plusEquals_withStringValue_buildsSetter() {
users.update(email += ".com")!
users.update(email2 += ".com")!
users.update(email += ".com")
users.update(email2 += ".com")
AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || '.com')", 2)
}
func test_plusEquals_withNumberExpression_buildsSetter() {
users.update(age += age)!
users.update(age += age2)!
users.update(age2 += age)!
users.update(age2 += age2)!
users.update(age += age)
users.update(age += age2)
users.update(age2 += age)
users.update(age2 += age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", 4)
users.update(salary += salary)!
users.update(salary += salary2)!
users.update(salary2 += salary)!
users.update(salary2 += salary2)!
users.update(salary += salary)
users.update(salary += salary2)
users.update(salary2 += salary)
users.update(salary2 += salary2)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", 4)
}
func test_plusEquals_withNumberValue_buildsSetter() {
users.update(age += 1)!
users.update(age2 += 1)!
users.update(age += 1)
users.update(age2 += 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2)
users.update(salary += 100)!
users.update(salary2 += 100)!
users.update(salary += 100)
users.update(salary2 += 100)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", 2)
}
func test_minusEquals_withNumberExpression_buildsSetter() {
users.update(age -= age)!
users.update(age -= age2)!
users.update(age2 -= age)!
users.update(age2 -= age2)!
users.update(age -= age)
users.update(age -= age2)
users.update(age2 -= age)
users.update(age2 -= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", 4)
users.update(salary -= salary)!
users.update(salary -= salary2)!
users.update(salary2 -= salary)!
users.update(salary2 -= salary2)!
users.update(salary -= salary)
users.update(salary -= salary2)
users.update(salary2 -= salary)
users.update(salary2 -= salary2)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", 4)
}
func test_minusEquals_withNumberValue_buildsSetter() {
users.update(age -= 1)!
users.update(age2 -= 1)!
users.update(age -= 1)
users.update(age2 -= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2)
users.update(salary -= 100)!
users.update(salary2 -= 100)!
users.update(salary -= 100)
users.update(salary2 -= 100)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", 2)
}
func test_timesEquals_withNumberExpression_buildsSetter() {
users.update(age *= age)!
users.update(age *= age2)!
users.update(age2 *= age)!
users.update(age2 *= age2)!
users.update(age *= age)
users.update(age *= age2)
users.update(age2 *= age)
users.update(age2 *= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", 4)
users.update(salary *= salary)!
users.update(salary *= salary2)!
users.update(salary2 *= salary)!
users.update(salary2 *= salary2)!
users.update(salary *= salary)
users.update(salary *= salary2)
users.update(salary2 *= salary)
users.update(salary2 *= salary2)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", 4)
}
func test_timesEquals_withNumberValue_buildsSetter() {
users.update(age *= 1)!
users.update(age2 *= 1)!
users.update(age *= 1)
users.update(age2 *= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * 1)", 2)
users.update(salary *= 100)!
users.update(salary2 *= 100)!
users.update(salary *= 100)
users.update(salary2 *= 100)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", 2)
}
func test_divideEquals_withNumberExpression_buildsSetter() {
users.update(age /= age)!
users.update(age /= age2)!
users.update(age2 /= age)!
users.update(age2 /= age2)!
users.update(age /= age)
users.update(age /= age2)
users.update(age2 /= age)
users.update(age2 /= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", 4)
users.update(salary /= salary)!
users.update(salary /= salary2)!
users.update(salary2 /= salary)!
users.update(salary2 /= salary2)!
users.update(salary /= salary)
users.update(salary /= salary2)
users.update(salary2 /= salary)
users.update(salary2 /= salary2)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", 4)
}
func test_divideEquals_withNumberValue_buildsSetter() {
users.update(age /= 1)!
users.update(age2 /= 1)!
users.update(age /= 1)
users.update(age2 /= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / 1)", 2)
users.update(salary /= 100)!
users.update(salary2 /= 100)!
users.update(salary /= 100)
users.update(salary2 /= 100)
AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", 2)
}
func test_moduloEquals_withIntegerExpression_buildsSetter() {
users.update(age %= age)!
users.update(age %= age2)!
users.update(age2 %= age)!
users.update(age2 %= age2)!
users.update(age %= age)
users.update(age %= age2)
users.update(age2 %= age)
users.update(age2 %= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", 4)
}
func test_moduloEquals_withIntegerValue_buildsSetter() {
users.update(age %= 10)!
users.update(age2 %= 10)!
users.update(age %= 10)
users.update(age2 %= 10)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % 10)", 2)
}
func test_rightShiftEquals_withIntegerExpression_buildsSetter() {
users.update(age >>= age)!
users.update(age >>= age2)!
users.update(age2 >>= age)!
users.update(age2 >>= age2)!
users.update(age >>= age)
users.update(age >>= age2)
users.update(age2 >>= age)
users.update(age2 >>= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", 4)
}
func test_rightShiftEquals_withIntegerValue_buildsSetter() {
users.update(age >>= 1)!
users.update(age2 >>= 1)!
users.update(age >>= 1)
users.update(age2 >>= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", 2)
}
func test_leftShiftEquals_withIntegerExpression_buildsSetter() {
users.update(age <<= age)!
users.update(age <<= age2)!
users.update(age2 <<= age)!
users.update(age2 <<= age2)!
users.update(age <<= age)
users.update(age <<= age2)
users.update(age2 <<= age)
users.update(age2 <<= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", 4)
}
func test_leftShiftEquals_withIntegerValue_buildsSetter() {
users.update(age <<= 1)!
users.update(age2 <<= 1)!
users.update(age <<= 1)
users.update(age2 <<= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << 1)", 2)
}
func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() {
users.update(age &= age)!
users.update(age &= age2)!
users.update(age2 &= age)!
users.update(age2 &= age2)!
users.update(age &= age)
users.update(age &= age2)
users.update(age2 &= age)
users.update(age2 &= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", 4)
}
func test_bitwiseAndEquals_withIntegerValue_buildsSetter() {
users.update(age &= 1)!
users.update(age2 &= 1)!
users.update(age &= 1)
users.update(age2 &= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & 1)", 2)
}
func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() {
users.update(age |= age)!
users.update(age |= age2)!
users.update(age2 |= age)!
users.update(age2 |= age2)!
users.update(age |= age)
users.update(age |= age2)
users.update(age2 |= age)
users.update(age2 |= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", 4)
}
func test_bitwiseOrEquals_withIntegerValue_buildsSetter() {
users.update(age |= 1)!
users.update(age2 |= 1)!
users.update(age |= 1)
users.update(age2 |= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | 1)", 2)
}
func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() {
users.update(age ^= age)!
users.update(age ^= age2)!
users.update(age2 ^= age)!
users.update(age2 ^= age2)!
users.update(age ^= age)
users.update(age ^= age2)
users.update(age2 ^= age)
users.update(age2 ^= age2)
AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", 4)
}
func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() {
users.update(age ^= 1)!
users.update(age2 ^= 1)!
users.update(age ^= 1)
users.update(age2 ^= 1)
AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", 2)
}
func test_postfixPlus_withIntegerValue_buildsSetter() {
users.update(age++)!
users.update(age2++)!
users.update(age++)
users.update(age2++)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2)
}
func test_postfixMinus_withIntegerValue_buildsSetter() {
users.update(age--)!
users.update(age2--)!
users.update(age--)
users.update(age2--)
AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2)
}

Просмотреть файл

@ -53,7 +53,7 @@ class FTSTests: SQLiteTestCase {
AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" 'tokenizer')")
emails.insert(subject <- "Aún más cáfe!")!
emails.insert(subject <- "Aún más cáfe!")
XCTAssertEqual(1, emails.filter(match("aun", emails)).count)
}

Просмотреть файл

@ -9,7 +9,7 @@ class FunctionsTests: SQLiteTestCase {
let table = db["table"]
db.create(table: table) { $0.column(Expression<Int>("id"), primaryKey: true) }
table.insert()!
table.insert().rowid!
XCTAssert(table.select(f1()).first![f1()])
AssertSQL("SELECT \"f1\"() FROM \"table\" LIMIT 1")
@ -31,7 +31,7 @@ class FunctionsTests: SQLiteTestCase {
t.column(s1)
t.column(s2)
}
table.insert(s1 <- "s1")!
table.insert(s1 <- "s1").rowid!
let null = Expression<String?>(value: nil as String?)
@ -60,7 +60,7 @@ class FunctionsTests: SQLiteTestCase {
db.create(table: table) { t in
t.column(b)
}
table.insert(b <- true)!
table.insert(b <- true).rowid!
XCTAssert(table.select(f1(b)).first![f1(b)])
AssertSQL("SELECT \"f1\"(\"b\") FROM \"table\" LIMIT 1")
@ -74,7 +74,7 @@ class FunctionsTests: SQLiteTestCase {
t.column(b1)
t.column(b2)
}
table.insert(b1 <- true)!
table.insert(b1 <- true).rowid!
let f1: (Bool, Expression<Bool>) -> Expression<Bool> = db.create(function: "f1") { $0 && $1 }
let f2: (Bool?, Expression<Bool>) -> Expression<Bool> = db.create(function: "f2") { $0 ?? $1 }

Просмотреть файл

@ -83,8 +83,8 @@ class QueryTests: SQLiteTestCase {
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 <- Int64(aliceId))!
let aliceId = users.insert(email <- "alice@example.com").rowid!
users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid!
let query = users
.select(users[*], managers[*])
@ -127,8 +127,8 @@ class QueryTests: SQLiteTestCase {
}
func test_namespacedColumnRowValueAccess() {
let aliceId = users.insert(email <- "alice@example.com")!
let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId))!
let aliceId = users.insert(email <- "alice@example.com").rowid!
let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid!
let alice = users.first!
XCTAssertEqual(Int64(aliceId), alice[id])
@ -141,7 +141,7 @@ class QueryTests: SQLiteTestCase {
}
func test_aliasedColumnRowValueAccess() {
users.insert(email <- "alice@example.com")!
users.insert(email <- "alice@example.com").rowid!
let alias = email.alias("user_email")
let query = users.select(alias)
@ -315,7 +315,7 @@ class QueryTests: SQLiteTestCase {
let emails = db["emails"]
let admins = users.select(email).filter(admin == true)
emails.insert(admins)!
emails.insert(admins)
AssertSQL("INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)")
}
@ -430,14 +430,43 @@ class QueryTests: SQLiteTestCase {
}
let date = NSDate(timeIntervalSince1970: 0)
touches.insert(timestamp <- date)!
touches.insert(timestamp <- date)
XCTAssertEqual(touches.first!.get(timestamp)!, date)
XCTAssertNil(touches.filter(id == Int64(touches.insert()!)).first!.get(timestamp))
XCTAssertNil(touches.filter(id == Int64(touches.insert().rowid!)).first!.get(timestamp))
XCTAssert(touches.filter(timestamp < NSDate()).first != nil)
}
func test_shortCircuitingInserts() {
db.transaction()
&& users.insert(email <- "alice@example.com")
&& users.insert(email <- "alice@example.com")
&& users.insert(email <- "alice@example.com")
&& db.commit()
|| db.rollback()
AssertSQL("BEGIN DEFERRED TRANSACTION")
AssertSQL("INSERT INTO \"users\" (\"email\") VALUES ('alice@example.com')", 2)
AssertSQL("ROLLBACK TRANSACTION")
AssertSQL("COMMIT TRANSACTION", 0)
}
func test_shortCircuitingChanges() {
db.transaction()
&& users.insert(email <- "foo@example.com")
&& users.insert(email <- "bar@example.com")
&& users.filter(email == "bar@example.com").update(email <- "foo@example.com")
&& users.filter(email == "bar@example.com").update(email <- "foo@example.com")
&& db.commit()
|| db.rollback()
AssertSQL("BEGIN DEFERRED TRANSACTION")
AssertSQL("UPDATE \"users\" SET \"email\" = 'foo@example.com' WHERE (\"email\" = 'bar@example.com')")
AssertSQL("ROLLBACK TRANSACTION")
AssertSQL("COMMIT TRANSACTION", 0)
}
}
private let formatter: NSDateFormatter = {

Просмотреть файл

@ -432,28 +432,6 @@ public struct Query {
// MARK: - Modifying
/// Runs an INSERT statement against the query.
///
/// :param: action An action to run in case of a conflict.
/// :param: value A value to set.
/// :param: more A list of additional values to set.
///
/// :returns: The statement.
public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Statement {
return insert([value] + more).statement
}
/// Runs an INSERT statement against the query.
///
/// :param: action An action to run in case of a conflict.
/// :param: value A value to set.
/// :param: more A list of additional values to set.
///
/// :returns: The rowid.
public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Int64? {
return insert(or: action, [value] + more).rowid
}
/// Runs an INSERT statement against the query.
///
/// :param: action An action to run in case of a conflict.
@ -461,104 +439,64 @@ public struct Query {
/// :param: more A list of additional values to set.
///
/// :returns: The rowid and statement.
public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> (rowid: Int64?, statement: Statement) {
public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Insert {
return insert(or: action, [value] + more)
}
/// Runs an INSERT statement against the query.
///
/// :param: action An action to run in case of a conflict.
/// :param: values An array of values to set.
///
/// :returns: The rowid.
public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> Int64? {
return insert(or: action, values).rowid
}
/// Runs an INSERT statement against the query.
///
/// :param: action An action to run in case of a conflict.
/// :param: values An array of values to set.
///
/// :returns: The rowid and statement.
public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> (rowid: Int64?, statement: Statement) {
public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> Insert {
let statement = insertStatement(or: action, values).run()
return (statement.failed ? nil : database.lastInsertRowid, statement)
}
public func insert(query: Query) -> Int? { return insert(query).changes }
public func insert(query: Query) -> Statement { return insert(query).statement }
public func insert(query: Query) -> (changes: Int?, statement: Statement) {
let expression = query.selectExpression
let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings)
return (statement.failed ? nil : database.changes, statement)
}
public func insert() -> Int64? { return insert().rowid }
public func insert() -> Statement { return insert().statement }
public func insert() -> (rowid: Int64?, statement: Statement) {
/// Runs an INSERT statement against the query with DEFAULT VALUES.
///
/// :returns: The rowid and statement.
public func insert() -> Insert {
let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) DEFAULT VALUES")
return (statement.failed ? nil : database.lastInsertRowid, statement)
}
/// Runs an UPDATE statement against the query.
/// Runs an INSERT statement against the query with the results of another
/// query.
///
/// :param: values A list of values to set.
/// :param: query A query to SELECT results from.
///
/// :returns: The statement.
public func update(values: Setter...) -> Statement { return update(values).statement }
/// Runs an UPDATE statement against the query.
///
/// :param: values A list of values to set.
///
/// :returns: The number of updated rows.
public func update(values: Setter...) -> Int? { return update(values).changes }
/// :returns: The number of updated rows and statement.
public func insert(query: Query) -> Change {
let expression = query.selectExpression
let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings)
return (statement.failed ? nil : database.changes, statement)
}
/// Runs an UPDATE statement against the query.
///
/// :param: values A list of values to set.
///
/// :returns: The number of updated rows and statement.
public func update(values: Setter...) -> (changes: Int?, statement: Statement) {
public func update(values: Setter...) -> Change {
return update(values)
}
/// Runs an UPDATE statement against the query.
///
/// :param: values An array of of values to set.
///
/// :returns: The number of updated rows.
public func update(values: [Setter]) -> Int? { return update(values).changes }
/// Runs an UPDATE statement against the query.
///
/// :param: values An array of of values to set.
///
/// :returns: The number of updated rows and statement.
public func update(values: [Setter]) -> (changes: Int?, statement: Statement) {
public func update(values: [Setter]) -> Change {
let statement = updateStatement(values).run()
return (statement.failed ? nil : database.changes, statement)
}
/// Runs a DELETE statement against the query.
///
/// :returns: The statement.
public func delete() -> Statement { return delete().statement }
/// Runs a DELETE statement against the query.
///
/// :returns: The number of deleted rows.
public func delete() -> Int? { return delete().changes }
/// Runs a DELETE statement against the query.
///
/// :returns: The number of deleted rows and statement.
public func delete() -> (changes: Int?, statement: Statement) {
public func delete() -> Change {
let statement = deleteStatement.run()
return (statement.failed ? nil : database.changes, statement)
}
@ -847,6 +785,28 @@ extension Query: Printable {
}
/// The result of an INSERT executed by a query.
public typealias Insert = (rowid: Int64?, statement: Statement)
/// The result of an UPDATE or DELETE executed by a query.
public typealias Change = (changes: Int?, statement: Statement)
public func && (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement {
return lhs && rhs().statement
}
public func || (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement {
return lhs || rhs().statement
}
public func && (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement {
return lhs && rhs().statement
}
public func || (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement {
return lhs || rhs().statement
}
extension Database {
public subscript(tableName: String) -> Query {