Skip to content

Commit

Permalink
Merge branch 'master' into update/scalafmt-core-3.5.9
Browse files Browse the repository at this point in the history
  • Loading branch information
jczuchnowski committed Oct 2, 2022
2 parents 696077e + 80b91f7 commit f97aa92
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 26 deletions.
30 changes: 24 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ addCommandAlias("fmtOnce", "all scalafmtSbt scalafmt test:scalafmt")
addCommandAlias("fmt", "fmtOnce;fmtOnce")
addCommandAlias("check", "all scalafmtSbtCheck scalafmtCheck test:scalafmtCheck")

val zioVersion = "2.0.0"
val zioSchemaVersion = "0.2.0"
val zioVersion = "2.0.2"
val zioSchemaVersion = "0.2.1"
val testcontainersVersion = "1.17.3"
val testcontainersScalaVersion = "0.40.10"
val logbackVersion = "1.2.11"
Expand All @@ -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 @@ -117,7 +118,7 @@ lazy val jdbc = project
libraryDependencies ++= Seq(
"dev.zio" %% "zio-test" % zioVersion % Test,
"dev.zio" %% "zio-test-sbt" % zioVersion % Test,
"org.postgresql" % "postgresql" % "42.4.1" % Test,
"org.postgresql" % "postgresql" % "42.4.2" % Test,
"com.dimafeng" %% "testcontainers-scala-postgresql" % testcontainersScalaVersion % Test
)
)
Expand All @@ -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" % "4.0.3", // 5.x doesn't support Java 1.8
"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 Expand Up @@ -162,7 +180,7 @@ lazy val oracle = project
"org.testcontainers" % "database-commons" % testcontainersVersion % Test,
"org.testcontainers" % "oracle-xe" % testcontainersVersion % Test,
"org.testcontainers" % "jdbc" % testcontainersVersion % Test,
"com.oracle.database.jdbc" % "ojdbc8" % "21.6.0.0.1" % Test,
"com.oracle.database.jdbc" % "ojdbc8" % "21.7.0.0" % Test,
"com.dimafeng" %% "testcontainers-scala-oracle-xe" % testcontainersScalaVersion % Test,
"ch.qos.logback" % "logback-classic" % logbackVersion % Test
)
Expand All @@ -181,7 +199,7 @@ lazy val postgres = project
"org.testcontainers" % "database-commons" % testcontainersVersion % Test,
"org.testcontainers" % "postgresql" % testcontainersVersion % Test,
"org.testcontainers" % "jdbc" % testcontainersVersion % Test,
"org.postgresql" % "postgresql" % "42.4.1" % Compile,
"org.postgresql" % "postgresql" % "42.4.2" % Compile,
"com.dimafeng" %% "testcontainers-scala-postgresql" % testcontainersScalaVersion % Test,
"ch.qos.logback" % "logback-classic" % logbackVersion % Test
)
Expand Down
30 changes: 30 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,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
}
}
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"
(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 jdbc-hikaricp/src/test/scala/zio/sql/MySqlTestContainer.scala
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)
}
7 changes: 4 additions & 3 deletions jdbc/src/test/scala/zio/sql/JdbcRunnableSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ trait JdbcRunnableSpec extends ZIOSpecDefault with Jdbc {
)
}

val connectionPool: ZLayer[Any, Throwable, ConnectionPool] = poolConfigLayer >>> ConnectionPool.live

private[this] final lazy val jdbcLayer: ZLayer[Any, Any, SqlDriver] =
ZLayer.make[SqlDriver](
poolConfigLayer.orDie,
ConnectionPool.live.orDie,
connectionPool.orDie,
SqlDriver.live
)

Expand All @@ -65,7 +66,7 @@ trait JdbcRunnableSpec extends ZIOSpecDefault with Jdbc {
def both[A, B](fa: => Gen[R, A], fb: => Gen[R, B]): Gen[R, (A, B)] = fa.zip(fb)
}

private[this] def testContainer: ZIO[Scope, Throwable, SingleContainer[_] with JdbcDatabaseContainer] =
val testContainer: ZIO[Scope, Throwable, SingleContainer[_] with JdbcDatabaseContainer] =
ZIO.acquireRelease {
ZIO.attemptBlocking {
val c = getContainer
Expand Down
5 changes: 3 additions & 2 deletions mysql/src/main/scala/zio/sql/mysql/MysqlRenderModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ trait MysqlRenderModule extends MysqlSqlModule { self =>

private def renderInsertValue[Z](z: Z)(implicit render: Renderer, schema: Schema[Z]): Unit =
schema.toDynamic(z) match {
case DynamicValue.Record(listMap) =>
case DynamicValue.Record(_, listMap) =>
listMap.values.toList match {
case head :: Nil => renderDynamicValue(head)
case head :: next =>
Expand All @@ -147,7 +147,7 @@ trait MysqlRenderModule extends MysqlSqlModule { self =>
renderDynamicValues(next)
case Nil => ()
}
case value => renderDynamicValue(value)
case value => renderDynamicValue(value)
}

private def renderDynamicValues(dynValues: List[DynamicValue])(implicit render: Renderer): Unit =
Expand All @@ -170,6 +170,7 @@ trait MysqlRenderModule extends MysqlSqlModule { self =>
render(value)
case StandardType.InstantType(formatter) =>
render(s"'${formatter.format(value.asInstanceOf[Instant])}'")
case ByteType => render(s"'${value}'")
case CharType => render(s"'${value}'")
case IntType => render(value)
case StandardType.MonthDayType => render(s"'${value}'")
Expand Down
1 change: 1 addition & 0 deletions mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ object MysqlModuleSpec extends MysqlRunnableSpec with ShopSchema {
)
implicit val customerRowSchema =
Schema.CaseClass5[UUID, LocalDate, String, String, Boolean, CustomerRow](
TypeId.parse("zio.sql.mysql.CustomerRow"),
Schema.Field("id", Schema.primitive[UUID](zio.schema.StandardType.UUIDType)),
Schema.Field(
"dateOfBirth",
Expand Down
Loading

0 comments on commit f97aa92

Please sign in to comment.