Search code examples
scalatypesslick

Slick insert, type mismatch


I'm trying to insert some data into my database using Slick. I've successfully been able to query the database, but can't quite understand how to insert data using the documentation examples.

I've gotten to the point where all that's supposedly wrong is that my action isn't of the correct type and it throws the error

type mismatch;
 found   : slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write with slick.dbio.Effect.Schema]
 required: slick.dbio.DBIOAction[com.ojolabs.customer.avro.CustomerEvent,slick.dbio.NoStream,Nothing]
      db.run(action)

I'm not quite certain how to return the types that are specified as required, using the code I've already written.

I'm calling my schema from here:

trait CustomerEventsComponent {
  def customEventsManager: CustomerEvents.Async
}

trait DefaultCustomerEvents extends CustomerEventsComponent{
  self: DatabaseComponent with ExecutionContextComponent =>

  lazy val customEventsManager = new Async {
    override def create(phoneNumber: String, createdAt: DateTime): Future[CustomerEvent] = {

      val action = Schema.CustomerEvents.userAction

      //this is the line that throws the error
      db.run(action)

    }
  }

}

And am creating the action in here

object Schema {
  class CustomerEvents(tag: Tag) extends Table[CustomerEvent](tag, "customer_events") {
    def id: Rep[UUID] = column[UUID]("id", O.PrimaryKey)
    def customerId: Rep[UUID] = column[UUID]("customer_id")
    def eventType: Rep[String] = column[String]("type")
    def createdAt: Rep[DateTime] = column[DateTime]("created_at")
    def version: Rep[Double] = column[Double]("version")
    def data: Rep[JsValue] = column[JsValue]("data")
    def metadata: Rep[JsValue] = column[JsValue]("metadata")

    def * = (id, customerId, eventType, createdAt, version, data, metadata) <> (CustomerEvent.tupled, CustomerEvent.unapply)

  }

  object CustomerEvents {
    val all = TableQuery[CustomerEvents]

      val userAction = DBIO.seq(
        all.schema.create,
        all += CustomerEvent(
          UUID.randomUUID(),
          UUID.randomUUID(),
          "hello",
          DateTime.now(),
          1.0,
          Json.toJson("{\"hello\" : \"its me\"}"),
          Json.toJson("{\"hello\" : \"its me\"}"))
    )

}

Solution

  • To keep this answer a little shorter, I'm going to refer to DBIO[T], which is an alias in Slick for a DBIOAction[T, NoStream, Effect.All]

    Why you see the compilation error

    The error is saying the compiler expects a DBIO[CustomerEvent] but found a DBIO[Unit]. It expects that type because create is defined to return a Future[CustomerEvent] (so db.run should return that).

    However, Schema.CustomerEvents.userAction calls DBIO.seq. seq is a way to combine actions and ignore the results. The return type of DBIO.seq is a DBIO[Unit] (reference: Scala Doc).

    So that's why you see the error: the code is combining actions using a method that throws away the result.

    What to do about this

    There are a few things you could do about this.

    1. If you really don't want the result of the insert, change the type of create to be Future[Unit]

    2. If you do want a non-Unit result, you'll need to switch to a different "combinator" than seq. I'd suggest andThen in this case, which combines two actions, keeping the value of the second one. I'll explain that in a moment...

    The result from an insert

    += defaults to returning the number of rows affected. That would be a Future[Int] type on create if that's what you want.

    The userAction would end up as: all.schema.create andThen (all += ...etc)

    Or you could use a for comprehension if you prefer:

    for {
      _            <- all.schema.create
      rowsAffected <- all += ...etc
    } yield rowsAffected
    

    (which isn't using andThen but ends up with the same result).

    However, if you want the case class as a result... well, you create the case class, so you could yield it in that for comprehension example above. Slick also supports returning and into as a way of changing the return type of an += expresson: it's described in the reference manual.