diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java
index 24e4ff093..39f2c2c04 100644
--- a/src/main/java/com/avaje/ebean/config/ServerConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java
@@ -214,6 +214,8 @@ public class ServerConfig {
private boolean ddlCreateOnly;
+ private String ddlSeedSql;
+
private boolean useJtaTransactionManager;
/**
@@ -1570,6 +1572,28 @@ public class ServerConfig {
this.ddlCreateOnly = ddlCreateOnly;
}
+ /**
+ * Return SQL script to execute after the "create all" DDL has been run.
+ *
+ * Typically this is a sql script that inserts test seed data when running tests.
+ * Place a sql script in src/test/resources that inserts test seed data.
+ *
+ */
+ public String getDdlSeedSql() {
+ return ddlSeedSql;
+ }
+
+ /**
+ * Set a SQL script to execute after the "create all" DDL has been run.
+ *
+ * Typically this is a sql script that inserts test seed data when running tests.
+ * Place a sql script in src/test/resources that inserts test seed data.
+ *
+ */
+ public void setDdlSeedSql(String ddlSeedSql) {
+ this.ddlSeedSql = ddlSeedSql;
+ }
+
/**
* Return true if the DDL should be generated.
*/
@@ -2261,6 +2285,7 @@ public class ServerConfig {
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
ddlRun = p.getBoolean("ddl.run", ddlRun);
ddlCreateOnly = p.getBoolean("ddl.createOnly", ddlCreateOnly);
+ ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
classes = getClasses(p);
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
index 852ce7527..ee4c7432c 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
@@ -1,34 +1,25 @@
package com.avaje.ebean.dbmigration;
-import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.model.CurrentModel;
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.LineNumberReader;
-import java.io.StringReader;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.Reader;
/**
* Controls the generation of DDL and potentially runs the resulting scripts.
*/
public class DdlGenerator implements SpiEbeanPlugin {
- private static final Logger logger = LoggerFactory.getLogger(DdlGenerator.class);
-
private SpiEbeanServer server;
private boolean generateDdl;
@@ -76,16 +67,9 @@ public class DdlGenerator implements SpiEbeanPlugin {
if (runDdl) {
try {
- if (dropContent == null) {
- dropContent = readFile(getDropFileName());
- }
- if (createContent == null) {
- createContent = readFile(getCreateFileName());
- }
- if (!createOnly) {
- runScript(true, dropContent);
- }
- runScript(false, createContent);
+ runDropSql();
+ runCreateSql();
+ runSeedSql();
} catch (IOException e) {
String msg = "Error reading drop/create script from file system";
@@ -94,6 +78,46 @@ public class DdlGenerator implements SpiEbeanPlugin {
}
}
+ protected void runDropSql() throws IOException {
+ if (!createOnly) {
+ if (dropContent == null) {
+ dropContent = readFile(getDropFileName());
+ }
+ runScript(true, dropContent, getDropFileName());
+ }
+ }
+
+ protected void runCreateSql() throws IOException {
+ if (createContent == null) {
+ createContent = readFile(getCreateFileName());
+ }
+ runScript(false, createContent, getCreateFileName());
+ }
+
+ protected void runSeedSql() throws IOException {
+
+ String seedSql = server.getServerConfig().getDdlSeedSql();
+ if (seedSql != null) {
+ InputStream is = getClassLoader().getResourceAsStream(seedSql);
+ if (is != null) {
+ DdlRunner runner = new DdlRunner(false, seedSql);
+ String content = readContent(new InputStreamReader(is));
+ runner.runAll(content, server);
+ }
+ }
+ }
+
+ /**
+ * Return the classLoader to use to read sql scripts as resources.
+ */
+ protected ClassLoader getClassLoader() {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ if (cl == null) {
+ cl = this.getClassLoader();
+ }
+ return cl;
+ }
+
protected void writeDrop(String dropFile) {
try {
@@ -169,201 +193,33 @@ public class DdlGenerator implements SpiEbeanPlugin {
return null;
}
+ return readContent(new FileReader(f));
+ }
+
+ private String readContent(Reader reader) throws IOException {
+
StringBuilder buf = new StringBuilder();
- FileReader fr = new FileReader(f);
- LineNumberReader lr = new LineNumberReader(fr);
+ LineNumberReader lineReader = new LineNumberReader(reader);
try {
String s;
- while ((s = lr.readLine()) != null) {
+ while ((s = lineReader.readLine()) != null) {
buf.append(s).append("\n");
}
- } finally {
- lr.close();
- }
+ return buf.toString();
- return buf.toString();
+ } finally {
+ lineReader.close();
+ }
}
/**
* Execute all the DDL statements in the script.
*/
- public void runScript(boolean expectErrors, String content) {
+ public int runScript(boolean expectErrors, String content, String scriptName) {
- StringReader sr = new StringReader(content);
- List statements = parseStatements(sr);
-
- Transaction t = server.createTransaction();
- try {
- Connection connection = t.getConnection();
-
- logger.info("Running DDL");
-
- runStatements(expectErrors, statements, connection);
-
- logger.info("Running DDL Complete");
-
- t.commit();
-
- } catch (Exception e) {
- throw new PersistenceException("Error: " + e.getMessage(), e);
- } finally {
- t.end();
- }
+ DdlRunner runner = new DdlRunner(expectErrors, scriptName);
+ return runner.runAll(content, server);
}
- /**
- * Execute the list of statements.
- */
- private void runStatements(boolean expectErrors, List statements, Connection c) {
- List noDuplicates = new ArrayList();
-
- for (String statement : statements) {
- if (!noDuplicates.contains(statement)) {
- noDuplicates.add(statement);
- }
- }
-
- for (int i = 0; i < noDuplicates.size(); i++) {
- String xOfy = (i + 1) + " of " + noDuplicates.size();
- runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
- }
- }
-
- /**
- * Execute the statement.
- */
- private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
-
- PreparedStatement pstmt = null;
- try {
-
- // trim and remove trailing ; or /
- stmt = stmt.trim();
- if (stmt.endsWith(";")) {
- stmt = stmt.substring(0, stmt.length() - 1);
- } else if (stmt.endsWith("/")) {
- stmt = stmt.substring(0, stmt.length() - 1);
- }
-
- logger.info("executing " + oneOf + " " + getSummary(stmt));
-
- pstmt = c.prepareStatement(stmt);
- pstmt.execute();
-
- } catch (Exception e) {
- if (expectErrors) {
- logger.info(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
- } else {
- String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
- throw new RuntimeException(msg, e);
- }
- } finally {
- if (pstmt != null) {
- try {
- pstmt.close();
- } catch (SQLException e) {
- logger.error("Error closing pstmt", e);
- }
- }
- }
- }
-
- /**
- * Local utility used to detect the end of statements / separate statements.
- * This is often just the semicolon character but for trigger/procedures this
- * detects the $$ demarcation used in the history DDL generation for MySql and
- * Postgres.
- */
- static class StatementsSeparator {
-
- ArrayList statements = new ArrayList();
-
- boolean trimDelimiter;
-
- boolean inDbProcedure;
-
- StringBuilder sb = new StringBuilder();
-
- void lineContainsDollars(String line) {
- if (inDbProcedure) {
- if (trimDelimiter) {
- line = line.replace("$$","");
- }
- endOfStatement(line);
- } else {
- // MySql style delimiter needs to be trimmed/removed
- trimDelimiter = line.equals("delimiter $$");
- if (!trimDelimiter) {
- sb.append(line).append(" ");
- }
- }
- inDbProcedure = !inDbProcedure;
- }
-
- void endOfStatement(String line) {
- // end of Db procedure
- sb.append(line);
- statements.add(sb.toString().trim());
- sb = new StringBuilder();
- }
-
- void nextLine(String line) {
-
- if (line.contains("$$")) {
- lineContainsDollars(line);
- return;
- }
-
- if (inDbProcedure) {
- sb.append(line).append(" ");
- return;
- }
-
- int semiPos = line.indexOf(';');
- if (semiPos == -1) {
- sb.append(line).append(" ");
-
- } else if (semiPos == line.length() - 1) {
- // semicolon at end of line
- endOfStatement(line);
-
- } else {
- // semicolon in middle of line
- String preSemi = line.substring(0, semiPos);
- endOfStatement(preSemi);
- sb.append(line.substring(semiPos + 1));
- }
- }
- }
-
- /**
- * Break up the sql in reader into a list of statements using the semi-colon
- * character;
- */
- protected List parseStatements(StringReader reader) {
-
- try {
- BufferedReader br = new BufferedReader(reader);
- StatementsSeparator statements = new StatementsSeparator();
-
- String s;
- while ((s = br.readLine()) != null) {
- s = s.trim();
- statements.nextLine(s);
- }
-
- return statements.statements;
-
- } catch (IOException e) {
- throw new PersistenceException(e);
- }
- }
-
- private String getSummary(String s) {
- if (s.length() > 80) {
- return s.substring(0, 80).trim() + "...";
- }
- return s;
- }
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlParser.java b/src/main/java/com/avaje/ebean/dbmigration/DdlParser.java
new file mode 100644
index 000000000..2f9f2cf25
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/DdlParser.java
@@ -0,0 +1,105 @@
+package com.avaje.ebean.dbmigration;
+
+import javax.persistence.PersistenceException;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses string content into separate SQL/DDL statements.
+ */
+public class DdlParser {
+
+ /**
+ * Break up the sql in reader into a list of statements using the semi-colon and $$ delimiters;
+ */
+ public List parse(StringReader reader) {
+
+ try {
+ BufferedReader br = new BufferedReader(reader);
+ StatementsSeparator statements = new StatementsSeparator();
+
+ String s;
+ while ((s = br.readLine()) != null) {
+ s = s.trim();
+ statements.nextLine(s);
+ }
+
+ return statements.statements;
+
+ } catch (IOException e) {
+ throw new PersistenceException(e);
+ }
+ }
+
+
+ /**
+ * Local utility used to detect the end of statements / separate statements.
+ * This is often just the semicolon character but for trigger/procedures this
+ * detects the $$ demarcation used in the history DDL generation for MySql and
+ * Postgres.
+ */
+ static class StatementsSeparator {
+
+ ArrayList statements = new ArrayList();
+
+ boolean trimDelimiter;
+
+ boolean inDbProcedure;
+
+ StringBuilder sb = new StringBuilder();
+
+ void lineContainsDollars(String line) {
+ if (inDbProcedure) {
+ if (trimDelimiter) {
+ line = line.replace("$$","");
+ }
+ endOfStatement(line);
+ } else {
+ // MySql style delimiter needs to be trimmed/removed
+ trimDelimiter = line.equals("delimiter $$");
+ if (!trimDelimiter) {
+ sb.append(line).append(" ");
+ }
+ }
+ inDbProcedure = !inDbProcedure;
+ }
+
+ void endOfStatement(String line) {
+ // end of Db procedure
+ sb.append(line);
+ statements.add(sb.toString().trim());
+ sb = new StringBuilder();
+ }
+
+ void nextLine(String line) {
+
+ if (line.contains("$$")) {
+ lineContainsDollars(line);
+ return;
+ }
+
+ if (inDbProcedure) {
+ sb.append(line).append(" ");
+ return;
+ }
+
+ int semiPos = line.indexOf(';');
+ if (semiPos == -1) {
+ sb.append(line).append(" ");
+
+ } else if (semiPos == line.length() - 1) {
+ // semicolon at end of line
+ endOfStatement(line);
+
+ } else {
+ // semicolon in middle of line
+ String preSemi = line.substring(0, semiPos);
+ endOfStatement(preSemi);
+ sb.append(line.substring(semiPos + 1));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java b/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
new file mode 100644
index 000000000..f1b1d2b58
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
@@ -0,0 +1,138 @@
+package com.avaje.ebean.dbmigration;
+
+import com.avaje.ebean.Transaction;
+import com.avaje.ebeaninternal.api.SpiEbeanServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.persistence.PersistenceException;
+import java.io.StringReader;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Runs DDL scripts.
+ */
+public class DdlRunner {
+
+ protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class);
+
+ protected DdlParser ddlParser = new DdlParser();
+
+ protected final String scriptName;
+
+ protected final boolean expectErrors;
+
+ /**
+ * Construct with a script name (for logging) and flag indicating if errors are expected.
+ */
+ public DdlRunner(boolean expectErrors, String scriptName) {
+ this.expectErrors = expectErrors;
+ this.scriptName = scriptName;
+ }
+
+ /**
+ * Parse the content into sql statements and execute them in a transaction.
+ */
+ public int runAll(String content, SpiEbeanServer server) {
+
+ List statements = ddlParser.parse(new StringReader(content));
+ return runStatements(statements, server);
+ }
+
+ /**
+ * Execute all the statements in a single transaction.
+ */
+ public int runStatements(List statements, SpiEbeanServer server) {
+
+ Transaction t = server.createTransaction();
+ try {
+ int statementCount = runStatements(expectErrors, statements, t.getConnection());
+ t.commit();
+
+ return statementCount;
+
+ } catch (Exception e) {
+ throw new PersistenceException("Error: " + e.getMessage(), e);
+
+ } finally {
+ t.end();
+ }
+ }
+
+ /**
+ * Execute the list of statements.
+ */
+ private int runStatements(boolean expectErrors, List statements, Connection c) {
+
+ List noDuplicates = new ArrayList();
+
+ for (String statement : statements) {
+ if (!noDuplicates.contains(statement)) {
+ noDuplicates.add(statement);
+ }
+ }
+
+ logger.info("Executing {} - {} statements", scriptName, noDuplicates.size());
+
+ for (int i = 0; i < noDuplicates.size(); i++) {
+ String xOfy = (i + 1) + " of " + noDuplicates.size();
+ runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
+ }
+
+ return noDuplicates.size();
+ }
+
+ /**
+ * Execute the statement.
+ */
+ private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
+
+ PreparedStatement pstmt = null;
+ try {
+
+ // trim and remove trailing ; or /
+ stmt = stmt.trim();
+ if (stmt.endsWith(";")) {
+ stmt = stmt.substring(0, stmt.length() - 1);
+ } else if (stmt.endsWith("/")) {
+ stmt = stmt.substring(0, stmt.length() - 1);
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("executing " + oneOf + " " + getSummary(stmt));
+ }
+
+ pstmt = c.prepareStatement(stmt);
+ pstmt.execute();
+
+ } catch (Exception e) {
+ if (expectErrors) {
+ logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
+ } else {
+ String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
+ throw new RuntimeException(msg, e);
+ }
+
+ } finally {
+ if (pstmt != null) {
+ try {
+ pstmt.close();
+ } catch (SQLException e) {
+ logger.error("Error closing pstmt", e);
+ }
+ }
+ }
+ }
+
+ private String getSummary(String s) {
+ if (s.length() > 80) {
+ return s.substring(0, 80).trim() + "...";
+ }
+ return s;
+ }
+
+}