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.nio.charset.Charset
import java.nio.file._
import scala.collection.immutable.ArraySeq
import play.api.db.DBApi
import play.api.db.Database
import play.api.inject.ApplicationLifecycle
import play.api.inject.DefaultApplicationLifecycle
import play.api.libs.Codecs.sha1
import play.api.Configuration
import play.api.Environment
import play.api.Logger
import play.api.Mode
import play.core.DefaultWebCommands
import play.core.WebCommands
import play.utils.PlayIO
/**
* An SQL evolution - database changes associated with a software version.
*
* An evolution includes ‘up’ changes, to upgrade to the next version, as well
* as ‘down’ changes, to downgrade the database to the previous version.
*
* @param revision revision number
* @param sql_up the SQL statements for UP application
* @param sql_down the SQL statements for DOWN application
*/
case class Evolution(revision: Int, sql_up: String = "", sql_down: String = "") {
/**
* Revision hash, automatically computed from the SQL content.
*/
val hash = sha1(sql_down.trim + sql_up.trim)
}
/**
* A Script to run on the database.
*/
trait Script {
/**
* Original evolution.
*/
def evolution: Evolution
/**
* The complete SQL to be run.
*/
def sql: String
/**
* The sql string separated into constituent ";"-delimited statements.
*
* Any ";;" found in the sql are escaped to ";".
*/
def statements: Seq[String] = {
// Regex matches on semicolons that neither precede nor follow other semicolons
ArraySeq.unsafeWrapArray(sql.split("(? "-- Rev:" + ev.revision + ",Ups - " + ev.hash.take(7) + "\n" + ev.sql_up + "\n"
case DownScript(ev) => "-- Rev:" + ev.revision + ",Downs - " + ev.hash.take(7) + "\n" + ev.sql_down + "\n"
}
.mkString("\n")
val hasDownWarning =
"-- !!! WARNING! This script contains DOWNS evolutions that are likely destructive\n\n"
if (scripts.exists(_.isInstanceOf[DownScript])) hasDownWarning + txt else txt
}
/**
* Compare two evolution sequences.
*
* @param downs the seq of downs
* @param ups the seq of ups
* @return the downs and ups to run to have the db synced to the current stage
*/
def conflictings(downs: Seq[Evolution], ups: Seq[Evolution]): (Seq[Evolution], Seq[Evolution]) =
downs
.zip(ups)
.reverse
.dropWhile {
case (down, up) => down.hash == up.hash
}
.reverse
.unzip
/**
* Apply evolutions for the given database.
*
* @param database The database to apply the evolutions to.
* @param evolutionsReader The reader to read the evolutions.
* @param autocommit Whether to use autocommit or not, evolutions will be manually committed if false.
* @param schema The schema that all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' meta 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 applyEvolutions(
database: Database,
evolutionsReader: EvolutionsReader = ThisClassLoaderEvolutionsReader,
autocommit: Boolean = true,
schema: String = "",
metaTable: String = "play_evolutions",
substitutionsMappings: Map[String, String] = Map.empty,
substitutionsPrefix: String = "$evolutions{{{",
substitutionsSuffix: String = "}}}",
substitutionsEscape: Boolean = true
): Unit = {
val dbEvolutions = new DatabaseEvolutions(
database,
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
)
val evolutions = dbEvolutions.scripts(evolutionsReader)
dbEvolutions.evolve(evolutions, autocommit)
}
/**
* Cleanup evolutions for the given database.
*
* This will leave the database in the original state it was before evolutions were applied, by running the down
* scripts for all the evolutions that have been previously applied to the database.
*
* @param database The database to clean the evolutions for.
* @param autocommit Whether to use autocommit or not, evolutions will be manually committed if false.
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' meta 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 cleanupEvolutions(
database: Database,
autocommit: Boolean = true,
schema: String = "",
metaTable: String = "play_evolutions",
substitutionsMappings: Map[String, String] = Map.empty,
substitutionsPrefix: String = "$evolutions{{{",
substitutionsSuffix: String = "}}}",
substitutionsEscape: Boolean = true
): Unit = {
val dbEvolutions = new DatabaseEvolutions(
database,
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
)
val evolutions = dbEvolutions.resetScripts()
dbEvolutions.evolve(evolutions, autocommit)
}
/**
* Execute the following code block with the evolutions for the database, cleaning up afterwards by running the downs.
*
* @param database The database to execute the evolutions on
* @param evolutionsReader The evolutions reader to use. Defaults to reading evolutions from the evolution readers own classloader.
* @param autocommit Whether to use autocommit or not, evolutions will be manually committed if false.
* @param block The block to execute
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' meta 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 withEvolutions[T](
database: Database,
evolutionsReader: EvolutionsReader = ThisClassLoaderEvolutionsReader,
autocommit: Boolean = true,
schema: String = "",
metaTable: String = "play_evolutions",
substitutionsMappings: Map[String, String] = Map.empty,
substitutionsPrefix: String = "$evolutions{{{",
substitutionsSuffix: String = "}}}",
substitutionsEscape: Boolean = true
)(block: => T): T = {
applyEvolutions(
database,
evolutionsReader,
autocommit,
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
)
try {
block
} finally {
try {
cleanupEvolutions(
database,
autocommit,
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
)
} catch {
case e: Exception =>
logger.warn("Error resetting evolutions", e)
}
}
}
}
/**
* Can be used to run off-line evolutions, i.e. outside a running application.
*/
object OfflineEvolutions {
// Get a logger that doesn't log in tests
private val nonTestLogger = Logger(this.getClass).forMode(Mode.Dev, Mode.Prod)
private def getEvolutions(appPath: File, classloader: ClassLoader, dbApi: DBApi): EvolutionsComponents = {
val _dbApi = dbApi
new EvolutionsComponents {
lazy val environment = Environment(appPath, classloader, Mode.Dev)
lazy val configuration = Configuration.load(environment)
lazy val applicationLifecycle: ApplicationLifecycle = new DefaultApplicationLifecycle
lazy val dbApi: DBApi = _dbApi
lazy val webCommands: WebCommands = new DefaultWebCommands
}
}
/**
* Computes and applies an evolutions script.
*
* @param appPath the application path
* @param classloader the classloader used to load the driver
* @param dbName the database name
* @param dbApi the database api for managing application databases
* @param schema The schema where all the play evolution tables are saved in
* @param metaTable Table to keep evolutions' meta 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 applyScript(
appPath: File,
classloader: ClassLoader,
dbApi: DBApi,
dbName: String,
autocommit: Boolean = true,
schema: String = "",
metaTable: String = "play_evolutions",
substitutionsMappings: Map[String, String] = Map.empty,
substitutionsPrefix: String = "$evolutions{{{",
substitutionsSuffix: String = "}}}",
substitutionsEscape: Boolean = true
): Unit = {
val evolutions = getEvolutions(appPath, classloader, dbApi)
val scripts = evolutions.evolutionsApi.scripts(dbName, evolutions.evolutionsReader, schema, metaTable)
nonTestLogger.warn(
"Applying evolution scripts for database '" + dbName + "':\n\n" + Evolutions.toHumanReadableScript(scripts)
)
evolutions.evolutionsApi.evolve(
dbName,
scripts,
autocommit,
schema,
metaTable,
substitutionsMappings,
substitutionsPrefix,
substitutionsSuffix,
substitutionsEscape
)
}
/**
* Resolve an inconsistent evolution.
*
* @param appPath the application path
* @param classloader the classloader used to load the driver
* @param dbApi the database api for managing application databases
* @param dbName the database name
* @param revision the revision
* @param schema The schema where all the play evolution tables are saved in
*/
def resolve(
appPath: File,
classloader: ClassLoader,
dbApi: DBApi,
dbName: String,
revision: Int,
schema: String = "",
metaTable: String = "play_evolutions"
): Unit = {
val evolutions = getEvolutions(appPath, classloader, dbApi)
nonTestLogger.warn("Resolving evolution [" + revision + "] for database '" + dbName + "'")
evolutions.evolutionsApi.resolve(dbName, revision, schema, metaTable)
}
}