diff --git a/src/main/java/io/ebean/EbeanServer.java b/src/main/java/io/ebean/EbeanServer.java index aad3eb6d9..face93fa3 100644 --- a/src/main/java/io/ebean/EbeanServer.java +++ b/src/main/java/io/ebean/EbeanServer.java @@ -1442,6 +1442,13 @@ public interface EbeanServer { */ JsonContext json(); + /** + * Return a ScriptRunner for running SQL or DDL scripts. + *

+ * Intended to use mostly in testing to run seed SQL scripts or truncate table scripts etc. + */ + ScriptRunner script(); + /** * Return the Document store. */ diff --git a/src/main/java/io/ebean/ScriptRunner.java b/src/main/java/io/ebean/ScriptRunner.java new file mode 100644 index 000000000..b9130d57b --- /dev/null +++ b/src/main/java/io/ebean/ScriptRunner.java @@ -0,0 +1,65 @@ +package io.ebean; + +import java.net.URL; +import java.util.Map; + +/** + * Runs DDL and SQL scripts. + *

+ * Typically these are scripts used for testing such as seed SQL scripts or truncate SQL scripts. + *

+ * Scripts are executed in their own transaction and committed on successful completion. + * + *

Example of simple use

+ *
{@code
+ *
+ *   EbeanServer server = Ebean.getDefaultServer();
+ *   server.script().run("/scripts/test-script.sql");
+ *
+ * }
+ * + * + *

Example using place holders in the script

+ *
{@code
+ *
+ *   Map placeholders = new HashMap<>();
+ *   placeholders.put("tableName", "e_basic");
+ *
+ *   EbeanServer server = Ebean.getDefaultServer();
+ *   server.script().run("/scripts/test-script.sql");
+ *
+ * }
+ */ +public interface ScriptRunner { + + /** + * Run a script given the resource path (that should start with "/"). + */ + void run(String path); + + /** + * Run a script given the resource path (that should start with "/") and place holders. + * + *
{@code
+   *
+   *   Map placeholders = new HashMap<>();
+   *   placeholders.put("tableName", "e_basic");
+   *
+   *   EbeanServer server = Ebean.getDefaultServer();
+   *   server.script().run("/scripts/test-script.sql");
+   *
+   * }
+ */ + void run(String path, Map placeholderMap); + + /** + * Run a DDL or SQL script given the resource. + */ + void run(URL resource); + + /** + * Run a DDL or SQL script given the resource and place holders. + */ + void run(URL resource, Map placeholderMap); + +} diff --git a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java index 8230bc65b..fa0b662cf 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java +++ b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java @@ -348,7 +348,6 @@ public class DdlGenerator { buf.append(s).append("\n"); } return buf.toString(); - } } diff --git a/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java b/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java new file mode 100644 index 000000000..cefe9f22a --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java @@ -0,0 +1,116 @@ +package io.ebeaninternal.server.core; + +import io.ebean.ScriptRunner; +import io.ebean.migration.ddl.DdlRunner; +import io.ebean.migration.runner.ScriptTransform; +import io.ebeaninternal.api.SpiEbeanServer; + +import javax.persistence.PersistenceException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.Reader; +import java.net.URL; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; + +class DScriptRunner implements ScriptRunner { + + private static final String NEWLINE = "\n"; + + private final SpiEbeanServer server; + + DScriptRunner(SpiEbeanServer server) { + this.server = server; + } + + @Override + public void run(String path) { + run(path, null); + } + + @Override + public void run(String path, Map placeholderMap) { + run(this.getClass().getResource(path), path, placeholderMap); + } + + @Override + public void run(URL resource) { + run(resource, null, null); + } + + @Override + public void run(URL resource, Map placeholderMap) { + run(resource, null, placeholderMap); + } + + private void run(URL resource, String scriptName, Map placeholderMap) { + if (resource == null) { + throw new IllegalArgumentException("resource is null?"); + } + if (scriptName == null) { + scriptName = resource.getFile(); + } + + String content = content(resource); + runScript(content, scriptName, placeholderMap); + } + + private String content(URL resource) { + if (resource == null) { + throw new IllegalArgumentException("resource is null?"); + } + + try (InputStream inputStream = resource.openStream()) { + return readContent(new InputStreamReader(inputStream)); + + } catch (IOException e) { + throw new PersistenceException("Failed to read script content", e); + } + } + + + /** + * Execute all the DDL statements in the script. + */ + private void runScript(String content, String scriptName, Map placeholderMap) { + + try { + if (placeholderMap != null) { + content = ScriptTransform.build(null, placeholderMap).transform(content); + } + + try (Connection connection = obtainConnection()) { + DdlRunner runner = new DdlRunner(false, scriptName); + runner.runAll(content, connection); + connection.commit(); + } + + } catch (SQLException e) { + throw new PersistenceException("Failed to run script", e); + } + } + + private Connection obtainConnection() { + try { + return server.getPluginApi().getDataSource().getConnection(); + } catch (SQLException e) { + throw new PersistenceException("Failed to obtain connection to run script", e); + } + } + + private String readContent(Reader reader) throws IOException { + + StringBuilder buf = new StringBuilder(); + try (LineNumberReader lineReader = new LineNumberReader(reader)) { + String line; + while ((line = lineReader.readLine()) != null) { + buf.append(line).append(NEWLINE); + } + return buf.toString(); + } + } + +} diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index df991c343..1872c0f5b 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -22,6 +22,7 @@ import io.ebean.Query; import io.ebean.QueryIterator; import io.ebean.RowConsumer; import io.ebean.RowMapper; +import io.ebean.ScriptRunner; import io.ebean.SqlQuery; import io.ebean.SqlRow; import io.ebean.SqlUpdate; @@ -185,6 +186,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final DdlGenerator ddlGenerator; + private final ScriptRunner scriptRunner; + private final ExpressionFactory expressionFactory; private final SpiBackgroundExecutor backgroundExecutor; @@ -291,6 +294,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { this.serverPlugins = config.getPlugins(); this.ddlGenerator = new DdlGenerator(this, serverConfig); + this.scriptRunner = new DScriptRunner(this); configureServerPlugins(); @@ -370,6 +374,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return databasePlatform; } + @Override + public ScriptRunner script() { + return scriptRunner; + } + @Override public DdlHandler createDdlHandler() { return PlatformDdlBuilder.create(databasePlatform).createDdlHandler(serverConfig); diff --git a/src/test/java/io/ebean/EbeanServer_refresh.java b/src/test/java/io/ebean/EbeanServer_refresh.java index 1c1188ea4..0c3e676a8 100644 --- a/src/test/java/io/ebean/EbeanServer_refresh.java +++ b/src/test/java/io/ebean/EbeanServer_refresh.java @@ -1,13 +1,13 @@ package io.ebean; -import io.ebean.Ebean; -import io.ebean.EbeanServer; +import org.junit.Test; import org.tests.model.basic.EBasic; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; -import org.junit.Test; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import static org.junit.Assert.assertEquals; @@ -16,7 +16,14 @@ public class EbeanServer_refresh { @Test public void basic() { + Map map = new HashMap<>(); + map.put("tableName", "e_basic"); + EbeanServer server = Ebean.getDefaultServer(); + server.script().run("/scripts/test-script.sql"); + server.script().run("/scripts/test-script-2.sql", map); + server.script().run(this.getClass().getResource("/scripts/test-script.sql")); + server.script().run(this.getClass().getResource("/scripts/test-script-2.sql"), map); EBasic basic = new EBasic("basic refresh"); basic.setStatus(EBasic.Status.NEW); diff --git a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index 2100f8d7c..afa192641 100644 --- a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -19,6 +19,7 @@ import io.ebean.Query; import io.ebean.QueryIterator; import io.ebean.RowConsumer; import io.ebean.RowMapper; +import io.ebean.ScriptRunner; import io.ebean.SqlQuery; import io.ebean.SqlRow; import io.ebean.SqlUpdate; @@ -106,6 +107,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } + @Override + public ScriptRunner script() { + return null; + } + @Override public void scopedTransactionEnter(TxScope txScope) { diff --git a/src/test/resources/scripts/test-script-2.sql b/src/test/resources/scripts/test-script-2.sql new file mode 100644 index 000000000..977ee0ec4 --- /dev/null +++ b/src/test/resources/scripts/test-script-2.sql @@ -0,0 +1,4 @@ +delete from ${tableName}; + +-- some other comment, tableName is replaced +select count(*) from ${tableName}; diff --git a/src/test/resources/scripts/test-script.sql b/src/test/resources/scripts/test-script.sql new file mode 100644 index 000000000..6118a0454 --- /dev/null +++ b/src/test/resources/scripts/test-script.sql @@ -0,0 +1,4 @@ +delete from e_basic; + +-- some comment, select not useful here +select count(*) from e_basic;