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:
Родитель
14573e4c83
Коммит
a5011f4a21
|
@ -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 [query’s](#queries) `insert` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator.
|
||||
We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator.
|
||||
|
||||
``` swift
|
||||
users.insert(email <- "alice@mac.com", name <- "Alice")?
|
||||
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 row’s [`ROWID`][ROWID] (or `nil` on failure) and the associated `Statement`.
|
||||
|
||||
- An `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure), for simplicity.
|
||||
|
||||
``` swift
|
||||
if let insertId = users.insert(email <- "alice@mac.com") {
|
||||
println("inserted id: \(insertId)")
|
||||
}
|
||||
```
|
||||
|
||||
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 aren’t 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 table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator.
|
||||
We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator.
|
||||
|
||||
When an unscoped query calls `update`, it will update _every_ row in the table.
|
||||
|
||||
``` 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 [query’s](#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 won’t execute till the preceding statement succeeds. This is why we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s 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 won’t execute till the preceding statement succeeds. This means we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s 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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче