Data Update

This chapter describes operations to modify data in the database and how to retrieve the updated results.

Insert

Insert is simple and works just like select. Here we define a method to create an Executor that will insert rows into the user table.

def insertUser(name: String, email: String): Executor[IO, Int] =
  sql"INSERT INTO user (name, email) VALUES ($name, $email)"
    .update

Let's insert a line.

insertUser("dave", "dave@example.com").commit.unsafeRunSync()

And then read it back.

sql"SELECT * FROM user"
  .query[(Int, String, String)] // Query[IO, (Int, String, String)]
  .to[List]                     // Executor[IO, List[(Int, String, String)]]
  .readOnly(conn)               // IO[List[(Int, String, String)]]
  .unsafeRunSync()              // List[(Int, String, String)]
  .foreach(println)             // Unit

Update

The same pattern applies to updates. Here we update the user's email address.

def updateUserEmail(id: Int, email: String): Executor[IO, Int] =
  sql"UPDATE user SET email = $email WHERE id = $id"
    .update

Getting Results

updateUserEmail(1, "alice+1@example.com").commit.unsafeRunSync()

sql"SELECT * FROM user WHERE id = 1"
  .query[(Int, String, String)] // Query[IO, (Int, String, String)]
  .to[Option]                   // Executor[IO, List[(Int, String, String)]]
  .readOnly(conn)               // IO[List[(Int, String, String)]]
  .unsafeRunSync()              // List[(Int, String, String)]
  .foreach(println)             // Unit
// Some((1,alice,alice+1@example.com))

Auto-generated keys

When inserting, we want to return the newly generated key. We do this the hard way, first by inserting and getting the last generated key with LAST_INSERT_ID and then selecting the specified row.

def insertUser(name: String, email: String): Executor[IO, (Int, String, String)] =
  for
    _    <- sql"INSERT INTO user (name, email) VALUES ($name, $email)".update
    id   <- sql"SELECT LAST_INSERT_ID()".query[Int].unsafe
    task <- sql"SELECT * FROM user WHERE id = $id".query[(Int, String, String)].to[Option]
  yield task
insertUser("eve", "eve@example.com").commit.unsafeRunSync()

This is frustrating, but supported by all databases (although the “get last used ID” feature varies from vendor to vendor).

In MySQL, only rows with AUTO_INCREMENT set can be returned on insert. The above operation can be reduced to two statements

If you are inserting rows using an auto-generated key, you can use the returning method to retrieve the auto-generated key.

def insertUser(name: String, email: String): Executor[IO, (Int, String, String)] =
  for
    id   <- sql"INSERT INTO user (name, email) VALUES ($name, $email)".returning[Int]
    user <- sql"SELECT * FROM user WHERE id = $id".query[(Int, String, String)].to[Option]
  yield user
insertUser("frank", "frank@example.com").commit.unsafeRunSync()

Batch update

To perform batch updates, define an insertManyUser method that inserts multiple rows using NonEmptyList.

def insertManyUser(users: NonEmptyList[(String, String)]): Executor[IO, Int] =
  val value = users.map { case (name, email) => sql"($name, $email)" }
  (sql"INSERT INTO user (name, email) VALUES" ++ values(value)).update

Running this program gives the updated row count.

val users = NonEmptyList.of(
  ("greg", "greg@example.com"),
  ("henry", "henry@example.com")
)

insertManyUser(users).commit.unsafeRunSync()