Created
February 27, 2020 15:01
-
-
Save tamanugi/f00af56bf870b77ae96dbebea4488dd8 to your computer and use it in GitHub Desktop.
Playframework Custom Evolutions
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
play.modules { | |
enabled += "jp.hogehoge.EvolutionsModule" | |
disabled += "play.api.db.evolutions.EvolutionsModule" | |
} | |
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
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], | |
// Change to Custom Evolition Reader | |
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() | |
// filtering with number-prefixed sql | |
.filter(file => file.getName.matches("^[0-9]+.*\\.sql")) | |
// sort by number-prefix | |
.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) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment