Skip to content

Commit a5011f4

Browse files
committed
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>
1 parent 14573e4 commit a5011f4

File tree

6 files changed

+222
-277
lines changed

6 files changed

+222
-277
lines changed

Documentation/Index.md

Lines changed: 46 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -393,62 +393,33 @@ Additional constraints may be provided outside the scope of a single column usin
393393

394394
## Inserting Rows
395395

396-
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.
396+
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.
397397

398398
``` swift
399-
users.insert(email <- "alice@mac.com", name <- "Alice")?
399+
users.insert(email <- "alice@mac.com", name <- "Alice")
400400
// INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice')
401401

402402
users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.")
403403
// INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.')
404404
```
405405

406-
The `insert` function can return several different types that are useful in different contexts.
406+
The `insert` function returns a tuple with an `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure) and the associated `Statement`.
407407

408-
- An `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure), for simplicity.
409-
410-
``` swift
411-
if let insertId = users.insert(email <- "alice@mac.com") {
412-
println("inserted id: \(insertId)")
413-
}
414-
```
415-
416-
If a value is always expected, we can disambiguate with a `!`.
417-
418-
``` swift
419-
users.insert(email <- "alice@mac.com")!
420-
```
421-
422-
- A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
423-
424-
``` swift
425-
db.transaction()
426-
&& users.insert(email <- "alice@mac.com")
427-
&& users.insert(email <- "betty@mac.com")
428-
&& db.commit() || db.rollback()
429-
// BEGIN DEFERRED TRANSACTION;
430-
// INSERT INTO "users" ("email") VALUES ('alice@mac.com');
431-
// INSERT INTO "users" ("email") VALUES ('betty@mac.com');
432-
// COMMIT TRANSACTION;
433-
```
434-
435-
- A tuple of the above [`ROWID`][ROWID] and statement: `(rowid: Int64?, statement: Statement)`, for flexibility.
436-
437-
``` swift
438-
let (rowid, statement) = users.insert(email <- "alice@mac.com")
439-
if let rowid = rowid {
440-
println("inserted id: \(rowid)")
441-
} else if statement.failed {
442-
println("insertion failed: \(statement.reason)")
443-
}
444-
```
408+
``` swift
409+
let insert = users.insert(email <- "alice@mac.com")
410+
if let rowid = insert.rowid {
411+
println("inserted id: \(rowid)")
412+
} else if insert.statement.failed {
413+
println("insertion failed: \(insert.statement.reason)")
414+
}
415+
```
445416

446417
The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns.
447418

448419
> _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.
449420
>
450421
> ``` swift
451-
> timestamps.insert()!
422+
> timestamps.insert()
452423
> // INSERT INTO "timestamps" DEFAULT VALUES
453424
> ```
454425

@@ -816,42 +787,33 @@ users.filter(name != nil).count
816787

817788
## Updating Rows
818789

819-
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.
790+
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.
820791

821792
When an unscoped query calls `update`, it will update _every_ row in the table.
822793

823794
``` swift
824-
users.update(email <- "alice@me.com")?
795+
users.update(email <- "alice@me.com")
825796
// UPDATE "users" SET "email" = 'alice@me.com'
826797
```
827798

828799
Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#filtering-rows).
829800

830801
``` swift
831802
let alice = users.filter(id == 1)
832-
alice.update(email <- "alice@me.com")?
803+
alice.update(email <- "alice@me.com")
833804
// UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1)
834805
```
835806

836-
Like [`insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can return several different types that are useful in different contexts.
837-
838-
- An `Int?` representing the number of updated rows (or `nil` on failure), for simplicity.
839-
840-
``` swift
841-
if alice.update(email <- "alice@me.com") > 0 {
842-
println("updated Alice")
843-
}
844-
```
807+
The `update` function returns a tuple with an `Int?` representing the number of updates (or `nil` on failure) and the associated `Statement`.
845808

846-
If a value is always expected, we can disambiguate with a `!`.
847-
848-
``` swift
849-
alice.update(email <- "alice@me.com")!
850-
```
851-
852-
- A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
853-
854-
- A tuple of the above number of updated rows and statement: `(changes: Int?, Statement)`, for flexibility.
809+
``` swift
810+
let update = alice.update(email <- "alice@me.com")
811+
if let changes = update.changes where changes > 0 {
812+
println("updated alice")
813+
} else if update.statement.failed {
814+
println("update failed: \(update.statement.reason)")
815+
}
816+
```
855817

856818

857819
## Deleting Rows
@@ -861,42 +823,33 @@ We can delete rows from a table by calling a [query’s](#queries) `delete` func
861823
When an unscoped query calls `delete`, it will delete _every_ row in the table.
862824

863825
``` swift
864-
users.delete()?
826+
users.delete()
865827
// DELETE FROM "users"
866828
```
867829

868830
Be sure to scope `DELETE` statements beforehand using [the `filter` function](#filtering-rows).
869831

870832
``` swift
871833
let alice = users.filter(id == 1)
872-
alice.delete()?
834+
alice.delete()
873835
// DELETE FROM "users" WHERE ("id" = 1)
874836
```
875837

876-
Like [`insert`](#inserting-rows) and [`update`](#updating-rows), `delete` can return several different types that are useful in different contexts.
877-
878-
- An `Int?` representing the number of deleted rows (or `nil` on failure), for simplicity.
879-
880-
``` swift
881-
if alice.delete() > 0 {
882-
println("deleted Alice")
883-
}
884-
```
885-
886-
If a value is always expected, we can disambiguate with a `!`.
887-
888-
``` swift
889-
alice.delete()!
890-
```
891-
892-
- A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements.
838+
The `delete` function returns a tuple with an `Int?` representing the number of deletes (or `nil` on failure) and the associated `Statement`.
893839

894-
- A tuple of the above number of deleted rows and statement: `(changes: Int?, Statement)`, for flexibility.
840+
``` swift
841+
let delete = delete.update(email <- "alice@me.com")
842+
if let changes = delete.changes where changes > 0 {
843+
println("deleted alice")
844+
} else if delete.statement.failed {
845+
println("delete failed: \(delete.statement.reason)")
846+
}
847+
```
895848

896849

897850
## Transactions and Savepoints
898851

899-
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.
852+
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.
900853

901854
``` swift
902855
db.transaction()
@@ -905,18 +858,21 @@ db.transaction()
905858
&& db.commit() || db.rollback()
906859
```
907860

908-
The former statement can also be written as
861+
> _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].
862+
863+
For more complex transactions and savepoints, block helpers exist. Using a block helper, the former statement can be written (more verbosely) as follows:
864+
909865
``` swift
910-
db.transaction { _ in
911-
for obj in objects {
912-
stmt.run(obj.email)
866+
db.transaction { txn in
867+
if let rowid = users.insert(email <- "betty@icloud.com").rowid {
868+
if users.insert(email <- "cathy@icloud.com", manager_id <- db.lastInsertRowid).rowid != nil {
869+
return .Commit
870+
}
913871
}
914-
return .Commit || .Rollback
872+
return .Rollback
915873
}
916874
```
917875

918-
> _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].
919-
920876

921877
## Altering the Schema
922878

0 commit comments

Comments
 (0)