I am looking to use the Slick 3 framework for a Scala application to manage database interactions. I have been able to automatically generate the necessary table objects using Slick, but I also would like an integration test that verifies that the schemas in the database match the schemas in the objects. This is because sometimes tables get altered without my team being alerted, and so we would prefer to catch the change in an integration test instead of a production application.
One way to do this is to simply run a select query on every single table in a test runner. However, I feel like there should be a more direct way. Furthermore, it is not clear to me how to systematically run through all the tables defined in the file, except to manually append the table object to some sequence the test runner moves through. I notice that there is a schema field, but it only has the ability to generate create and drop statements.
Any help would be greatly appreciated. Thank you!
EDIT:
Here is my solution, but I was hoping for a better one:
class TablesIT extends FunSuite with BeforeAndAfter with ScalaFutures {
var db: Database = _
before{ db = Database.forURL( /* personal details */ )}
object ResultMap extends GetResult[Map[String,Any]] { //Object borrowed from http://stackoverflow.com/questions/20262036/slick-query-multiple-tables-databases-with-getting-column-names
def apply(pr: PositionedResult) = {
val rs = pr.rs // <- jdbc result set
val md = rs.getMetaData
val res = (1 to pr.numColumns).map{ i=> md.getColumnName(i) -> rs.getObject(i) }.toMap
pr.nextRow // <- use Slick's advance method to avoid endless loop
res
}
}
def testTableHasCols[A <: Table[_]](table: slick.lifted.TableQuery[A]): Unit = {
whenReady(db.run(table.take(1).result.headOption.asTry)) { case Success(t) => t match {
case Some(r) => logTrace(r.toString)
case None => logTrace("Empty table")
}
case Failure(ex) => fail("Query exception: " + ex.toString)
}
}
def plainSqlSelect[A](query: String)(implicit gr: GetResult[A]): Future[Seq[A]] = {
val stmt = sql"""#$query""".as[A]
db.run(stmt)
}
def compareNumOfCols[A <: Table[_]](table: slick.lifted.TableQuery[A]) = {
val tableName = table.baseTableRow.tableName
val selectStar = whenReady(db.run(sql"""select * from #$tableName limit 1""".as(ResultMap).headOption)) {
case Some(m) => m.size
case None => 0
}
val model = whenReady(db.run(sql"""#${table.take(1).result.statements.head}""".as(ResultMap).headOption)) {
case Some(m) => m.size
case None => 0
}
assert(selectStar === model, "The number of columns do not match")
}
test("Test table1") {
testTableHasCols(Table1)
compareNumOfCols(Table1)
}
// And on for each table
}
I ended up devising a better solution that uses the following idea. It is more or less the same, and unfortunately I still have to manually create a test for each table, but the method is cleaner, I think. Note, however, that this only works for PostgreSQL because of the information schema, but other database systems have other methods.
class TablesIT extends FunSuite with BeforeAndAfter with ScalaFutures {
var db: Database = _
before{ db = Database.forURL( /* personal details */ )}
def testTableHasCols[A <: Table[_]](table: slick.lifted.TableQuery[A]): Unit = {
whenReady(db.run(table.take(1).result.headOption.asTry)) { case Success(t) => t match {
case Some(r) => logTrace(r.toString)
case None => logTrace("Empty table")
}
case Failure(ex) => fail("Query exception: " + ex.toString)
}
}
def compareNumOfCols[A <: Table[_]](table: slick.lifted.TableQuery[A]) = {
val tableName = table.baseTableRow.tableName
val selectStar = whenReady(db.run(sql"""select column_name from information_schema.columns where table_name='#$tableName'""".as[String])) {
case m: Seq[String] => m.size
case _ => 0
}
val model = table.baseTableRow.create_*.map(_.name).toSeq.size
assert(selectStar === model, "The number of columns do not match")
}
test("Test table1") {
testTableHasCols(Table1)
compareNumOfCols(Table1)
}
// And on for each table
}
Related
I'm new to Scala and trying to write some programs to get better at it. I wrote a flow (version 1) that is very Java-like and I'm trying to write it using higher order functions (version 2).
version 1:
val entry: Option[Int] = getEntry()
if (entry.isDefined) {
val cachedEntry = entry.get
if (cachedEntry.state.isActive) {
return cachedEntry
} else {
Cache.invalidateCachedEntry(cachedEntry)
}
}
Cache.createNewEntry()
version 2:
val entry: Option[Int] = getEntry()
entry.filter(_.state.isActive).orElse((() => {
Cache.invalidateCachedEntry _
Option(Cache.createNewEntry())
})()).get
I'm not sure if this is the correct approach or there is a better way of doing this?
Let's consider following scenerio:
case class Entry(state: AnyState)
case class AnyState(isActive: Boolean = true)
object Cache {
def invalidateCachedEntry(entry: Entry): Unit = println("cleaned")
}
def getEntry: Option[Entry] = Some(Entry(AnyState()))
val optEntry: Option[Entry] = getEntry
val result: Option[Entry] = optEntry match {
case Some(entry) if entry.state.isActive =>
entry // do something
println("did something")
Some(entry)
case Some(entry) =>
Cache.invalidateCachedEntry(entry)
None
case _ =>
println("Entry not found")
None
}
This would be a one scenario. In general you should return something. But sometimes you don't have enough information. Such cases you can return Option and if you want to throw an error you can use Either
I prefer using match for clarity:
getEntry() match {
case Some(entry) if entry.state.isActive => entry
case opt => opt.foreach(Cache.invalidateCachedEntry); Cache.createNewEntry()
}
I have the following Play Slick DAO class. Note that the database configuration is a constant control0001. The DAO has a function readUser that reads a user based on its user id:
class UsersDAO #Inject()(#NamedDatabase("control0001")
protected val dbConfigProvider: DatabaseConfigProvider)
extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
def readUser (userid: String) = {
val users = TableQuery[UserDB]
val action = users.filter(_.userid === userid).result
val future = db.run(action.asTry)
future.map{
case Success(s) =>
if (s.length>0)
Some(s(0))
else
None
case Failure(e) => throw new Exception ("Failure in readUser: " + e.getMessage)
}
}
}
Instead of having a constant in #NamedDatabase("control0001"), I need the database to be variable. In the application, I have multiple databases (control0001, control002 and so on) configured in application.conf. Depending on a variable value, I need to determine the database to be used in the DAO. All the databases are similar and have the same tables (the data in each database differs).
The following Play class calls the DAO function, but first it needs to determine the database name to be injected:
class TestSlick #Inject()(dao: UsersDAO) extends Controller {
def test(someCode: Int, userId: String) = Action { request =>
val databaseName = if (someCode == 1) "control0001" else "control0002"
// Run the method in UsersDAO accessing the database set by databaseName
val future = dao.readUser(userId)
future.map { result =>
result match {
case Some(user) => Ok(user.firstName)
case _ => Ok("user not found")
}
}
}
}
How can this be achieved in Play Slick?
You can try to initialize slick db object overriding default config:
val db = Database.forURL("jdbc:mysql://localhost/" + databaseName, driver="org.h2.Driver")
more information in slick docs http://slick.lightbend.com/doc/3.0.0/database.html
Instead of trying to use Play's runtime dependency injection utilities in this case, use the SlickApi directly in your DAO and pass the datasource name to the dbConfig(DbName(name)) method. To obtain the SlickApi, mix in the SlickComponents trait:
class UsersDAO extends SlickComponents {
def readUser(userid: String, dbName: String) = {
val users = TableQuery[UserDB]
val action = users.filter(_.userid === userid).result
val dbConfig = slickApi.dbConfig(DbName(dbName))
val future = dbConfig.db.run(action.asTry)
...
}
}
Then in your controller:
def test(someCode: Int, userId: String) = Action { request =>
val databaseName = if (someCode == 1) "control0001" else "control0002"
val future = dao.readUser(userId, databaseName)
...
}
I am just new to learning Scala and the related technologies.I am coming across the problem where the loadUser should return a record but its coming empty.
I am getting the following error:
java.util.NoSuchElementException: None.get
I appreciate this is not ideal Scala, so feel free to suggest me improvements.
class MongoDataAccess extends Actor {
val message = "Hello message"
override def receive: Receive = {
case data: Payload => {
val user: Future[Option[User]] = MongoDataAccess.loadUser(data.deviceId)
val twillioApiAccess = context.actorOf(Props[TwillioApiAccess], "TwillioApiAccess")
user onComplete {
case Failure(exception) => println(exception)
case p: Try[Option[User]] => p match {
case Failure(exception) => println(exception)
case u: Try[Option[User]] => twillioApiAccess ! Action(data, u.get.get.phoneNumber, message)
}
}
}
case _ => println("received unknown message")
}
}
object MongoDataAccess extends MongoDataApi {
def connect(): Future[DefaultDB] = {
// gets an instance of the driver
val driver = new MongoDriver
val connection = driver.connection(List("192.168.99.100:32768"))
// Gets a reference to the database "sensor"
connection.database("sensor")
}
def props = Props(new MongoDataAccess)
def loadUser(deviceId: UUID): Future[Option[User]] = {
println(s"Loading user from the database with device id: $deviceId")
val query = BSONDocument("deviceId" -> deviceId.toString)
// By default, you get a Future[BSONCollection].
val collection: Future[BSONCollection] = connect().map(_.collection("profile"))
collection flatMap { x => x.find(query).one[User] }
}
}
Thanks
There is no guaranty the find-one (.one[T]) matches at least one document in your DB, so you get an Option[T].
Then it's up to you to consider (or not) that having found no document is a failure (or not); e.g.
val u: Future[User] = x.find(query).one[User].flatMap[User] {
case Some(matchingUser) => Future.successful(matchingUser)
case _ => Future.failed(new MySemanticException("No matching user found"))
}
Using .get on Option is a bad idea anyway.
I have following code which inserts row into table named luczekInfo and function to get data from database. My question is how to make function to update columns from table luczekInfo, on rows returned by get(id) function. What is the best way to update columns values in Slick?
def create(profil: luczekInfo): Either[Failure, luczekInfo] = {
try {
val id = db.withSession {
LuczekInfo returning LuczekInfo.id insert profil
}
Right(profil.copy(id = Some(id)))
} catch {
case e: SQLException =>
Left(databaseError(e))
}
}
def get(id: Int): Either[Failure, luczekInfo] = {
try {
db.withSession {
LuczekInfo.findById(id).firstOption match {
case Some(profil: luczekInfo) =>
Right(profil)
case _ =>
Left(notFoundError(id))
}
}
} catch {
case e: SQLException =>
Left(databaseError(e))
}
}
Thanks in advance for answers.
Slick 2.X
You can update a row in two ways (as far as I know), the first one would be to create a row object of type luczekInfo#TableElementTypeand use it to update the full row:
def updateById(id: Long, row: luczekInfo#TableElementType)(implicit s: Session): Boolean =
luczekInfo.filter(_.id === id).update(row)
Or you can update single fields using:
def updateNameById(mId: Long, mName: String)(implicit s: Session) = {
val q = for { l <- luczekInfo if l.id === mId } yield l.name
q.update(mName).run
}
Where I supposed your table has a file called name.
You can find it also on the Slick documentation in the section on updating.
Slick 3.1.X
there's an additional support for the insertOrUpdate (upsert) operation:
luczekInfo.insertOrUpdate(row)
I'm currently experimenting with Scala and looking for best practices. I found myself having two opposite approaches to solving a single problem. I'd like to know which is better and why, which is more conventional, and if maybe you know of some other better approaches. The second one looks prettier to me.
1. Enumeration-based solution
import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver
object DBType extends Enumeration {
val MySql, PostgreSql, H2 = Value
def fromUrl(url: String) = {
url match {
case u if u.startsWith("jdbc:mysql:") => Some(MySql)
case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql)
case u if u.startsWith("jdbc:h2:") => Some(H2)
case _ => None
}
}
}
case class DBType(typ: DBType) {
lazy val driver: Driver = {
val name = typ match {
case DBType.MySql => "com.mysql.jdbc.Driver"
case DBType.PostgreSql => "org.postgresql.Driver"
case DBType.H2 => "org.h2.Driver"
}
Class.forName(name).newInstance().asInstanceOf[Driver]
}
lazy val adapter: DatabaseAdapter = {
typ match {
case DBType.MySql => new MySQLAdapter
case DBType.PostgreSql => new PostgreSqlAdapter
case DBType.H2 => new H2Adapter
}
}
}
2. Singleton-based solution
import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver
trait DBType {
def driver: Driver
def adapter: DatabaseAdapter
}
object DBType {
object MySql extends DBType {
lazy val driver = Class.forName("com.mysql.jdbc.Driver").newInstance().asInstanceOf[Driver]
lazy val adapter = new MySQLAdapter
}
object PostgreSql extends DBType {
lazy val driver = Class.forName("org.postgresql.Driver").newInstance().asInstanceOf[Driver]
lazy val adapter = new PostgreSqlAdapter
}
object H2 extends DBType {
lazy val driver = Class.forName("org.h2.Driver").newInstance().asInstanceOf[Driver]
lazy val adapter = new H2Adapter
}
def fromUrl(url: String) = {
url match {
case u if u.startsWith("jdbc:mysql:") => Some(MySql)
case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql)
case u if u.startsWith("jdbc:h2:") => Some(H2)
case _ => None
}
}
}
If you declare a sealed trait DBType, you can pattern match on it with exhaustiveness checking (ie, Scala will tell you if you forget one case).
Anyway, I dislike Scala's Enumeration, and I'm hardly alone in that. I never use it, and if there's something for which enumeration is really the cleanest solution, it is better to just write it in Java, using Java's enumeration.
For this particular case you don't really need classes for each database type; it's just data. Unless the real case is dramatically more complex, I would use a map and string parsing based solution to minimize the amount of code duplication:
case class DBRecord(url: String, driver: String, adapter: () => DatabaseAdapter) {}
class DBType(record: DBRecord) {
lazy val driver = Class.forName(record.driver).newInstance().asInstanceOf[Driver]
lazy val adapter = record.adapter()
}
object DBType {
val knownDB = List(
DBRecord("mysql", "com.mysql.jdbc.Driver", () => new MySQLAdapter),
DBRecord("postgresql", "org.postgresql.Driver", () => new PostgreSqlAdapter),
DBRecord("h2", "org.h2.Driver", () => new H2Adapter)
)
val urlLookup = knownDB.map(rec => rec.url -> rec).toMap
def fromURL(url: String) = {
val parts = url.split(':')
if (parts.length < 3 || parts(0) != "jdbc") None
else urlLookup.get(parts(1)).map(rec => new DBType(rec))
}
}
I'd go for the singleton variant, since it allows clearer subclassing.
Also you might need to do db-specific things/overrides, since some queries/subqueries/operators might be different.
But i'd try something like this:
import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver
abstract class DBType(jdbcDriver: String) {
lazy val driver = Class.forName(jdbcDriver).newInstance().asInstanceOf[Driver]
def adapter: DatabaseAdapter
}
object DBType {
object MySql extends DBType("com.mysql.jdbc.Driver") {
lazy val adapter = new MySQLAdapter
}
object PostgreSql extends DBType("org.postgresql.Driver") {
lazy val adapter = new PostgreSqlAdapter
}
object H2 extends DBType("org.h2.Driver") {
lazy val adapter = new H2Adapter
}
def fromUrl(url: String) = {
url match {
case _ if url.startsWith("jdbc:mysql:") => Some(MySql(url))
case _ if url.startsWith("jdbc:postgresql:") => Some(PostgreSql(url))
case _ if url.startsWith("jdbc:h2:") => Some(H2(url))
case _ => None
}
}
if this helped, please consider to +1 this :)