diff --git a/build.sbt b/build.sbt index 222b7465d..c6e6c43ea 100644 --- a/build.sbt +++ b/build.sbt @@ -23,6 +23,8 @@ lazy val root = project doobieHikari, doobieHikariPureConfig, example, + flyway, + flywayPureConfig, http4sClientBlaze, http4sClientBlazePureConfig, http4sServer, @@ -85,7 +87,7 @@ lazy val doobieHikariPureConfig = project lazy val example = project .in(file("example")) - .dependsOn(bundleZioHttp4sBlaze, doobieHikari, doobieHikariPureConfig, micrometerJmxPureConfig, sslConfig) + .dependsOn(bundleZioHttp4sBlaze, doobieHikari, doobieHikariPureConfig, flyway, flywayPureConfig, micrometerJmxPureConfig, sslConfig) .enablePlugins(MdocPlugin) .settings(commonSettings) .settings( @@ -101,6 +103,23 @@ lazy val example = project ) ) +lazy val flyway = project + .in(file("flyway")) + .settings(commonSettings) + .settings( + name := "sst-flyway", + libraryDependencies += Dependencies.flywayCore + ) + +lazy val flywayPureConfig = project + .in(file("flyway-pureconfig")) + .dependsOn(flyway) + .settings(commonSettings) + .settings( + name := "sst-flyway-pureconfig", + libraryDependencies += Dependencies.pureConfig + ) + lazy val http4sClientBlaze = project .in(file("http4s-client-blaze")) .settings(commonSettings) diff --git a/docs/flyway.md b/docs/flyway.md new file mode 100644 index 000000000..175c6cc3b --- /dev/null +++ b/docs/flyway.md @@ -0,0 +1,25 @@ +# Flyway + +[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-flyway_2.12)](https://repo1.maven.org/maven2/com/avast/sst-flyway_2.12/) + +`libraryDependencies += "com.avast" %% "sst-flyway" % ""` + +This module initializes `Flyway` which can be used to do automated SQL DB migrations. See the [documentation of Flyway](https://flywaydb.org/documentation/) +on how to go about that. + +The method `make` requires `javax.sql.DataSource` which you can for example obtain from `doobie-hikari` module: + +```scala +import cats.effect.Resource +import com.avast.sst.doobie.DoobieHikariModule +import com.avast.sst.flyway.FlywayModule +import zio.Task +import zio.interop.catz._ +import zio.interop.catz.implicits._ + +for { + doobieTransactor <- DoobieHikariModule.make[Task](???, ???, ???, ???) + flyway <- Resource.liftF(FlywayModule.make[Task](doobieTransactor.kernel, ???)) +} yield () +``` + diff --git a/docs/index.md b/docs/index.md index dc708e26f..34c4abf9a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,6 +10,7 @@ * [Module PureConfig](pureconfig.md) * [Module SSL Config](ssl-config.md) * [Module doobie](doobie.md) +* [Module Flyway](flyway.md) ## Getting Started diff --git a/example/mdoc/flyway.md b/example/mdoc/flyway.md new file mode 100644 index 000000000..c9a9361c1 --- /dev/null +++ b/example/mdoc/flyway.md @@ -0,0 +1,24 @@ +# Flyway + +[![Maven Central](https://img.shields.io/maven-central/v/com.avast/sst-flyway_2.12)](https://repo1.maven.org/maven2/com/avast/sst-flyway_2.12/) + +`libraryDependencies += "com.avast" %% "sst-flyway" % ""` + +This module initializes `Flyway` which can be used to do automated SQL DB migrations. See the [documentation of Flyway](https://flywaydb.org/documentation/) +on how to go about that. + +The method `make` requires `javax.sql.DataSource` which you can for example obtain from `doobie-hikari` module: + +```scala +import cats.effect.Resource +import com.avast.sst.doobie.DoobieHikariModule +import com.avast.sst.flyway.FlywayModule +import zio.Task +import zio.interop.catz._ +import zio.interop.catz.implicits._ + +for { + doobieTransactor <- DoobieHikariModule.make[Task](???, ???, ???, ???) + flyway <- Resource.liftF(FlywayModule.make[Task](doobieTransactor.kernel, ???)) +} yield () +``` diff --git a/example/mdoc/index.md b/example/mdoc/index.md index 3a95ed9f3..6868bef4a 100644 --- a/example/mdoc/index.md +++ b/example/mdoc/index.md @@ -10,6 +10,7 @@ * [Module PureConfig](pureconfig.md) * [Module SSL Config](ssl-config.md) * [Module doobie](doobie.md) +* [Module Flyway](flyway.md) ## Getting Started diff --git a/flyway-pureconfig/src/main/scala/com/avast/sst/flyway/pureconfig/ConfigReaders.scala b/flyway-pureconfig/src/main/scala/com/avast/sst/flyway/pureconfig/ConfigReaders.scala new file mode 100644 index 000000000..43ed674b9 --- /dev/null +++ b/flyway-pureconfig/src/main/scala/com/avast/sst/flyway/pureconfig/ConfigReaders.scala @@ -0,0 +1,22 @@ +package com.avast.sst.flyway.pureconfig + +import java.nio.charset.Charset + +import cats.syntax.either._ +import com.avast.sst.flyway.FlywayConfig +import org.flywaydb.core.api.MigrationVersion +import pureconfig.ConfigReader +import pureconfig.error.ExceptionThrown +import pureconfig.generic.semiauto.deriveReader + +trait ConfigReaders { + + implicit private[pureconfig] val charsetReader: ConfigReader[Charset] = ConfigReader[String].emap { value => + Either.catchNonFatal(Charset.forName(value)).leftMap(ExceptionThrown.apply) + } + + implicit val migrationVersionReader: ConfigReader[MigrationVersion] = ConfigReader[String].map(MigrationVersion.fromVersion) + + implicit val configReader: ConfigReader[FlywayConfig] = deriveReader + +} diff --git a/flyway-pureconfig/src/main/scala/com/avast/sst/flyway/pureconfig/implicits.scala b/flyway-pureconfig/src/main/scala/com/avast/sst/flyway/pureconfig/implicits.scala new file mode 100644 index 000000000..c4d6da818 --- /dev/null +++ b/flyway-pureconfig/src/main/scala/com/avast/sst/flyway/pureconfig/implicits.scala @@ -0,0 +1,3 @@ +package com.avast.sst.flyway.pureconfig + +object implicits extends ConfigReaders diff --git a/flyway/src/main/scala/com/avast/sst/flyway/FlywayConfig.scala b/flyway/src/main/scala/com/avast/sst/flyway/FlywayConfig.scala new file mode 100644 index 000000000..afefdf248 --- /dev/null +++ b/flyway/src/main/scala/com/avast/sst/flyway/FlywayConfig.scala @@ -0,0 +1,26 @@ +package com.avast.sst.flyway + +import java.nio.charset.{Charset, StandardCharsets} + +import org.flywaydb.core.api.MigrationVersion + +final case class FlywayConfig(baselineOnMigrate: Boolean = false, + baselineVersion: Option[MigrationVersion] = None, + targetVersion: Option[MigrationVersion] = None, + baselineDescription: Option[String] = None, + batch: Boolean = false, + cleanDisabled: Boolean = false, + cleanOnValidationError: Boolean = false, + connectRetries: Int = 0, + encoding: Charset = StandardCharsets.UTF_8, + group: Boolean = false, + ignoreFutureMigrations: Boolean = true, + ignoreIgnoredMigrations: Boolean = false, + ignoreMissingMigrations: Boolean = false, + ignorePendingMigrations: Boolean = false, + installedBy: Option[String] = None, + mixed: Boolean = false, + locations: List[String] = List.empty, + outOfOrder: Boolean = false, + validateOnMigrate: Boolean = true, + licenseKey: Option[String] = None) diff --git a/flyway/src/main/scala/com/avast/sst/flyway/FlywayModule.scala b/flyway/src/main/scala/com/avast/sst/flyway/FlywayModule.scala new file mode 100644 index 000000000..9ce40de7e --- /dev/null +++ b/flyway/src/main/scala/com/avast/sst/flyway/FlywayModule.scala @@ -0,0 +1,43 @@ +package com.avast.sst.flyway + +import cats.effect.Sync +import javax.sql.DataSource +import org.flywaydb.core.Flyway + +object FlywayModule { + + /** Makes [[org.flywaydb.core.Flyway]] from the given [[javax.sql.DataSource]] and config. */ + @SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements")) + def make[F[_]: Sync](dataSource: DataSource, config: FlywayConfig): F[Flyway] = { + Sync[F].delay { + val builder = Flyway + .configure + .dataSource(dataSource) + .baselineOnMigrate(config.baselineOnMigrate) + .batch(config.batch) + .cleanDisabled(config.cleanDisabled) + .cleanOnValidationError(config.cleanOnValidationError) + .connectRetries(config.connectRetries) + .encoding(config.encoding) + .group(config.group) + .ignoreFutureMigrations(config.ignoreFutureMigrations) + .ignoreIgnoredMigrations(config.ignoreIgnoredMigrations) + .ignoreMissingMigrations(config.ignoreMissingMigrations) + .ignorePendingMigrations(config.ignorePendingMigrations) + .mixed(config.mixed) + .outOfOrder(config.outOfOrder) + .validateOnMigrate(config.validateOnMigrate) + + config.baselineVersion.foreach(builder.baselineVersion) + config.targetVersion.foreach(builder.target) + config.baselineDescription.foreach(builder.baselineDescription) + config.installedBy.foreach(builder.installedBy) + if (config.locations.nonEmpty) builder.locations(config.locations: _*) + + config.licenseKey.foreach(builder.licenseKey) + + builder.load() + } + } + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ed5c0c26a..f53e21190 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,6 +5,7 @@ object Dependencies { val catsEffect = "org.typelevel" %% "cats-effect" % "2.0.0" val doobie = "org.tpolecat" %% "doobie-core" % Versions.doobie val doobieHikari = "org.tpolecat" %% "doobie-hikari" % Versions.doobie + val flywayCore = "org.flywaydb" % "flyway-core" % "6.0.7" val http4sBlazeClient = "org.http4s" %% "http4s-blaze-client" % Versions.http4s val http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % Versions.http4s val http4sDsl = "org.http4s" %% "http4s-dsl" % Versions.http4s