0

I'm getting what seems to be a strange error when I run my Play app (which seems to be the story of my life right now). The other day, I ran into this issue, solved that, was able to run the evolutions and create the tables, and then ran into the following:

java.lang.RuntimeException: Property driver does not exist on target class com.zaxxer.hikari.HikariConfig

Here is the relevant section from my current application.conf:

db.default {
    dataSourceClassName = org.postgresql.ds.PGSimpleDataSource
    url = "jdbc:postgresql://localhost:5432/app-users?user=root&password=root"
    driver = org.postgresql.Driver
    username = root
    password = root
    databaseName = app-users
    portNumber = 5432
    serverName = localhost
    connectionTimeout = 30000
}

I did some digging and found that Hikari expects driverClassName (along with a few other differences). The Hikari docs and the Quill docs say that something like this should be in the application.conf:

dataSourceClassName=org.postgresql.ds.PGSimpleDataSource
dataSource.user=root
dataSource.password=root
dataSource.databaseName=app-users
dataSource.portNumber=5432
dataSource.serverName=localhost

However, when I have that, the evolutions won't run. They will only run when I have it as I currently do. Since the evolutions are running, it seems like it is connecting to the db. So why would I get a message saying that it can't? Is there a way for me to have a separate db config for Hikari and Postgres/Quill?

Full error message:

play.api.UnexpectedException: Unexpected exception[IllegalStateException: Failed to load data source for config: 'Config(SimpleConfigObject({"connectionTimeout":30000,"dataSourceClassName":"org.postgresql.ds.PGSimpleDataSource","databaseName":"mack-users","driver":"org.postgresql.Driver","password":"root","portNumber":5432,"serverName":"localhost","url":"jdbc:postgresql://localhost:5432/mack-users?user=root&password=root","username":"root"}))']
    at play.core.server.DevServerStart$$anon$1.reload(DevServerStart.scala:186)
    at play.core.server.DevServerStart$$anon$1.get(DevServerStart.scala:124)
    at play.core.server.AkkaHttpServer.modelConversion(AkkaHttpServer.scala:183)
    at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:189)
    at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$3(AkkaHttpServer.scala:106)
    at akka.stream.impl.fusing.MapAsync$$anon$24.onPush(Ops.scala:1191)
    at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:512)
    at akka.stream.impl.fusing.GraphInterpreter.processEvent(GraphInterpreter.scala:475)
    at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:371)
    at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:584)
Caused by: java.lang.IllegalStateException: Failed to load data source for config: 'Config(SimpleConfigObject({"connectionTimeout":30000,"dataSourceClassName":"org.postgresql.ds.PGSimpleDataSource","databaseName":"mack-users","driver":"org.postgresql.Driver","password":"root","portNumber":5432,"serverName":"localhost","url":"jdbc:postgresql://localhost:5432/mack-users?user=root&password=root","username":"root"}))'
    at io.getquill.JdbcContextConfig.dataSource(JdbcContextConfig.scala:24)
    at io.getquill.PostgresJdbcContext.<init>(PostgresJdbcContext.scala:17)
    at io.getquill.PostgresJdbcContext.<init>(PostgresJdbcContext.scala:18)
    at io.getquill.PostgresJdbcContext.<init>(PostgresJdbcContext.scala:19)
    at db.db.package$DBContext.<init>(package.scala:6)
    at MyComponents.ctx$lzycompute(MyApplicationLoader.scala:25)
    at MyComponents.ctx(MyApplicationLoader.scala:25)
    at MyComponents.userService$lzycompute(MyApplicationLoader.scala:28)
    at MyComponents.userService(MyApplicationLoader.scala:28)
    at MyComponents.applicationController$lzycompute(MyApplicationLoader.scala:35)
Caused by: java.lang.RuntimeException: Property driver does not exist on target class com.zaxxer.hikari.HikariConfig
    at com.zaxxer.hikari.util.PropertyElf.setProperty(PropertyElf.java:131)
    at com.zaxxer.hikari.util.PropertyElf.lambda$setTargetFromProperties$0(PropertyElf.java:57)
    at java.util.Hashtable.forEach(Hashtable.java:879)
    at com.zaxxer.hikari.util.PropertyElf.setTargetFromProperties(PropertyElf.java:52)
    at com.zaxxer.hikari.HikariConfig.<init>(HikariConfig.java:132)
    at io.getquill.JdbcContextConfig.dataSource(JdbcContextConfig.scala:21)
    at io.getquill.PostgresJdbcContext.<init>(PostgresJdbcContext.scala:17)
    at io.getquill.PostgresJdbcContext.<init>(PostgresJdbcContext.scala:18)
    at io.getquill.PostgresJdbcContext.<init>(PostgresJdbcContext.scala:19)
    at db.db.package$DBContext.<init>(package.scala:6)

db/package.scala

import io.getquill.{PostgresJdbcContext, SnakeCase}

package object db {
  class DBContext(config: String) extends PostgresJdbcContext(SnakeCase, config)

  trait Repository {
    val ctx: DBContext
  }
}

Any ideas on what could cause this? Or what I am missing?

Using:

  • Scala 2.12.4
  • Quill 2.3.2
  • Play 2.6.6
  • Postgres JDBC Driver 42.2.1
  • PostgreSQL 10.2

Error message when I put what Quill/Hikari expect:

! @776d39n6c - Internal server error, for (GET) [/] ->

