Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Slick's mapped tables with foreign keys?

I'm struggling with Slick's lifted embedding and mapped tables. The API feels strange to me, maybe just because it is structured in a way that's unfamiliar to me.

I want to build a Task/Todo-List. There are two entities:

  • Task: Each task has a an optional reference to the next task. That way a linked list is build. The intention is that the user can order the tasks by his priority. This order is represented by the references from task to task.
  • TaskList: Represents a TaskList with a label and a reference to the first Task of the list.

    case class Task(id: Option[Long], title: String, nextTask: Option[Task])
    case class TaskList(label: String, firstTask: Option[Task])


Now I tried to write a data access object (DAO) for these two entities.

import scala.slick.driver.H2Driver.simple._
import slick.lifted.MappedTypeMapper

implicit val session: Session = Database.threadLocalSession
val queryById = Tasks.createFinderBy( t => t.id )

def task(id: Long): Option[Task] = queryById(id).firstOption

  private object Tasks extends Table[Task]("TASKS") {
    def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def title = column[String]("TITLE")
    def nextTaskId = column[Option[Long]]("NEXT_TASK_ID")
    def nextTask = foreignKey("NEXT_TASK_FK", nextTaskId, Tasks)(_.id)
    def * = id ~ title ~ nextTask <> (Task, Task.unapply _)
  }

  private object TaskLists extends Table[TaskList]("TASKLISTS") {
    def label = column[String]("LABEL", O.PrimaryKey)
    def firstTaskId = column[Option[Long]]("FIRST_TASK_ID")
    def firstTask = foreignKey("FIRST_TASK_FK", firstTaskId, Tasks)(_.id)
    def * = label ~ firstTask <> (Task, Task.unapply _)
  }

Unfortunately it does not compile. The problems are in the * projection of both tables at nextTask respective firstTask.

  • could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[scala.slick.lifted.ForeignKeyQuery[SlickTaskRepository.this.Tasks.type,justf0rfun.bookmark.model.Task]]
  • could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[scala.slick.lifted.ForeignKeyQuery[SlickTaskRepository.this.Tasks.type,justf0rfun.bookmark.model.Task]]

I tried to solve that with the following TypeMapper but that does not compile, too.

  implicit val taskMapper = MappedTypeMapper.base[Option[Long], Option[Task]](
    option => option match {
      case Some(id) => task(id)
      case _ => None
    },
    option => option match {
      case Some(task) => task.id
      case _ => None
    })
  • could not find implicit value for parameter tm: scala.slick.lifted.TypeMapper[Option[justf0rfun.bookmark.model.Task]]
  • not enough arguments for method base: (implicit tm: scala.slick.lifted.TypeMapper[Option[justf0rfun.bookmark.model.Task]])scala.slick.lifted.BaseTypeMapper[Option[Long]]. Unspecified value parameter tm.

Main question: How to use Slick's lifted embedding and mapped tables the right way? How to I get this to work?

Thanks in advance.

like image 546
user573215 Avatar asked Jul 24 '13 09:07

user573215


People also ask

Do foreign keys link tables?

A Foreign Key is a database key that is used to link two tables together. The FOREIGN KEY constraint identifies the relationships between the database tables by referencing a column, or set of columns, in the Child table that contains the foreign key, to the PRIMARY KEY column or set of columns, in the Parent table.

How do you decide which table has a foreign key?

The table that contains the foreign key is considered the child table, and the table that the foreign key references is the parent table. A foreign key must also have the same number of columns as the number of columns in the referenced constraint, and the data types must match between corresponding columns.

Does each table need a foreign key?

Note that foreign keys are not mandatory, and a table may have no foreign keys. Conversely, every column in a table may have a foreign key constraint.


1 Answers

The short answer is: Use ids instead of object references and use Slick queries to dereference ids. You can put the queries into methods for re-use.

That would make your case classes look like this:

case class Task(id: Option[Long], title: String, nextTaskId: Option[Long])
case class TaskList(label: String, firstTaskId: Option[Long])

I'll publish an article about this topic at some point and link it here.

like image 186
cvogt Avatar answered Oct 21 '22 08:10

cvogt