Reverse engineering using Slick 3.2 and Scala

Reading Time: < 1 minute

Sometime, we have requirements for create classes corresponding to the existing database or Sometimes, in our initial phase of project, we are going to design database and tables first, after that, we are going to create classes for mapping our tables.

As we know, Slick is a FRM(Functional Relational Mapping) for scala. Its a kind of ORM but for scala, it supports Functional and Reactive programming supports. Like in my Java experience with Hibernate, we are mostly use Jboss tools for reverse engineering from database tables to Java Pojo classes or Entities, Or While we are using JOOQ , JOOQ also provide some plugins for generate the code corresponding to tables.  Like, same as Hibernate, JOOQ and many more, Slick also provide a way for create classes corresponding to database tables by using Schema Code Generation.  Below are simple steps for generate scala classes using slick code generator Or we said “Reverse Engineering” using generator. The steps are:

Step I: Create tables in Database.

CREATE TABLE public.search_keys
(
id uuid NOT NULL,
key character varying(50),
CONSTRAINT search_keys_pkey PRIMARY KEY (id)
)
CREATE TABLE public.repo_info
(
id integer NOT NULL,
key uuid NOT NULL,
name character varying(150),
full_name character varying(150),
owner character varying(150),
url character varying(150),
languages_url character varying(150),
ssh_url character varying(150),
clone_url character varying(150),
homepage character varying(150),
watchers_count bigint,
watchers bigint,
forks bigint,
CONSTRAINT repo_info_pkey PRIMARY KEY (id),
CONSTRAINT valid_key FOREIGN KEY (key)
REFERENCES public.search_keys (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)

view raw
gistfile1.sql
hosted with ❤ by GitHub

Step II: Add dependencies and build.sbt settings.

lazy val slickVersion = "3.2.0"
libraryDependencies ++= Seq(
"com.typesafe.slick" %% "slick" % slickVersion,
"com.typesafe.slick" %% "slick-hikaricp" % slickVersion,
"com.typesafe.slick" %% "slick-codegen" % slickVersion % "compile",
"org.postgresql" % "postgresql" % "9.4.1212",
"com.typesafe.slick" %% "slick-testkit" % "3.2.0" % "test"
)
// code generation task
lazy val slick = TaskKey[Seq[File]]("gen-tables")
lazy val slickCodeGenTask = (sourceManaged, dependencyClasspath in Compile, runner in Compile, streams) map { (dir, cp, r, s) =>
val outputDir = (dir / "slick").getPath // place generated files in sbt's managed sources folder
val url = "jdbc:postgresql://localhost:5432/github_info?user=postgres&password=root&sslmode=disable" // connection info for a pre-populated throw-away, in-memory db for this demo, which is freshly initialized on every run
val jdbcDriver = "org.postgresql.Driver"
val slickDriver = "slick.jdbc.PostgresProfile"
val pkg = "com.knoldus.github.models"
toError(r.run("slick.codegen.SourceCodeGenerator", cp.files, Array(slickDriver, jdbcDriver, url, outputDir, pkg), s.log))
val fname = outputDir + "/com/knoldus/github/models/Tables.scala"
Seq(file(fname))
}
slick <<= slickCodeGenTask // register manual sbt command
sourceGenerators in Compile <+= slickCodeGenTask // register automatic code generation on every compile, remove for only manual use
testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v", "-s", "-a")
parallelExecution in Test := false

view raw
build.sbt
hosted with ❤ by GitHub

After following these above steps, we are just execute sbt clean compile command. After that, the source file name Tables.scala in generated at target/scala-/src_managed/slick/com/knoldus/github/models directory. The Tables.scala contains scala classes corresponding to database tables. The generated code as below:

package com.knoldus.github.repo
// AUTO-GENERATED Slick data model
/** Stand-alone Slick data model for immediate use */
object Tables extends {
val profile = slick.jdbc.PostgresProfile
} with Tables
/** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */
trait Tables {
val profile: slick.jdbc.JdbcProfile
import profile.api._
import slick.model.ForeignKeyAction
// NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns.
import slick.jdbc.{GetResult => GR}
/** DDL for all tables. Call .create to execute. */
lazy val schema: profile.SchemaDescription = RepoInfo.schema ++ SearchKeys.schema
@deprecated("Use .schema instead of .ddl", "3.0")
def ddl = schema
/** Entity class storing rows of table RepoInfo
* @param id Database column id SqlType(int4), PrimaryKey
* @param key Database column key SqlType(uuid)
* @param name Database column name SqlType(varchar), Length(150,true), Default(None)
* @param fullName Database column full_name SqlType(varchar), Length(150,true), Default(None)
* @param owner Database column owner SqlType(varchar), Length(150,true), Default(None)
* @param url Database column url SqlType(varchar), Length(150,true), Default(None)
* @param languagesUrl Database column languages_url SqlType(varchar), Length(150,true), Default(None)
* @param sshUrl Database column ssh_url SqlType(varchar), Length(150,true), Default(None)
* @param cloneUrl Database column clone_url SqlType(varchar), Length(150,true), Default(None)
* @param homepage Database column homepage SqlType(varchar), Length(150,true), Default(None)
* @param watchersCount Database column watchers_count SqlType(int8), Default(None)
* @param watchers Database column watchers SqlType(int8), Default(None)
* @param forks Database column forks SqlType(int8), Default(None) */
final case class RepoInfoRow(id: Int, key: java.util.UUID, name: Option[String] = None, fullName: Option[String] = None, owner: Option[String] = None, url: Option[String] = None, languagesUrl: Option[String] = None, sshUrl: Option[String] = None, cloneUrl: Option[String] = None, homepage: Option[String] = None, watchersCount: Option[Long] = None, watchers: Option[Long] = None, forks: Option[Long] = None)
/** GetResult implicit for fetching RepoInfoRow objects using plain SQL queries */
implicit def GetResultRepoInfoRow(implicit e0: GR[Int], e1: GR[java.util.UUID], e2: GR[Option[String]], e3: GR[Option[Long]]): GR[RepoInfoRow] = GR{
prs => import prs._
RepoInfoRow.tupled((<<[Int], <<[java.util.UUID], <<?[String], <<?[String], <<?[String], <<?[String], <<?[String], <<?[String], <<?[String], <<?[String], <<?[Long], <<?[Long], <<?[Long]))
}
/** Table description of table repo_info. Objects of this class serve as prototypes for rows in queries. */
class RepoInfo(_tableTag: Tag) extends profile.api.Table[RepoInfoRow](_tableTag, "repo_info") {
def * = (id, key, name, fullName, owner, url, languagesUrl, sshUrl, cloneUrl, homepage, watchersCount, watchers, forks) <> (RepoInfoRow.tupled, RepoInfoRow.unapply)
/** Maps whole row to an option. Useful for outer joins. */
def ? = (Rep.Some(id), Rep.Some(key), name, fullName, owner, url, languagesUrl, sshUrl, cloneUrl, homepage, watchersCount, watchers, forks).shaped.<>({r=>import r._; _1.map(_=> RepoInfoRow.tupled((_1.get, _2.get, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
/** Database column id SqlType(int4), PrimaryKey */
val id: Rep[Int] = column[Int]("id", O.PrimaryKey)
/** Database column key SqlType(uuid) */
val key: Rep[java.util.UUID] = column[java.util.UUID]("key")
/** Database column name SqlType(varchar), Length(150,true), Default(None) */
val name: Rep[Option[String]] = column[Option[String]]("name", O.Length(150,varying=true), O.Default(None))
/** Database column full_name SqlType(varchar), Length(150,true), Default(None) */
val fullName: Rep[Option[String]] = column[Option[String]]("full_name", O.Length(150,varying=true), O.Default(None))
/** Database column owner SqlType(varchar), Length(150,true), Default(None) */
val owner: Rep[Option[String]] = column[Option[String]]("owner", O.Length(150,varying=true), O.Default(None))
/** Database column url SqlType(varchar), Length(150,true), Default(None) */
val url: Rep[Option[String]] = column[Option[String]]("url", O.Length(150,varying=true), O.Default(None))
/** Database column languages_url SqlType(varchar), Length(150,true), Default(None) */
val languagesUrl: Rep[Option[String]] = column[Option[String]]("languages_url", O.Length(150,varying=true), O.Default(None))
/** Database column ssh_url SqlType(varchar), Length(150,true), Default(None) */
val sshUrl: Rep[Option[String]] = column[Option[String]]("ssh_url", O.Length(150,varying=true), O.Default(None))
/** Database column clone_url SqlType(varchar), Length(150,true), Default(None) */
val cloneUrl: Rep[Option[String]] = column[Option[String]]("clone_url", O.Length(150,varying=true), O.Default(None))
/** Database column homepage SqlType(varchar), Length(150,true), Default(None) */
val homepage: Rep[Option[String]] = column[Option[String]]("homepage", O.Length(150,varying=true), O.Default(None))
/** Database column watchers_count SqlType(int8), Default(None) */
val watchersCount: Rep[Option[Long]] = column[Option[Long]]("watchers_count", O.Default(None))
/** Database column watchers SqlType(int8), Default(None) */
val watchers: Rep[Option[Long]] = column[Option[Long]]("watchers", O.Default(None))
/** Database column forks SqlType(int8), Default(None) */
val forks: Rep[Option[Long]] = column[Option[Long]]("forks", O.Default(None))
/** Foreign key referencing SearchKeys (database name valid_key) */
lazy val searchKeysFk = foreignKey("valid_key", key, SearchKeys)(r => r.id, onUpdate=ForeignKeyAction.NoAction, onDelete=ForeignKeyAction.NoAction)
}
/** Collection-like TableQuery object for table RepoInfo */
lazy val RepoInfo = new TableQuery(tag => new RepoInfo(tag))
/** Entity class storing rows of table SearchKeys
* @param id Database column id SqlType(uuid), PrimaryKey
* @param key Database column key SqlType(varchar), Length(50,true), Default(None) */
final case class SearchKeysRow(id: java.util.UUID, key: Option[String] = None)
/** GetResult implicit for fetching SearchKeysRow objects using plain SQL queries */
implicit def GetResultSearchKeysRow(implicit e0: GR[java.util.UUID], e1: GR[Option[String]]): GR[SearchKeysRow] = GR{
prs => import prs._
SearchKeysRow.tupled((<<[java.util.UUID], <<?[String]))
}
/** Table description of table search_keys. Objects of this class serve as prototypes for rows in queries. */
class SearchKeys(_tableTag: Tag) extends profile.api.Table[SearchKeysRow](_tableTag, "search_keys") {
def * = (id, key) <> (SearchKeysRow.tupled, SearchKeysRow.unapply)
/** Maps whole row to an option. Useful for outer joins. */
def ? = (Rep.Some(id), key).shaped.<>({r=>import r._; _1.map(_=> SearchKeysRow.tupled((_1.get, _2)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
/** Database column id SqlType(uuid), PrimaryKey */
val id: Rep[java.util.UUID] = column[java.util.UUID]("id", O.PrimaryKey)
/** Database column key SqlType(varchar), Length(50,true), Default(None) */
val key: Rep[Option[String]] = column[Option[String]]("key", O.Length(50,varying=true), O.Default(None))
}
/** Collection-like TableQuery object for table SearchKeys */
lazy val SearchKeys = new TableQuery(tag => new SearchKeys(tag))
}

view raw
Tables.scala
hosted with ❤ by GitHub

For further customization on slick code generator, please click on this link.


KNOLDUS-advt-sticker

Written by 

Harmeet Singh is a lead consultant, with experience of more than 5 years. He has expertise in Scala, Java, JVM, and functional programming. On a personal front; he is a food lover.

3 thoughts on “Reverse engineering using Slick 3.2 and Scala1 min read

Comments are closed.

%d bloggers like this: