Pug Schema Manager

A purely functional toolkit for using Doobie to manage your database schemata.

Quickstart

Add the necessary dependency, e.g. in build.sbt

libraryDependencies += "works.pugcode" % "pug-schema-manager" %% "0.1.0"

Define a schema component. This is a set of related SQL which should be versioned (and migrated) together.

At a minimum, this must include the currentVersion number (an integer which is incremented each time a migration needs to be applied), and currentSchema, which is the complete definition of the current version of the schema, and will be applied if the schema does not yet exist in any form.

You may also define a set of migrations, which specify the incremental steps to migrate from previous versions of the schema to the current one. These are defined as a partial function from a (fromVersion, toVersion) tuple to the migration steps to be performed. Migrations are applied atomically, in order, to advance the database's current version to the one defined by code. If migrations exist to advance more than one version, the migration which advances the current version as far as possible will be applied.

import doobie.implicits._
import pug.schema._

object PetsSchema extends SchemaComponent("pets") {
  val currentVersion = 2

  val currentSchema = sql"""
    CREATE TABLE cats (
      name CHARACTER VARYING PRIMARY KEY,
      ennui REAL CHECK (ennui > 0),
      frolic_factor REAL CHECK (frolic_factor > 0)
    );
  """

  override val migrations = {
    case (1, 2) => sql"""
      ALTER TABLE cats ADD COLUMN frolic_factor REAL
        CHECK (frolic_factor > 0);
    """
  }
}

The metadata maintained by the schema manager, as well as any required migrations, can be applied by executing the effect returned from the SchemaManager.bootstrap() method

import cats.effect.{ExitCode, IO, IOApp}
import doobie._
import doobie.implicits._
import pug.schema._

object MyPetsApp extends IOApp {
  val schemaManagement = new SchemaManagement()
  def transactor = Transactor.fromDriverManager[IO](
    "org.h2.Driver",
    "jdbc:h2:mem:example;USER=sa;DB_CLOSE_DELAY=-1;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE"
  )

  def run(args: List[String] = List()): IO[ExitCode] =
    for {
      _ <- schemaManagement.bootstrap(PetsSchema).transact(transactor)
    } yield ExitCode.Success
}

Specifying Migration Actions

Migration steps (as well as the initial schema creation) can be specified in a variety of ways: