Skip to content

Instantly share code, notes, and snippets.

@davidhoyt
Last active August 24, 2017 14:10
Show Gist options
  • Save davidhoyt/98cbe0a94840373b6e62 to your computer and use it in GitHub Desktop.
Save davidhoyt/98cbe0a94840373b6e62 to your computer and use it in GitHub Desktop.
Slick 3 and cats
case class UserId(value: String) extends AnyVal with slick.lifted.MappedTo[String]
case class User(id: UserId, userName: String, password: String)
trait UsersTable extends TableDefinition with TableSchema { provider: DriverProvider =>
import driver.api._
override type TableRecordType = User
override val table = TableQuery[TableDefinition]
class TableDefinition(tag: Tag) extends Table[User](tag, "Users") {
def id = column[UserId]("ID", O.PrimaryKey, O.Length(length = 256, varying = true))
def userName = column[String]("UserName", O.Length(length = 256, varying = true))
def password = column[String]("Password", O.Length(length = 256, varying = true))
override def * = (id, userName, password) <> ((User.apply _).tupled, User.unapply)
}
}
trait UsersQuery { provider: DriverProvider with UsersTable =>
import provider.driver._
import provider.driver.api._
def byId(id: UserId) = table.filter(_.id === id).result
def drop(users: Seq[UserId]) = table.filter(_.id inSetBind users).delete
}
object Users {
class Users(val driver: JdbcDriver)
extends UsersTable
with UsersQuery
with DriverProvider
trait Provider {
val users: Users
}
def apply(driver: JdbcDriver): Users =
new Users(driver)
def samples = Seq(
User(UserId("M1"), userName = "user1", password = "password1"),
User(UserId("M2"), userName = "user2", password = "password2"),
User(UserId("M3"), userName = "user3", password = "password3")
)
}
class MyDomainRegistry(path: String, config: Config, val driver: JdbcDriver)
extends Users.Provider {
import scala.concurrent.Future
import driver.api._
override val users = Users(driver)
val db = Database.forConfig(path, config)
val tables = Seq[TableSchema](users)
val createTablesAction = TableSchema.create(tables)
val createTablesIfNotExistAction = TableSchema.createIfNotExists(tables)(db.ioExecutionContext)
val dropTablesAction = TableSchema.drop(tables)
def runCreateTables(): Future[Unit] = db.run(createTablesAction)
def runCreateTablesIfNotExist(): Future[Unit] = db.run(createTablesIfNotExistAction)
}
import scala.concurrent.ExecutionContext
import slick.driver.JdbcDriver
import slick.jdbc.meta.MTable
import slick.profile.SqlProfile
trait DriverProvider {
val driver: JdbcDriver
}
trait TableDefinition { provider: DriverProvider =>
import cats.{Monoid, Foldable}
import slick.dbio._
import driver.api.{Table, TableQuery}
type TableRecordType
type TableDefinition <: Table[TableRecordType]
val table: TableQuery[TableDefinition]
def batch[R <: TableRecordType, R2, F[_] : Foldable, E <: Effect](entities: F[R])(fn: (TableQuery[TableDefinition], R) => DBIOAction[R2, NoStream, E])(implicit m: Monoid[DBIOAction[R2, NoStream, E]]): DBIOAction[R2, NoStream, E] =
Foldable[F].foldMap(entities) { r =>
fn(table, r)
}
def insertOrUpdateBatch[R <: TableRecordType, F[_] : Foldable](entities: F[R])(implicit m: Monoid[DBIOAction[Int, NoStream, Effect.Write]]) =
batch(entities) { (tbl, entity) =>
import driver.api._
tbl.insertOrUpdate(entity)
}
}
trait TableSchema { provider: DriverProvider with TableDefinition =>
import TableSchema.SchemaDbIoAction
import slick.dbio.{Effect => GenEffect}
import driver._
import driver.api._
lazy val schema: SchemaDescription = table.schema
lazy val schemaName: Option[String] = table.baseTableRow.schemaName
lazy val tableName: String = table.baseTableRow.tableName
def createSchema(): SchemaDbIoAction[GenEffect.Schema] =
schema.create
def dropSchema(): SchemaDbIoAction[GenEffect.Schema] =
schema.drop
}
object TableSchema {
import slick.dbio._
type SchemaDbIoAction[T <: Effect] = DBIOAction[Unit, NoStream, T]
type SchemaDescription = SqlProfile#SchemaDescription
def dbioActionZero[T <: Effect]: SchemaDbIoAction[T] =
DBIO.successful(()).asInstanceOf[SchemaDbIoAction[T]]
def create(tableSchemas: TableSchema*): SchemaDbIoAction[Effect.Schema] =
create[Seq](tableSchemas)
def create[M[T] <: TraversableOnce[T]](tableSchemas: M[TableSchema]): SchemaDbIoAction[Effect.Schema] =
tableSchemas.foldLeft(dbioActionZero[Effect.Schema])(_ >> _.createSchema())
def createIfNotExists(tableSchemas: TableSchema*)(implicit executor: ExecutionContext): SchemaDbIoAction[Effect.Read with Effect.Schema] =
createIfNotExists[Seq](tableSchemas)
def createIfNotExists[M[T] <: TraversableOnce[T]](tableSchemas: M[TableSchema])(implicit executor: ExecutionContext): SchemaDbIoAction[Effect.Read with Effect.Schema] =
foldWithMTables(tableSchemas)(_.isEmpty, _.createSchema())
def drop(tableSchemas: TableSchema*): SchemaDbIoAction[Effect.Schema] =
drop[Seq](tableSchemas)
def drop[M[T] <: TraversableOnce[T]](tableSchemas: M[TableSchema]): SchemaDbIoAction[Effect.Schema] =
tableSchemas.foldLeft(dbioActionZero[Effect.Schema])(_ >> _.dropSchema())
def dropIfExists(tableSchemas: TableSchema*)(implicit executor: ExecutionContext): SchemaDbIoAction[Effect.Read with Effect.Schema] =
dropIfExists[Seq](tableSchemas)
def dropIfExists[M[T] <: TraversableOnce[T]](tableSchemas: M[TableSchema])(implicit executor: ExecutionContext): SchemaDbIoAction[Effect.Read with Effect.Schema] =
foldWithMTables(tableSchemas)(_.nonEmpty, _.dropSchema())
def foldWithMTables[M[T] <: TraversableOnce[T]](tableSchemas: M[TableSchema])(predicate: Vector[MTable] => Boolean, fn: TableSchema => SchemaDbIoAction[Effect.Schema])(implicit executor: ExecutionContext): SchemaDbIoAction[Effect.Read with Effect.Schema] =
tableSchemas.foldLeft(dbioActionZero[Effect.Read with Effect.Schema]) {
case (actions, schema) =>
actions >> MTable.getTables(schema.tableName).flatMap { tables =>
//filter() gives a runtime error.
//TODO: Reexamine after the next slick 3.x release.
if (predicate(tables))
fn(schema)
else
dbioActionZero[Effect.Schema]
}
}
}
//Not using kind projector
import cats._
import slick.dbio._
implicit def dbioActionMonoid[R : Monoid, E <: Effect]: Monoid[DBIOAction[R, NoStream, E]] =
new Monoid[DBIOAction[R, NoStream, E]] {
override val empty: DBIOAction[R, NoStream, E] =
DBIO.successful(Monoid[R].empty)
override def combine(x: DBIOAction[R, NoStream, E], y: DBIOAction[R, NoStream, E]): DBIOAction[R, NoStream, E] =
x >> y
}
implicit def dbioActionFunctor[E <: Effect](implicit ec: ExecutionContext): Functor[({type F[A] = DBIOAction[A, NoStream, E]})#F] =
new Functor[({type F[A] = DBIOAction[A, NoStream, E]})#F] {
override def map[A, B](fa: DBIOAction[A, NoStream, E])(f: A => B): DBIOAction[B, NoStream, E] =
fa map f
}
implicit def dbioActionMonad[E <: Effect](implicit ec: ExecutionContext) =
new Monad[({type M[A] = DBIOAction[A, NoStream, E]})#M] {
override def pure[A](x: A): DBIOAction[A, NoStream, E] =
DBIO.successful(x)
override def flatMap[A, B](fa: DBIOAction[A, NoStream, E])(f: A => DBIOAction[B, NoStream, E]): DBIOAction[B, NoStream, E] =
fa flatMap f
}
@davidhoyt
Copy link
Author

On second thought, DBIOActions are not necessarily associative when composed and so the Monoid implementation should be ignored... 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment