Cookbook

This section of the manual is for solutions to common questions.

Mapping more than 22 fields

Problem

You have a table with more than 22 fields, and the Scala 2 compiler has told you:

too many elements for tuple: 23, allowed: 22

Solution

Switch from using a tuple in your default projection to using an HList.

First, add the HList imports:

sourceimport slick.collection.heterogeneous._
import slick.collection.heterogeneous.syntax._

Your case class and table definition are as unchanged, but your default projection ( the * method) changes:

sourcecase class Row(id: Int, name: String /* ... as many as you like */)

class MyTable(tag: Tag) extends Table[Row](tag, "ROW") {
  def id   = column[Int]("ID", O.PrimaryKey)
  def name = column[String]("NAME")
  /* ... as many as you like */

  def * = (id :: name /* as many as you like... */ :: HNil).mapTo[Row]
}

Track the Number of Query Compilations

Problem

Query compilation can be expensive. We can use the Compiled construct to avoid re-compiling queries. However, it can also be easy to accidentally forget to use this construct or revert its usage. When this happens in a high-traffic deployment, query times and CPU usage can increase drastically.

To identify this type of regression, we’d like to track the number of query compilations. We might then expose this count as an application metric and setup an alert which triggers when an abnormally high number of query compilations occur.

Solution

To track the number of query compilations, we can override the computeQueryCompiler method in our profile. The new QueryCompiler will have an additional phase, which simply increments a counter.

sourceimport slick.jdbc.JdbcProfile
import slick.compiler._
import java.util.concurrent.atomic.AtomicLong

trait MyProfile extends JdbcProfile {

  // This counter can be accessed from an instance of `MyProfile`
  // and exposed as an application metric.
  final val queryCompilationCounter: AtomicLong = new AtomicLong(0)

  // This method gets called to setup the QueryCompiler.
  // We'll attach a CompilerPhase that just increments the compilation counter
  // and leave the compiler otherwise unchanged.
  override def computeQueryCompiler: QueryCompiler = {
    val phase = new Phase {
      override type State = this.type
      override val name: String = "IncrementQueryCompilationCounter"
      override def apply(state: CompilerState): CompilerState = {
        // When this Phase gets applied, it just increments the counter and
        // passes the CompilerState through unchanged.
        queryCompilationCounter.incrementAndGet()
        state
      }
    }
    super.computeQueryCompiler + phase
  }
}

Distinguish DBIOActions by Effect type at runtime

Problem

When using a replicated database with a primary (read/write) node and one or more replica (read-only) nodes, we have to maintain separate database handles and run each DBIOAction on the appropriate database handle. If a DBIOAction containing a write goes to the replica, it will fail. If a read-only DBIOAction goes to the primary, we lose the benefit of using a replicated database. In a large application, it’s tedious to remember to use the right handle for every DBIOAction, and it’s easy to mix them up.

Ideally we would have a single method like run that takes a DBIOAction and determines whether to run it on the primary or the secondary.

Slick provides a phantom Effect type parameter in each DBIOAction, but it’s not immediately obvious how to use at runtime. For example, we cannot pattern-match on the Effect type parameter, as this parameter is erased at runtime due to type erasure.

Solution

This solution shows one way that we can introspect the Effect type in order to determine whether the DBIOAction should go to the primary or the secondary. We define a type class called EffectInfo.ReadOnly, which has a single member isReadOnly: Boolean. We provide instances of the type class for the various subtypes of Effect, such that isReadOnly is true only for Effect types Read and Read with Transactional. For any other subtypes, isReadOnly is false. We implement a run method which takes a DBIOAction and an implicit instance of the EffectInfo.ReadOnly type class for the DBIOAction’s Effect type. We summon the type class instance and check the isReadOnly member to determine whether the given DBIOAction should go to the primary or to the secondary.

sourceimport slick.dbio._

object EffectInfo {

  /**
   * Type class which can be summoned to determine if a given DBIOAction is read-only,
   * e.g., to let us determine whether to run the DBIOAction on the primary or the secondary.
   */
  sealed trait ReadOnly[E <: Effect] {
    def isReadOnly: Boolean
  }

  /**
   * Base trait provides an instance of ReadOnly where isReadOnly = false.
   * This is extended below by the ReadOnly companion object, which overrides the instance of ReadOnly for effect
   * types where isReadOnly should be true.
   */
  sealed trait ReadOnlyFalse {

    implicit def readOnlyFalse[E <: Effect]: ReadOnly[E] = new ReadOnly[E] {
      val isReadOnly: Boolean = false
    }
  }

  /**
   * Companion object providing implementations of ReadOnly for specific types where isReadOnly = true.
   */
  object ReadOnly extends ReadOnlyFalse {

    // When Effect.Read is used by itself, isReadOnly is true.
    implicit object Read extends ReadOnly[Effect.Read] {
      val isReadOnly: Boolean = true
    }

    // When Effect.Read is used with Effect.Transactional, isReadOnly is true,
    // as a transactional read is still a read.
    implicit object ReadWithTransactional extends ReadOnly[Effect.Read with Effect.Transactional] {
      val isReadOnly: Boolean = true
    }
  }
}

// For brevity, this run method just demonstrates that we can distinguish the effect type at runtime.
// In a real application, we could place this method in a class that contains a handle to the primary and a handle
// to the secondary. Then the callers just call `run(action)` and don't worry about distinguishing the two handles.
def run[A, E <: Effect: EffectInfo.ReadOnly](action: DBIOAction[A, NoStream, E]): Unit =
  if (implicitly[EffectInfo.ReadOnly[E]].isReadOnly) println("Action is read-only, run it on the secondary")
  else println("Action is not read-only, run it on the primary")

// We define a generic action and narrow it to specific effect types below.
val action = DBIO.successful(42)

// Read-only actions.
// Each of these prints "Action is read-only, run it on the secondary"
run(action: DBIOAction[Int, NoStream, Effect.Read])
run(action: DBIOAction[Int, NoStream, Effect.Read with Effect.Transactional])
run(action: DBIOAction[Int, NoStream, Effect with Effect.Read with Effect.Transactional])
run(action: DBIOAction[Int, NoStream, Effect.Transactional with Effect.Read with Effect])

// Other actions.
// "Action is not read-only, run it on the primary"
run(action: DBIOAction[Int, NoStream, Effect])
run(action: DBIOAction[Int, NoStream, Effect.Write])
run(action: DBIOAction[Int, NoStream, Effect.Schema])
run(action: DBIOAction[Int, NoStream, Effect.All])
The source code for this page can be found here.