[Playframework]Evolutionsのファイル名を連番SQLじゃなくする

PlayframeworkではDB Migration として、Evolutionsを採用しています  
参考: https://www.playframework.com/documentation/2.7.x/Evolutions

これ自体は普通に便利なのですが、SQLファイル名が `1.sql` `2.sql` といった連番SQLであるために、
複数開発者の間でよくSQLファイルがコンフリクトするといったことがありました。

そこで、`1_add_table.sql` `1_alter_columns_hogehoge.sql` といったように、数字以外の文言も
SQLファイルに含められるようにしてみました


# コード

Evolutionsまわりの処理はEvolutionsModuleで提供されているみたいなので、
そこの `EvolutionsReader` をカスタマイズしたものに変更します

```java:EvolutiosModule.scala
import java.io.{ByteArrayOutputStream, Closeable, IOException, InputStream}

import javax.inject._
import play.Logger
import play.api.Environment
import play.api.db.evolutions._
import play.api.inject._
import play.api.libs.Collections

import scala.io.Codec

/**
  * Default module for evolutions API.
  */
class EvolutionsModule extends SimpleModule(
  bind[EvolutionsConfig].toProvider[DefaultEvolutionsConfigParser],
  // EvolutionReaderを変更する
  bind[EvolutionsReader].to[MyEnvironmentEvolutionsReader],
  bind[EvolutionsApi].to[DefaultEvolutionsApi],
  bind[ApplicationEvolutions].toProvider[ApplicationEvolutionsProvider].eagerly
)

/**
  * Read evolution files from the application environment.
  */
@Singleton
class MyEnvironmentEvolutionsReader @Inject() (environment: Environment) extends ResourceEvolutionsReader {

  /**
   * Not used
   */
  def loadResource(db: String, revision: Int): Option[InputStream] = {
    Option.empty
  }

  override def evolutions(db: String): Seq[Evolution] = {

    val upsMarker = """^#.*!Ups.*$""".r
    val downsMarker = """^#.*!Downs.*$""".r

    val UPS = "UPS"
    val DOWNS = "DOWNS"
    val UNKNOWN = "UNKNOWN"

    val mapUpsAndDowns: PartialFunction[String, String] = {
      case upsMarker() => UPS
      case downsMarker() => DOWNS
      case _ => UNKNOWN
    }

    val isMarker: PartialFunction[String, Boolean] = {
      case upsMarker() => true
      case downsMarker() => true
      case _ => false
    }


    val folder = environment.getFile(Evolutions.directoryName(db))
    val sqlFiles = folder.listFiles()

        // 数字が先頭についているSQLファイルのみをEvolutionの対象にする
        .filter(file => file.getName.matches("^[0-9]+.*\\.sql"))

        // 先頭の数字順になるように
        .sortBy(file => {
          file.getName.split("\\.")(0).split("_")(0).toInt
        })
        .toSeq

    sqlFiles.zip(1 to sqlFiles.size)
        .map {
          case (file, revision) => {
            val script = readStreamAsString(file.toURI.toURL.openStream())

            val parsed = Collections.unfoldLeft(("", script.split('\n').toList.map(_.trim))) {
              case (_, Nil) => None
              case (context, lines) => {
                val (some, next) = lines.span(l => !isMarker(l))
                Some((next.headOption.map(c => (mapUpsAndDowns(c), next.tail)).getOrElse("" -> Nil),
                  context -> some.mkString("\n")))
              }
            }.reverse.drop(1).groupBy(i => i._1).mapValues { _.map(_._2).mkString("\n").trim }
            Evolution(
              revision,
              parsed.getOrElse(UPS, ""),
              parsed.getOrElse(DOWNS, "")
            )
          }

        }

  }

  /**
    * Read the given stream into a byte array.
    *
    * Closes the stream.
    */
  private def readStream(stream: InputStream): Array[Byte] = {
    try {
      val buffer = new Array[Byte](8192)
      var len = stream.read(buffer)
      val out = new ByteArrayOutputStream() // Doesn't need closing
      while (len != -1) {
        out.write(buffer, 0, len)
        len = stream.read(buffer)
      }
      out.toByteArray
    } finally closeQuietly(stream)
  }

  /**
    * Read the given stream into a String.
    *
    * Closes the stream.
    */
  def readStreamAsString(stream: InputStream)(implicit codec: Codec): String = {
    new String(readStream(stream), codec.name)
  }
  /**
    * Close the given closeable quietly.
    *
    * Logs any IOExceptions encountered.
    */
  def closeQuietly(closeable: Closeable) = {
    try {
      if (closeable != null) {
        closeable.close()
      }
    } catch {
      case e: IOException => Logger.warn("Error closing stream", e)
    }
  }
}
```

```application.conf
play.modules {
  enabled += "jp.hogehoge.EvolutionsModule"
  disabled += "play.api.db.evolutions.EvolutionsModule"
}

```


---

参考: https://stackoverflow.com/questions/43093432/is-it-possible-to-name-play-evolution-sql-scripts