Skip to content

Commit

Permalink
Merge pull request #704 from sergeda/639_hikaricp
Browse files Browse the repository at this point in the history
Adds HikariCP (#639)
  • Loading branch information
sviezypan committed Aug 27, 2022
2 parents 34829cf + 36ca79a commit 781242c
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 1 deletion.
20 changes: 19 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ lazy val root = project
mysql,
oracle,
postgres,
sqlserver
sqlserver,
jdbc_hikaricp
)

lazy val core = crossProject(JSPlatform, JVMPlatform)
Expand Down Expand Up @@ -131,6 +132,23 @@ lazy val jdbc = project
.settings(testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"))
.dependsOn(core.jvm)

lazy val jdbc_hikaricp = project
.in(file("jdbc-hikaricp"))
.settings(stdSettings("zio-sql-jdbc-hickaricp"))
.settings(buildInfoSettings("zio.sql.jdbc-hickaricp"))
.settings(
libraryDependencies ++= Seq(
"com.zaxxer" % "HikariCP" % "5.0.1",
"dev.zio" %% "zio-test" % zioVersion % Test,
"dev.zio" %% "zio-test-sbt" % zioVersion % Test,
"org.testcontainers" % "mysql" % testcontainersVersion % Test,
"mysql" % "mysql-connector-java" % "8.0.29" % Test,
"com.dimafeng" %% "testcontainers-scala-mysql" % testcontainersScalaVersion % Test
)
)
.settings(testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"))
.dependsOn(jdbc)

lazy val mysql = project
.in(file("mysql"))
.dependsOn(jdbc % "compile->compile;test->test")
Expand Down
35 changes: 35 additions & 0 deletions jdbc-hikaricp/src/main/scala/zio/sql/HikariConnectionPool.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package zio.sql
import com.zaxxer.hikari.{ HikariConfig, HikariDataSource }
import zio.{ Scope, ZIO, ZLayer }

import java.sql.{ Connection, SQLException }

class HikariConnectionPool private (hikariDataSource: HikariDataSource) extends ConnectionPool {

private[sql] val dataSource = hikariDataSource

/**
* Retrieves a JDBC java.sql.Connection as a [[ZIO[Scope, Exception, Connection]]] resource.
* The managed resource will safely acquire and release the connection, and
* may be interrupted or timed out if necessary.
*/
override def connection: ZIO[Scope, Exception, Connection] =
ZIO.acquireRelease(ZIO.attemptBlocking(hikariDataSource.getConnection).refineToOrDie[SQLException])(con =>
ZIO.attemptBlocking(hikariDataSource.evictConnection(con)).orDie
)
}

object HikariConnectionPool {

private[sql] def initDataSource(config: HikariConfig): ZIO[Scope, Throwable, HikariDataSource] =
ZIO.acquireRelease(ZIO.attemptBlocking(new HikariDataSource(config)))(ds => ZIO.attemptBlocking(ds.close()).orDie)

val live: ZLayer[HikariConnectionPoolConfig, Throwable, HikariConnectionPool] =
ZLayer.scoped {
for {
config <- ZIO.service[HikariConnectionPoolConfig]
dataSource <- initDataSource(config.toHikariConfig)
pool = new HikariConnectionPool(dataSource)
} yield pool
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package zio.sql

import com.zaxxer.hikari.HikariConfig

/**
* Configuration information for the connection pool.
*
* @param url The JDBC connection string.
* @param properties JDBC connection properties (username / password could go here).
* @param poolSize The size of the pool.
* @param connectionTimeout Maximum number of milliseconds that a client will wait for a connection from the pool.
* If this time is exceeded without a connection becoming available, a SQLException will be thrown from javax.sql.DataSource.getConnection().
* @param idleTimeout This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit idle in the pool.
* Whether a connection is retired as idle or not is subject to a maximum variation of +30 seconds, and average variation of +15 seconds.
* A connection will never be retired as idle before this timeout. A value of 0 means that idle connections are never removed from the pool.
* @param initializationFailTimeout the number of milliseconds before the
* pool initialization fails, or 0 to validate connection setup but continue with
* pool start, or less than zero to skip all initialization checks and start the
* pool without delay.
* @param maxLifetime This property controls the maximum lifetime of a connection in the pool.
* When a connection reaches this timeout, even if recently used, it will be retired from the pool.
* An in-use connection will never be retired, only when it is idle will it be removed. Should be bigger then 30000
* @param minimumIdle The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool, including both idle and in-use connections.
* If the idle connections dip below this value, HikariCP will make a best effort to restore them quickly and efficiently.
* @param connectionInitSql the SQL to execute on new connections
* Set the SQL string that will be executed on all new connections when they are
* created, before they are added to the pool. If this query fails, it will be
* treated as a failed connection attempt.
*/
final case class HikariConnectionPoolConfig(
url: String,
userName: String,
password: String,
poolSize: Int = 10,
autoCommit: Boolean = true,
connectionTimeout: Option[Long] = None,
idleTimeout: Option[Long] = None,
initializationFailTimeout: Option[Long] = None,
maxLifetime: Option[Long] = None,
minimumIdle: Option[Int] = None,
connectionInitSql: Option[String] = None
) {
private[sql] def toHikariConfig = {
val hikariConfig = new HikariConfig()
hikariConfig.setJdbcUrl(this.url)
hikariConfig.setAutoCommit(this.autoCommit)
hikariConfig.setMaximumPoolSize(this.poolSize)
hikariConfig.setUsername(userName)
hikariConfig.setPassword(password)
connectionTimeout.foreach(hikariConfig.setConnectionTimeout)
idleTimeout.foreach(hikariConfig.setIdleTimeout)
initializationFailTimeout.foreach(hikariConfig.setInitializationFailTimeout)
maxLifetime.foreach(hikariConfig.setMaxLifetime)
minimumIdle.foreach(hikariConfig.setMinimumIdle)
connectionInitSql.foreach(hikariConfig.setConnectionInitSql)
hikariConfig
}
}
131 changes: 131 additions & 0 deletions jdbc-hikaricp/src/test/scala/zio/sql/HikariConnectionPoolSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package zio.sql

import zio.test.TestAspect.{ sequential, timeout, withLiveClock }
import zio.test.{ TestEnvironment, _ }
import zio.{ durationInt, ZIO, ZLayer }

object HikariConnectionPoolSpec extends ZIOSpecDefault {

val mySqlConfigLayer: ZLayer[Any, Throwable, MySqlConfig] =
ZLayer.scoped {
MySqlTestContainer
.mysql()
.map(a =>
MySqlConfig(
url = a.jdbcUrl,
username = a.username,
password = a.password
)
)
}

val hikariPoolConfigLayer: ZLayer[MySqlConfig, Nothing, HikariConnectionPoolConfig] =
ZLayer.fromFunction((conf: MySqlConfig) =>
HikariConnectionPoolConfig(url = conf.url, userName = conf.username, password = conf.password)
)
val poolLayer: ZLayer[HikariConnectionPoolConfig, Nothing, HikariConnectionPool] = HikariConnectionPool.live.orDie

override def spec: Spec[TestEnvironment, Any] =
specLayered.provideCustomShared(mySqlConfigLayer.orDie)

def specLayered: Spec[TestEnvironment with MySqlConfig, Any] =
suite("Hikaricp module")(
test("Pool size should be configurable") {
val poolSize = 20
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.getMaximumPoolSize == poolSize))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(poolSize = poolSize))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("Pool size should have 10 connections by default") {
(for {
cp <- ZIO.service[HikariConnectionPool]
_ <- ZIO.replicateZIO(10)(ZIO.scoped(cp.connection))
} yield assertTrue(cp.dataSource.getMaximumPoolSize == 10))
.provideSomeLayer[TestEnvironment with MySqlConfig](hikariPoolConfigLayer >>> poolLayer)
} @@ timeout(10.minutes) @@ withLiveClock,
test("It should be possible to acquire connections from the pool") {
val poolSize = 20
(for {
cp <- ZIO.service[HikariConnectionPool]
_ <-
ZIO.collectAllParDiscard(ZIO.replicate(poolSize)(ZIO.scoped(cp.connection *> ZIO.sleep(500.millisecond))))
} yield assert("")(Assertion.anything)).provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(poolSize = poolSize))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("Auto commit should be configurable") {
val autoCommit = false
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.isAutoCommit == autoCommit))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(autoCommit = autoCommit))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("Auto commit should be true by default") {
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.isAutoCommit))
.provideSomeLayer[TestEnvironment with MySqlConfig](hikariPoolConfigLayer >>> poolLayer)
} @@ timeout(10.seconds) @@ withLiveClock,
test("Connection timeout should be configurable") {
val connectionTimeout = 2000L
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.getConnectionTimeout == connectionTimeout))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(connectionTimeout = Some(connectionTimeout)))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("Idle timeout should be configurable") {
val idleTimeout = 2000L
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.getIdleTimeout == idleTimeout))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(idleTimeout = Some(idleTimeout)))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("initialization fail timeout should be configurable") {
val initializationFailTimeout = 2000L
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.getInitializationFailTimeout == initializationFailTimeout))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(
_.update(_.copy(initializationFailTimeout = Some(initializationFailTimeout)))
) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("max lifetime should be configurable") {
val maxLifetime = 40000L
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.getMaxLifetime == maxLifetime))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(maxLifetime = Some(maxLifetime)))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("minimum idle should be configurable") {
val minimumIdle = 2
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.getMinimumIdle == minimumIdle))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(minimumIdle = Some(minimumIdle)))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock,
test("connection init SQL should be configurable") {
val initialSql = "SELECT 1 FROM test.test"
(for {
cp <- ZIO.service[HikariConnectionPool]
} yield assertTrue(cp.dataSource.getConnectionInitSql == initialSql))
.provideSomeLayer[TestEnvironment with MySqlConfig](
hikariPoolConfigLayer.map(_.update(_.copy(connectionInitSql = Some(initialSql)))) >>> poolLayer
)
} @@ timeout(10.seconds) @@ withLiveClock
) @@ sequential
}
23 changes: 23 additions & 0 deletions jdbc-hikaricp/src/test/scala/zio/sql/MySqlTestContainer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package zio.sql

import com.dimafeng.testcontainers.MySQLContainer
import org.testcontainers.utility.DockerImageName
import zio._

final case class MySqlConfig(username: String, password: String, url: String)
object MySqlTestContainer {

def mysql(imageName: String = "mysql"): ZIO[Scope, Throwable, MySQLContainer] =
ZIO.acquireRelease {
ZIO.attemptBlocking {
val c = new MySQLContainer(
mysqlImageVersion = Option(imageName).map(DockerImageName.parse)
).configure { a =>
a.withInitScript("test_schema.sql")
()
}
c.start()
c
}
}(container => ZIO.attemptBlocking(container.stop()).orDie)
}

0 comments on commit 781242c

Please sign in to comment.