Skip to content
This repository has been archived by the owner on Jul 13, 2024. It is now read-only.

Commit

Permalink
[playframework#407] Add play evolutions:* commands
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumebort committed Mar 28, 2011
1 parent 5d8d987 commit 70b04b2
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 18 deletions.
51 changes: 51 additions & 0 deletions framework/pym/play/commands/evolutions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os, os.path
import shutil
import urllib, urllib2
import subprocess
import simplejson as json

from play.utils import *

COMMANDS = ['evolutions','ev', 'evolutions:apply', 'ev:apply', 'evolutions:markApplied', 'ev:markApplied', 'evolutions:resolve', 'ev:resolve']

HELP = {
'evolutions': 'Run the evolution check',
'evolutions:apply': 'Automatically apply pending evolutions',
'evolutions:markApplied': 'Mark pending evolutions as manually applied',
'evolutions:resolve': 'Resolve partially applied evolution'
}

def execute(**kargs):

args = kargs.get("args")
play_env = kargs.get("env")

command = kargs.get("command")
app = kargs.get("app")
args = kargs.get("args")
play_env = kargs.get("env")


if command.find(':resolve') > 0:
args.append('-Dmode=resolve')

if command.find(':apply') > 0:
args.append('-Dmode=apply')

if command.find(':markApplied') > 0:
args.append('-Dmode=markApplied')

classpath = app.getClasspath()

add_options = ['-Dapplication.path=%s' % (app.path), '-Dframework.path=%s' % (play_env['basedir']), '-Dplay.id=%s' % play_env['id'], '-Dplay.version=%s' % play_env['version']]
if args.count('--jpda'):
print "~ Waiting for JPDA client to continue"
add_options.extend(['-Xdebug', '-Xrunjdwp:transport=dt_socket,address=8888,server=y,suspend=y'])
for arg in args:
if arg.startswith("-D"):
add_options.append(arg)

java_cmd = [app.java_path()] + add_options + ['-classpath', app.cp_args(), 'play.db.Evolutions']

subprocess.call(java_cmd, env=os.environ)

2 changes: 1 addition & 1 deletion framework/src/play/Play.java
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ public static void init(File root, String id) {
/**
* Read application.conf and resolve overriden key using the play id mechanism.
*/
static void readConfiguration() {
public static void readConfiguration() {
VirtualFile appRoot = VirtualFile.open(applicationPath);
conf = appRoot.child("conf/application.conf");
try {
Expand Down
178 changes: 161 additions & 17 deletions framework/src/play/db/Evolutions.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package play.db;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.io.File;
import java.sql.Connection;
import java.sql.Date;
Expand All @@ -13,16 +14,146 @@
import play.Logger;
import play.Play;
import play.PlayPlugin;
import play.classloading.ApplicationClasses;
import play.classloading.ApplicationClassloader;
import play.exceptions.PlayException;
import play.exceptions.UnexpectedException;
import play.libs.Codec;
import play.libs.IO;
import play.mvc.Http.Request;
import play.mvc.Http.Response;
import play.mvc.results.Redirect;
import play.vfs.VirtualFile;

public class Evolutions extends PlayPlugin {

public static void main(String[] args) {

/** Check that evolutions are enabled **/
if (!evolutionsDirectory.exists()) {
System.out.println("~ Evolutions are not enabled. Create a db/evolutions directory to create your first 1.sql evolution script.");
System.out.println("~");
return;
}

/** Start the DB plugin **/
Play.id = System.getProperty("play.id");
Play.applicationPath = new File(System.getProperty("application.path"));
Play.readConfiguration();
Play.javaPath = new ArrayList<VirtualFile>();
Play.classes = new ApplicationClasses();
Play.classloader = new ApplicationClassloader();
Logger.init();
Logger.setUp("ERROR");
new DBPlugin().onApplicationStart();

/** Connected **/
System.out.println("~ Connected to " + ((ComboPooledDataSource) DB.datasource).getJdbcUrl());

/** Sumary **/
Evolution database = listDatabaseEvolutions().peek();
Evolution application = listApplicationEvolutions().peek();

if ("resolve".equals(System.getProperty("mode"))) {
try {
checkEvolutionsState();
System.out.println("~");
System.out.println("~ Nothing to resolve...");
System.out.println("~");
return;
} catch (InconsistentDatabase e) {
resolve(e.revision);
System.out.println("~");
System.out.println("~ Revision " + e.revision + " has been resolved;");
System.out.println("~");
} catch (InvalidDatabaseRevision e) {
// see later
}
}

/** Check inconsistency **/
try {
checkEvolutionsState();
} catch (InconsistentDatabase e) {
System.out.println("~");
System.out.println("~ Your database is an inconsistent state!");
System.out.println("~");
System.out.println("~ While applying this script part:");
System.out.println("");
System.out.println(e.evolutionScript);
System.out.println("");
System.out.println("~ The following error occured:");
System.out.println("");
System.out.println(e.error);
System.out.println("");
System.out.println("~ Please correct it manually, and mark it resolved by running `play evolutions:resolve`");
System.out.println("~");
return;
} catch (InvalidDatabaseRevision e) {
// see later
}

System.out.print("~ Application revision is " + application.revision + " [" + application.hash.substring(0, 7) + "]");
System.out.println(" and Database revision is " + database.revision + " [" + database.hash.substring(0, 7) + "]");
System.out.println("~");

/** Evolution script **/
List<Evolution> evolutions = getEvolutionScript();
if (evolutions.isEmpty()) {
System.out.println("~ Your database is up to date");
System.out.println("~");
} else {

if ("apply".equals(System.getProperty("mode"))) {

System.out.println("~ Applying evolutions:");
System.out.println("");
System.out.println("# ------------------------------------------------------------------------------");
System.out.println("");
System.out.println(toHumanReadableScript(evolutions));
System.out.println("");
System.out.println("# ------------------------------------------------------------------------------");
System.out.println("");
if (applyScript(true)) {
System.out.println("~");
System.out.println("~ Evolutions script successfully applied!");
System.out.println("~");
} else {
System.out.println("~");
System.out.println("~ Can't apply evolutions...");
System.out.println("~");
}


} else if ("markApplied".equals(System.getProperty("mode"))) {

if (applyScript(false)) {
System.out.println("~ Evolutions script marked as applied!");
System.out.println("~");
} else {
System.out.println("~ Can't apply evolutions...");
System.out.println("~");
}

} else {

System.out.println("~ Your database needs evolutions!");
System.out.println("");
System.out.println("# ------------------------------------------------------------------------------");
System.out.println("");
System.out.println(toHumanReadableScript(evolutions));
System.out.println("");
System.out.println("# ------------------------------------------------------------------------------");
System.out.println("");
System.out.println("~ Run `play evolutions:apply` to automatically apply this script to the database");
System.out.println("~ or apply it yourself and mark it done using `play evolutions:markApplied`");
System.out.println("~");
}



}
}
static File evolutionsDirectory = Play.getFile("db/evolutions");

@Override
Expand All @@ -31,15 +162,14 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio
// Mark an evolution as resolved
if (Play.mode.isDev() && request.method.equals("POST") && request.url.matches("^/@evolutions/force/[0-9]+$")) {
int revision = Integer.parseInt(request.url.substring(request.url.lastIndexOf("/") + 1));
execute("update play_evolutions set state = 'applied' where state = 'applying_up' and id = " + revision);
execute("delete from play_evolutions where state = 'applying_down' and id = " + revision);
resolve(revision);
new Redirect("/").apply(request, response);
return true;
}

// Apply the current evolution script
if (Play.mode.isDev() && request.method.equals("POST") && request.url.equals("/@evolutions/apply")) {
applyScript();
applyScript(true);
new Redirect("/").apply(request, response);
return true;
}
Expand All @@ -53,7 +183,7 @@ public void beforeInvocation() {
} catch (InvalidDatabaseRevision e) {
if ("mem".equals(Play.configuration.getProperty("db")) && listDatabaseEvolutions().peek().revision == 0) {
Logger.info("Automatically applying evolutions in in-memory database");
applyScript();
applyScript(true);
} else {
throw e;
}
Expand All @@ -66,15 +196,26 @@ public void onApplicationStart() {
try {
checkEvolutionsState();
} catch (InvalidDatabaseRevision e) {
Logger.warn("*** Your database needs evolution! You must run this script on your database: \n\n" + toHumanReadableScript(getEvolutionScript()) + "\n\n");
Logger.warn("");
Logger.warn("Your database is not up to date.");
Logger.warn("Use `play evolutions` command to manage database evolutions.");
throw e;
}
}
}

public synchronized void applyScript() {
public static synchronized void resolve(int revision) {
try {
Connection connection = getNewConnection();;
execute("update play_evolutions set state = 'applied' where state = 'applying_up' and id = " + revision);
execute("delete from play_evolutions where state = 'applying_down' and id = " + revision);
} catch (Exception e) {
throw new UnexpectedException(e);
}
}

public static synchronized boolean applyScript(boolean runScript) {
try {
Connection connection = getNewConnection();
int applying = -1;
try {
for (Evolution evolution : getEvolutionScript()) {
Expand All @@ -95,11 +236,13 @@ public synchronized void applyScript() {
execute("update play_evolutions set state = 'applying_down' where id = " + evolution.revision);
}
// Execute script
for (String sql : (evolution.applyUp ? evolution.sql_up : evolution.sql_down).split(";")) {
if (sql.trim().isEmpty()) {
continue;
if (runScript) {
for (String sql : (evolution.applyUp ? evolution.sql_up : evolution.sql_down).split(";")) {
if (sql.trim().isEmpty()) {
continue;
}
execute(sql);
}
execute(sql);
}
// Insert into logs
if (evolution.applyUp) {
Expand All @@ -108,7 +251,7 @@ public synchronized void applyScript() {
execute("delete from play_evolutions where id = " + evolution.revision);
}
}

return true;
} catch (Exception e) {
String message = e.getMessage();
if (e instanceof SQLException) {
Expand All @@ -121,13 +264,14 @@ public synchronized void applyScript() {
ps.execute();
closeConnection(connection);
Logger.error(e, "Can't apply evolution");
return false;
}
} catch(Exception e) {
} catch (Exception e) {
throw new UnexpectedException(e);
}
}

public String toHumanReadableScript(List<Evolution> evolutionScript) {
public static String toHumanReadableScript(List<Evolution> evolutionScript) {
// Construct the script
StringBuilder sql = new StringBuilder();
boolean containsDown = false;
Expand All @@ -148,7 +292,7 @@ public String toHumanReadableScript(List<Evolution> evolutionScript) {
return sql.toString().trim();
}

public synchronized void checkEvolutionsState() {
public synchronized static void checkEvolutionsState() {
if (DB.datasource != null && evolutionsDirectory.exists()) {
List<Evolution> evolutionScript = getEvolutionScript();
Connection connection = null;
Expand Down Expand Up @@ -232,7 +376,7 @@ public synchronized static Stack<Evolution> listApplicationEvolutions() {
} else if (line.startsWith("#")) {
// skip
} else if (!line.trim().isEmpty()) {
current.append(line.trim() + "\n");
current.append(line + "\n");
}
}
evolutions.add(new Evolution(version, sql_up.toString(), sql_down.toString(), true));
Expand Down Expand Up @@ -344,7 +488,7 @@ public String getErrorTitle() {

@Override
public String getErrorDescription() {
return "An SQL script will be run on your database. Please check the generated script before applying it.";
return "An SQL script will be run on your database.";
}

@Override
Expand Down

0 comments on commit 70b04b2

Please sign in to comment.