Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc.
*/
package play.api.db.evolutions
import java.io.File
import java.io.InputStream
import java.net.URI
import java.nio.file.Path
import java.sql._
import javax.inject.Inject
import javax.inject.Singleton
import scala.annotation.tailrec
import scala.io.Codec
import scala.util.control.NonFatal
import play.api.db.DBApi
import play.api.db.Database
import play.api.Environment
import play.api.Logger
import play.api.PlayException
import play.utils.PlayIO
/**
* Evolutions API.
*/
trait EvolutionsApi {
/**
* Create evolution scripts.
*
* @param db the database name
* @param evolutions the evolutions for the application
* @param schema The schema where all the play evolution tables are saved in
* @return evolution scripts
*/
def scripts(db: String, evolutions: Seq[Evolution], schema: String): Seq[Script]
/**
* Create evolution scripts.
*
* @param db the database name
* @param evolutions the evolutions for the application
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' meta data
* @return evolution scripts
*/
def scripts(db: String, evolutions: Seq[Evolution], schema: String, metaTable: String): Seq[Script]
/**
* Create evolution scripts.
*
* @param db the database name
* @param reader evolution file reader
* @param schema The schema where all the play evolution tables are saved in
* @return evolution scripts
*/
def scripts(db: String, reader: EvolutionsReader, schema: String): Seq[Script]
/**
* Create evolution scripts.
*
* @param db the database name
* @param reader evolution file reader
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' meta data
* @return evolution scripts
*/
def scripts(db: String, reader: EvolutionsReader, schema: String, metaTable: String): Seq[Script]
/**
* Get all scripts necessary to reset the database state to its initial state.
*
* @param db the database name
* @param schema The schema where all the play evolution tables are saved in
* @return evolution scripts
*/
def resetScripts(db: String, schema: String): Seq[Script]
/**
* Get all scripts necessary to reset the database state to its initial state.
*
* @param db the database name
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' data
* @return evolution scripts
*/
def resetScripts(db: String, schema: String, metaTable: String): Seq[Script]
/**
* Apply evolution scripts to the database.
*
* @param db the database name
* @param scripts the evolution scripts to run
* @param autocommit determines whether the connection uses autocommit
* @param schema The schema where all the play evolution tables are saved in
*/
def evolve(db: String, scripts: Seq[Script], autocommit: Boolean, schema: String): Unit
/**
* Apply evolution scripts to the database.
*
* @param db the database name
* @param scripts the evolution scripts to run
* @param autocommit determines whether the connection uses autocommit
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' data
*/
def evolve(db: String, scripts: Seq[Script], autocommit: Boolean, schema: String, metaTable: String): Unit
/**
* Apply evolution scripts to the database.
*
* @param db the database name
* @param scripts the evolution scripts to run
* @param autocommit determines whether the connection uses autocommit
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' data
* @param substitutionsMappings Mappings of variables (without the prefix and suffix) and their
* replacements.
* @param substitutionsPrefix Prefix of the variable to substitute, e.g. "$evolutions{{{".
* @param substitutionsSuffix Suffix of the variable to substitute, e.g. "}}}".
* @param substitutionsEscape Whetever escaping of variables is enabled via a preceding "!". E.g.
* "!$evolutions{{{my_variable}}}" ends up as "$evolutions{{{my_variable}}}" in the
* final sql instead of replacing it with its substitution.
*/
def evolve(
db: String,
scripts: Seq[Script],
autocommit: Boolean,
schema: String,
metaTable: String,
substitutionsMappings: Map[String, String],
substitutionsPrefix: String,
substitutionsSuffix: String,
substitutionsEscape: Boolean
): Unit
/**
* Resolve evolution conflicts.
*
* @param db the database name
* @param revision the revision to mark as resolved
* @param schema The schema where all the play evolution tables are saved in
*/
def resolve(db: String, revision: Int, schema: String): Unit
/**
* Resolve evolution conflicts.
*
* @param db the database name
* @param revision the revision to mark as resolved
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' meta data
*/
def resolve(db: String, revision: Int, schema: String, metaTable: String): Unit
/**
* Apply pending evolutions for the given database.
*/
def applyFor(
dbName: String,
path: File = new File("."),
autocommit: Boolean = true,
schema: String = "",
metaTable: String = "play_evolutions",
substitutionsMappings: Map[String, String] = Map.empty,
substitutionsPrefix: String = "$evolutions{{{",
substitutionsSuffix: String = "}}}",
substitutionsEscape: Boolean = true,
evolutionsPath: String = "evolutions"
): Unit = {
val evolutionsConfig = new EvolutionsConfig {
// Using a dummy evolutions config, however the only config that is relevant here for the EnvironmentEvolutionsReader
// is the "path" setting, everything else does not matter actually. Using default values from the reference.conf
override def forDatasource(db: String): EvolutionsDatasourceConfig = DefaultEvolutionsDatasourceConfig(
true, // enabled
schema,
metaTable,
autocommit,
false, // useLocks
false, // autoApply
false, // autoApplyDowns
false, // skipApplyDownsOnly
substitutionsPrefix,
substitutionsSuffix,
substitutionsMappings,
substitutionsEscape,
evolutionsPath // <-- this is all that matters for the EnvironmentEvolutionsReader
)
}
val scripts =
this.scripts(
dbName,
new EnvironmentEvolutionsReader(Environment.simple(path = path), evolutionsConfig),
schema,
metaTable
)
this.evolve(
dbName,
scripts,
autocommit,
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
)
}
}
/**
* Default implementation of the evolutions API.
*/
@Singleton
class DefaultEvolutionsApi @Inject() (dbApi: DBApi) extends EvolutionsApi {
private def databaseEvolutions(
name: String,
schema: String,
metaTable: String = "play_evolutions",
substitutionsMappings: Map[String, String] = Map.empty,
substitutionsPrefix: String = "$evolutions{{{",
substitutionsSuffix: String = "}}}",
substitutionsEscape: Boolean = true
) =
new DatabaseEvolutions(
dbApi.database(name),
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
)
def scripts(db: String, evolutions: Seq[Evolution], schema: String) =
databaseEvolutions(db, schema).scripts(evolutions)
def scripts(db: String, evolutions: Seq[Evolution], schema: String, metaTable: String) =
databaseEvolutions(db, schema, metaTable).scripts(evolutions)
def scripts(db: String, reader: EvolutionsReader, schema: String) = databaseEvolutions(db, schema).scripts(reader)
def scripts(db: String, reader: EvolutionsReader, schema: String, metaTable: String) =
databaseEvolutions(db, schema, metaTable).scripts(reader)
def resetScripts(db: String, schema: String) = databaseEvolutions(db, schema).resetScripts()
def resetScripts(db: String, schema: String, metaTable: String) =
databaseEvolutions(db, schema, metaTable).resetScripts()
def evolve(db: String, scripts: Seq[Script], autocommit: Boolean, schema: String) =
databaseEvolutions(db, schema).evolve(scripts, autocommit)
def evolve(db: String, scripts: Seq[Script], autocommit: Boolean, schema: String, metaTable: String) =
databaseEvolutions(db, schema, metaTable).evolve(scripts, autocommit)
def evolve(
db: String,
scripts: Seq[Script],
autocommit: Boolean,
schema: String,
metaTable: String,
substitutionsMappings: Map[String, String],
substitutionsPrefix: String,
substitutionsSuffix: String,
substitutionsEscape: Boolean
): Unit =
databaseEvolutions(
db,
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
).evolve(scripts, autocommit)
def resolve(db: String, revision: Int, schema: String) = databaseEvolutions(db, schema).resolve(revision)
def resolve(db: String, revision: Int, schema: String, metaTable: String) =
databaseEvolutions(db, schema, metaTable).resolve(revision)
}
/**
* Evolutions for a particular database.
*/
class DatabaseEvolutions(
database: Database,
schema: String = "",
metaTable: String = "play_evolutions",
substitutionsMappings: Map[String, String] = Map.empty,
substitutionsPrefix: String = "$evolutions{{{",
substitutionsSuffix: String = "}}}",
substitutionsEscape: Boolean = true
) {
def this(database: Database, schema: String, metaTable: String) = {
this(database, schema, metaTable, Map.empty, "$evolutions{{{", "}}}", true)
}
def this(database: Database, schema: String) = {
this(database, schema, "play_evolutions")
}
import DatabaseUrlPatterns._
import DefaultEvolutionsApi._
def scripts(evolutions: Seq[Evolution]): Seq[Script] = {
if (evolutions.nonEmpty) {
val application = evolutions.reverse
val database = databaseEvolutions()
val (nonConflictingDowns, dRest) = database.span(e => !application.headOption.exists(e.revision <= _.revision))
val (nonConflictingUps, uRest) = application.span(e => !database.headOption.exists(_.revision >= e.revision))
val (conflictingDowns, conflictingUps) = Evolutions.conflictings(dRest, uRest)
val ups = (nonConflictingUps ++ conflictingUps).reverseIterator.map(e => UpScript(e)).toSeq
val downs = (nonConflictingDowns ++ conflictingDowns).map(e => DownScript(e))
downs ++ ups
} else Nil
}
def scripts(reader: EvolutionsReader): Seq[Script] = {
scripts(reader.evolutions(database.name).toList)
}
/**
* Read evolutions from the database.
*/
def databaseEvolutions(): Seq[Evolution] = {
checkEvolutionsState()
implicit val connection = database.getConnection(autocommit = true)
try {
executeQuery(
"select id, hash, apply_script, revert_script from ${schema}${evolutions_table} order by id"
) { rs =>
Seq
.unfold(rs) { rs =>
rs.next match {
case false => None
case true => {
Some(
(
Evolution(
rs.getInt(1),
Option(rs.getString(3)).getOrElse(""),
Option(rs.getString(4)).getOrElse("")
),
rs
)
)
}
}
}
.reverse
}
} finally {
connection.close()
}
}
def evolve(scripts: Seq[Script], autocommit: Boolean): Unit = {
def logBefore(script: Script)(implicit conn: Connection): Unit = {
script match {
case UpScript(e) =>
prepareAndExecute(
"insert into ${schema}${evolutions_table} " +
"(id, hash, applied_at, apply_script, revert_script, state, last_problem) " +
"values(?, ?, ?, ?, ?, ?, ?)"
) { ps =>
ps.setInt(1, e.revision)
ps.setString(2, e.hash)
ps.setTimestamp(3, new Timestamp(System.currentTimeMillis()))
ps.setString(4, e.sql_up)
ps.setString(5, e.sql_down)
ps.setString(6, "applying_up")
ps.setString(7, "")
}
case DownScript(e) =>
execute("update ${schema}${evolutions_table} set state = 'applying_down' where id = " + e.revision)
}
}
def logAfter(script: Script)(implicit conn: Connection): Boolean = {
script match {
case UpScript(e) => {
execute("update ${schema}${evolutions_table} set state = 'applied' where id = " + e.revision)
}
case DownScript(e) => {
execute("delete from ${schema}${evolutions_table} where id = " + e.revision)
}
}
}
def updateLastProblem(message: String, revision: Int)(implicit conn: Connection): Boolean = {
prepareAndExecute("update ${schema}${evolutions_table} set last_problem = ? where id = ?") { ps =>
ps.setString(1, message)
ps.setInt(2, revision)
}
}
checkEvolutionsState()
implicit val connection = database.getConnection(autocommit = autocommit)
var applying = -1
var lastScript: Script = null
try {
scripts.foreach { script =>
lastScript = script
applying = script.evolution.revision
logBefore(script)
// Execute script
script.statements.foreach { statement =>
logger.debug(s"Execute: $statement")
val start = System.currentTimeMillis()
execute(statement, false)
logger.debug(s"Finished in ${System.currentTimeMillis() - start}ms")
}
logAfter(script)
}
if (!autocommit) {
connection.commit()
}
} catch {
case NonFatal(e) => {
val message = e match {
case ex: SQLException => ex.getMessage + " [ERROR:" + ex.getErrorCode + ", SQLSTATE:" + ex.getSQLState + "]"
case ex => ex.getMessage
}
logger.error(message)
if (!autocommit) {
connection.rollback()
val humanScript = "-- Rev:" + lastScript.evolution.revision + "," + (if (lastScript.isInstanceOf[UpScript])
"Ups"
else
"Downs") + " - " + lastScript.evolution.hash + "\n\n" + (if (
lastScript
.isInstanceOf[UpScript]
)
lastScript.evolution.sql_up
else
lastScript.evolution.sql_down)
throw InconsistentDatabase(database.name, humanScript, message, lastScript.evolution.revision, autocommit)
} else {
updateLastProblem(message, applying)
}
}
} finally {
connection.close()
}
checkEvolutionsState()
}
// SQL helpers
import EvolutionsHelper._
/**
* Checks the evolutions state in the database.
*
* @throws NonFatal error if the database is in an inconsistent state
*/
private def checkEvolutionsState(): Unit = {
def createPlayEvolutionsTable()(implicit conn: Connection): Unit = {
try {
val createScript = database.url match {
case SqlServerJdbcUrl() => CreatePlayEvolutionsSqlServerSql
case OracleJdbcUrl() => CreatePlayEvolutionsOracleSql
case MysqlJdbcUrl(_, _) => CreatePlayEvolutionsMySql
case DerbyJdbcUrl() => CreatePlayEvolutionsDerby
case HsqlJdbcUrl() => CreatePlayEvolutionsHsql
case _ => CreatePlayEvolutionsSql
}
execute(createScript)
} catch {
case NonFatal(ex) =>
logger.warn(
applySchemaAndTable(
"could not create ${schema}${evolutions_table} table",
schema = schema,
table = metaTable
),
ex
)
}
}
val autocommit = true
implicit val connection = database.getConnection(autocommit = autocommit)
try {
executeQuery(
"select id, hash, apply_script, revert_script, state, last_problem from ${schema}${evolutions_table} where state like 'applying_%'"
) { problem =>
if (problem.next) {
val revision = problem.getInt("id")
val state = problem.getString("state")
val hash = problem.getString("hash").take(7)
val script = state match {
case "applying_up" => problem.getString("apply_script")
case _ => problem.getString("revert_script")
}
val error = problem.getString("last_problem")
logger.error(error)
val humanScript =
"-- Rev:" + revision + "," + (if (state == "applying_up") "Ups"
else "Downs") + " - " + hash + "\n\n" + script
throw InconsistentDatabase(database.name, humanScript, error, revision, autocommit)
}
}
} catch {
case e: InconsistentDatabase => throw e
case NonFatal(_) => createPlayEvolutionsTable()
} finally {
connection.close()
}
}
def resetScripts(): Seq[Script] = {
val appliedEvolutions = databaseEvolutions()
appliedEvolutions.map(DownScript.apply)
}
def resolve(revision: Int): Unit = {
implicit val connection = database.getConnection(autocommit = true)
try {
execute(
"update ${schema}${evolutions_table} set state = 'applied' where state = 'applying_up' and id = " + revision
)
execute("delete from ${schema}${evolutions_table} where state = 'applying_down' and id = " + revision);
} finally {
connection.close()
}
}
private def executeQuery[T](sql: String)(f: ResultSet => T)(implicit c: Connection): T = {
val ps = c.createStatement
try {
val rs = ps.executeQuery(applySchemaAndTable(sql, schema = schema, table = metaTable))
f(rs)
} finally {
ps.close()
}
}
private def execute(sql: String, metaQuery: Boolean = true)(implicit c: Connection): Boolean = {
val s = c.createStatement
try {
s.execute(
if (metaQuery) applySchemaAndTable(sql, schema = schema, table = metaTable)
else
substituteVariables(sql, substitutionsMappings, substitutionsPrefix, substitutionsSuffix, substitutionsEscape)
)
} finally {
s.close()
}
}
private def prepareAndExecute(sql: String)(block: PreparedStatement => Unit)(implicit c: Connection): Boolean = {
val ps = c.prepareStatement(applySchemaAndTable(sql, schema = schema, table = metaTable))
try {
block(ps)
ps.execute()
} finally {
ps.close()
}
}
}
private object DefaultEvolutionsApi {
val logger = Logger(classOf[DefaultEvolutionsApi])
val CreatePlayEvolutionsSql =
"""
create table ${schema}${evolutions_table} (
id int not null primary key,
hash varchar(255) not null,
applied_at timestamp not null,
apply_script text,
revert_script text,
state varchar(255),
last_problem text
)
"""
val CreatePlayEvolutionsSqlServerSql =
"""
create table ${schema}${evolutions_table} (
id int not null primary key,
hash varchar(255) not null,
applied_at datetime not null,
apply_script text,
revert_script text,
state varchar(255),
last_problem text
)
"""
val CreatePlayEvolutionsOracleSql =
"""
CREATE TABLE ${schema}${evolutions_table} (
id Number(10,0) Not Null Enable,
hash VARCHAR2(255 Byte),
applied_at Timestamp Not Null,
apply_script clob,
revert_script clob,
state Varchar2(255),
last_problem clob,
CONSTRAINT ${evolutions_table}_pk PRIMARY KEY (id)
)
"""
val CreatePlayEvolutionsMySql =
"""
CREATE TABLE ${schema}${evolutions_table} (
id int not null primary key,
hash varchar(255) not null,
applied_at timestamp not null,
apply_script mediumtext,
revert_script mediumtext,
state varchar(255),
last_problem mediumtext
)
"""
val CreatePlayEvolutionsDerby =
"""
create table ${schema}${evolutions_table} (
id int not null primary key,
hash varchar(255) not null,
applied_at timestamp not null,
apply_script clob,
revert_script clob,
state varchar(255),
last_problem clob
)
"""
val CreatePlayEvolutionsHsql =
"""
create table ${schema}${evolutions_table} (
id int not null primary key,
hash varchar(255) not null,
applied_at timestamp not null,
apply_script clob,
revert_script clob,
state varchar(255),
last_problem clob
)
"""
}
/**
* Reader for evolutions
*/
trait EvolutionsReader {
/**
* Read the evolutions for the given db
*/
def evolutions(db: String): scala.collection.Seq[Evolution]
}
/**
* Evolutions reader that reads evolutions from resources, for example, the file system or the classpath
*/
abstract class ResourceEvolutionsReader extends EvolutionsReader {
/**
* Load the evolutions resource for the given database and revision.
*
* @return An InputStream to consume the resource, if such a resource exists.
*/
def loadResource(db: String, revision: Int): Option[InputStream]
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
}
Seq
.unfold(1) { revision =>
loadResource(db, revision).map { stream =>
((revision, PlayIO.readStreamAsString(stream)(Codec.UTF8)), revision + 1)
}
}
.sortBy(_._1)
.map {
case (revision, script) => {
val parsed = Seq
.unfold(("", script.split('\n').toList.map(_.trim))) {
case (_, Nil) => None
case (context, lines) => {
val (some, next) = lines.span(l => !isMarker(l))
Some(
(
context -> some.mkString("\n"),
next.headOption.map(c => (mapUpsAndDowns(c), next.tail)).getOrElse("" -> Nil)
)
)
}
}
.drop(1)
.groupBy(i => i._1)
.view
.mapValues { _.map(_._2).mkString("\n").trim }
Evolution(revision, parsed.getOrElse(UPS, ""), parsed.getOrElse(DOWNS, ""))
}
}
}
}
/**
* Read evolution files from the application environment.
*/
@Singleton
class EnvironmentEvolutionsReader @Inject() (environment: Environment, evolutionsConfig: EvolutionsConfig)
extends ResourceEvolutionsReader {
import DefaultEvolutionsApi._
def this(environment: Environment, path: String) = this(
environment,
new EvolutionsConfig {
// Using a dummy evolutions config, however the only config that is relevant here for the EnvironmentEvolutionsReader
// is the "path" setting, everything else does not matter actually. Using default values from the reference.conf
override def forDatasource(db: String): EvolutionsDatasourceConfig = DefaultEvolutionsDatasourceConfig(
true, // enabled
"", // schema
"play_evolutions", // metaTable
true, // autocommit",
false, // useLocks
false, // autoApply
false, // autoApplyDowns
false, // skipApplyDownsOnly
"$evolutions{{{", // substitutions.prefix
"}}}", // substitutions.suffix
Map.empty, // substitutions.mappings
true, // substitutions.escapeEnabled
path // <-- this is all that matters for the EnvironmentEvolutionsReader
)
}
)
def this(environment: Environment) = this(environment, "evolutions")
def loadResource(db: String, revision: Int): Option[InputStream] = {
@tailrec def findPaddedRevisionResource(paddedRevision: String, uri: Option[URI]): Option[InputStream] = {
if (paddedRevision.length > 15) {
uri.map(u => u.toURL.openStream()) // Revision string has reached max padding
} else {
val evolution = {
// First try a file on the filesystem
val filename = Evolutions.fileName(db, paddedRevision, evolutionsConfig.forDatasource(db).path)
val path = Path.of(filename)
if (path.isAbsolute) {
Some(path.toFile).filter(_.exists()).map(_.toURI)
} else {
// If not an absolute path we try to retrieve it relative to the root path
environment.getExistingFile(filename).map(_.toURI)
}
}.orElse {
// If file was not found, try a resource on the classpath
val resourceName = Evolutions.resourceName(db, paddedRevision, evolutionsConfig.forDatasource(db).path)
environment.resource(resourceName).map(url => url.toURI)
}
for {
u <- uri
e <- evolution
} {
val original = e.toString.substring(e.toString.lastIndexOf('/') + 1)
val substitute = u.toString.substring(u.toString.lastIndexOf('/') + 1)
logger.warn(s"Ignoring evolution script $original, using $substitute instead already")
}
findPaddedRevisionResource(s"0$paddedRevision", uri.orElse(evolution))
}
}
findPaddedRevisionResource(revision.toString, None)
}
}
/**
* Evolutions reader that reads evolution files from a class loader.
*
* @param classLoader The classloader to read from, defaults to the classloader for this class.
* @param prefix A prefix that gets added to the resource file names, for example, this could be used to namespace
* evolutions in different environments to work with different databases.
*/
class ClassLoaderEvolutionsReader(
classLoader: ClassLoader = classOf[ClassLoaderEvolutionsReader].getClassLoader,
prefix: String = ""
) extends ResourceEvolutionsReader {
def loadResource(db: String, revision: Int) = {
Option(classLoader.getResourceAsStream(prefix + Evolutions.resourceName(db, revision)))
}
}
/**
* Evolutions reader that reads evolution files from a class loader.
*/
object ClassLoaderEvolutionsReader {
/**
* Create a class loader evolutions reader for the given prefix.
*/
def forPrefix(prefix: String) = new ClassLoaderEvolutionsReader(prefix = prefix)
}
/**
* Evolutions reader that reads evolution files from its own classloader. Only suitable for simple (flat) classloading
* environments.
*/
object ThisClassLoaderEvolutionsReader
extends ClassLoaderEvolutionsReader(classOf[ClassLoaderEvolutionsReader].getClassLoader)
/**
* Simple map based implementation of the evolutions reader.
*/
class SimpleEvolutionsReader(evolutionsMap: Map[String, Seq[Evolution]]) extends EvolutionsReader {
def evolutions(db: String): scala.collection.Seq[Evolution] = evolutionsMap.getOrElse(db, Nil)
}
/**
* Simple map based implementation of the evolutions reader.
*/
object SimpleEvolutionsReader {
/**
* Create a simple evolutions reader from the given data.
*
* @param data A map of database name to a sequence of evolutions.
*/
def from(data: (String, Seq[Evolution])*) = new SimpleEvolutionsReader(data.toMap)
/**
* Create a simple evolutions reader from the given evolutions for the default database.
*
* @param evolutions The evolutions.
*/
def forDefault(evolutions: Evolution*) = new SimpleEvolutionsReader(Map("default" -> evolutions))
}
/**
* Exception thrown when the database is in an inconsistent state.
*
* @param db the database name
* @param script the evolution script
* @param error an inconsistent state error
* @param rev the revision
*/
case class InconsistentDatabase(db: String, script: String, error: String, rev: Int, autocommit: Boolean)
extends PlayException.RichDescription(
"Database '" + db + "' is in an inconsistent state!",
"An evolution has not been applied properly. Please check the problem and resolve it manually" + (if (autocommit)
" before marking it as resolved."
else ".")
) {
def subTitle = "We got the following error: " + error + ", while trying to run this SQL script:"
def content = script
private val resolvePathJavascript =
if (autocommit) s"'/@evolutions/resolve/$db/$rev?redirect=' + encodeURIComponent(window.location)"
else "'/@evolutions'"
private val redirectJavascript =
s"""window.location = window.location.href.split(/[?#]/)[0].replace(/\\/@evolutions.*$$|\\/$$/, '') + $resolvePathJavascript"""
private val sentenceEnd = if (autocommit) " before marking it as resolved." else "."
private val buttonLabel = if (autocommit) """Mark it resolved""" else """Try again"""
def htmlDescription: String = {
An evolution has not been applied properly. Please check the problem and resolve it manually{
sentenceEnd
} -
}.mkString
}