Question

How does Slick translate code such as:

val q2 = for {
  c <- Coffees if c.price < 9.0
  s <- Suppliers if s.id === c.supID
} yield (c.name, s.name)
for(t <- q2) println("  " + t._1 + " supplied by " + t._2)

Into JDBC?

Does it use Scala Virtualized? Does it use some other method?

Was it helpful?

Solution

Slick's stable API achieves this via what it calls lifted embedding. Your example is clearly using the stable API (as you use === for equality and not ==).

The beauty of Slick (and in turn Scala) is that - this much is achieved without using macros or Scala-Virtualized. (Side Note: Slick's experimental API does use macros - and this will allow you to use == instead of === or is)

The translation to SQL is achieved using:

  1. Scala's for comprehension syntax, which is translated to method calls. Tables defined in Slick are Monads - they have the magic foreach, map, flatMap, and filter methods which allow them to be expressed in for 'loops' while Scala translates them to method calls (as correctly illustrated in the code provided by the other answer by @emil-ivanov).

    As with regular Scala collections, the for is syntactic sugar for nested method calls to flatMap/map and filter; unlike regular collections, the Slick Table objects' versions of map and filter return representations of a query, building it along with every filter condition (if) or join (as in s <- Suppliers if s.id is c.supID)

    So the type of q2 is not your usual collection (as a for comprehension in Scala is typically used to return), but rather a representation of a query. (Just as the Scala Option Monad also works with for comprehensions despite not being a 'collection' (in the way that List or Map is))

    You can see the underlying query with q2.selectStatement.

  2. Scala's implicit lifting - c.price is not an Int but rather a representation of a column value - so the expression c.price < 9.0 becomes c.price.<(Const(9.0)) (an Int is lifted to the desired type), and < is a just a method of the class that represents c.price, a Column. The < method does not do what < usually does (in the case of plain Ints) - it simply returns a representation of the SQL AST corresponding to price < 9 that becomes part of the SQL that is generated and sent off to JDBC to execute.

There's a whole lot else going on, in terms of details, but I think the query monad and the implicit lifting are the chief ingredients.

OTHER TIPS

In Scala the for "loop" isn't actually a special language construct, but rather syntactic sugar. Your first example

val q2 = for {
  c <- Coffees if c.price < 9.0
  s <- Suppliers if s.id === c.supID
} yield (c.name, s.name)

translates to something in the lines of:

val q2 = Coffees.withFilter(_.price < 9.0).flatMap(c =>
    Suppliers.withFilter(_.id === c.supID).map(s =>
        (c.name, s.name)
    )
)

Now, the flatMap, map, withFilter (and foreach) do not actually filter the collection, but rather collect what's hapening in an AST (Abstract Syntax Tree), which is then handled to Slick to translate to SQL.

Also, c.price, c.supID are actually Slick columns, whose <, >, === (and so on) methods don't return bool, but collect the comparison as well, which is later passed down to be converted to SQL.

This is a talk by the creators, where most of this is described (correctly).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top