tessl install tessl/maven-com-typesafe-slick--slick_2-12@2.1.0Scala Language-Integrated Connection Kit - A modern database query and access library for Scala that allows you to work with stored data almost as if you were using Scala collections while at the same time giving you full control over when a database access happens and which data is transferred.
The Lifted Embedding DSL is Slick's main query language that provides type-safe database queries using a collection-like API. It "lifts" Scala expressions to database queries, allowing you to write queries that look like Scala collection operations but compile to efficient SQL.
The foundation of Slick's query system:
// Base trait for all query representations
sealed trait QueryBase[T] extends Rep[T]
// Main query class
sealed abstract class Query[+E, U, C[_]] extends QueryBase[C[U]] {
def shaped: ShapedValue[_ <: E, U]
}
// Base representation trait
trait Rep[T]The main Query class provides all query operations:
sealed abstract class Query[+E, U, C[_]] extends QueryBase[C[U]] {
// Monadic operations
def map[F, G, T](f: E => F)(implicit shape: Shape[_ <: FlatShapeLevel, F, T, G]): Query[G, T, C]
def flatMap[F, T, D[_]](f: E => Query[F, T, D]): Query[F, T, C]
// Filtering
def filter[T <: Column[_]](f: E => T)(implicit wt: CanBeQueryCondition[T]): Query[E, U, C]
def withFilter[T <: Column[_]](f: E => T)(implicit wt: CanBeQueryCondition[T]): Query[E, U, C]
def filterNot[T <: Column[_]](f: E => T)(implicit wt: CanBeQueryCondition[T]): Query[E, U, C]
// Sorting
def sortBy[T <% Ordered](f: E => T): Query[E, U, C]
// Grouping
def groupBy[K, T, G, P](f: E => K)(implicit kshape: Shape[_ <: FlatShapeLevel, K, T, G]): Query[G, P, C]
// Joins
def join[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(E, E2), (U, U2), C]
def leftJoin[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(E, Option[E2]), (U, Option[U2]), C]
def rightJoin[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(Option[E], E2), (Option[U], U2), C]
def outerJoin[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(Option[E], Option[E2]), (Option[U], Option[U2]), C]
def zip[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(E, E2), (U, U2), C]
def zipWith[E2, U2, F, G, T, D[_]](q2: Query[E2, U2, D], f: (E, E2) => F): Query[G, T, C]
def zipWithIndex: Query[(E, Column[Long]), (U, Long), C]
// Pagination
def take(num: Long): Query[E, U, C]
def drop(num: Long): Query[E, U, C]
// Aggregation
def length: Column[Int]
def countDistinct: Column[Int]
def exists: Column[Boolean]
// Set operations
def union[O >: E, R, D[_]](other: Query[O, R, D]): Query[O, R, C]
def unionAll[O >: E, R, D[_]](other: Query[O, R, D]): Query[O, R, C]
def ++[O >: E, R, D[_]](other: Query[O, R, D]): Query[O, R, C]
// Execution (when imported from driver)
def list()(implicit session: Session): List[U]
def first()(implicit session: Session): U
def firstOption()(implicit session: Session): Option[U]
def run()(implicit session: Session): C[U]
}Specialized query type for table operations:
class TableQuery[E <: AbstractTable[_]] extends Query[E, E#TableElementType, Seq] {
// Insert operations
def +=(value: E#TableElementType)(implicit session: Session): Unit
def ++=(values: Iterable[E#TableElementType])(implicit session: Session): Unit
def insert(value: E#TableElementType)(implicit session: Session): Int
def insertAll(values: E#TableElementType*)(implicit session: Session): Option[Int]
// Update operations
def update(value: E#TableElementType)(implicit session: Session): Int
// Delete operations
def delete()(implicit session: Session): Int
// DDL operations
def ddl: DDL
}
// Factory object
object TableQuery {
def apply[E <: AbstractTable[_]](cons: Tag => E): TableQuery[E]
def apply[E <: AbstractTable[_]](implicit tt: TypeTag[E]): TableQuery[E] = macro TableQueryMacro.apply[E]
}// Base column representation
abstract class Column[T] extends Rep[T] {
// Ordering
def asc: ColumnOrdered[T]
def desc: ColumnOrdered[T]
// Null operations
def isNull: Column[Boolean]
def isNotNull: Column[Boolean]
def ifNull[S >: T](replacement: S): Column[S]
// Optional conversion
def ? : Column[Option[T]]
}
// Constant columns (client-side values)
abstract class ConstColumn[T] extends Column[T]
// Literal values known at compile-time
class LiteralColumn[T](val value: T) extends ConstColumn[T]
// Runtime parameters
class ParameterColumn[T] extends ConstColumn[T]
// Ordered columns for sorting
sealed trait ColumnOrdered[+T]Type-safe operations on columns:
// Base extension framework
trait ExtensionMethods[B1, P1] {
protected implicit def c1: Column[P1]
}
// Core column operations
final class ColumnExtensionMethods[B1, P1](val c: Column[P1]) extends ExtensionMethods[B1, P1] {
// Equality operations (type-safe)
def ===[P2, R](e: Column[P2])(implicit om: o#arg[B1, P2]#to[Boolean, R]): Column[R]
def =!=[P2, R](e: Column[P2])(implicit om: o#arg[B1, P2]#to[Boolean, R]): Column[R]
// Comparison operations
def <[P2, R](e: Column[P2])(implicit om: o#arg[B1, P2]#to[Boolean, R]): Column[R]
def <=[P2, R](e: Column[P2])(implicit om: o#arg[B1, P2]#to[Boolean, R]): Column[R]
def >[P2, R](e: Column[P2])(implicit om: o#arg[B1, P2]#to[Boolean, R]): Column[R]
def >=[P2, R](e: Column[P2])(implicit om: o#arg[B1, P2]#to[Boolean, R]): Column[R]
// Set membership
def inSet[R](values: Traversable[B1]): Column[Boolean]
def inSetBind[R](values: Traversable[B1]): Column[Boolean]
def between[P2, P3, R](start: Column[P2], end: Column[P3]): Column[Boolean]
}
// String-specific operations
final class StringColumnExtensionMethods[P1](val c: Column[P1]) extends ExtensionMethods[String, P1] {
def like[R](e: Column[String]): Column[Boolean]
def ++[P2, R](e: Column[P2]): Column[String] // String concatenation
def startsWith[R](s: String): Column[Boolean]
def endsWith[R](s: String): Column[Boolean]
def toUpperCase: Column[String]
def toLowerCase: Column[String]
def ltrim: Column[String]
def rtrim: Column[String]
def trim: Column[String]
}
// Numeric operations
final class NumericColumnExtensionMethods[B1, P1](val c: Column[P1]) extends ExtensionMethods[B1, P1] {
def +[P2, R](e: Column[P2]): Column[R]
def -[P2, R](e: Column[P2]): Column[R]
def *[P2, R](e: Column[P2]): Column[R]
def /[P2, R](e: Column[P2]): Column[R]
def %[P2, R](e: Column[P2]): Column[R]
def abs: Column[B1]
def ceil: Column[B1]
def floor: Column[B1]
def sign: Column[Int]
}
// Boolean operations
final class BooleanColumnExtensionMethods[P1](val c: Column[P1]) extends ExtensionMethods[Boolean, P1] {
def &&[P2, R](b: Column[P2]): Column[Boolean]
def ||[P2, R](b: Column[P2]): Column[Boolean]
def unary_! : Column[Boolean]
}import scala.slick.driver.H2Driver.simple._
// Table definition
class Users(tag: Tag) extends Table[(Int, String, Option[String])](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def email = column[Option[String]]("email")
def * = (id, name, email)
}
val users = TableQuery[Users]
// Basic queries
val query1 = users.filter(_.name === "John")
val query2 = users.map(_.email)
val query3 = users.filter(_.id > 10).map(u => (u.name, u.email))
// Chaining operations
val activeUsers = users
.filter(_.email.isDefined)
.sortBy(_.name.asc)
.take(50)class Orders(tag: Tag) extends Table[(Int, Int, String)](tag, "orders") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def userId = column[Int]("user_id")
def product = column[String]("product")
def * = (id, userId, product)
}
val orders = TableQuery[Orders]
// Inner join
val userOrders = users.join(orders).on(_.id === _.userId)
val userOrdersQuery = userOrders.map { case (u, o) => (u.name, o.product) }
// Left join
val allUsersOrders = users.joinLeft(orders).on(_.id === _.userId)
val leftJoinQuery = allUsersOrders.map { case (u, o) => (u.name, o.map(_.product)) }
// Complex join with filtering
val recentOrders = users
.join(orders).on(_.id === _.userId)
.filter { case (u, o) => u.email.isDefined && o.product.like("%laptop%") }
.map { case (u, o) => (u.name, o.product) }// Count operations
val userCount = users.length
val usersWithEmail = users.filter(_.email.isDefined).length
// Grouping
val ordersByUser = orders
.groupBy(_.userId)
.map { case (userId, orders) => (userId, orders.length) }
// Complex aggregation
class OrderItems(tag: Tag) extends Table[(Int, Int, Double)](tag, "order_items") {
def orderId = column[Int]("order_id")
def quantity = column[Int]("quantity")
def price = column[Double]("price")
def * = (orderId, quantity, price)
}
val orderItems = TableQuery[OrderItems]
val orderTotals = orderItems
.groupBy(_.orderId)
.map { case (orderId, items) =>
(orderId, items.map(i => i.quantity * i.price).sum)
}// Subqueries
val richUsers = users.filter(_.id in orders.filter(_.product === "luxury").map(_.userId))
// Exists
val usersWithOrders = users.filter(u => orders.filter(_.userId === u.id).exists)
// Union
val activeUsers = users.filter(_.email.isDefined)
val vipUsers = users.filter(_.name.startsWith("VIP"))
val combinedUsers = activeUsers.union(vipUsers)
// Case expressions
val userType = users.map { u =>
Case
.when(u.email.isDefined).then("active")
.when(u.name.startsWith("VIP")).then("vip")
.otherwise("regular")
}
// Window functions (if supported by database)
val rankedUsers = users
.map(u => (u.name, Functions.rowNumber.over.sortBy(u.id.desc)))
.sortBy(_._2.asc)Type-safe query conditions using the CanBeQueryCondition typeclass:
// Typeclass for valid query conditions
trait CanBeQueryCondition[-T] {
def apply(value: T): Column[Boolean]
}
// Standard instances
implicit val columnCanBeQueryCondition: CanBeQueryCondition[Column[Boolean]]
implicit val booleanCanBeQueryCondition: CanBeQueryCondition[Boolean]
implicit def optionCanBeQueryCondition[T](implicit ev: CanBeQueryCondition[T]): CanBeQueryCondition[Option[T]]Usage in queries:
// Valid conditions
users.filter(_.email.isDefined) // Column[Boolean]
users.filter(_ => true) // Boolean literal
users.filter(_.name === "John") // Column comparison
// Combining conditions
users.filter(u => u.email.isDefined && u.name.startsWith("J"))
users.filter(u => u.id > 10 || u.name === "Admin")The shape system handles type mapping between query expressions and result types:
// Core shape abstraction
abstract class Shape[Level, -Mixed_, Unpacked_, Packed_] {
def pack(from: Mixed_): Packed_
def unpack(from: Packed_): Mixed_
def packedShape: Shape[Level, Packed_, Unpacked_, Packed_]
def toNode(value: Mixed_): Node
}
// Proven shape for table projections
type ProvenShape[T] = Shape[FlatShapeLevel, T, T, Column[T]]
// Shape levels
sealed trait ShapeLevel
trait FlatShapeLevel extends ShapeLevel
trait NestedShapeLevel extends ShapeLevel
// Shaped values
case class ShapedValue[T, U](value: T, shape: Shape[_ <: ShapeLevel, T, U, _]) {
def toNode: Node = shape.toNode(value)
}Common shape patterns:
// Single column
val nameShape: ProvenShape[String] = users.map(_.name).shaped
// Tuple shapes
val userTuple: ProvenShape[(Int, String)] = users.map(u => (u.id, u.name)).shaped
// Case class mapping (with macro support)
case class User(id: Int, name: String, email: Option[String])
val userProjection = users.map(u => (u.id, u.name, u.email)) <> (User.tupled, User.unapply)Optimize frequently-used parameterized queries:
// Compiled query wrapper
class Compiled[T] {
def apply(param: T#Param): T#Result
}
// Compilation functions
object Compiled {
def apply[V, R](f: V => R): Compiled[V => R]
}Usage examples:
// Simple parameter
val userById = Compiled { id: Column[Int] =>
users.filter(_.id === id)
}
// Multiple parameters
val userByNameAndEmail = Compiled { (name: Column[String], email: Column[String]) =>
users.filter(u => u.name === name && u.email === email)
}
// Usage
Database.forURL("...") withSession { implicit session =>
val user1 = userById(1).first
val user2 = userByNameAndEmail("John", "john@example.com").firstOption
}Custom SQL function calls:
// Simple function calls
object SimpleFunction {
def apply[R](name: String): SimpleBinaryOperator[R]
def apply[T1, R](name: String): SimpleFunction[T1, R]
def apply[T1, T2, R](name: String): SimpleFunction[(T1, T2), R]
// ... up to 22 parameters
}
// Binary operators
object SimpleBinaryOperator {
def apply[R](name: String): SimpleBinaryOperator[R]
}Usage examples:
// Custom functions
val upper = SimpleFunction.unary[String]("upper")
val concat = SimpleFunction.binary[String]("concat")
val random = SimpleFunction.nullary[Double]("random")
// Usage in queries
val upperNames = users.map(u => upper(u.name))
val fullNames = users.map(u => concat(u.name, " Suffix"))
val randomScores = users.map(u => (u.name, random()))Custom type mapping for user-defined types:
// Base trait for custom mappings
trait MappedTo[T] extends Any {
def value: T
}
// Column type construction
trait ColumnType[T] extends TypedType[T]Usage examples:
// Status enum mapping
sealed trait Status extends MappedTo[String]
case object Active extends Status { val value = "active" }
case object Inactive extends Status { val value = "inactive" }
implicit val statusColumnType = MappedColumnType.base[Status, String](
_.value,
{
case "active" => Active
case "inactive" => Inactive
}
)
// Usage in tables
class Users(tag: Tag) extends Table[(Int, String, Status)](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def status = column[Status]("status") // Uses custom mapping
def * = (id, name, status)
}