This repository has been archived by the owner on Jan 13, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into update/scalafmt-core-3.5.9
- Loading branch information
Showing
14 changed files
with
305 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
jdbc-hikaricp/src/main/scala/zio/sql/HikariConnectionPool.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
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 | ||
|
||
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 | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
jdbc-hikaricp/src/main/scala/zio/sql/HikariConnectionPoolConfig.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
131
jdbc-hikaricp/src/test/scala/zio/sql/HikariConnectionPoolSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
(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 | ||
} |
20 changes: 20 additions & 0 deletions
20
jdbc-hikaricp/src/test/scala/zio/sql/MySqlTestContainer.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
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) | ||
) | ||
c.start() | ||
c | ||
} | ||
}(container => ZIO.attemptBlocking(container.stop()).orDie) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.