Skip to content

Commit

Permalink
useful updates (!)
Browse files Browse the repository at this point in the history
  • Loading branch information
tpolecat committed Jan 29, 2015
1 parent 98305f4 commit 0fed710
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 15 deletions.
51 changes: 36 additions & 15 deletions doc/src/main/tut/07-Updating.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ number: 7
title: DDL, Inserting, and Updating
---

In this chapter we examine operations that modify data in the database, and ways to retrieve the results of these updates.

### Setting Up

Again we set up an H2 transactor and pull in YOLO mode, but this time we're not using the world database.
Again we set up a transactor and pull in YOLO mode, but this time we're not using the world database.

```tut:silent
import doobie.imports._, scalaz._, Scalaz._, scalaz.concurrent.Task
Expand All @@ -19,39 +20,46 @@ import xa.yolo._

### Data Definition

Let's create a new table, which we will use for the examples to follow. This looks a lot like our prior usage of the `sql` interpolator, but this time we're using `update` rather than `query`. The types are indicated.
It is uncommon to define database structures at runtime, but **doobie** handles it just fine and treats such operations like any other kind of update. And it happens to be useful here!

Let's create a new table, which we will use for the examples to follow. This looks a lot like our prior usage of the `sql` interpolator, but this time we're using `update` rather than `query`. The `.run` method gives a `ConnectionIO[Int]` that yields the total number of rows modified, and the YOLO-mode `.quick` gives a `Task[Unit]` that prints out the row count.

```tut:silent
val drop: ConnectionIO[Int] =
val drop: Update0 =
sql"""
DROP TABLE IF EXISTS person
""".update.run
""".update
val create: ConnectionIO[Int] =
val create: Update0 =
sql"""
CREATE TABLE person (
id SERIAL,
name VARCHAR NOT NULL UNIQUE,
age SMALLINT
)
""".update.run
""".update
```

We can compose these and run them together.

```tut
(drop *> create).quick.run
(drop.quick *> create.quick).run
```


### Inserting


Inserting is straightforward and works just as with selects.
Inserting is straightforward and works just as with selects. Here we define a method that constructs an `Update0` that inserts a row into the `person` table.

```tut
```tut:silent
def insert1(name: String, age: Option[Short]): Update0 =
sql"insert into person (name, age) values ($name, $age)".update
```

Let's insert a few rows.

```tut
insert1("Alice", Some(12)).quick.run
insert1("Bob", None).quick.run
```
Expand All @@ -70,7 +78,7 @@ sql"select id, name, age from person".query[Person].quick.run
### Updating


Updating follows the same pattern.
Updating follows the same pattern. Here we update Alice's age.

```tut
sql"update person set age = 15 where name = 'Alice'".update.quick.run
Expand All @@ -79,7 +87,7 @@ sql"select id, name, age from person".query[Person].quick.run

### Retrieving Results

Of course when we insert we usually want the row back, so let's do that. First we'll do it the hard way, by inserting, getting the last used key via `lastVal()`, then selecting the indicated row.
When we insert we usually want the new row back, so let's do that. First we'll do it the hard way, by inserting, getting the last used key via `lastVal()`, then selecting the indicated row.

```tut:silent
def insert2(name: String, age: Option[Short]): ConnectionIO[Person] =
Expand All @@ -99,22 +107,35 @@ This is irritating but it is supported by all databases (although the "get the l
```tut:silent
def insert3(name: String, age: Option[Short]): ConnectionIO[Person] = {
sql"insert into person (name, age) values ($name, $age)"
.update
.withUniqueGeneratedKeys("id", "name", "age")
.update.withUniqueGeneratedKeys("id", "name", "age")
}
```

The `withUniqueGeneratedKeys` specifies that we expect exactly one row back (otherwise an exception will be thrown), and requires a list of columns to return. This isn't the most beautiful API but it's what JDBC gives us. And it does work.

```tut
insert3("Elvis", None).quick.run
```

This mechanism also works for updates, for databases that support it. In the case of multiple row updates we use `.withGeneratedKeys[A](cols...)` to get a `Process[ConnectionIO, A]`.
This mechanism also works for updates, for databases that support it. In the case of multiple row updates we omit `unique` and get a `Process[ConnectionIO, Person]` back.


```tut:silent
val up = {
sql"update person set age = age + 1 where age is not null"
.update.withGeneratedKeys[Person]("id", "name", "age")
}
```
TODO

Running this process updates all rows with a non-`NULL` age and returns them.

```tut
up.quick.run
up.quick.run // and again!
```






11 changes: 11 additions & 0 deletions doc/src/main/tut/08-Error-Handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ number: 8
title: Error Handling
---

In this chapter we examine a set of combinators that allow us to construct programs that trap and handle exceptions.

### Setting Up

```tut:silent
Expand Down Expand Up @@ -64,6 +66,7 @@ See the ScalaDoc for more information.

### Example: Unique Constraint Violation

Ok let's set up a `person` table again, using a slightly different formulation just for fun. Note that the `name` column is marked as being unique.

```tut
List(sql"""DROP TABLE IF EXISTS person""",
Expand All @@ -73,6 +76,8 @@ List(sql"""DROP TABLE IF EXISTS person""",
)""").traverse(_.update.quick).void.run
```

Alright, let's define a `Person` class and a way to insert them.


```tut:silent
Expand Down Expand Up @@ -103,6 +108,9 @@ try {
}
```

So let's change our method to return a `String \/ Person` by using the `attemptSomeSql` combinator. This allows us to specify the `SQLState` value that we want to trap. In this case the culprit `"23505"` (yes, it's a string) is provided as a constant in the `contrib-postgresql` add-on.


```tut:silent
import doobie.contrib.postgresql.sqlstate.class23.UNIQUE_VIOLATION
Expand All @@ -112,6 +120,9 @@ def safeInsert(s: String): ConnectionIO[String \/ Person] =
}
```

Given this definition we can safely attempt to insert duplicate records and get a helpful error message rather than an exception.


```tut
safeInsert("bob").quick.run
Expand Down

0 comments on commit 0fed710

Please sign in to comment.