play.api.Configuration$$anon$1: Configuration error[Cannot connect to database [default]]
    at play.api.Configuration$.configError(Configuration.scala:156)
    at play.api.Configuration.reportError(Configuration.scala:990)
    at play.api.db.DefaultDBApi.$anonfun$connect$1(DefaultDBApi.scala:48)
    at play.api.db.DefaultDBApi.$anonfun$connect$1$adapted(DefaultDBApi.scala:42)
    at scala.collection.immutable.List.foreach(List.scala:389)
    at play.api.db.DefaultDBApi.connect(DefaultDBApi.scala:42)
    at play.api.db.DBApiProvider.get$lzycompute(DBModule.scala:86)
    at play.api.db.DBApiProvider.get(DBModule.scala:75)
    at play.api.db.DBComponents.dbApi(DBModule.scala:49)
    at play.api.db.DBComponents.dbApi$(DBModule.scala:49)
Caused by: play.api.Configuration$$anon$1: Configuration error[dataSource or dataSourceClassName or jdbcUrl is required.]
    at play.api.Configuration$.configError(Configuration.scala:156)
    at play.api.Configuration.reportError(Configuration.scala:990)
    at play.api.db.HikariCPConnectionPool.create(HikariCPModule.scala:63)
    at play.api.db.PooledDatabase.createDataSource(Databases.scala:199)
    at play.api.db.DefaultDatabase.dataSource$lzycompute(Databases.scala:123)
    at play.api.db.DefaultDatabase.dataSource(Databases.scala:121)
    at play.api.db.DefaultDatabase.getConnection(Databases.scala:142)
    at play.api.db.DefaultDatabase.getConnection(Databases.scala:138)
    at play.api.db.DefaultDBApi.$anonfun$connect$1(DefaultDBApi.scala:44)
    at play.api.db.DefaultDBApi.$anonfun$connect$1$adapted(DefaultDBApi.scala:42)
Caused by: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
    at com.zaxxer.hikari.HikariConfig.validate(HikariConfig.java:997)
    at play.api.db.HikariCPConfig.toHikariConfig(HikariCPModule.scala:136)
    at play.api.db.HikariCPConnectionPool.$anonfun$create$1(HikariCPModule.scala:50)
    at scala.util.Try$.apply(Try.scala:209)
    at play.api.db.HikariCPConnectionPool.create(HikariCPModule.scala:47)
    at play.api.db.PooledDatabase.createDataSource(Databases.scala:199)
    at play.api.db.DefaultDatabase.dataSource$lzycompute(Databases.scala:123)
    at play.api.db.DefaultDatabase.dataSource(Databases.scala:121)
    at play.api.db.DefaultDatabase.getConnection(Databases.scala:142)
    at play.api.db.DefaultDatabase.getConnection(Databases.scala:138)
jmkoni
  • 463
  • 4
  • 14
  • Do you get any error when you put what quill wants in `application.conf` or do evolutions just not run at all? Also how do you create your jdbc context? We use something like `@Singleton class DefaultDbContext extends PostgresJdbcContext[SnakeCase](SnakeCase, "ctx")` which means we prefix every config param with `ctx`, so `ctx.dataSourceClassName` - not sure but maybe that could resolve any conflicts. – Matt Fowler Mar 02 '18 at 19:18
  • @MattFowler I do get a different error when I put what Quill wants in `application.conf` and evolutions don't run. I'll update the question with that error message and the jdbc context. – jmkoni Mar 02 '18 at 20:14
  • Actually from docs I take that Quill expects `application.properties` (with Java Properties format) and Play (which I guess uses Slick) needs `application.conf` with HOCON format. I guess, that if you provided both files, it could work - that would be a nice fallback option before figuring out how to use one config for both. – Mateusz Kubuszok Mar 03 '18 at 01:39

1 Answers1

2

Unfortunately Hikari and Play don't use quite the same naming conventions for setting up their data source, for instance Quill expects a bare dataSourceClassName but Play wants it in a format like db.default.dataSourceClassName. As far as I can tell there is no way to rework your config so that it will satisfy both of them and you may not want to for the following reason:

Play actually uses Hikari as it's pooling data source, so when you create a db via db.default you're actually already creating a pooled data source. When you call PostgresJdbcContext with a bare config you're actually asking Quill to create a totally separate connection pool. Not sure if this is what you want but I'd assume you'd want only one connection pool.

Quill actually exposes a constructor that takes in a DataSource, so what you can do is use the Database that Play has created and just pass it in directly to Quill:

import io.getquill.{PostgresJdbcContext, SnakeCase}
import com.zaxxer.hikari.HikariDataSource
import play.api.db.Database

package object db {
    class DBContext(db: Database) extends PostgresJdbcContext(SnakeCase, db.dataSource.asInstanceOf[HikariDataSource])
}

This should solve your problem and will only create one connection pool. You can get at a Database object via dependency injection.

You have to do an ugly asInstanceOf here because the PostgresJdbcContext requires the dataSource to implement Closeable, which the bare DataSource class does not implement. I've ran into a similar issue and haven't found a more elegant way to do this.

Matt Fowler
  • 2,563
  • 2
  • 15
  • 18
  • Thanks! One more question though: if I make the updates like you suggest, how would I go about initializing the DBContext later in the code? `val ctx = new DBContext(???)` – jmkoni Mar 05 '18 at 14:42
  • 2
    @jmkoni I would consider letting dependency injection take care of things for you. If you made a class like this: `@Singleton class DBContext @Inject()(db: Database) extends PostgresJdbcContext(SnakeCase, db.dataSource.asInstanceOf[HikariDataSource])` Then in any controller you need a DBContext you can just do: `@Singleton class MyController @Inject() (cc: ControllerComponents, dbContext: DBContext)` and you don't ever need to create a DBContext directly yourself. – Matt Fowler Mar 05 '18 at 18:38